From 8346e53ffb94699ce8051156204d2a79f5922374 Mon Sep 17 00:00:00 2001 From: Utkarsh-Singhal-26 Date: Sun, 15 Feb 2026 14:18:07 +0530 Subject: [PATCH] fix(webgl-cleanup): fixes webgl cleanup in various components --- public/r/ASCIIText-JS-CSS.json | 2 +- public/r/ASCIIText-JS-TW.json | 2 +- public/r/ASCIIText-TS-CSS.json | 2 +- public/r/ASCIIText-TS-TW.json | 2 +- public/r/Ballpit-JS-CSS.json | 2 +- public/r/Ballpit-JS-TW.json | 2 +- public/r/Ballpit-TS-CSS.json | 6 +- public/r/Ballpit-TS-TW.json | 6 +- public/r/ColorBends-JS-CSS.json | 2 +- public/r/ColorBends-JS-TW.json | 2 +- public/r/ColorBends-TS-CSS.json | 2 +- public/r/ColorBends-TS-TW.json | 2 +- public/r/FloatingLines-JS-CSS.json | 2 +- public/r/FloatingLines-JS-TW.json | 2 +- public/r/FloatingLines-TS-CSS.json | 2 +- public/r/FloatingLines-TS-TW.json | 2 +- public/r/GhostCursor-JS-CSS.json | 2 +- public/r/GhostCursor-JS-TW.json | 2 +- public/r/GhostCursor-TS-CSS.json | 2 +- public/r/GhostCursor-TS-TW.json | 2 +- public/r/GridDistortion-JS-CSS.json | 2 +- public/r/GridDistortion-JS-TW.json | 2 +- public/r/GridDistortion-TS-CSS.json | 2 +- public/r/GridDistortion-TS-TW.json | 2 +- public/r/GridScan-JS-CSS.json | 6 +- public/r/GridScan-JS-TW.json | 6 +- public/r/GridScan-TS-CSS.json | 6 +- public/r/GridScan-TS-TW.json | 6 +- public/r/Hyperspeed-JS-CSS.json | 6 +- public/r/Hyperspeed-JS-TW.json | 6 +- public/r/Hyperspeed-TS-CSS.json | 6 +- public/r/Hyperspeed-TS-TW.json | 6 +- public/r/LaserFlow-JS-CSS.json | 2 +- public/r/LaserFlow-JS-TW.json | 2 +- public/r/LaserFlow-TS-CSS.json | 2 +- public/r/LaserFlow-TS-TW.json | 2 +- public/r/LiquidEther-JS-CSS.json | 2 +- public/r/LiquidEther-JS-TW.json | 2 +- public/r/LiquidEther-TS-CSS.json | 2 +- public/r/LiquidEther-TS-TW.json | 2 +- public/r/PixelBlast-JS-CSS.json | 6 +- public/r/PixelBlast-JS-TW.json | 6 +- public/r/PixelBlast-TS-CSS.json | 6 +- public/r/PixelBlast-TS-TW.json | 6 +- public/r/PixelSnow-JS-CSS.json | 2 +- public/r/PixelSnow-JS-TW.json | 2 +- public/r/PixelSnow-TS-CSS.json | 2 +- public/r/PixelSnow-TS-TW.json | 2 +- public/r/PixelTrail-TS-TW.json | 4 +- public/r/ShapeBlur-JS-CSS.json | 2 +- public/r/ShapeBlur-JS-TW.json | 2 +- public/r/ShapeBlur-TS-CSS.json | 2 +- public/r/ShapeBlur-TS-TW.json | 2 +- public/r/registry.json | 58 +++++++++---------- .../Animations/GhostCursor/GhostCursor.jsx | 3 +- .../Animations/LaserFlow/LaserFlow.jsx | 1 + .../Animations/ShapeBlur/ShapeBlur.jsx | 3 +- src/content/Backgrounds/Ballpit/Ballpit.jsx | 27 ++++----- .../Backgrounds/ColorBends/ColorBends.jsx | 1 + .../FloatingLines/FloatingLines.jsx | 11 ++-- .../GridDistortion/GridDistortion.jsx | 3 +- src/content/Backgrounds/GridScan/GridScan.jsx | 5 +- .../Backgrounds/Hyperspeed/Hyperspeed.jsx | 39 ++++++++++--- .../Backgrounds/LiquidEther/LiquidEther.jsx | 1 + .../Backgrounds/PixelBlast/PixelBlast.jsx | 4 +- .../Backgrounds/PixelSnow/PixelSnow.jsx | 11 ++-- .../TextAnimations/ASCIIText/ASCIIText.jsx | 3 +- .../Animations/GhostCursor/GhostCursor.jsx | 3 +- .../Animations/LaserFlow/LaserFlow.jsx | 1 + .../Animations/ShapeBlur/ShapeBlur.jsx | 3 +- src/tailwind/Backgrounds/Ballpit/Ballpit.jsx | 27 ++++----- .../Backgrounds/ColorBends/ColorBends.jsx | 1 + .../FloatingLines/FloatingLines.jsx | 13 +++-- .../GridDistortion/GridDistortion.jsx | 3 +- .../Backgrounds/GridScan/GridScan.jsx | 9 +-- .../Backgrounds/Hyperspeed/Hyperspeed.jsx | 39 ++++++++++--- .../Backgrounds/LiquidEther/LiquidEther.jsx | 1 + .../Backgrounds/PixelBlast/PixelBlast.jsx | 4 +- .../Backgrounds/PixelSnow/PixelSnow.jsx | 11 ++-- .../TextAnimations/ASCIIText/ASCIIText.jsx | 3 +- .../Animations/GhostCursor/GhostCursor.tsx | 3 +- .../Animations/LaserFlow/LaserFlow.tsx | 1 + .../Animations/ShapeBlur/ShapeBlur.tsx | 3 +- .../Backgrounds/Ballpit/Ballpit.tsx | 36 ++++++------ .../Backgrounds/ColorBends/ColorBends.tsx | 1 + .../FloatingLines/FloatingLines.tsx | 11 ++-- .../GridDistortion/GridDistortion.tsx | 3 +- .../Backgrounds/GridScan/GridScan.tsx | 5 +- .../Backgrounds/Hyperspeed/Hyperspeed.tsx | 40 ++++++++++--- .../Backgrounds/LiquidEther/LiquidEther.tsx | 1 + .../Backgrounds/PixelBlast/PixelBlast.tsx | 4 +- .../Backgrounds/PixelSnow/PixelSnow.tsx | 11 ++-- .../TextAnimations/ASCIIText/ASCIIText.tsx | 3 +- .../Animations/GhostCursor/GhostCursor.tsx | 3 +- .../Animations/LaserFlow/LaserFlow.tsx | 47 ++++++++------- .../Animations/PixelTrail/PixelTrail.tsx | 28 ++++++--- .../Animations/ShapeBlur/ShapeBlur.tsx | 55 +++++++++++++----- .../Backgrounds/Ballpit/Ballpit.tsx | 36 ++++++------ .../Backgrounds/ColorBends/ColorBends.tsx | 1 + .../FloatingLines/FloatingLines.tsx | 13 +++-- .../GridDistortion/GridDistortion.tsx | 3 +- .../Backgrounds/GridScan/GridScan.tsx | 9 +-- .../Backgrounds/Hyperspeed/Hyperspeed.tsx | 40 ++++++++++--- .../Backgrounds/LiquidEther/LiquidEther.tsx | 1 + .../Backgrounds/PixelBlast/PixelBlast.tsx | 4 +- .../Backgrounds/PixelSnow/PixelSnow.tsx | 11 ++-- .../TextAnimations/ASCIIText/ASCIIText.tsx | 3 +- 107 files changed, 510 insertions(+), 318 deletions(-) diff --git a/public/r/ASCIIText-JS-CSS.json b/public/r/ASCIIText-JS-CSS.json index 9e177db5..0588c795 100644 --- a/public/r/ASCIIText-JS-CSS.json +++ b/public/r/ASCIIText-JS-CSS.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ASCIIText/ASCIIText.jsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {\n // Font loading failed, continue with fallback\n }\n await document.fonts.ready;\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n }\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer = null;\n let ro = null;\n\n const createAndInit = async (container, w, h) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {\n // Font loading failed, continue with fallback\n }\n await document.fonts.ready;\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n }\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer = null;\n let ro = null;\n\n const createAndInit = async (container, w, h) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/ASCIIText-JS-TW.json b/public/r/ASCIIText-JS-TW.json index 3d58d43e..d34e9d39 100644 --- a/public/r/ASCIIText-JS-TW.json +++ b/public/r/ASCIIText-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ASCIIText/ASCIIText.jsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {}\n await document.fonts.ready;\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n }\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer = null;\n let ro = null;\n\n const createAndInit = async (container, w, h) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {}\n await document.fonts.ready;\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n }\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer = null;\n let ro = null;\n\n const createAndInit = async (container, w, h) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/ASCIIText-TS-CSS.json b/public/r/ASCIIText-TS-CSS.json index 019375bc..c298787b 100644 --- a/public/r/ASCIIText-TS-CSS.json +++ b/public/r/ASCIIText-TS-CSS.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ASCIIText/ASCIIText.tsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer!: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry: THREE.PlaneGeometry | undefined;\n material: THREE.ShaderMaterial | undefined;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center: { x: number; y: number } = { x: 0, y: 0 };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {}\n await document.fonts.ready;\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n }\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer: IntersectionObserver | null = null;\n let ro: ResizeObserver | null = null;\n\n const createAndInit = async (container: HTMLDivElement, w: number, h: number) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current!.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer?.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current!, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current!);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current!, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current!);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer!: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry: THREE.PlaneGeometry | undefined;\n material: THREE.ShaderMaterial | undefined;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center: { x: number; y: number } = { x: 0, y: 0 };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {}\n await document.fonts.ready;\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n }\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer: IntersectionObserver | null = null;\n let ro: ResizeObserver | null = null;\n\n const createAndInit = async (container: HTMLDivElement, w: number, h: number) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current!.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer?.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current!, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current!);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current!, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current!);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/ASCIIText-TS-TW.json b/public/r/ASCIIText-TS-TW.json index fa7a1f9f..bb16ee55 100644 --- a/public/r/ASCIIText-TS-TW.json +++ b/public/r/ASCIIText-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ASCIIText/ASCIIText.tsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry!: THREE.PlaneGeometry;\n material!: THREE.ShaderMaterial;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center!: { x: number; y: number };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {}\n await document.fonts.ready;\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n }\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer: IntersectionObserver | null = null;\n let ro: ResizeObserver | null = null;\n\n const createAndInit = async (container: HTMLDivElement, w: number, h: number) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current!.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer?.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current!, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current!);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current!, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current!);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry!: THREE.PlaneGeometry;\n material!: THREE.ShaderMaterial;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center!: { x: number; y: number };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n }\n\n async init() {\n try {\n await document.fonts.load('600 200px \"IBM Plex Mono\"');\n await document.fonts.load('500 12px \"IBM Plex Mono\"');\n } catch (e) {}\n await document.fonts.ready;\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n if (this.filter) {\n this.filter.dispose();\n if (this.filter.domElement.parentNode) {\n this.container.removeChild(this.filter.domElement);\n }\n }\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n }\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n let cancelled = false;\n let observer: IntersectionObserver | null = null;\n let ro: ResizeObserver | null = null;\n\n const createAndInit = async (container: HTMLDivElement, w: number, h: number) => {\n const instance = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n container,\n w,\n h\n );\n await instance.init();\n return instance;\n };\n\n const setup = async () => {\n const { width, height } = containerRef.current!.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n observer = new IntersectionObserver(\n async ([entry]) => {\n if (cancelled) return;\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n observer?.disconnect();\n observer = null;\n\n if (!cancelled) {\n asciiRef.current = await createAndInit(containerRef.current!, w, h);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n }\n }\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(containerRef.current!);\n return;\n }\n\n asciiRef.current = await createAndInit(containerRef.current!, width, height);\n if (!cancelled && asciiRef.current) {\n asciiRef.current.load();\n\n ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current!);\n }\n };\n\n setup();\n\n return () => {\n cancelled = true;\n if (observer) observer.disconnect();\n if (ro) ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n asciiRef.current = null;\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/Ballpit-JS-CSS.json b/public/r/Ballpit-JS-CSS.json index fa0c174c..6779a2e1 100644 --- a/public/r/Ballpit-JS-CSS.json +++ b/public/r/Ballpit-JS-CSS.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Ballpit/Ballpit.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + "content": "import { useEffect, useRef } from 'react';\nimport {\n Vector3 as a,\n MeshPhysicalMaterial as c,\n InstancedMesh as d,\n Clock as e,\n AmbientLight as f,\n SphereGeometry as g,\n ShaderChunk as h,\n Scene as i,\n Color as l,\n Object3D as m,\n SRGBColorSpace as n,\n MathUtils as o,\n PMREMGenerator as p,\n Vector2 as r,\n WebGLRenderer as s,\n PerspectiveCamera as t,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Plane as w,\n Raycaster as y\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" } ], "registryDependencies": [], diff --git a/public/r/Ballpit-JS-TW.json b/public/r/Ballpit-JS-TW.json index eda060c6..28fc5164 100644 --- a/public/r/Ballpit-JS-TW.json +++ b/public/r/Ballpit-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "Ballpit/Ballpit.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + "content": "import { useEffect, useRef } from 'react';\nimport {\n Vector3 as a,\n MeshPhysicalMaterial as c,\n InstancedMesh as d,\n Clock as e,\n AmbientLight as f,\n SphereGeometry as g,\n ShaderChunk as h,\n Scene as i,\n Color as l,\n Object3D as m,\n SRGBColorSpace as n,\n MathUtils as o,\n PMREMGenerator as p,\n Vector2 as r,\n WebGLRenderer as s,\n PerspectiveCamera as t,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Plane as w,\n Raycaster as y\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" } ], "registryDependencies": [], diff --git a/public/r/Ballpit-TS-CSS.json b/public/r/Ballpit-TS-CSS.json index 49685ea7..e57686b6 100644 --- a/public/r/Ballpit-TS-CSS.json +++ b/public/r/Ballpit-TS-CSS.json @@ -8,12 +8,12 @@ { "type": "registry:component", "path": "Ballpit/Ballpit.tsx", - "content": "import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, { passive: false });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, { passive: false });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, { passive: false });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, { passive: false });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nconst { randFloat, randFloatSpread } = MathUtils;\nconst F = new Vector3();\nconst I = new Vector3();\nconst O = new Vector3();\nconst V = new Vector3();\nconst B = new Vector3();\nconst N = new Vector3();\nconst _ = new Vector3();\nconst j = new Vector3();\nconst H = new Vector3();\nconst T = new Vector3();\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + "content": "import { gsap } from 'gsap';\nimport { Observer } from 'gsap/Observer';\nimport React, { useEffect, useRef } from 'react';\nimport {\n ACESFilmicToneMapping,\n AmbientLight,\n Clock,\n Color,\n InstancedMesh,\n MathUtils,\n MeshPhysicalMaterial,\n Object3D,\n PerspectiveCamera,\n Plane,\n PMREMGenerator,\n PointLight,\n Raycaster,\n Scene,\n ShaderChunk,\n SphereGeometry,\n SRGBColorSpace,\n Vector2,\n Vector3,\n WebGLRenderer,\n WebGLRendererParameters\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n defines: { USE_UV: string };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, { passive: false });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, { passive: false });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, { passive: false });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, { passive: false });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nconst { randFloat, randFloatSpread } = MathUtils;\nconst F = new Vector3();\nconst I = new Vector3();\nconst O = new Vector3();\nconst V = new Vector3();\nconst B = new Vector3();\nconst N = new Vector3();\nconst _ = new Vector3();\nconst j = new Vector3();\nconst H = new Vector3();\nconst T = new Vector3();\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "gsap@^3.13.0" + "gsap@^3.13.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/Ballpit-TS-TW.json b/public/r/Ballpit-TS-TW.json index acc2a2bb..cf9b1360 100644 --- a/public/r/Ballpit-TS-TW.json +++ b/public/r/Ballpit-TS-TW.json @@ -8,12 +8,12 @@ { "type": "registry:component", "path": "Ballpit/Ballpit.tsx", - "content": "import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, {\n passive: false\n });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + "content": "import { gsap } from 'gsap';\nimport { Observer } from 'gsap/Observer';\nimport React, { useEffect, useRef } from 'react';\nimport {\n ACESFilmicToneMapping,\n AmbientLight,\n Clock,\n Color,\n InstancedMesh,\n MathUtils,\n MeshPhysicalMaterial,\n Object3D,\n PerspectiveCamera,\n Plane,\n PMREMGenerator,\n PointLight,\n Raycaster,\n Scene,\n ShaderChunk,\n SphereGeometry,\n SRGBColorSpace,\n Vector2,\n Vector3,\n WebGLRenderer,\n WebGLRendererParameters\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n defines: { USE_UV: string; };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, {\n passive: false\n });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "gsap@^3.13.0" + "gsap@^3.13.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/ColorBends-JS-CSS.json b/public/r/ColorBends-JS-CSS.json index 53d1cbcf..8ed5dace 100644 --- a/public/r/ColorBends-JS-CSS.json +++ b/public/r/ColorBends-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "ColorBends/ColorBends.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './ColorBends.css';\n\nconst MAX_COLORS = 8;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n material.uniforms.uCanvas.value.set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n material.uniforms.uRot.value.set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n material.uniforms.uPointer.value.copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else window.removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, [frequency, mouseInfluence, noise, parallax, scale, speed, transparent, warpStrength]);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = hex => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = material.uniforms.uColors.value[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './ColorBends.css';\n\nconst MAX_COLORS = 8;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n material.uniforms.uCanvas.value.set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n material.uniforms.uRot.value.set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n material.uniforms.uPointer.value.copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else window.removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, [frequency, mouseInfluence, noise, parallax, scale, speed, transparent, warpStrength]);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = hex => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = material.uniforms.uColors.value[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/ColorBends-JS-TW.json b/public/r/ColorBends-JS-TW.json index 5d537f8c..2af6396d 100644 --- a/public/r/ColorBends-JS-TW.json +++ b/public/r/ColorBends-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ColorBends/ColorBends.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst MAX_COLORS = 8;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n // Three r152+ uses outputColorSpace and SRGBColorSpace\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n material.uniforms.uCanvas.value.set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n material.uniforms.uRot.value.set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n material.uniforms.uPointer.value.copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else window.removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, [frequency, mouseInfluence, noise, parallax, scale, speed, transparent, warpStrength]);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = hex => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = material.uniforms.uColors.value[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst MAX_COLORS = 8;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n // Three r152+ uses outputColorSpace and SRGBColorSpace\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n material.uniforms.uCanvas.value.set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n material.uniforms.uRot.value.set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n material.uniforms.uPointer.value.copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else window.removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, [frequency, mouseInfluence, noise, parallax, scale, speed, transparent, warpStrength]);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = hex => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = material.uniforms.uColors.value[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = e => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/ColorBends-TS-CSS.json b/public/r/ColorBends-TS-CSS.json index 22511f44..bdbd60d9 100644 --- a/public/r/ColorBends-TS-CSS.json +++ b/public/r/ColorBends-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "ColorBends/ColorBends.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './ColorBends.css';\n\ntype ColorBendsProps = {\n className?: string;\n style?: React.CSSProperties;\n rotation?: number;\n speed?: number;\n colors?: string[];\n transparent?: boolean;\n autoRotate?: number;\n scale?: number;\n frequency?: number;\n warpStrength?: number;\n mouseInfluence?: number;\n parallax?: number;\n noise?: number;\n};\n\nconst MAX_COLORS = 8 as const;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}: ColorBendsProps) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current!;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n (renderer as any).outputColorSpace = (THREE as any).SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n (material.uniforms.uCanvas.value as THREE.Vector2).set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n (window as Window).addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n (material.uniforms.uRot.value as THREE.Vector2).set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n (material.uniforms.uPointer.value as THREE.Vector2).copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else (window as Window).removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, []);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = (hex: string) => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = (material.uniforms.uColors.value as THREE.Vector3[])[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './ColorBends.css';\n\ntype ColorBendsProps = {\n className?: string;\n style?: React.CSSProperties;\n rotation?: number;\n speed?: number;\n colors?: string[];\n transparent?: boolean;\n autoRotate?: number;\n scale?: number;\n frequency?: number;\n warpStrength?: number;\n mouseInfluence?: number;\n parallax?: number;\n noise?: number;\n};\n\nconst MAX_COLORS = 8 as const;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}: ColorBendsProps) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current!;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n (renderer as any).outputColorSpace = (THREE as any).SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n (material.uniforms.uCanvas.value as THREE.Vector2).set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n (window as Window).addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n (material.uniforms.uRot.value as THREE.Vector2).set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n (material.uniforms.uPointer.value as THREE.Vector2).copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else (window as Window).removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, []);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = (hex: string) => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = (material.uniforms.uColors.value as THREE.Vector3[])[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/ColorBends-TS-TW.json b/public/r/ColorBends-TS-TW.json index 42901580..56bc833c 100644 --- a/public/r/ColorBends-TS-TW.json +++ b/public/r/ColorBends-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ColorBends/ColorBends.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\ntype ColorBendsProps = {\n className?: string;\n style?: React.CSSProperties;\n rotation?: number;\n speed?: number;\n colors?: string[];\n transparent?: boolean;\n autoRotate?: number;\n scale?: number;\n frequency?: number;\n warpStrength?: number;\n mouseInfluence?: number;\n parallax?: number;\n noise?: number;\n};\n\nconst MAX_COLORS = 8 as const;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}: ColorBendsProps) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current!;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n (renderer as any).outputColorSpace = (THREE as any).SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n (material.uniforms.uCanvas.value as THREE.Vector2).set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n (window as Window).addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n (material.uniforms.uRot.value as THREE.Vector2).set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n (material.uniforms.uPointer.value as THREE.Vector2).copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else (window as Window).removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, []);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = (hex: string) => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = (material.uniforms.uColors.value as THREE.Vector3[])[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\ntype ColorBendsProps = {\n className?: string;\n style?: React.CSSProperties;\n rotation?: number;\n speed?: number;\n colors?: string[];\n transparent?: boolean;\n autoRotate?: number;\n scale?: number;\n frequency?: number;\n warpStrength?: number;\n mouseInfluence?: number;\n parallax?: number;\n noise?: number;\n};\n\nconst MAX_COLORS = 8 as const;\n\nconst frag = `\n#define MAX_COLORS ${MAX_COLORS}\nuniform vec2 uCanvas;\nuniform float uTime;\nuniform float uSpeed;\nuniform vec2 uRot;\nuniform int uColorCount;\nuniform vec3 uColors[MAX_COLORS];\nuniform int uTransparent;\nuniform float uScale;\nuniform float uFrequency;\nuniform float uWarpStrength;\nuniform vec2 uPointer; // in NDC [-1,1]\nuniform float uMouseInfluence;\nuniform float uParallax;\nuniform float uNoise;\nvarying vec2 vUv;\n\nvoid main() {\n float t = uTime * uSpeed;\n vec2 p = vUv * 2.0 - 1.0;\n p += uPointer * uParallax * 0.1;\n vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);\n vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);\n q /= max(uScale, 0.0001);\n q /= 0.5 + 0.2 * dot(q, q);\n q += 0.2 * cos(t) - 7.56;\n vec2 toward = (uPointer - rp);\n q += toward * uMouseInfluence * 0.2;\n\n vec3 col = vec3(0.0);\n float a = 1.0;\n\n if (uColorCount > 0) {\n vec2 s = q;\n vec3 sumCol = vec3(0.0);\n float cover = 0.0;\n for (int i = 0; i < MAX_COLORS; ++i) {\n if (i >= uColorCount) break;\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3); // strong response across 0..1\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);\n float m = mix(m0, m1, kMix);\n float w = 1.0 - exp(-6.0 / exp(6.0 * m));\n sumCol += uColors[i] * w;\n cover = max(cover, w);\n }\n col = clamp(sumCol, 0.0, 1.0);\n a = uTransparent > 0 ? cover : 1.0;\n } else {\n vec2 s = q;\n for (int k = 0; k < 3; ++k) {\n s -= 0.01;\n vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));\n float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float kBelow = clamp(uWarpStrength, 0.0, 1.0);\n float kMix = pow(kBelow, 0.3);\n float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);\n vec2 disp = (r - s) * kBelow;\n vec2 warped = s + disp * gain;\n float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);\n float m = mix(m0, m1, kMix);\n col[k] = 1.0 - exp(-6.0 / exp(6.0 * m));\n }\n a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;\n }\n\n if (uNoise > 0.0001) {\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n }\n\n vec3 rgb = (uTransparent > 0) ? col * a : col;\n gl_FragColor = vec4(rgb, a);\n}\n`;\n\nconst vert = `\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nexport default function ColorBends({\n className,\n style,\n rotation = 45,\n speed = 0.2,\n colors = [],\n transparent = true,\n autoRotate = 0,\n scale = 1,\n frequency = 1,\n warpStrength = 1,\n mouseInfluence = 1,\n parallax = 0.5,\n noise = 0.1\n}: ColorBendsProps) {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const rafRef = useRef(null);\n const materialRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rotationRef = useRef(rotation);\n const autoRotateRef = useRef(autoRotate);\n const pointerTargetRef = useRef(new THREE.Vector2(0, 0));\n const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));\n const pointerSmoothRef = useRef(8);\n\n useEffect(() => {\n const container = containerRef.current!;\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.PlaneGeometry(2, 2);\n const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));\n const material = new THREE.ShaderMaterial({\n vertexShader: vert,\n fragmentShader: frag,\n uniforms: {\n uCanvas: { value: new THREE.Vector2(1, 1) },\n uTime: { value: 0 },\n uSpeed: { value: speed },\n uRot: { value: new THREE.Vector2(1, 0) },\n uColorCount: { value: 0 },\n uColors: { value: uColorsArray },\n uTransparent: { value: transparent ? 1 : 0 },\n uScale: { value: scale },\n uFrequency: { value: frequency },\n uWarpStrength: { value: warpStrength },\n uPointer: { value: new THREE.Vector2(0, 0) },\n uMouseInfluence: { value: mouseInfluence },\n uParallax: { value: parallax },\n uNoise: { value: noise }\n },\n premultipliedAlpha: true,\n transparent: true\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geometry, material);\n scene.add(mesh);\n\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n });\n rendererRef.current = renderer;\n (renderer as any).outputColorSpace = (THREE as any).SRGBColorSpace;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setClearColor(0x000000, transparent ? 0 : 1);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n container.appendChild(renderer.domElement);\n\n const clock = new THREE.Clock();\n\n const handleResize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n (material.uniforms.uCanvas.value as THREE.Vector2).set(w, h);\n };\n\n handleResize();\n\n if ('ResizeObserver' in window) {\n const ro = new ResizeObserver(handleResize);\n ro.observe(container);\n resizeObserverRef.current = ro;\n } else {\n (window as Window).addEventListener('resize', handleResize);\n }\n\n const loop = () => {\n const dt = clock.getDelta();\n const elapsed = clock.elapsedTime;\n material.uniforms.uTime.value = elapsed;\n\n const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;\n const rad = (deg * Math.PI) / 180;\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n (material.uniforms.uRot.value as THREE.Vector2).set(c, s);\n\n const cur = pointerCurrentRef.current;\n const tgt = pointerTargetRef.current;\n const amt = Math.min(1, dt * pointerSmoothRef.current);\n cur.lerp(tgt, amt);\n (material.uniforms.uPointer.value as THREE.Vector2).copy(cur);\n renderer.render(scene, camera);\n rafRef.current = requestAnimationFrame(loop);\n };\n rafRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) resizeObserverRef.current.disconnect();\n else (window as Window).removeEventListener('resize', handleResize);\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement && renderer.domElement.parentElement === container) {\n container.removeChild(renderer.domElement);\n }\n };\n }, []);\n\n useEffect(() => {\n const material = materialRef.current;\n const renderer = rendererRef.current;\n if (!material) return;\n\n rotationRef.current = rotation;\n autoRotateRef.current = autoRotate;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uScale.value = scale;\n material.uniforms.uFrequency.value = frequency;\n material.uniforms.uWarpStrength.value = warpStrength;\n material.uniforms.uMouseInfluence.value = mouseInfluence;\n material.uniforms.uParallax.value = parallax;\n material.uniforms.uNoise.value = noise;\n\n const toVec3 = (hex: string) => {\n const h = hex.replace('#', '').trim();\n const v =\n h.length === 3\n ? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]\n : [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);\n };\n\n const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);\n for (let i = 0; i < MAX_COLORS; i++) {\n const vec = (material.uniforms.uColors.value as THREE.Vector3[])[i];\n if (i < arr.length) vec.copy(arr[i]);\n else vec.set(0, 0, 0);\n }\n material.uniforms.uColorCount.value = arr.length;\n\n material.uniforms.uTransparent.value = transparent ? 1 : 0;\n if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);\n }, [\n rotation,\n autoRotate,\n speed,\n scale,\n frequency,\n warpStrength,\n mouseInfluence,\n parallax,\n noise,\n colors,\n transparent\n ]);\n\n useEffect(() => {\n const material = materialRef.current;\n const container = containerRef.current;\n if (!material || !container) return;\n\n const handlePointerMove = (e: PointerEvent) => {\n const rect = container.getBoundingClientRect();\n const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;\n const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);\n pointerTargetRef.current.set(x, y);\n };\n\n container.addEventListener('pointermove', handlePointerMove);\n return () => {\n container.removeEventListener('pointermove', handlePointerMove);\n };\n }, []);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/FloatingLines-JS-CSS.json b/public/r/FloatingLines-JS-CSS.json index d797a956..59415351 100644 --- a/public/r/FloatingLines-JS-CSS.json +++ b/public/r/FloatingLines-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "FloatingLines/FloatingLines.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n Mesh,\n ShaderMaterial,\n Vector3,\n Vector2,\n Clock\n} from 'three';\n\nimport './FloatingLines.css';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\nfunction hexToVec3(hex) {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = waveType => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = waveType => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = event => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport {\n Clock,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nimport './FloatingLines.css';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\nfunction hexToVec3(hex) {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = waveType => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = waveType => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = event => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/FloatingLines-JS-TW.json b/public/r/FloatingLines-JS-TW.json index b355e4cd..99a9657d 100644 --- a/public/r/FloatingLines-JS-TW.json +++ b/public/r/FloatingLines-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "FloatingLines/FloatingLines.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n Mesh,\n ShaderMaterial,\n Vector3,\n Vector2,\n Clock\n} from 'three';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\nfunction hexToVec3(hex) {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = waveType => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = waveType => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = event => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport {\n Clock,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\nfunction hexToVec3(hex) {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = waveType => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = waveType => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = event => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/FloatingLines-TS-CSS.json b/public/r/FloatingLines-TS-CSS.json index b2e64328..f9d82360 100644 --- a/public/r/FloatingLines-TS-CSS.json +++ b/public/r/FloatingLines-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "FloatingLines/FloatingLines.tsx", - "content": "import { useEffect, useRef } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n Mesh,\n ShaderMaterial,\n Vector3,\n Vector2,\n Clock\n} from 'three';\n\nimport './FloatingLines.css';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\ntype WavePosition = {\n x: number;\n y: number;\n rotate: number;\n};\n\ntype FloatingLinesProps = {\n linesGradient?: string[];\n enabledWaves?: Array<'top' | 'middle' | 'bottom'>;\n lineCount?: number | number[];\n lineDistance?: number | number[];\n topWavePosition?: WavePosition;\n middleWavePosition?: WavePosition;\n bottomWavePosition?: WavePosition;\n animationSpeed?: number;\n interactive?: boolean;\n bendRadius?: number;\n bendStrength?: number;\n mouseDamping?: number;\n parallax?: boolean;\n parallaxStrength?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n};\n\nfunction hexToVec3(hex: string): Vector3 {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}: FloatingLinesProps) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current!;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = (event: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport {\n Clock,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nimport './FloatingLines.css';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\ntype WavePosition = {\n x: number;\n y: number;\n rotate: number;\n};\n\ntype FloatingLinesProps = {\n linesGradient?: string[];\n enabledWaves?: Array<'top' | 'middle' | 'bottom'>;\n lineCount?: number | number[];\n lineDistance?: number | number[];\n topWavePosition?: WavePosition;\n middleWavePosition?: WavePosition;\n bottomWavePosition?: WavePosition;\n animationSpeed?: number;\n interactive?: boolean;\n bendRadius?: number;\n bendStrength?: number;\n mouseDamping?: number;\n parallax?: boolean;\n parallaxStrength?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n};\n\nfunction hexToVec3(hex: string): Vector3 {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}: FloatingLinesProps) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current!;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = (event: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/FloatingLines-TS-TW.json b/public/r/FloatingLines-TS-TW.json index 7b41fe06..05700a8d 100644 --- a/public/r/FloatingLines-TS-TW.json +++ b/public/r/FloatingLines-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "FloatingLines/FloatingLines.tsx", - "content": "import { useEffect, useRef } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n Mesh,\n ShaderMaterial,\n Vector3,\n Vector2,\n Clock\n} from 'three';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\ntype WavePosition = {\n x: number;\n y: number;\n rotate: number;\n};\n\ntype FloatingLinesProps = {\n linesGradient?: string[];\n enabledWaves?: Array<'top' | 'middle' | 'bottom'>;\n lineCount?: number | number[];\n lineDistance?: number | number[];\n topWavePosition?: WavePosition;\n middleWavePosition?: WavePosition;\n bottomWavePosition?: WavePosition;\n animationSpeed?: number;\n interactive?: boolean;\n bendRadius?: number;\n bendStrength?: number;\n mouseDamping?: number;\n parallax?: boolean;\n parallaxStrength?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n};\n\nfunction hexToVec3(hex: string): Vector3 {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}: FloatingLinesProps) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current!;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = (event: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport {\n Clock,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nconst vertexShader = `\nprecision highp float;\n\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float animationSpeed;\n\nuniform bool enableTop;\nuniform bool enableMiddle;\nuniform bool enableBottom;\n\nuniform int topLineCount;\nuniform int middleLineCount;\nuniform int bottomLineCount;\n\nuniform float topLineDistance;\nuniform float middleLineDistance;\nuniform float bottomLineDistance;\n\nuniform vec3 topWavePosition;\nuniform vec3 middleWavePosition;\nuniform vec3 bottomWavePosition;\n\nuniform vec2 iMouse;\nuniform bool interactive;\nuniform float bendRadius;\nuniform float bendStrength;\nuniform float bendInfluence;\n\nuniform bool parallax;\nuniform float parallaxStrength;\nuniform vec2 parallaxOffset;\n\nuniform vec3 lineGradient[8];\nuniform int lineGradientCount;\n\nconst vec3 BLACK = vec3(0.0);\nconst vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;\nconst vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;\n\nmat2 rotate(float r) {\n return mat2(cos(r), sin(r), -sin(r), cos(r));\n}\n\nvec3 background_color(vec2 uv) {\n vec3 col = vec3(0.0);\n\n float y = sin(uv.x - 0.2) * 0.3 - 0.1;\n float m = uv.y - y;\n\n col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));\n col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));\n return col * 0.5;\n}\n\nvec3 getLineColor(float t, vec3 baseColor) {\n if (lineGradientCount <= 0) {\n return baseColor;\n }\n\n vec3 gradientColor;\n \n if (lineGradientCount == 1) {\n gradientColor = lineGradient[0];\n } else {\n float clampedT = clamp(t, 0.0, 0.9999);\n float scaled = clampedT * float(lineGradientCount - 1);\n int idx = int(floor(scaled));\n float f = fract(scaled);\n int idx2 = min(idx + 1, lineGradientCount - 1);\n\n vec3 c1 = lineGradient[idx];\n vec3 c2 = lineGradient[idx2];\n \n gradientColor = mix(c1, c2, f);\n }\n \n return gradientColor * 0.5;\n}\n\n float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {\n float time = iTime * animationSpeed;\n\n float x_offset = offset;\n float x_movement = time * 0.1;\n float amp = sin(offset + time * 0.2) * 0.3;\n float y = sin(uv.x + x_offset + x_movement) * amp;\n\n if (shouldBend) {\n vec2 d = screenUv - mouseUv;\n float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor\n float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;\n y += bendOffset;\n }\n\n float m = uv.y - y;\n return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n baseUv.y *= -1.0;\n \n if (parallax) {\n baseUv += parallaxOffset;\n }\n\n vec3 col = vec3(0.0);\n\n vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);\n\n vec2 mouseUv = vec2(0.0);\n if (interactive) {\n mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;\n mouseUv.y *= -1.0;\n }\n \n if (enableBottom) {\n for (int i = 0; i < bottomLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(bottomLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),\n 1.5 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.2;\n }\n }\n\n if (enableMiddle) {\n for (int i = 0; i < middleLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(middleLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = middleWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n col += lineCol * wave(\n ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),\n 2.0 + 0.15 * fi,\n baseUv,\n mouseUv,\n interactive\n );\n }\n }\n\n if (enableTop) {\n for (int i = 0; i < topLineCount; ++i) {\n float fi = float(i);\n float t = fi / max(float(topLineCount - 1), 1.0);\n vec3 lineCol = getLineColor(t, b);\n \n float angle = topWavePosition.z * log(length(baseUv) + 1.0);\n vec2 ruv = baseUv * rotate(angle);\n ruv.x *= -1.0;\n col += lineCol * wave(\n ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),\n 1.0 + 0.2 * fi,\n baseUv,\n mouseUv,\n interactive\n ) * 0.1;\n }\n }\n\n fragColor = vec4(col, 1.0);\n}\n\nvoid main() {\n vec4 color = vec4(0.0);\n mainImage(color, gl_FragCoord.xy);\n gl_FragColor = color;\n}\n`;\n\nconst MAX_GRADIENT_STOPS = 8;\n\ntype WavePosition = {\n x: number;\n y: number;\n rotate: number;\n};\n\ntype FloatingLinesProps = {\n linesGradient?: string[];\n enabledWaves?: Array<'top' | 'middle' | 'bottom'>;\n lineCount?: number | number[];\n lineDistance?: number | number[];\n topWavePosition?: WavePosition;\n middleWavePosition?: WavePosition;\n bottomWavePosition?: WavePosition;\n animationSpeed?: number;\n interactive?: boolean;\n bendRadius?: number;\n bendStrength?: number;\n mouseDamping?: number;\n parallax?: boolean;\n parallaxStrength?: number;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n};\n\nfunction hexToVec3(hex: string): Vector3 {\n let value = hex.trim();\n\n if (value.startsWith('#')) {\n value = value.slice(1);\n }\n\n let r = 255;\n let g = 255;\n let b = 255;\n\n if (value.length === 3) {\n r = parseInt(value[0] + value[0], 16);\n g = parseInt(value[1] + value[1], 16);\n b = parseInt(value[2] + value[2], 16);\n } else if (value.length === 6) {\n r = parseInt(value.slice(0, 2), 16);\n g = parseInt(value.slice(2, 4), 16);\n b = parseInt(value.slice(4, 6), 16);\n }\n\n return new Vector3(r / 255, g / 255, b / 255);\n}\n\nexport default function FloatingLines({\n linesGradient,\n enabledWaves = ['top', 'middle', 'bottom'],\n lineCount = [6],\n lineDistance = [5],\n topWavePosition,\n middleWavePosition,\n bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },\n animationSpeed = 1,\n interactive = true,\n bendRadius = 5.0,\n bendStrength = -0.5,\n mouseDamping = 0.05,\n parallax = true,\n parallaxStrength = 0.2,\n mixBlendMode = 'screen'\n}: FloatingLinesProps) {\n const containerRef = useRef(null);\n const targetMouseRef = useRef(new Vector2(-1000, -1000));\n const currentMouseRef = useRef(new Vector2(-1000, -1000));\n const targetInfluenceRef = useRef(0);\n const currentInfluenceRef = useRef(0);\n const targetParallaxRef = useRef(new Vector2(0, 0));\n const currentParallaxRef = useRef(new Vector2(0, 0));\n\n const getLineCount = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineCount === 'number') return lineCount;\n if (!enabledWaves.includes(waveType)) return 0;\n const index = enabledWaves.indexOf(waveType);\n return lineCount[index] ?? 6;\n };\n\n const getLineDistance = (waveType: 'top' | 'middle' | 'bottom'): number => {\n if (typeof lineDistance === 'number') return lineDistance;\n if (!enabledWaves.includes(waveType)) return 0.1;\n const index = enabledWaves.indexOf(waveType);\n return lineDistance[index] ?? 0.1;\n };\n\n const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;\n const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;\n const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;\n\n const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;\n const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;\n const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new Scene();\n\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n camera.position.z = 1;\n\n const renderer = new WebGLRenderer({ antialias: true, alpha: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n containerRef.current.appendChild(renderer.domElement);\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new Vector3(1, 1, 1) },\n animationSpeed: { value: animationSpeed },\n\n enableTop: { value: enabledWaves.includes('top') },\n enableMiddle: { value: enabledWaves.includes('middle') },\n enableBottom: { value: enabledWaves.includes('bottom') },\n\n topLineCount: { value: topLineCount },\n middleLineCount: { value: middleLineCount },\n bottomLineCount: { value: bottomLineCount },\n\n topLineDistance: { value: topLineDistance },\n middleLineDistance: { value: middleLineDistance },\n bottomLineDistance: { value: bottomLineDistance },\n\n topWavePosition: {\n value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)\n },\n middleWavePosition: {\n value: new Vector3(\n middleWavePosition?.x ?? 5.0,\n middleWavePosition?.y ?? 0.0,\n middleWavePosition?.rotate ?? 0.2\n )\n },\n bottomWavePosition: {\n value: new Vector3(\n bottomWavePosition?.x ?? 2.0,\n bottomWavePosition?.y ?? -0.7,\n bottomWavePosition?.rotate ?? 0.4\n )\n },\n\n iMouse: { value: new Vector2(-1000, -1000) },\n interactive: { value: interactive },\n bendRadius: { value: bendRadius },\n bendStrength: { value: bendStrength },\n bendInfluence: { value: 0 },\n\n parallax: { value: parallax },\n parallaxStrength: { value: parallaxStrength },\n parallaxOffset: { value: new Vector2(0, 0) },\n\n lineGradient: {\n value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))\n },\n lineGradientCount: { value: 0 }\n };\n\n if (linesGradient && linesGradient.length > 0) {\n const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);\n uniforms.lineGradientCount.value = stops.length;\n\n stops.forEach((hex, i) => {\n const color = hexToVec3(hex);\n uniforms.lineGradient.value[i].set(color.x, color.y, color.z);\n });\n }\n\n const material = new ShaderMaterial({\n uniforms,\n vertexShader,\n fragmentShader\n });\n\n const geometry = new PlaneGeometry(2, 2);\n const mesh = new Mesh(geometry, material);\n scene.add(mesh);\n\n const clock = new Clock();\n\n const setSize = () => {\n const el = containerRef.current!;\n const width = el.clientWidth || 1;\n const height = el.clientHeight || 1;\n\n renderer.setSize(width, height, false);\n\n const canvasWidth = renderer.domElement.width;\n const canvasHeight = renderer.domElement.height;\n uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);\n };\n\n setSize();\n\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(setSize) : null;\n\n if (ro && containerRef.current) {\n ro.observe(containerRef.current);\n }\n\n const handlePointerMove = (event: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const dpr = renderer.getPixelRatio();\n\n targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);\n targetInfluenceRef.current = 1.0;\n\n if (parallax) {\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const offsetX = (x - centerX) / rect.width;\n const offsetY = -(y - centerY) / rect.height;\n targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);\n }\n };\n\n const handlePointerLeave = () => {\n targetInfluenceRef.current = 0.0;\n };\n\n if (interactive) {\n renderer.domElement.addEventListener('pointermove', handlePointerMove);\n renderer.domElement.addEventListener('pointerleave', handlePointerLeave);\n }\n\n let raf = 0;\n const renderLoop = () => {\n uniforms.iTime.value = clock.getElapsedTime();\n\n if (interactive) {\n currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);\n uniforms.iMouse.value.copy(currentMouseRef.current);\n\n currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;\n uniforms.bendInfluence.value = currentInfluenceRef.current;\n }\n\n if (parallax) {\n currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);\n uniforms.parallaxOffset.value.copy(currentParallaxRef.current);\n }\n\n renderer.render(scene, camera);\n raf = requestAnimationFrame(renderLoop);\n };\n renderLoop();\n\n return () => {\n cancelAnimationFrame(raf);\n if (ro && containerRef.current) {\n ro.disconnect();\n }\n\n if (interactive) {\n renderer.domElement.removeEventListener('pointermove', handlePointerMove);\n renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);\n }\n\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n };\n }, [\n linesGradient,\n enabledWaves,\n lineCount,\n lineDistance,\n topWavePosition,\n middleWavePosition,\n bottomWavePosition,\n animationSpeed,\n interactive,\n bendRadius,\n bendStrength,\n mouseDamping,\n parallax,\n parallaxStrength\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/GhostCursor-JS-CSS.json b/public/r/GhostCursor-JS-CSS.json index 0abf0643..54f1b703 100644 --- a/public/r/GhostCursor-JS-CSS.json +++ b/public/r/GhostCursor-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "GhostCursor/GhostCursor.jsx", - "content": "import { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\nimport './GhostCursor.css';\n\nconst GhostCursor = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current;\n const comp = composerRef.current;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value;\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = e => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n materialRef.current.uniforms.iBaseColor.value.set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return
;\n};\n\nexport default GhostCursor;\n" + "content": "import { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\nimport './GhostCursor.css';\n\nconst GhostCursor = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current;\n const comp = composerRef.current;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value;\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = e => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n materialRef.current.uniforms.iBaseColor.value.set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return
;\n};\n\nexport default GhostCursor;\n" } ], "registryDependencies": [], diff --git a/public/r/GhostCursor-JS-TW.json b/public/r/GhostCursor-JS-TW.json index 31523a1e..c32ea90e 100644 --- a/public/r/GhostCursor-JS-TW.json +++ b/public/r/GhostCursor-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "GhostCursor/GhostCursor.jsx", - "content": "import { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\n\nconst GhostCursor = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n renderer.domElement.style.display = 'block';\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.background = 'transparent';\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current;\n const comp = composerRef.current;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value;\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = e => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n materialRef.current.uniforms.iBaseColor.value.set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return
;\n};\n\nexport default GhostCursor;\n" + "content": "import { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\n\nconst GhostCursor = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n renderer.domElement.style.display = 'block';\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.background = 'transparent';\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current;\n const comp = composerRef.current;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value;\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = e => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n materialRef.current.uniforms.iBaseColor.value.set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return
;\n};\n\nexport default GhostCursor;\n" } ], "registryDependencies": [], diff --git a/public/r/GhostCursor-TS-CSS.json b/public/r/GhostCursor-TS-CSS.json index a62cb7e4..5ba7a8e3 100644 --- a/public/r/GhostCursor-TS-CSS.json +++ b/public/r/GhostCursor-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "GhostCursor/GhostCursor.tsx", - "content": "import React, { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\nimport './GhostCursor.css';\n\ntype GhostCursorProps = {\n className?: string;\n style?: React.CSSProperties;\n\n trailLength?: number;\n inertia?: number;\n grainIntensity?: number;\n bloomStrength?: number;\n bloomRadius?: number;\n bloomThreshold?: number;\n\n brightness?: number;\n color?: string;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n edgeIntensity?: number;\n\n maxDevicePixelRatio?: number;\n targetPixels?: number;\n fadeDelayMs?: number;\n fadeDurationMs?: number;\n zIndex?: number;\n};\n\nconst GhostCursor: React.FC = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n // Trail circular buffer\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el: HTMLElement) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader as any);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current!;\n const comp = composerRef.current!;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value as THREE.Vector2[];\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n (materialRef.current.uniforms.iBaseColor.value as THREE.Vector3).set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return
;\n};\n\nexport default GhostCursor;\n" + "content": "import React, { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\nimport './GhostCursor.css';\n\ntype GhostCursorProps = {\n className?: string;\n style?: React.CSSProperties;\n\n trailLength?: number;\n inertia?: number;\n grainIntensity?: number;\n bloomStrength?: number;\n bloomRadius?: number;\n bloomThreshold?: number;\n\n brightness?: number;\n color?: string;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n edgeIntensity?: number;\n\n maxDevicePixelRatio?: number;\n targetPixels?: number;\n fadeDelayMs?: number;\n fadeDurationMs?: number;\n zIndex?: number;\n};\n\nconst GhostCursor: React.FC = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n // Trail circular buffer\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el: HTMLElement) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader as any);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current!;\n const comp = composerRef.current!;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value as THREE.Vector2[];\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n (materialRef.current.uniforms.iBaseColor.value as THREE.Vector3).set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return
;\n};\n\nexport default GhostCursor;\n" } ], "registryDependencies": [], diff --git a/public/r/GhostCursor-TS-TW.json b/public/r/GhostCursor-TS-TW.json index cf7088c8..eec85cd9 100644 --- a/public/r/GhostCursor-TS-TW.json +++ b/public/r/GhostCursor-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "GhostCursor/GhostCursor.tsx", - "content": "import React, { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\n\ntype GhostCursorProps = {\n className?: string;\n style?: React.CSSProperties;\n\n trailLength?: number;\n inertia?: number;\n grainIntensity?: number;\n bloomStrength?: number;\n bloomRadius?: number;\n bloomThreshold?: number;\n\n brightness?: number;\n color?: string;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n edgeIntensity?: number;\n\n maxDevicePixelRatio?: number;\n targetPixels?: number;\n fadeDelayMs?: number;\n fadeDurationMs?: number;\n zIndex?: number;\n};\n\nconst GhostCursor: React.FC = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n // Trail circular buffer\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el: HTMLElement) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n renderer.domElement.style.display = 'block';\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.background = 'transparent';\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader as any);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current!;\n const comp = composerRef.current!;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value as THREE.Vector2[];\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n (materialRef.current.uniforms.iBaseColor.value as THREE.Vector3).set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return (\n
\n );\n};\n\nexport default GhostCursor;\n" + "content": "import React, { useEffect, useMemo, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';\nimport { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';\nimport { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';\nimport { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';\n\ntype GhostCursorProps = {\n className?: string;\n style?: React.CSSProperties;\n\n trailLength?: number;\n inertia?: number;\n grainIntensity?: number;\n bloomStrength?: number;\n bloomRadius?: number;\n bloomThreshold?: number;\n\n brightness?: number;\n color?: string;\n mixBlendMode?: React.CSSProperties['mixBlendMode'];\n edgeIntensity?: number;\n\n maxDevicePixelRatio?: number;\n targetPixels?: number;\n fadeDelayMs?: number;\n fadeDurationMs?: number;\n zIndex?: number;\n};\n\nconst GhostCursor: React.FC = ({\n className,\n style,\n trailLength = 50,\n inertia = 0.5,\n grainIntensity = 0.05,\n bloomStrength = 0.1,\n bloomRadius = 1.0,\n bloomThreshold = 0.025,\n\n brightness = 1,\n color = '#B19EEF',\n mixBlendMode = 'screen',\n edgeIntensity = 0,\n\n maxDevicePixelRatio = 0.5,\n targetPixels,\n\n fadeDelayMs,\n fadeDurationMs,\n zIndex = 10\n}) => {\n const containerRef = useRef(null);\n const rendererRef = useRef(null);\n const composerRef = useRef(null);\n const materialRef = useRef(null);\n const bloomPassRef = useRef(null);\n const filmPassRef = useRef(null);\n\n // Trail circular buffer\n const trailBufRef = useRef([]);\n const headRef = useRef(0);\n\n const rafRef = useRef(null);\n const resizeObsRef = useRef(null);\n const currentMouseRef = useRef(new THREE.Vector2(0.5, 0.5));\n const velocityRef = useRef(new THREE.Vector2(0, 0));\n const fadeOpacityRef = useRef(1.0);\n const lastMoveTimeRef = useRef(typeof performance !== 'undefined' ? performance.now() : Date.now());\n const pointerActiveRef = useRef(false);\n const runningRef = useRef(false);\n\n const isTouch = useMemo(\n () => typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0),\n []\n );\n\n const pixelBudget = targetPixels ?? (isTouch ? 0.9e6 : 1.3e6);\n const fadeDelay = fadeDelayMs ?? (isTouch ? 500 : 1000);\n const fadeDuration = fadeDurationMs ?? (isTouch ? 1000 : 1500);\n\n const baseVertexShader = `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n }\n `;\n\n const fragmentShader = `\n uniform float iTime;\n uniform vec3 iResolution;\n uniform vec2 iMouse;\n uniform vec2 iPrevMouse[MAX_TRAIL_LENGTH];\n uniform float iOpacity;\n uniform float iScale;\n uniform vec3 iBaseColor;\n uniform float iBrightness;\n uniform float iEdgeIntensity;\n varying vec2 vUv;\n\n float hash(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7))) * 43758.5453123); }\n float noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n f *= f * (3. - 2. * f);\n return mix(mix(hash(i + vec2(0.,0.)), hash(i + vec2(1.,0.)), f.x),\n mix(hash(i + vec2(0.,1.)), hash(i + vec2(1.,1.)), f.x), f.y);\n }\n float fbm(vec2 p){\n float v = 0.0;\n float a = 0.5;\n mat2 m = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for(int i=0;i<5;i++){\n v += a * noise(p);\n p = m * p * 2.0;\n a *= 0.5;\n }\n return v;\n }\n vec3 tint1(vec3 base){ return mix(base, vec3(1.0), 0.15); }\n vec3 tint2(vec3 base){ return mix(base, vec3(0.8, 0.9, 1.0), 0.25); }\n\n vec4 blob(vec2 p, vec2 mousePos, float intensity, float activity) {\n vec2 q = vec2(fbm(p * iScale + iTime * 0.1), fbm(p * iScale + vec2(5.2,1.3) + iTime * 0.1));\n vec2 r = vec2(fbm(p * iScale + q * 1.5 + iTime * 0.15), fbm(p * iScale + q * 1.5 + vec2(8.3,2.8) + iTime * 0.15));\n\n float smoke = fbm(p * iScale + r * 0.8);\n float radius = 0.5 + 0.3 * (1.0 / iScale);\n float distFactor = 1.0 - smoothstep(0.0, radius * activity, length(p - mousePos));\n float alpha = pow(smoke, 2.5) * distFactor;\n\n vec3 c1 = tint1(iBaseColor);\n vec3 c2 = tint2(iBaseColor);\n vec3 color = mix(c1, c2, sin(iTime * 0.5) * 0.5 + 0.5);\n\n return vec4(color * alpha * intensity, alpha * intensity);\n }\n\n void main() {\n vec2 uv = (gl_FragCoord.xy / iResolution.xy * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n vec2 mouse = (iMouse * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n\n vec3 colorAcc = vec3(0.0);\n float alphaAcc = 0.0;\n\n vec4 b = blob(uv, mouse, 1.0, iOpacity);\n colorAcc += b.rgb;\n alphaAcc += b.a;\n\n for (int i = 0; i < MAX_TRAIL_LENGTH; i++) {\n vec2 pm = (iPrevMouse[i] * 2.0 - 1.0) * vec2(iResolution.x / iResolution.y, 1.0);\n float t = 1.0 - float(i) / float(MAX_TRAIL_LENGTH);\n t = pow(t, 2.0);\n if (t > 0.01) {\n vec4 bt = blob(uv, pm, t * 0.8, iOpacity);\n colorAcc += bt.rgb;\n alphaAcc += bt.a;\n }\n }\n\n colorAcc *= iBrightness;\n\n vec2 uv01 = gl_FragCoord.xy / iResolution.xy;\n float edgeDist = min(min(uv01.x, 1.0 - uv01.x), min(uv01.y, 1.0 - uv01.y));\n float distFromEdge = clamp(edgeDist * 2.0, 0.0, 1.0);\n float k = clamp(iEdgeIntensity, 0.0, 1.0);\n float edgeMask = mix(1.0 - k, 1.0, distFromEdge);\n\n float outAlpha = clamp(alphaAcc * iOpacity * edgeMask, 0.0, 1.0);\n gl_FragColor = vec4(colorAcc, outAlpha);\n }\n `;\n\n const FilmGrainShader = useMemo(() => {\n return {\n uniforms: {\n tDiffuse: { value: null },\n iTime: { value: 0 },\n intensity: { value: grainIntensity }\n },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n uniform float iTime;\n uniform float intensity;\n varying vec2 vUv;\n\n float hash1(float n){ return fract(sin(n)*43758.5453); }\n\n void main(){\n vec4 color = texture2D(tDiffuse, vUv);\n float n = hash1(vUv.x*1000.0 + vUv.y*2000.0 + iTime) * 2.0 - 1.0;\n color.rgb += n * intensity * color.rgb;\n gl_FragColor = color;\n }\n `\n };\n }, [grainIntensity]);\n\n const UnpremultiplyPass = useMemo(\n () =>\n new ShaderPass({\n uniforms: { tDiffuse: { value: null } },\n vertexShader: `\n varying vec2 vUv;\n void main(){\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform sampler2D tDiffuse;\n varying vec2 vUv;\n void main(){\n vec4 c = texture2D(tDiffuse, vUv);\n float a = max(c.a, 1e-5);\n vec3 straight = c.rgb / a;\n gl_FragColor = vec4(clamp(straight, 0.0, 1.0), c.a);\n }\n `\n }),\n []\n );\n\n function calculateScale(el: HTMLElement) {\n const r = el.getBoundingClientRect();\n const base = 600;\n const current = Math.min(Math.max(1, r.width), Math.max(1, r.height));\n return Math.max(0.5, Math.min(2.0, current / base));\n }\n\n useEffect(() => {\n const host = containerRef.current;\n const parent = host?.parentElement;\n if (!host || !parent) return;\n\n const prevParentPos = parent.style.position;\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = 'relative';\n }\n\n const renderer = new THREE.WebGLRenderer({\n antialias: !isTouch,\n alpha: true,\n depth: false,\n stencil: false,\n powerPreference: isTouch ? 'low-power' : 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false\n });\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n renderer.domElement.style.pointerEvents = 'none';\n if (mixBlendMode) {\n renderer.domElement.style.mixBlendMode = String(mixBlendMode);\n } else {\n renderer.domElement.style.removeProperty('mix-blend-mode');\n }\n\n renderer.domElement.style.display = 'block';\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.background = 'transparent';\n\n host.appendChild(renderer.domElement);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geom = new THREE.PlaneGeometry(2, 2);\n\n const maxTrail = Math.max(1, Math.floor(trailLength));\n trailBufRef.current = Array.from({ length: maxTrail }, () => new THREE.Vector2(0.5, 0.5));\n headRef.current = 0;\n\n const baseColor = new THREE.Color(color);\n\n const material = new THREE.ShaderMaterial({\n defines: { MAX_TRAIL_LENGTH: maxTrail },\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector2(0.5, 0.5) },\n iPrevMouse: { value: trailBufRef.current.map(v => v.clone()) },\n iOpacity: { value: 1.0 },\n iScale: { value: 1.0 },\n iBaseColor: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },\n iBrightness: { value: brightness },\n iEdgeIntensity: { value: edgeIntensity }\n },\n vertexShader: baseVertexShader,\n fragmentShader,\n transparent: true,\n depthTest: false,\n depthWrite: false\n });\n materialRef.current = material;\n\n const mesh = new THREE.Mesh(geom, material);\n scene.add(mesh);\n\n const composer = new EffectComposer(renderer);\n composerRef.current = composer;\n\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloomPass = new UnrealBloomPass(new THREE.Vector2(1, 1), bloomStrength, bloomRadius, bloomThreshold);\n bloomPassRef.current = bloomPass;\n composer.addPass(bloomPass);\n\n const filmPass = new ShaderPass(FilmGrainShader as any);\n filmPassRef.current = filmPass;\n composer.addPass(filmPass);\n\n composer.addPass(UnpremultiplyPass);\n\n const resize = () => {\n const rect = host.getBoundingClientRect();\n const cssW = Math.max(1, Math.floor(rect.width));\n const cssH = Math.max(1, Math.floor(rect.height));\n\n const currentDPR = Math.min(\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\n maxDevicePixelRatio\n );\n const need = cssW * cssH * currentDPR * currentDPR;\n const scale = need <= pixelBudget ? 1 : Math.max(0.5, Math.min(1, Math.sqrt(pixelBudget / Math.max(1, need))));\n const pixelRatio = currentDPR * scale;\n\n renderer.setPixelRatio(pixelRatio);\n renderer.setSize(cssW, cssH, false);\n\n composer.setPixelRatio?.(pixelRatio);\n composer.setSize(cssW, cssH);\n\n const wpx = Math.max(1, Math.floor(cssW * pixelRatio));\n const hpx = Math.max(1, Math.floor(cssH * pixelRatio));\n material.uniforms.iResolution.value.set(wpx, hpx, 1);\n material.uniforms.iScale.value = calculateScale(host);\n bloomPass.setSize(wpx, hpx);\n };\n\n resize();\n const ro = new ResizeObserver(resize);\n resizeObsRef.current = ro;\n ro.observe(parent);\n ro.observe(host);\n\n const start = typeof performance !== 'undefined' ? performance.now() : Date.now();\n const animate = () => {\n const now = performance.now();\n const t = (now - start) / 1000;\n\n const mat = materialRef.current!;\n const comp = composerRef.current!;\n\n if (pointerActiveRef.current) {\n velocityRef.current.set(\n currentMouseRef.current.x - mat.uniforms.iMouse.value.x,\n currentMouseRef.current.y - mat.uniforms.iMouse.value.y\n );\n mat.uniforms.iMouse.value.copy(currentMouseRef.current);\n fadeOpacityRef.current = 1.0;\n } else {\n velocityRef.current.multiplyScalar(inertia);\n if (velocityRef.current.lengthSq() > 1e-6) {\n mat.uniforms.iMouse.value.add(velocityRef.current);\n }\n const dt = now - lastMoveTimeRef.current;\n if (dt > fadeDelay) {\n const k = Math.min(1, (dt - fadeDelay) / fadeDuration);\n fadeOpacityRef.current = Math.max(0, 1 - k);\n }\n }\n\n const N = trailBufRef.current.length;\n headRef.current = (headRef.current + 1) % N;\n trailBufRef.current[headRef.current].copy(mat.uniforms.iMouse.value);\n const arr = mat.uniforms.iPrevMouse.value as THREE.Vector2[];\n for (let i = 0; i < N; i++) {\n const srcIdx = (headRef.current - i + N) % N;\n arr[i].copy(trailBufRef.current[srcIdx]);\n }\n\n mat.uniforms.iOpacity.value = fadeOpacityRef.current;\n mat.uniforms.iTime.value = t;\n\n if (filmPassRef.current?.uniforms?.iTime) {\n filmPassRef.current.uniforms.iTime.value = t;\n }\n\n comp.render();\n\n if (!pointerActiveRef.current && fadeOpacityRef.current <= 0.001) {\n runningRef.current = false;\n rafRef.current = null;\n return;\n }\n\n rafRef.current = requestAnimationFrame(animate);\n };\n\n const ensureLoop = () => {\n if (!runningRef.current) {\n runningRef.current = true;\n rafRef.current = requestAnimationFrame(animate);\n }\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const rect = parent.getBoundingClientRect();\n const x = THREE.MathUtils.clamp((e.clientX - rect.left) / Math.max(1, rect.width), 0, 1);\n const y = THREE.MathUtils.clamp(1 - (e.clientY - rect.top) / Math.max(1, rect.height), 0, 1);\n currentMouseRef.current.set(x, y);\n pointerActiveRef.current = true;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n const onPointerEnter = () => {\n pointerActiveRef.current = true;\n ensureLoop();\n };\n const onPointerLeave = () => {\n pointerActiveRef.current = false;\n lastMoveTimeRef.current = performance.now();\n ensureLoop();\n };\n\n parent.addEventListener('pointermove', onPointerMove, { passive: true });\n parent.addEventListener('pointerenter', onPointerEnter, { passive: true });\n parent.addEventListener('pointerleave', onPointerLeave, { passive: true });\n\n ensureLoop();\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n runningRef.current = false;\n rafRef.current = null;\n\n parent.removeEventListener('pointermove', onPointerMove);\n parent.removeEventListener('pointerenter', onPointerEnter);\n parent.removeEventListener('pointerleave', onPointerLeave);\n resizeObsRef.current?.disconnect();\n\n scene.clear();\n geom.dispose();\n material.dispose();\n composer.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n\n if (renderer.domElement && renderer.domElement.parentElement) {\n renderer.domElement.parentElement.removeChild(renderer.domElement);\n }\n if (!prevParentPos || prevParentPos === 'static') {\n parent.style.position = prevParentPos;\n }\n };\n }, [\n trailLength,\n inertia,\n grainIntensity,\n bloomStrength,\n bloomRadius,\n bloomThreshold,\n pixelBudget,\n fadeDelay,\n fadeDuration,\n isTouch,\n color,\n brightness,\n mixBlendMode,\n edgeIntensity\n ]);\n\n useEffect(() => {\n if (materialRef.current) {\n const c = new THREE.Color(color);\n (materialRef.current.uniforms.iBaseColor.value as THREE.Vector3).set(c.r, c.g, c.b);\n }\n }, [color]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iBrightness.value = brightness;\n }\n }, [brightness]);\n\n useEffect(() => {\n if (materialRef.current) {\n materialRef.current.uniforms.iEdgeIntensity.value = edgeIntensity;\n }\n }, [edgeIntensity]);\n\n useEffect(() => {\n if (filmPassRef.current?.uniforms?.intensity) {\n filmPassRef.current.uniforms.intensity.value = grainIntensity;\n }\n }, [grainIntensity]);\n\n useEffect(() => {\n const el = rendererRef.current?.domElement;\n if (!el) return;\n if (mixBlendMode) {\n el.style.mixBlendMode = String(mixBlendMode);\n } else {\n el.style.removeProperty('mix-blend-mode');\n }\n }, [mixBlendMode]);\n\n const mergedStyle = useMemo(() => ({ zIndex, ...style }), [zIndex, style]);\n\n return (\n
\n );\n};\n\nexport default GhostCursor;\n" } ], "registryDependencies": [], diff --git a/public/r/GridDistortion-JS-CSS.json b/public/r/GridDistortion-JS-CSS.json index dfe3f1dc..0794611c 100644 --- a/public/r/GridDistortion-JS-CSS.json +++ b/public/r/GridDistortion-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "GridDistortion/GridDistortion.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\nimport './GridDistortion.css';\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}`;\n\nconst GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = 0.9, imageSrc, className = '' }) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null },\n uDataTexture: { value: null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n const data = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './GridDistortion.css';\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}`;\n\nconst GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = 0.9, imageSrc, className = '' }) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null },\n uDataTexture: { value: null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n const data = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n renderer.forceContextLoss();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" } ], "registryDependencies": [], diff --git a/public/r/GridDistortion-JS-TW.json b/public/r/GridDistortion-JS-TW.json index 74cc6236..902b15b9 100644 --- a/public/r/GridDistortion-JS-TW.json +++ b/public/r/GridDistortion-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "GridDistortion/GridDistortion.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}`;\n\nconst GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = 0.9, imageSrc, className = '' }) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null },\n uDataTexture: { value: null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n const data = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}`;\n\nconst GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = 0.9, imageSrc, className = '' }) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null },\n uDataTexture: { value: null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = e => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n const data = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n renderer.forceContextLoss();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" } ], "registryDependencies": [], diff --git a/public/r/GridDistortion-TS-CSS.json b/public/r/GridDistortion-TS-CSS.json index d45a2867..0600d95f 100644 --- a/public/r/GridDistortion-TS-CSS.json +++ b/public/r/GridDistortion-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "GridDistortion/GridDistortion.tsx", - "content": "import React, { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\nimport './GridDistortion.css';\n\ninterface GridDistortionProps {\n grid?: number;\n mouse?: number;\n strength?: number;\n relaxation?: number;\n imageSrc: string;\n className?: string;\n}\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}\n`;\n\nconst GridDistortion: React.FC = ({\n grid = 15,\n mouse = 0.1,\n strength = 0.15,\n relaxation = 0.9,\n imageSrc,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null as THREE.Texture | null },\n uDataTexture: { value: null as THREE.DataTexture | null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n if (!(dataTexture.image.data instanceof Float32Array)) {\n console.error('dataTexture.image.data is not a Float32Array');\n return;\n }\n const data: Float32Array = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './GridDistortion.css';\n\ninterface GridDistortionProps {\n grid?: number;\n mouse?: number;\n strength?: number;\n relaxation?: number;\n imageSrc: string;\n className?: string;\n}\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}\n`;\n\nconst GridDistortion: React.FC = ({\n grid = 15,\n mouse = 0.1,\n strength = 0.15,\n relaxation = 0.9,\n imageSrc,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null as THREE.Texture | null },\n uDataTexture: { value: null as THREE.DataTexture | null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n if (!(dataTexture.image.data instanceof Float32Array)) {\n console.error('dataTexture.image.data is not a Float32Array');\n return;\n }\n const data: Float32Array = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n renderer.forceContextLoss();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" } ], "registryDependencies": [], diff --git a/public/r/GridDistortion-TS-TW.json b/public/r/GridDistortion-TS-TW.json index 4fb2181e..5c66be76 100644 --- a/public/r/GridDistortion-TS-TW.json +++ b/public/r/GridDistortion-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "GridDistortion/GridDistortion.tsx", - "content": "import React, { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\ninterface GridDistortionProps {\n grid?: number;\n mouse?: number;\n strength?: number;\n relaxation?: number;\n imageSrc: string;\n className?: string;\n}\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}\n`;\n\nconst GridDistortion: React.FC = ({\n grid = 15,\n mouse = 0.1,\n strength = 0.15,\n relaxation = 0.9,\n imageSrc,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null as THREE.Texture | null },\n uDataTexture: { value: null as THREE.DataTexture | null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n if (!(dataTexture.image.data instanceof Float32Array)) {\n console.error('dataTexture.image.data is not a Float32Array');\n return;\n }\n const data: Float32Array = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\ninterface GridDistortionProps {\n grid?: number;\n mouse?: number;\n strength?: number;\n relaxation?: number;\n imageSrc: string;\n className?: string;\n}\n\nconst vertexShader = `\nuniform float time;\nvarying vec2 vUv;\nvarying vec3 vPosition;\n\nvoid main() {\n vUv = uv;\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nuniform sampler2D uDataTexture;\nuniform sampler2D uTexture;\nuniform vec4 resolution;\nvarying vec2 vUv;\n\nvoid main() {\n vec2 uv = vUv;\n vec4 offset = texture2D(uDataTexture, vUv);\n gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);\n}\n`;\n\nconst GridDistortion: React.FC = ({\n grid = 15,\n mouse = 0.1,\n strength = 0.15,\n relaxation = 0.9,\n imageSrc,\n className = ''\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const rendererRef = useRef(null);\n const cameraRef = useRef(null);\n const planeRef = useRef(null);\n const imageAspectRef = useRef(1);\n const animationIdRef = useRef(null);\n const resizeObserverRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n const scene = new THREE.Scene();\n sceneRef.current = scene;\n\n const renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setClearColor(0x000000, 0);\n rendererRef.current = renderer;\n\n container.innerHTML = '';\n container.appendChild(renderer.domElement);\n\n const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);\n camera.position.z = 2;\n cameraRef.current = camera;\n\n const uniforms = {\n time: { value: 0 },\n resolution: { value: new THREE.Vector4() },\n uTexture: { value: null as THREE.Texture | null },\n uDataTexture: { value: null as THREE.DataTexture | null }\n };\n\n const textureLoader = new THREE.TextureLoader();\n textureLoader.load(imageSrc, texture => {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.wrapS = THREE.ClampToEdgeWrapping;\n texture.wrapT = THREE.ClampToEdgeWrapping;\n imageAspectRef.current = texture.image.width / texture.image.height;\n uniforms.uTexture.value = texture;\n handleResize();\n });\n\n const size = grid;\n const data = new Float32Array(4 * size * size);\n for (let i = 0; i < size * size; i++) {\n data[i * 4] = Math.random() * 255 - 125;\n data[i * 4 + 1] = Math.random() * 255 - 125;\n }\n\n const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);\n dataTexture.needsUpdate = true;\n uniforms.uDataTexture.value = dataTexture;\n\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n uniforms,\n vertexShader,\n fragmentShader,\n transparent: true\n });\n\n const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);\n const plane = new THREE.Mesh(geometry, material);\n planeRef.current = plane;\n scene.add(plane);\n\n const handleResize = () => {\n if (!container || !renderer || !camera) return;\n\n const rect = container.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n const containerAspect = width / height;\n\n renderer.setSize(width, height);\n\n if (plane) {\n plane.scale.set(containerAspect, 1, 1);\n }\n\n const frustumHeight = 1;\n const frustumWidth = frustumHeight * containerAspect;\n camera.left = -frustumWidth / 2;\n camera.right = frustumWidth / 2;\n camera.top = frustumHeight / 2;\n camera.bottom = -frustumHeight / 2;\n camera.updateProjectionMatrix();\n\n uniforms.resolution.value.set(width, height, 1, 1);\n };\n\n if (window.ResizeObserver) {\n const resizeObserver = new ResizeObserver(() => {\n handleResize();\n });\n resizeObserver.observe(container);\n resizeObserverRef.current = resizeObserver;\n } else {\n window.addEventListener('resize', handleResize);\n }\n\n const mouseState = {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n };\n\n const handleMouseMove = (e: MouseEvent) => {\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1 - (e.clientY - rect.top) / rect.height;\n mouseState.vX = x - mouseState.prevX;\n mouseState.vY = y - mouseState.prevY;\n Object.assign(mouseState, { x, y, prevX: x, prevY: y });\n };\n\n const handleMouseLeave = () => {\n if (dataTexture) {\n dataTexture.needsUpdate = true;\n }\n Object.assign(mouseState, {\n x: 0,\n y: 0,\n prevX: 0,\n prevY: 0,\n vX: 0,\n vY: 0\n });\n };\n\n container.addEventListener('mousemove', handleMouseMove);\n container.addEventListener('mouseleave', handleMouseLeave);\n\n handleResize();\n\n const animate = () => {\n animationIdRef.current = requestAnimationFrame(animate);\n\n if (!renderer || !scene || !camera) return;\n\n uniforms.time.value += 0.05;\n\n if (!(dataTexture.image.data instanceof Float32Array)) {\n console.error('dataTexture.image.data is not a Float32Array');\n return;\n }\n const data: Float32Array = dataTexture.image.data;\n for (let i = 0; i < size * size; i++) {\n data[i * 4] *= relaxation;\n data[i * 4 + 1] *= relaxation;\n }\n\n const gridMouseX = size * mouseState.x;\n const gridMouseY = size * mouseState.y;\n const maxDist = size * mouse;\n\n for (let i = 0; i < size; i++) {\n for (let j = 0; j < size; j++) {\n const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);\n if (distSq < maxDist * maxDist) {\n const index = 4 * (i + size * j);\n const power = Math.min(maxDist / Math.sqrt(distSq), 10);\n data[index] += strength * 100 * mouseState.vX * power;\n data[index + 1] -= strength * 100 * mouseState.vY * power;\n }\n }\n }\n\n dataTexture.needsUpdate = true;\n renderer.render(scene, camera);\n };\n\n animate();\n\n return () => {\n if (animationIdRef.current) {\n cancelAnimationFrame(animationIdRef.current);\n }\n\n if (resizeObserverRef.current) {\n resizeObserverRef.current.disconnect();\n } else {\n window.removeEventListener('resize', handleResize);\n }\n\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeEventListener('mouseleave', handleMouseLeave);\n\n if (renderer) {\n renderer.dispose();\n renderer.forceContextLoss();\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n }\n\n if (geometry) geometry.dispose();\n if (material) material.dispose();\n if (dataTexture) dataTexture.dispose();\n if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();\n\n sceneRef.current = null;\n rendererRef.current = null;\n cameraRef.current = null;\n planeRef.current = null;\n };\n }, [grid, mouse, strength, relaxation, imageSrc]);\n\n return (\n \n );\n};\n\nexport default GridDistortion;\n" } ], "registryDependencies": [], diff --git a/public/r/GridScan-JS-CSS.json b/public/r/GridScan-JS-CSS.json index 7a5f3fab..b177a2a9 100644 --- a/public/r/GridScan-JS-CSS.json +++ b/public/r/GridScan-JS-CSS.json @@ -13,13 +13,13 @@ { "type": "registry:component", "path": "GridScan/GridScan.jsx", - "content": "import { useEffect, useRef, useState } from 'react';\nimport { EffectComposer, RenderPass, EffectPass, BloomEffect, ChromaticAberrationEffect } from 'postprocessing';\nimport * as THREE from 'three';\nimport * as faceapi from 'face-api.js';\nimport './GridScan.css';\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = t => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer = null;\n const onMove = e => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n window.DeviceOrientationEvent &&\n DeviceOrientationEvent.requestPermission\n ) {\n try {\n await DeviceOrientationEvent.requestPermission();\n } catch {\n // noop\n }\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n quad.geometry.dispose();\n\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost,\n noiseIntensity,\n bloomIntensity,\n scanGlow,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n smoothTime,\n maxSpeed,\n skewScale,\n yBoost,\n tiltScale,\n yawScale\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n u.uLinesColor.value.copy(srgbColor(linesColor));\n u.uScanColor.value.copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current.luminanceMaterial.threshold = bloomThreshold;\n bloomRef.current.luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = e => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n const video = videoRef.current;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async ts => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n video.requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n if (video) {\n const stream = video.srcObject;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(current, target, velRef, smoothTime, maxSpeed, deltaTime) {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf, v, maxLen) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a, b) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" + "content": "import * as faceapi from 'face-api.js';\nimport { BloomEffect, ChromaticAberrationEffect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport { useEffect, useRef, useState } from 'react';\nimport * as THREE from 'three';\nimport './GridScan.css';\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = t => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer = null;\n const onMove = e => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n window.DeviceOrientationEvent &&\n DeviceOrientationEvent.requestPermission\n ) {\n try {\n await DeviceOrientationEvent.requestPermission();\n } catch {\n // noop\n }\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n quad.geometry.dispose();\n\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n renderer.forceContextLoss();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost,\n noiseIntensity,\n bloomIntensity,\n scanGlow,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n smoothTime,\n maxSpeed,\n skewScale,\n yBoost,\n tiltScale,\n yawScale\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n u.uLinesColor.value.copy(srgbColor(linesColor));\n u.uScanColor.value.copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current.luminanceMaterial.threshold = bloomThreshold;\n bloomRef.current.luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = e => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n const video = videoRef.current;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async ts => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n video.requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n if (video) {\n const stream = video.srcObject;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(current, target, velRef, smoothTime, maxSpeed, deltaTime) {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf, v, maxLen) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a, b) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" } ], "registryDependencies": [], "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/GridScan-JS-TW.json b/public/r/GridScan-JS-TW.json index 780735b8..0ec42e21 100644 --- a/public/r/GridScan-JS-TW.json +++ b/public/r/GridScan-JS-TW.json @@ -8,13 +8,13 @@ { "type": "registry:component", "path": "GridScan/GridScan.jsx", - "content": "import { useEffect, useRef, useState } from 'react';\nimport { EffectComposer, RenderPass, EffectPass, BloomEffect, ChromaticAberrationEffect } from 'postprocessing';\nimport * as THREE from 'three';\nimport * as faceapi from 'face-api.js';\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = t => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer = null;\n const onMove = e => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n window.DeviceOrientationEvent &&\n DeviceOrientationEvent.requestPermission\n ) {\n try {\n await DeviceOrientationEvent.requestPermission();\n } catch {\n // noop\n }\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n quad.geometry.dispose();\n\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost,\n noiseIntensity,\n bloomIntensity,\n scanGlow,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n smoothTime,\n maxSpeed,\n skewScale,\n yBoost,\n tiltScale,\n yawScale\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n u.uLinesColor.value.copy(srgbColor(linesColor));\n u.uScanColor.value.copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current.luminanceMaterial.threshold = bloomThreshold;\n bloomRef.current.luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = e => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n const video = videoRef.current;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async ts => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n video.requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n if (video) {\n const stream = video.srcObject;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(current, target, velRef, smoothTime, maxSpeed, deltaTime) {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf, v, maxLen) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a, b) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" + "content": "import * as faceapi from 'face-api.js';\nimport { BloomEffect, ChromaticAberrationEffect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport { useEffect, useRef, useState } from 'react';\nimport * as THREE from 'three';\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = t => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer = null;\n const onMove = e => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n window.DeviceOrientationEvent &&\n DeviceOrientationEvent.requestPermission\n ) {\n try {\n await DeviceOrientationEvent.requestPermission();\n } catch {\n // noop\n }\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n quad.geometry.dispose();\n\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n renderer.forceContextLoss();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost,\n noiseIntensity,\n bloomIntensity,\n scanGlow,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n smoothTime,\n maxSpeed,\n skewScale,\n yBoost,\n tiltScale,\n yawScale\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n u.uLinesColor.value.copy(srgbColor(linesColor));\n u.uScanColor.value.copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current.luminanceMaterial.threshold = bloomThreshold;\n bloomRef.current.luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = e => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n const video = videoRef.current;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async ts => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n video.requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n if (video) {\n const stream = video.srcObject;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(current, target, velRef, smoothTime, maxSpeed, deltaTime) {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf, v, maxLen) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a, b) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" } ], "registryDependencies": [], "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/GridScan-TS-CSS.json b/public/r/GridScan-TS-CSS.json index 3ea8b703..2260e6df 100644 --- a/public/r/GridScan-TS-CSS.json +++ b/public/r/GridScan-TS-CSS.json @@ -13,13 +13,13 @@ { "type": "registry:component", "path": "GridScan/GridScan.tsx", - "content": "import React, { useEffect, useRef, useState } from 'react';\nimport { EffectComposer, RenderPass, EffectPass, BloomEffect, ChromaticAberrationEffect } from 'postprocessing';\nimport * as THREE from 'three';\nimport * as faceapi from 'face-api.js';\nimport './GridScan.css';\n\ntype GridScanProps = {\n enableWebcam?: boolean;\n showPreview?: boolean;\n modelsPath?: string;\n sensitivity?: number;\n\n lineThickness?: number;\n linesColor?: string;\n\n gridScale?: number;\n lineStyle?: 'solid' | 'dashed' | 'dotted';\n lineJitter?: number;\n\n enablePost?: boolean;\n bloomIntensity?: number;\n bloomThreshold?: number;\n bloomSmoothing?: number;\n chromaticAberration?: number;\n noiseIntensity?: number;\n\n scanColor?: string;\n scanOpacity?: number;\n scanDirection?: 'forward' | 'backward' | 'pingpong';\n scanSoftness?: number;\n scanGlow?: number;\n scanPhaseTaper?: number;\n scanDuration?: number;\n scanDelay?: number;\n enableGyro?: boolean;\n scanOnClick?: boolean;\n snapBackDelay?: number;\n className?: string;\n style?: React.CSSProperties;\n};\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan: React.FC = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = (t: number) => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer: number | null = null;\n const onMove = (e: MouseEvent) => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n (window as any).DeviceOrientationEvent &&\n (DeviceOrientationEvent as any).requestPermission\n ) {\n try {\n await (DeviceOrientationEvent as any).requestPermission();\n } catch {}\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer: EffectComposer | null = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n (quad.geometry as THREE.BufferGeometry).dispose();\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n (u.uLinesColor.value as THREE.Color).copy(srgbColor(linesColor));\n (u.uScanColor.value as THREE.Color).copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n (bloomRef.current as any).luminanceMaterial.threshold = bloomThreshold;\n (bloomRef.current as any).luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = (e: DeviceOrientationEvent) => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n const video = videoRef.current;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async (ts: number) => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n (video as any).requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n const video = videoRef.current;\n if (video) {\n const stream = video.srcObject as MediaStream | null;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex: string) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(\n current: THREE.Vector2,\n target: THREE.Vector2,\n currentVelocity: THREE.Vector2,\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): THREE.Vector2 {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(\n current: number,\n target: number,\n velRef: { v: number },\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): { value: number; v: number } {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf: number[], v: number, maxLen: number) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf: number[]) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points: { x: number; y: number }[]) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a: { x: number; y: number }, b: { x: number; y: number }) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" + "content": "import * as faceapi from 'face-api.js';\nimport { BloomEffect, ChromaticAberrationEffect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport React, { useEffect, useRef, useState } from 'react';\nimport * as THREE from 'three';\nimport './GridScan.css';\n\ntype GridScanProps = {\n enableWebcam?: boolean;\n showPreview?: boolean;\n modelsPath?: string;\n sensitivity?: number;\n\n lineThickness?: number;\n linesColor?: string;\n\n gridScale?: number;\n lineStyle?: 'solid' | 'dashed' | 'dotted';\n lineJitter?: number;\n\n enablePost?: boolean;\n bloomIntensity?: number;\n bloomThreshold?: number;\n bloomSmoothing?: number;\n chromaticAberration?: number;\n noiseIntensity?: number;\n\n scanColor?: string;\n scanOpacity?: number;\n scanDirection?: 'forward' | 'backward' | 'pingpong';\n scanSoftness?: number;\n scanGlow?: number;\n scanPhaseTaper?: number;\n scanDuration?: number;\n scanDelay?: number;\n enableGyro?: boolean;\n scanOnClick?: boolean;\n snapBackDelay?: number;\n className?: string;\n style?: React.CSSProperties;\n};\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan: React.FC = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = (t: number) => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer: number | null = null;\n const onMove = (e: MouseEvent) => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n (window as any).DeviceOrientationEvent &&\n (DeviceOrientationEvent as any).requestPermission\n ) {\n try {\n await (DeviceOrientationEvent as any).requestPermission();\n } catch {}\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer: EffectComposer | null = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n (quad.geometry as THREE.BufferGeometry).dispose();\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n renderer.forceContextLoss();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n (u.uLinesColor.value as THREE.Color).copy(srgbColor(linesColor));\n (u.uScanColor.value as THREE.Color).copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n (bloomRef.current as any).luminanceMaterial.threshold = bloomThreshold;\n (bloomRef.current as any).luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = (e: DeviceOrientationEvent) => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n const video = videoRef.current;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async (ts: number) => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n (video as any).requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n const video = videoRef.current;\n if (video) {\n const stream = video.srcObject as MediaStream | null;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex: string) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(\n current: THREE.Vector2,\n target: THREE.Vector2,\n currentVelocity: THREE.Vector2,\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): THREE.Vector2 {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(\n current: number,\n target: number,\n velRef: { v: number },\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): { value: number; v: number } {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf: number[], v: number, maxLen: number) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf: number[]) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points: { x: number; y: number }[]) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a: { x: number; y: number }, b: { x: number; y: number }) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" } ], "registryDependencies": [], "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/GridScan-TS-TW.json b/public/r/GridScan-TS-TW.json index 666dfca4..61539c0b 100644 --- a/public/r/GridScan-TS-TW.json +++ b/public/r/GridScan-TS-TW.json @@ -8,13 +8,13 @@ { "type": "registry:component", "path": "GridScan/GridScan.tsx", - "content": "import React, { useEffect, useRef, useState } from 'react';\nimport { EffectComposer, RenderPass, EffectPass, BloomEffect, ChromaticAberrationEffect } from 'postprocessing';\nimport * as THREE from 'three';\nimport * as faceapi from 'face-api.js';\n\ntype GridScanProps = {\n enableWebcam?: boolean;\n showPreview?: boolean;\n modelsPath?: string;\n sensitivity?: number;\n\n lineThickness?: number;\n linesColor?: string;\n\n gridScale?: number;\n lineStyle?: 'solid' | 'dashed' | 'dotted';\n lineJitter?: number;\n\n enablePost?: boolean;\n bloomIntensity?: number;\n bloomThreshold?: number;\n bloomSmoothing?: number;\n chromaticAberration?: number;\n noiseIntensity?: number;\n\n scanColor?: string;\n scanOpacity?: number;\n scanDirection?: 'forward' | 'backward' | 'pingpong';\n scanSoftness?: number;\n scanGlow?: number;\n scanPhaseTaper?: number;\n scanDuration?: number;\n scanDelay?: number;\n enableGyro?: boolean;\n scanOnClick?: boolean;\n snapBackDelay?: number;\n className?: string;\n style?: React.CSSProperties;\n};\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan: React.FC = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = (t: number) => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer: number | null = null;\n const onMove = (e: MouseEvent) => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n (window as any).DeviceOrientationEvent &&\n (DeviceOrientationEvent as any).requestPermission\n ) {\n try {\n await (DeviceOrientationEvent as any).requestPermission();\n } catch {}\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer: EffectComposer | null = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n (quad.geometry as THREE.BufferGeometry).dispose();\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n (u.uLinesColor.value as THREE.Color).copy(srgbColor(linesColor));\n (u.uScanColor.value as THREE.Color).copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n (bloomRef.current as any).luminanceMaterial.threshold = bloomThreshold;\n (bloomRef.current as any).luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = (e: DeviceOrientationEvent) => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n const video = videoRef.current;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async (ts: number) => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n (video as any).requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n const video = videoRef.current;\n if (video) {\n const stream = video.srcObject as MediaStream | null;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex: string) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(\n current: THREE.Vector2,\n target: THREE.Vector2,\n currentVelocity: THREE.Vector2,\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): THREE.Vector2 {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(\n current: number,\n target: number,\n velRef: { v: number },\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): { value: number; v: number } {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf: number[], v: number, maxLen: number) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf: number[]) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points: { x: number; y: number }[]) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a: { x: number; y: number }, b: { x: number; y: number }) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" + "content": "import * as faceapi from 'face-api.js';\nimport { BloomEffect, ChromaticAberrationEffect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport React, { useEffect, useRef, useState } from 'react';\nimport * as THREE from 'three';\n\ntype GridScanProps = {\n enableWebcam?: boolean;\n showPreview?: boolean;\n modelsPath?: string;\n sensitivity?: number;\n\n lineThickness?: number;\n linesColor?: string;\n\n gridScale?: number;\n lineStyle?: 'solid' | 'dashed' | 'dotted';\n lineJitter?: number;\n\n enablePost?: boolean;\n bloomIntensity?: number;\n bloomThreshold?: number;\n bloomSmoothing?: number;\n chromaticAberration?: number;\n noiseIntensity?: number;\n\n scanColor?: string;\n scanOpacity?: number;\n scanDirection?: 'forward' | 'backward' | 'pingpong';\n scanSoftness?: number;\n scanGlow?: number;\n scanPhaseTaper?: number;\n scanDuration?: number;\n scanDelay?: number;\n enableGyro?: boolean;\n scanOnClick?: boolean;\n snapBackDelay?: number;\n className?: string;\n style?: React.CSSProperties;\n};\n\nconst vert = `\nvarying vec2 vUv;\nvoid main(){\n vUv = uv;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst frag = `\nprecision highp float;\nuniform vec3 iResolution;\nuniform float iTime;\nuniform vec2 uSkew;\nuniform float uTilt;\nuniform float uYaw;\nuniform float uLineThickness;\nuniform vec3 uLinesColor;\nuniform vec3 uScanColor;\nuniform float uGridScale;\nuniform float uLineStyle;\nuniform float uLineJitter;\nuniform float uScanOpacity;\nuniform float uScanDirection;\nuniform float uNoise;\nuniform float uBloomOpacity;\nuniform float uScanGlow;\nuniform float uScanSoftness;\nuniform float uPhaseTaper;\nuniform float uScanDuration;\nuniform float uScanDelay;\nvarying vec2 vUv;\n\nuniform float uScanStarts[8];\nuniform float uScanCount;\n\nconst int MAX_SCANS = 8;\n\nfloat smoother01(float a, float b, float x){\n float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord)\n{\n vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n vec3 ro = vec3(0.0);\n vec3 rd = normalize(vec3(p, 2.0));\n\n float cR = cos(uTilt), sR = sin(uTilt);\n rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;\n\n float cY = cos(uYaw), sY = sin(uYaw);\n rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;\n\n vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));\n rd.xy += skew * rd.z;\n\n vec3 color = vec3(0.0);\n float minT = 1e20;\n float gridScale = max(1e-5, uGridScale);\n float fadeStrength = 2.0;\n vec2 gridUV = vec2(0.0);\n\n float hitIsY = 1.0;\n for (int i = 0; i < 4; i++)\n {\n float isY = float(i < 2);\n float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);\n float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);\n float den = isY * rd.y + (1.0 - isY) * rd.x;\n float t = num / den;\n vec3 h = ro + rd * t;\n\n float depthBoost = smoothstep(0.0, 3.0, h.z);\n h.xy += skew * 0.15 * depthBoost;\n\n bool use = t > 0.0 && t < minT;\n gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;\n minT = use ? t : minT;\n hitIsY = use ? isY : hitIsY;\n }\n\n vec3 hit = ro + rd * minT;\n float dist = length(hit - ro);\n\n float jitterAmt = clamp(uLineJitter, 0.0, 1.0);\n if (jitterAmt > 0.0) {\n vec2 j = vec2(\n sin(gridUV.y * 2.7 + iTime * 1.8),\n cos(gridUV.x * 2.3 - iTime * 1.6)\n ) * (0.15 * jitterAmt);\n gridUV += j;\n }\n float fx = fract(gridUV.x);\n float fy = fract(gridUV.y);\n float ax = min(fx, 1.0 - fx);\n float ay = min(fy, 1.0 - fy);\n float wx = fwidth(gridUV.x);\n float wy = fwidth(gridUV.y);\n float halfPx = max(0.0, uLineThickness) * 0.5;\n\n float tx = halfPx * wx;\n float ty = halfPx * wy;\n\n float aax = wx;\n float aay = wy;\n\n float lineX = 1.0 - smoothstep(tx, tx + aax, ax);\n float lineY = 1.0 - smoothstep(ty, ty + aay, ay);\n if (uLineStyle > 0.5) {\n float dashRepeat = 4.0;\n float dashDuty = 0.5;\n float vy = fract(gridUV.y * dashRepeat);\n float vx = fract(gridUV.x * dashRepeat);\n float dashMaskY = step(vy, dashDuty);\n float dashMaskX = step(vx, dashDuty);\n if (uLineStyle < 1.5) {\n lineX *= dashMaskY;\n lineY *= dashMaskX;\n } else {\n float dotRepeat = 6.0;\n float dotWidth = 0.18;\n float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);\n float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);\n float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);\n float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);\n lineX *= dotMaskY;\n lineY *= dotMaskX;\n }\n }\n float primaryMask = max(lineX, lineY);\n\n vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;\n if (jitterAmt > 0.0) {\n vec2 j2 = vec2(\n cos(gridUV2.y * 2.1 - iTime * 1.4),\n sin(gridUV2.x * 2.5 + iTime * 1.7)\n ) * (0.15 * jitterAmt);\n gridUV2 += j2;\n }\n float fx2 = fract(gridUV2.x);\n float fy2 = fract(gridUV2.y);\n float ax2 = min(fx2, 1.0 - fx2);\n float ay2 = min(fy2, 1.0 - fy2);\n float wx2 = fwidth(gridUV2.x);\n float wy2 = fwidth(gridUV2.y);\n float tx2 = halfPx * wx2;\n float ty2 = halfPx * wy2;\n float aax2 = wx2;\n float aay2 = wy2;\n float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);\n float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);\n if (uLineStyle > 0.5) {\n float dashRepeat2 = 4.0;\n float dashDuty2 = 0.5;\n float vy2m = fract(gridUV2.y * dashRepeat2);\n float vx2m = fract(gridUV2.x * dashRepeat2);\n float dashMaskY2 = step(vy2m, dashDuty2);\n float dashMaskX2 = step(vx2m, dashDuty2);\n if (uLineStyle < 1.5) {\n lineX2 *= dashMaskY2;\n lineY2 *= dashMaskX2;\n } else {\n float dotRepeat2 = 6.0;\n float dotWidth2 = 0.18;\n float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);\n float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);\n float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);\n float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);\n lineX2 *= dotMaskY2;\n lineY2 *= dotMaskX2;\n }\n }\n float altMask = max(lineX2, lineY2);\n\n float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));\n float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));\n float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);\n float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);\n altMask *= edgeGate;\n\n float lineMask = max(primaryMask, altMask);\n\n float fade = exp(-dist * fadeStrength);\n\n float dur = max(0.05, uScanDuration);\n float del = max(0.0, uScanDelay);\n float scanZMax = 2.0;\n float widthScale = max(0.1, uScanGlow);\n float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);\n float sigmaA = sigma * 2.0;\n\n float combinedPulse = 0.0;\n float combinedAura = 0.0;\n\n float cycle = dur + del;\n float tCycle = mod(iTime, cycle);\n float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);\n float phase = scanPhase;\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phase = 1.0 - phase;\n } else if (uScanDirection > 1.5) {\n float t2 = mod(max(0.0, iTime - del), 2.0 * dur);\n phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);\n }\n float scanZ = phase * scanZMax;\n float dz = abs(hit.z - scanZ);\n float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));\n float taper = clamp(uPhaseTaper, 0.0, 0.49);\n float headW = taper;\n float tailW = taper;\n float headFade = smoother01(0.0, headW, phase);\n float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);\n float phaseWindow = headFade * tailFade;\n float pulseBase = lineBand * phaseWindow;\n combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);\n float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));\n combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);\n\n for (int i = 0; i < MAX_SCANS; i++) {\n if (float(i) >= uScanCount) break;\n float tActiveI = iTime - uScanStarts[i];\n float phaseI = clamp(tActiveI / dur, 0.0, 1.0);\n if (uScanDirection > 0.5 && uScanDirection < 1.5) {\n phaseI = 1.0 - phaseI;\n } else if (uScanDirection > 1.5) {\n phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);\n }\n float scanZI = phaseI * scanZMax;\n float dzI = abs(hit.z - scanZI);\n float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));\n float headFadeI = smoother01(0.0, headW, phaseI);\n float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);\n float phaseWindowI = headFadeI * tailFadeI;\n combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));\n combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);\n }\n\n float lineVis = lineMask;\n vec3 gridCol = uLinesColor * lineVis * fade;\n vec3 scanCol = uScanColor * combinedPulse;\n vec3 scanAura = uScanColor * combinedAura;\n\n color = gridCol + scanCol + scanAura;\n\n float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);\n color += (n - 0.5) * uNoise;\n color = clamp(color, 0.0, 1.0);\n float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);\n float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);\n float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);\n float halo = max(gx, gy) * fade;\n alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));\n fragColor = vec4(color, alpha);\n}\n\nvoid main(){\n vec4 c;\n mainImage(c, vUv * iResolution.xy);\n gl_FragColor = c;\n}\n`;\n\nexport const GridScan: React.FC = ({\n enableWebcam = false,\n showPreview = false,\n modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',\n sensitivity = 0.55,\n lineThickness = 1,\n linesColor = '#392e4e',\n scanColor = '#FF9FFC',\n scanOpacity = 0.4,\n gridScale = 0.1,\n lineStyle = 'solid',\n lineJitter = 0.1,\n scanDirection = 'pingpong',\n enablePost = true,\n bloomIntensity = 0,\n bloomThreshold = 0,\n bloomSmoothing = 0,\n chromaticAberration = 0.002,\n noiseIntensity = 0.01,\n scanGlow = 0.5,\n scanSoftness = 2,\n scanPhaseTaper = 0.9,\n scanDuration = 2.0,\n scanDelay = 2.0,\n enableGyro = false,\n scanOnClick = false,\n snapBackDelay = 250,\n className,\n style\n}) => {\n const containerRef = useRef(null);\n const videoRef = useRef(null);\n\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const composerRef = useRef(null);\n const bloomRef = useRef(null);\n const chromaRef = useRef(null);\n const rafRef = useRef(null);\n\n const [modelsReady, setModelsReady] = useState(false);\n const [uiFaceActive, setUiFaceActive] = useState(false);\n\n const lookTarget = useRef(new THREE.Vector2(0, 0));\n const tiltTarget = useRef(0);\n const yawTarget = useRef(0);\n\n const lookCurrent = useRef(new THREE.Vector2(0, 0));\n const lookVel = useRef(new THREE.Vector2(0, 0));\n const tiltCurrent = useRef(0);\n const tiltVel = useRef(0);\n const yawCurrent = useRef(0);\n const yawVel = useRef(0);\n\n const MAX_SCANS = 8;\n const scanStartsRef = useRef([]);\n\n const pushScan = (t: number) => {\n const arr = scanStartsRef.current.slice();\n if (arr.length >= MAX_SCANS) arr.shift();\n arr.push(t);\n scanStartsRef.current = arr;\n if (materialRef.current) {\n const u = materialRef.current.uniforms;\n const buf = new Array(MAX_SCANS).fill(0);\n for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];\n u.uScanStarts.value = buf;\n u.uScanCount.value = arr.length;\n }\n };\n\n const bufX = useRef([]);\n const bufY = useRef([]);\n const bufT = useRef([]);\n const bufYaw = useRef([]);\n\n const s = THREE.MathUtils.clamp(sensitivity, 0, 1);\n const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);\n const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);\n const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);\n const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);\n const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);\n const maxSpeed = Infinity;\n\n const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n let leaveTimer: number | null = null;\n const onMove = (e: MouseEvent) => {\n if (uiFaceActive) return;\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n const rect = el.getBoundingClientRect();\n const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);\n lookTarget.current.set(nx, ny);\n };\n const onClick = async () => {\n const nowSec = performance.now() / 1000;\n if (scanOnClick) pushScan(nowSec);\n if (\n enableGyro &&\n typeof window !== 'undefined' &&\n (window as any).DeviceOrientationEvent &&\n (DeviceOrientationEvent as any).requestPermission\n ) {\n try {\n await (DeviceOrientationEvent as any).requestPermission();\n } catch {}\n }\n };\n const onEnter = () => {\n if (leaveTimer) {\n clearTimeout(leaveTimer);\n leaveTimer = null;\n }\n };\n const onLeave = () => {\n if (uiFaceActive) return;\n if (leaveTimer) clearTimeout(leaveTimer);\n leaveTimer = window.setTimeout(\n () => {\n lookTarget.current.set(0, 0);\n tiltTarget.current = 0;\n yawTarget.current = 0;\n },\n Math.max(0, snapBackDelay || 0)\n );\n };\n el.addEventListener('mousemove', onMove);\n el.addEventListener('mouseenter', onEnter);\n if (scanOnClick) el.addEventListener('click', onClick);\n el.addEventListener('mouseleave', onLeave);\n return () => {\n el.removeEventListener('mousemove', onMove);\n el.removeEventListener('mouseenter', onEnter);\n el.removeEventListener('mouseleave', onLeave);\n if (scanOnClick) el.removeEventListener('click', onClick);\n if (leaveTimer) clearTimeout(leaveTimer);\n };\n }, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n rendererRef.current = renderer;\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.toneMapping = THREE.NoToneMapping;\n renderer.autoClear = false;\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n\n const uniforms = {\n iResolution: {\n value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())\n },\n iTime: { value: 0 },\n uSkew: { value: new THREE.Vector2(0, 0) },\n uTilt: { value: 0 },\n uYaw: { value: 0 },\n uLineThickness: { value: lineThickness },\n uLinesColor: { value: srgbColor(linesColor) },\n uScanColor: { value: srgbColor(scanColor) },\n uGridScale: { value: gridScale },\n uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },\n uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },\n uScanOpacity: { value: scanOpacity },\n uNoise: { value: noiseIntensity },\n uBloomOpacity: { value: bloomIntensity },\n uScanGlow: { value: scanGlow },\n uScanSoftness: { value: scanSoftness },\n uPhaseTaper: { value: scanPhaseTaper },\n uScanDuration: { value: scanDuration },\n uScanDelay: { value: scanDelay },\n uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },\n uScanStarts: { value: new Array(MAX_SCANS).fill(0) },\n uScanCount: { value: 0 }\n };\n\n const material = new THREE.ShaderMaterial({\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n transparent: true,\n depthWrite: false,\n depthTest: false\n });\n materialRef.current = material;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);\n scene.add(quad);\n\n let composer: EffectComposer | null = null;\n if (enablePost) {\n composer = new EffectComposer(renderer);\n composerRef.current = composer;\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const bloom = new BloomEffect({\n intensity: 1.0,\n luminanceThreshold: bloomThreshold,\n luminanceSmoothing: bloomSmoothing\n });\n bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);\n bloomRef.current = bloom;\n\n const chroma = new ChromaticAberrationEffect({\n offset: new THREE.Vector2(chromaticAberration, chromaticAberration),\n radialModulation: true,\n modulationOffset: 0.0\n });\n chromaRef.current = chroma;\n\n const effectPass = new EffectPass(camera, bloom, chroma);\n effectPass.renderToScreen = true;\n composer.addPass(effectPass);\n }\n\n const onResize = () => {\n renderer.setSize(container.clientWidth, container.clientHeight);\n material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());\n if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);\n };\n window.addEventListener('resize', onResize);\n\n let last = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));\n last = now;\n\n lookCurrent.current.copy(\n smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)\n );\n\n const tiltSm = smoothDampFloat(\n tiltCurrent.current,\n tiltTarget.current,\n { v: tiltVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n tiltCurrent.current = tiltSm.value;\n tiltVel.current = tiltSm.v;\n\n const yawSm = smoothDampFloat(\n yawCurrent.current,\n yawTarget.current,\n { v: yawVel.current },\n smoothTime,\n maxSpeed,\n dt\n );\n yawCurrent.current = yawSm.value;\n yawVel.current = yawSm.v;\n\n const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);\n material.uniforms.uSkew.value.set(skew.x, skew.y);\n material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;\n material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);\n\n material.uniforms.iTime.value = now / 1000;\n renderer.clear(true, true, true);\n if (composerRef.current) {\n composerRef.current.render(dt);\n } else {\n renderer.render(scene, camera);\n }\n rafRef.current = requestAnimationFrame(tick);\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n window.removeEventListener('resize', onResize);\n material.dispose();\n (quad.geometry as THREE.BufferGeometry).dispose();\n if (composerRef.current) {\n composerRef.current.dispose();\n composerRef.current = null;\n }\n renderer.dispose();\n renderer.forceContextLoss();\n container.removeChild(renderer.domElement);\n };\n }, [\n sensitivity,\n lineThickness,\n linesColor,\n scanColor,\n scanOpacity,\n gridScale,\n lineStyle,\n lineJitter,\n scanDirection,\n enablePost\n ]);\n\n useEffect(() => {\n const m = materialRef.current;\n if (m) {\n const u = m.uniforms;\n u.uLineThickness.value = lineThickness;\n (u.uLinesColor.value as THREE.Color).copy(srgbColor(linesColor));\n (u.uScanColor.value as THREE.Color).copy(srgbColor(scanColor));\n u.uGridScale.value = gridScale;\n u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;\n u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));\n u.uBloomOpacity.value = Math.max(0, bloomIntensity);\n u.uNoise.value = Math.max(0, noiseIntensity);\n u.uScanGlow.value = scanGlow;\n u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));\n u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;\n u.uScanSoftness.value = scanSoftness;\n u.uPhaseTaper.value = scanPhaseTaper;\n u.uScanDuration.value = Math.max(0.05, scanDuration);\n u.uScanDelay.value = Math.max(0.0, scanDelay);\n }\n if (bloomRef.current) {\n bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);\n (bloomRef.current as any).luminanceMaterial.threshold = bloomThreshold;\n (bloomRef.current as any).luminanceMaterial.smoothing = bloomSmoothing;\n }\n if (chromaRef.current) {\n chromaRef.current.offset.set(chromaticAberration, chromaticAberration);\n }\n }, [\n lineThickness,\n linesColor,\n scanColor,\n gridScale,\n lineStyle,\n lineJitter,\n bloomIntensity,\n bloomThreshold,\n bloomSmoothing,\n chromaticAberration,\n noiseIntensity,\n scanGlow,\n scanOpacity,\n scanDirection,\n scanSoftness,\n scanPhaseTaper,\n scanDuration,\n scanDelay\n ]);\n\n useEffect(() => {\n if (!enableGyro) return;\n const handler = (e: DeviceOrientationEvent) => {\n if (uiFaceActive) return;\n const gamma = e.gamma ?? 0;\n const beta = e.beta ?? 0;\n const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);\n const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);\n lookTarget.current.set(nx, ny);\n tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;\n };\n window.addEventListener('deviceorientation', handler);\n return () => {\n window.removeEventListener('deviceorientation', handler);\n };\n }, [enableGyro, uiFaceActive]);\n\n useEffect(() => {\n let canceled = false;\n const load = async () => {\n try {\n await Promise.all([\n faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),\n faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)\n ]);\n if (!canceled) setModelsReady(true);\n } catch {\n if (!canceled) setModelsReady(false);\n }\n };\n load();\n return () => {\n canceled = true;\n };\n }, [modelsPath]);\n\n useEffect(() => {\n let stop = false;\n let lastDetect = 0;\n\n const start = async () => {\n if (!enableWebcam || !modelsReady) return;\n const video = videoRef.current;\n if (!video) return;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },\n audio: false\n });\n video.srcObject = stream;\n await video.play();\n } catch {\n return;\n }\n\n const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });\n\n const detect = async (ts: number) => {\n if (stop) return;\n\n if (ts - lastDetect >= 33) {\n lastDetect = ts;\n try {\n const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);\n if (res && res.detection) {\n const det = res.detection;\n const box = det.box;\n const vw = video.videoWidth || 1;\n const vh = video.videoHeight || 1;\n\n const cx = box.x + box.width * 0.5;\n const cy = box.y + box.height * 0.5;\n const nx = (cx / vw) * 2 - 1;\n const ny = (cy / vh) * 2 - 1;\n medianPush(bufX.current, nx, 5);\n medianPush(bufY.current, ny, 5);\n const nxm = median(bufX.current);\n const nym = median(bufY.current);\n\n const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));\n\n const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));\n const depthScale = 1 + depthResponse * (faceSize - 0.25);\n lookTarget.current.copy(look.multiplyScalar(depthScale));\n\n const leftEye = res.landmarks.getLeftEye();\n const rightEye = res.landmarks.getRightEye();\n const lc = centroid(leftEye);\n const rc = centroid(rightEye);\n const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);\n medianPush(bufT.current, tilt, 5);\n tiltTarget.current = median(bufT.current);\n\n const nose = res.landmarks.getNose();\n const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];\n const jaw = res.landmarks.getJawOutline();\n const leftCheek = jaw[3] || jaw[2];\n const rightCheek = jaw[13] || jaw[14];\n const dL = dist2(tip, leftCheek);\n const dR = dist2(tip, rightCheek);\n const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;\n let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);\n yawSignal = Math.tanh(yawSignal);\n medianPush(bufYaw.current, yawSignal, 5);\n yawTarget.current = median(bufYaw.current);\n\n setUiFaceActive(true);\n } else {\n setUiFaceActive(false);\n }\n } catch {\n setUiFaceActive(false);\n }\n }\n\n if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {\n (video as any).requestVideoFrameCallback(() => detect(performance.now()));\n } else {\n requestAnimationFrame(detect);\n }\n };\n\n requestAnimationFrame(detect);\n };\n\n start();\n\n return () => {\n stop = true;\n const video = videoRef.current;\n if (video) {\n const stream = video.srcObject as MediaStream | null;\n if (stream) stream.getTracks().forEach(t => t.stop());\n video.pause();\n video.srcObject = null;\n }\n };\n }, [enableWebcam, modelsReady, depthResponse]);\n\n return (\n
\n {showPreview && (\n
\n
\n )}\n
\n );\n};\n\nfunction srgbColor(hex: string) {\n const c = new THREE.Color(hex);\n return c.convertSRGBToLinear();\n}\n\nfunction smoothDampVec2(\n current: THREE.Vector2,\n target: THREE.Vector2,\n currentVelocity: THREE.Vector2,\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): THREE.Vector2 {\n const out = current.clone();\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current.clone().sub(target);\n const originalTo = target.clone();\n\n const maxChange = maxSpeed * smoothTime;\n if (change.length() > maxChange) change.setLength(maxChange);\n\n target = current.clone().sub(change);\n const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);\n currentVelocity.sub(temp.clone().multiplyScalar(omega));\n currentVelocity.multiplyScalar(exp);\n\n out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));\n\n const origMinusCurrent = originalTo.clone().sub(current);\n const outMinusOrig = out.clone().sub(originalTo);\n if (origMinusCurrent.dot(outMinusOrig) > 0) {\n out.copy(originalTo);\n currentVelocity.set(0, 0);\n }\n return out;\n}\n\nfunction smoothDampFloat(\n current: number,\n target: number,\n velRef: { v: number },\n smoothTime: number,\n maxSpeed: number,\n deltaTime: number\n): { value: number; v: number } {\n smoothTime = Math.max(0.0001, smoothTime);\n const omega = 2 / smoothTime;\n const x = omega * deltaTime;\n const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);\n\n let change = current - target;\n const originalTo = target;\n\n const maxChange = maxSpeed * smoothTime;\n change = Math.sign(change) * Math.min(Math.abs(change), maxChange);\n\n target = current - change;\n const temp = (velRef.v + omega * change) * deltaTime;\n velRef.v = (velRef.v - omega * temp) * exp;\n\n let out = target + (change + temp) * exp;\n\n const origMinusCurrent = originalTo - current;\n const outMinusOrig = out - originalTo;\n if (origMinusCurrent * outMinusOrig > 0) {\n out = originalTo;\n velRef.v = 0;\n }\n return { value: out, v: velRef.v };\n}\n\nfunction medianPush(buf: number[], v: number, maxLen: number) {\n buf.push(v);\n if (buf.length > maxLen) buf.shift();\n}\n\nfunction median(buf: number[]) {\n if (buf.length === 0) return 0;\n const a = [...buf].sort((x, y) => x - y);\n const mid = Math.floor(a.length / 2);\n return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;\n}\n\nfunction centroid(points: { x: number; y: number }[]) {\n let x = 0,\n y = 0;\n const n = points.length || 1;\n for (const p of points) {\n x += p.x;\n y += p.y;\n }\n return { x: x / n, y: y / n };\n}\n\nfunction dist2(a: { x: number; y: number }, b: { x: number; y: number }) {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n" } ], "registryDependencies": [], "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/Hyperspeed-JS-CSS.json b/public/r/Hyperspeed-JS-CSS.json index b27f4990..da2fc870 100644 --- a/public/r/Hyperspeed-JS-CSS.json +++ b/public/r/Hyperspeed-JS-CSS.json @@ -18,12 +18,12 @@ { "type": "registry:component", "path": "Hyperspeed/Hyperspeed.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\n\nimport './Hyperspeed.css';\n\nconst DEFAULT_EFFECT_OPTIONS = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nconst Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n const mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n };\n\n const xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n };\n\n const LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n };\n\n const turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n };\n\n const deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n };\n\n let nsin = val => Math.sin(val) * 0.5 + 0.5;\n\n const distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = mountainUniforms.uFreq.value;\n let uAmp = mountainUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n let lookAtAmp = new THREE.Vector3(2, 2, 2);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = xyUniforms.uFreq.value;\n let uAmp = xyUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n let lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let camProgress = 0.0125;\n let uFreq = LongRaceUniforms.uFreq.value;\n let uAmp = LongRaceUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(1, 1, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = p =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = p =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -5, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = p => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = p => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -4, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n };\n\n class App {\n constructor(container, options = {}) {\n this.options = options;\n if (this.options.distortion == null) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.composer = new EffectComposer(this.renderer);\n container.append(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(\n options.fov,\n container.offsetWidth / container.offsetHeight,\n 0.1,\n 10000\n );\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n let fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n window.addEventListener('resize', this.onWindowResize.bind(this));\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM,\n searchImage: SMAAEffect.searchImageDataURL,\n areaImage: SMAAEffect.areaImageDataURL\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets() {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, { passive: true });\n this.container.addEventListener('touchend', this.onTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true });\n\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev) {\n ev.preventDefault();\n }\n\n update(delta) {\n let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n\n let time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n\n }\n\n render(delta) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.renderer) {\n this.renderer.dispose();\n }\n if (this.composer) {\n this.composer.dispose();\n }\n if (this.scene) {\n this.scene.clear();\n }\n\n window.removeEventListener('resize', this.onWindowResize.bind(this));\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width, height, updateStyles) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n }\n\n const distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n };\n\n const distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n `;\n\n const random = base => {\n if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0];\n return Math.random() * base;\n };\n\n const pickRandom = arr => {\n if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)];\n return arr;\n };\n\n function lerp(current, target, speed = 0.1, limit = 0.001) {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n }\n\n class CarLights {\n constructor(webgl, options, colors, speed, fade) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n let curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n let geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n let laneWidth = options.roadWidth / options.lanesPerRoad;\n\n let aOffset = [];\n let aMetrics = [];\n let aColor = [];\n\n let colors = this.colors;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n let radius = random(options.carLightsRadius);\n let length = random(options.carLightsLength);\n let speed = random(this.speed);\n\n let carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n let carWidth = random(options.carWidthPercentage) * laneWidth;\n let carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n let offsetY = random(options.carFloorSeparation) + radius * 1.3;\n\n let offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n let material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n let mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n class LightsSticks {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n let totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n let stickoffset = options.length / (totalSticks - 1);\n const aOffset = [];\n const aColor = [];\n const aMetrics = [];\n\n let colors = options.colors.sticks;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < totalSticks; i++) {\n let width = random(options.lightStickWidth);\n let height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\tcos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t1.0,\t\t\t 0,\t0,\n -sin(angle),\t0,\t\tcos(angle),\t0,\n 0, \t\t0,\t\t\t\t0,\t1);\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n const sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n class Road {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side, width, isRoad) {\n const options = this.options;\n let segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n let uniforms = {\n uTravelLength: { value: options.length },\n uColor: { value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor) },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: { value: new THREE.Color(options.colors.brokenLines) },\n uShoulderLinesColor: { value: new THREE.Color(options.colors.shoulderLines) },\n uShoulderLinesWidthPercentage: { value: options.shoulderLinesWidthPercentage },\n uBrokenLinesLengthPercentage: { value: options.brokenLinesLengthPercentage },\n uBrokenLinesWidthPercentage: { value: options.brokenLinesWidthPercentage }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(uniforms, this.webgl.fogUniforms, options.distortion.uniforms)\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n this.webgl.scene.add(mesh);\n\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time) {\n this.uTime.value = time;\n }\n }\n\n const roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\n const roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n `;\n\n const roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n `;\n\n const roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\n const roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n function resizeRendererToDisplaySize(renderer, setSize) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n }\n\n (function () {\n const container = document.getElementById('lights');\n const options = { ...DEFAULT_EFFECT_OPTIONS, ...effectOptions, colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors } };\n options.distortion = distortions[options.distortion];\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n })();\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" + "content": "import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nimport './Hyperspeed.css';\n\nconst DEFAULT_EFFECT_OPTIONS = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nconst Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n const mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n };\n\n const xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n };\n\n const LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n };\n\n const turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n };\n\n const deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n };\n\n let nsin = val => Math.sin(val) * 0.5 + 0.5;\n\n const distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = mountainUniforms.uFreq.value;\n let uAmp = mountainUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n let lookAtAmp = new THREE.Vector3(2, 2, 2);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = xyUniforms.uFreq.value;\n let uAmp = xyUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n let lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let camProgress = 0.0125;\n let uFreq = LongRaceUniforms.uFreq.value;\n let uAmp = LongRaceUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(1, 1, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = p =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = p =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -5, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = p => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = p => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -4, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n };\n\n class App {\n constructor(container, options = {}) {\n this.options = options;\n if (this.options.distortion == null) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.composer = new EffectComposer(this.renderer);\n container.append(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(\n options.fov,\n container.offsetWidth / container.offsetHeight,\n 0.1,\n 10000\n );\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n let fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n this.onWindowResize = this.onWindowResize.bind(this);\n window.addEventListener('resize', this.onWindowResize);\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM,\n searchImage: SMAAEffect.searchImageDataURL,\n areaImage: SMAAEffect.areaImageDataURL\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets() {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, { passive: true });\n this.container.addEventListener('touchend', this.onTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true });\n\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev) {\n ev.preventDefault();\n }\n\n update(delta) {\n let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n\n let time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n }\n\n render(delta) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.scene) {\n this.scene.traverse(object => {\n const obj = object;\n if (!obj.isMesh) return;\n\n if (obj.geometry) obj.geometry.dispose();\n\n if (obj.material) {\n if (Array.isArray(obj.material)) {\n obj.material.forEach(material => material.dispose());\n } else {\n obj.material.dispose();\n }\n }\n });\n this.scene.clear();\n }\n\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n if (this.renderer.domElement && this.renderer.domElement.parentNode) {\n this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);\n }\n }\n if (this.composer) {\n this.composer.dispose();\n }\n\n window.removeEventListener('resize', this.onWindowResize);\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width, height, updateStyles) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n }\n\n const distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n };\n\n const distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n `;\n\n const random = base => {\n if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0];\n return Math.random() * base;\n };\n\n const pickRandom = arr => {\n if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)];\n return arr;\n };\n\n function lerp(current, target, speed = 0.1, limit = 0.001) {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n }\n\n class CarLights {\n constructor(webgl, options, colors, speed, fade) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n let curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n let geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n let laneWidth = options.roadWidth / options.lanesPerRoad;\n\n let aOffset = [];\n let aMetrics = [];\n let aColor = [];\n\n let colors = this.colors;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n let radius = random(options.carLightsRadius);\n let length = random(options.carLightsLength);\n let speed = random(this.speed);\n\n let carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n let carWidth = random(options.carWidthPercentage) * laneWidth;\n let carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n let offsetY = random(options.carFloorSeparation) + radius * 1.3;\n\n let offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n let material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n let mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n class LightsSticks {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n let totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n let stickoffset = options.length / (totalSticks - 1);\n const aOffset = [];\n const aColor = [];\n const aMetrics = [];\n\n let colors = options.colors.sticks;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < totalSticks; i++) {\n let width = random(options.lightStickWidth);\n let height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\tcos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t1.0,\t\t\t 0,\t0,\n -sin(angle),\t0,\t\tcos(angle),\t0,\n 0, \t\t0,\t\t\t\t0,\t1);\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n const sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n class Road {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side, width, isRoad) {\n const options = this.options;\n let segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n let uniforms = {\n uTravelLength: { value: options.length },\n uColor: { value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor) },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: { value: new THREE.Color(options.colors.brokenLines) },\n uShoulderLinesColor: { value: new THREE.Color(options.colors.shoulderLines) },\n uShoulderLinesWidthPercentage: { value: options.shoulderLinesWidthPercentage },\n uBrokenLinesLengthPercentage: { value: options.brokenLinesLengthPercentage },\n uBrokenLinesWidthPercentage: { value: options.brokenLinesWidthPercentage }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(uniforms, this.webgl.fogUniforms, options.distortion.uniforms)\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n this.webgl.scene.add(mesh);\n\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time) {\n this.uTime.value = time;\n }\n }\n\n const roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\n const roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n `;\n\n const roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n `;\n\n const roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\n const roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n function resizeRendererToDisplaySize(renderer, setSize) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n }\n\n (function () {\n const container = document.getElementById('lights');\n const options = {\n ...DEFAULT_EFFECT_OPTIONS,\n ...effectOptions,\n colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors }\n };\n options.distortion = distortions[options.distortion];\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n })();\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/Hyperspeed-JS-TW.json b/public/r/Hyperspeed-JS-TW.json index 10e081b0..1527d42b 100644 --- a/public/r/Hyperspeed-JS-TW.json +++ b/public/r/Hyperspeed-JS-TW.json @@ -13,12 +13,12 @@ { "type": "registry:component", "path": "Hyperspeed/Hyperspeed.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\n\nconst DEFAULT_EFFECT_OPTIONS = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nconst Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n\n const mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n };\n\n const xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n };\n\n const LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n };\n\n const turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n };\n\n const deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n };\n\n let nsin = val => Math.sin(val) * 0.5 + 0.5;\n\n const distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = mountainUniforms.uFreq.value;\n let uAmp = mountainUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n let lookAtAmp = new THREE.Vector3(2, 2, 2);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = xyUniforms.uFreq.value;\n let uAmp = xyUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n let lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let camProgress = 0.0125;\n let uFreq = LongRaceUniforms.uFreq.value;\n let uAmp = LongRaceUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(1, 1, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = p =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = p =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -5, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = p => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = p => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -4, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n };\n\n class App {\n constructor(container, options = {}) {\n this.options = options;\n if (this.options.distortion == null) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.composer = new EffectComposer(this.renderer);\n container.append(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(\n options.fov,\n container.offsetWidth / container.offsetHeight,\n 0.1,\n 10000\n );\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n let fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n window.addEventListener('resize', this.onWindowResize.bind(this));\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM,\n searchImage: SMAAEffect.searchImageDataURL,\n areaImage: SMAAEffect.areaImageDataURL\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets() {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, { passive: true });\n this.container.addEventListener('touchend', this.onTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true });\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev) {\n ev.preventDefault();\n }\n\n update(delta) {\n let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n\n let time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n\n }\n\n render(delta) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.renderer) {\n this.renderer.dispose();\n }\n if (this.composer) {\n this.composer.dispose();\n }\n if (this.scene) {\n this.scene.clear();\n }\n\n window.removeEventListener('resize', this.onWindowResize.bind(this));\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width, height, updateStyles) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n }\n\n const distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n };\n\n const distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n `;\n\n const random = base => {\n if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0];\n return Math.random() * base;\n };\n\n const pickRandom = arr => {\n if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)];\n return arr;\n };\n\n function lerp(current, target, speed = 0.1, limit = 0.001) {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n }\n\n class CarLights {\n constructor(webgl, options, colors, speed, fade) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n let curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n let geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n let laneWidth = options.roadWidth / options.lanesPerRoad;\n\n let aOffset = [];\n let aMetrics = [];\n let aColor = [];\n\n let colors = this.colors;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n let radius = random(options.carLightsRadius);\n let length = random(options.carLightsLength);\n let speed = random(this.speed);\n\n let carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n let carWidth = random(options.carWidthPercentage) * laneWidth;\n let carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n let offsetY = random(options.carFloorSeparation) + radius * 1.3;\n\n let offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n let material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n let mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n class LightsSticks {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n let totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n let stickoffset = options.length / (totalSticks - 1);\n const aOffset = [];\n const aColor = [];\n const aMetrics = [];\n\n let colors = options.colors.sticks;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < totalSticks; i++) {\n let width = random(options.lightStickWidth);\n let height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\tcos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t1.0,\t\t\t 0,\t0,\n -sin(angle),\t0,\t\tcos(angle),\t0,\n 0, \t\t0,\t\t\t\t0,\t1);\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n const sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n class Road {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side, width, isRoad) {\n const options = this.options;\n let segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n let uniforms = {\n uTravelLength: { value: options.length },\n uColor: { value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor) },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: { value: new THREE.Color(options.colors.brokenLines) },\n uShoulderLinesColor: { value: new THREE.Color(options.colors.shoulderLines) },\n uShoulderLinesWidthPercentage: { value: options.shoulderLinesWidthPercentage },\n uBrokenLinesLengthPercentage: { value: options.brokenLinesLengthPercentage },\n uBrokenLinesWidthPercentage: { value: options.brokenLinesWidthPercentage }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(uniforms, this.webgl.fogUniforms, options.distortion.uniforms)\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n this.webgl.scene.add(mesh);\n\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time) {\n this.uTime.value = time;\n }\n }\n\n const roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\n const roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n `;\n\n const roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n `;\n\n const roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\n const roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n function resizeRendererToDisplaySize(renderer, setSize) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n }\n\n (function () {\n const container = document.getElementById('lights');\n const options = { ...DEFAULT_EFFECT_OPTIONS, ...effectOptions, colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors } };\n options.distortion = distortions[options.distortion];\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n })();\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" + "content": "import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst DEFAULT_EFFECT_OPTIONS = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nconst Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n\n const mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n };\n\n const xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n };\n\n const LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n };\n\n const turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n };\n\n const deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n };\n\n let nsin = val => Math.sin(val) * 0.5 + 0.5;\n\n const distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = mountainUniforms.uFreq.value;\n let uAmp = mountainUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n let lookAtAmp = new THREE.Vector3(2, 2, 2);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let movementProgressFix = 0.02;\n let uFreq = xyUniforms.uFreq.value;\n let uAmp = xyUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n let lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n let camProgress = 0.0125;\n let uFreq = LongRaceUniforms.uFreq.value;\n let uAmp = LongRaceUniforms.uAmp.value;\n let distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n let lookAtAmp = new THREE.Vector3(1, 1, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = p =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = p =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -5, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress, time) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = p => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = p => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n let distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n let lookAtAmp = new THREE.Vector3(-2, -4, 0);\n let lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n };\n\n class App {\n constructor(container, options = {}) {\n this.options = options;\n if (this.options.distortion == null) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.composer = new EffectComposer(this.renderer);\n container.append(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(\n options.fov,\n container.offsetWidth / container.offsetHeight,\n 0.1,\n 10000\n );\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n let fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n this.onWindowResize = this.onWindowResize.bind(this);\n window.addEventListener('resize', this.onWindowResize);\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM,\n searchImage: SMAAEffect.searchImageDataURL,\n areaImage: SMAAEffect.areaImageDataURL\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets() {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, { passive: true });\n this.container.addEventListener('touchend', this.onTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true });\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev) {\n ev.preventDefault();\n }\n\n update(delta) {\n let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n\n let time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n }\n\n render(delta) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.scene) {\n this.scene.traverse(object => {\n const obj = object;\n if (!obj.isMesh) return;\n\n if (obj.geometry) obj.geometry.dispose();\n\n if (obj.material) {\n if (Array.isArray(obj.material)) {\n obj.material.forEach(material => material.dispose());\n } else {\n obj.material.dispose();\n }\n }\n });\n this.scene.clear();\n }\n\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n if (this.renderer.domElement && this.renderer.domElement.parentNode) {\n this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);\n }\n }\n if (this.composer) {\n this.composer.dispose();\n }\n\n window.removeEventListener('resize', this.onWindowResize);\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width, height, updateStyles) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n }\n\n const distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n };\n\n const distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n `;\n\n const random = base => {\n if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0];\n return Math.random() * base;\n };\n\n const pickRandom = arr => {\n if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)];\n return arr;\n };\n\n function lerp(current, target, speed = 0.1, limit = 0.001) {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n }\n\n class CarLights {\n constructor(webgl, options, colors, speed, fade) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n let curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n let geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n let laneWidth = options.roadWidth / options.lanesPerRoad;\n\n let aOffset = [];\n let aMetrics = [];\n let aColor = [];\n\n let colors = this.colors;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n let radius = random(options.carLightsRadius);\n let length = random(options.carLightsLength);\n let speed = random(this.speed);\n\n let carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n let carWidth = random(options.carWidthPercentage) * laneWidth;\n let carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n let offsetY = random(options.carFloorSeparation) + radius * 1.3;\n\n let offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(speed);\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n let material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n let mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n class LightsSticks {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n let instanced = new THREE.InstancedBufferGeometry().copy(geometry);\n let totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n let stickoffset = options.length / (totalSticks - 1);\n const aOffset = [];\n const aColor = [];\n const aMetrics = [];\n\n let colors = options.colors.sticks;\n if (Array.isArray(colors)) {\n colors = colors.map(c => new THREE.Color(c));\n } else {\n colors = new THREE.Color(colors);\n }\n\n for (let i = 0; i < totalSticks; i++) {\n let width = random(options.lightStickWidth);\n let height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n let color = pickRandom(colors);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n options.distortion.uniforms\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n\n const sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\tcos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t1.0,\t\t\t 0,\t0,\n -sin(angle),\t0,\t\tcos(angle),\t0,\n 0, \t\t0,\t\t\t\t0,\t1);\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n const sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n class Road {\n constructor(webgl, options) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side, width, isRoad) {\n const options = this.options;\n let segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n let uniforms = {\n uTravelLength: { value: options.length },\n uColor: { value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor) },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: { value: new THREE.Color(options.colors.brokenLines) },\n uShoulderLinesColor: { value: new THREE.Color(options.colors.shoulderLines) },\n uShoulderLinesWidthPercentage: { value: options.shoulderLinesWidthPercentage },\n uBrokenLinesLengthPercentage: { value: options.brokenLinesLengthPercentage },\n uBrokenLinesWidthPercentage: { value: options.brokenLinesWidthPercentage }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(uniforms, this.webgl.fogUniforms, options.distortion.uniforms)\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n options.distortion.getDistortion\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n this.webgl.scene.add(mesh);\n\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time) {\n this.uTime.value = time;\n }\n }\n\n const roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n `;\n\n const islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\n const roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n `;\n\n const roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n `;\n\n const roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\n const roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n `;\n\n function resizeRendererToDisplaySize(renderer, setSize) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n }\n\n (function () {\n const container = document.getElementById('lights');\n const options = {\n ...DEFAULT_EFFECT_OPTIONS,\n ...effectOptions,\n colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors }\n };\n options.distortion = distortions[options.distortion];\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n })();\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/Hyperspeed-TS-CSS.json b/public/r/Hyperspeed-TS-CSS.json index 1cffff99..37b2f8ef 100644 --- a/public/r/Hyperspeed-TS-CSS.json +++ b/public/r/Hyperspeed-TS-CSS.json @@ -18,12 +18,12 @@ { "type": "registry:component", "path": "Hyperspeed/Hyperspeed.tsx", - "content": "import { useEffect, useRef, FC } from 'react';\nimport * as THREE from 'three';\nimport { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\n\nimport './Hyperspeed.css';\n\ninterface Distortion {\n uniforms: Record;\n getDistortion: string;\n getJS?: (progress: number, time: number) => THREE.Vector3;\n}\n\ninterface Distortions {\n [key: string]: Distortion;\n}\n\ninterface Colors {\n roadColor: number;\n islandColor: number;\n background: number;\n shoulderLines: number;\n brokenLines: number;\n leftCars: number[];\n rightCars: number[];\n sticks: number;\n}\n\ninterface HyperspeedOptions {\n onSpeedUp?: (ev: MouseEvent | TouchEvent) => void;\n onSlowDown?: (ev: MouseEvent | TouchEvent) => void;\n distortion?: string | Distortion;\n length: number;\n roadWidth: number;\n islandWidth: number;\n lanesPerRoad: number;\n fov: number;\n fovSpeedUp: number;\n speedUp: number;\n carLightsFade: number;\n totalSideLightSticks: number;\n lightPairsPerRoadWay: number;\n shoulderLinesWidthPercentage: number;\n brokenLinesWidthPercentage: number;\n brokenLinesLengthPercentage: number;\n lightStickWidth: [number, number];\n lightStickHeight: [number, number];\n movingAwaySpeed: [number, number];\n movingCloserSpeed: [number, number];\n carLightsLength: [number, number];\n carLightsRadius: [number, number];\n carWidthPercentage: [number, number];\n carShiftX: [number, number];\n carFloorSeparation: [number, number];\n colors: Colors;\n isHyper?: boolean;\n}\n\ninterface HyperspeedProps {\n effectOptions?: Partial;\n}\n\nconst defaultOptions: HyperspeedOptions = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nfunction nsin(val: number) {\n return Math.sin(val) * 0.5 + 0.5;\n}\n\nconst mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n};\n\nconst xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n};\n\nconst LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n};\n\nconst turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n};\n\nconst deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n};\n\nconst distortions: Distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = mountainUniforms.uFreq.value;\n const uAmp = mountainUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n const lookAtAmp = new THREE.Vector3(2, 2, 2);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = xyUniforms.uFreq.value;\n const uAmp = xyUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n const lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const camProgress = 0.0125;\n const uFreq = LongRaceUniforms.uFreq.value;\n const uAmp = LongRaceUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(1, 1, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = (p: number) =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = (p: number) =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -5, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = (p: number) => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = (p: number) => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -4, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n};\n\nconst distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n};\n\nconst distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n`;\n\nfunction random(base: number | [number, number]): number {\n if (Array.isArray(base)) {\n return Math.random() * (base[1] - base[0]) + base[0];\n }\n return Math.random() * base;\n}\n\nfunction pickRandom(arr: T | T[]): T {\n if (Array.isArray(arr)) {\n return arr[Math.floor(Math.random() * arr.length)];\n }\n return arr;\n}\n\nfunction lerp(current: number, target: number, speed = 0.1, limit = 0.001): number {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n}\n\nclass CarLights {\n webgl: App;\n options: HyperspeedOptions;\n colors: number[] | THREE.Color;\n speed: [number, number];\n fade: THREE.Vector2;\n mesh!: THREE.Mesh;\n\n constructor(\n webgl: App,\n options: HyperspeedOptions,\n colors: number[] | THREE.Color,\n speed: [number, number],\n fade: THREE.Vector2\n ) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n const curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n const geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n const laneWidth = options.roadWidth / options.lanesPerRoad;\n\n const aOffset: number[] = [];\n const aMetrics: number[] = [];\n const aColor: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(this.colors)) {\n colorArray = this.colors.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(this.colors)];\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n const radius = random(options.carLightsRadius);\n const length = random(options.carLightsLength);\n const spd = random(this.speed);\n\n const carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n const carWidth = random(options.carWidthPercentage) * laneWidth;\n const carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n const offsetY = random(options.carFloorSeparation) + radius * 1.3;\n const offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n (typeof this.options.distortion === 'object' ? this.options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nclass LightsSticks {\n webgl: App;\n options: HyperspeedOptions;\n mesh!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n const totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n const stickoffset = options.length / (totalSticks - 1);\n const aOffset: number[] = [];\n const aColor: number[] = [];\n const aMetrics: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(options.colors.sticks)) {\n colorArray = options.colors.sticks.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(options.colors.sticks)];\n }\n\n for (let i = 0; i < totalSticks; i++) {\n const width = random(options.lightStickWidth);\n const height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\n cos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t 1.0,\t0,\t\t\t0,\n -sin(angle),\t 0,\t\tcos(angle),\t0,\n 0, \t\t 0,\t\t0,\t\t\t1\n );\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nconst sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nclass Road {\n webgl: App;\n options: HyperspeedOptions;\n uTime: { value: number };\n leftRoadWay!: THREE.Mesh;\n rightRoadWay!: THREE.Mesh;\n island!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side: number, width: number, isRoad: boolean) {\n const options = this.options;\n const segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n\n let uniforms: Record = {\n uTravelLength: { value: options.length },\n uColor: {\n value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor)\n },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: {\n value: new THREE.Color(options.colors.brokenLines)\n },\n uShoulderLinesColor: {\n value: new THREE.Color(options.colors.shoulderLines)\n },\n uShoulderLinesWidthPercentage: {\n value: options.shoulderLinesWidthPercentage\n },\n uBrokenLinesLengthPercentage: {\n value: options.brokenLinesLengthPercentage\n },\n uBrokenLinesWidthPercentage: {\n value: options.brokenLinesWidthPercentage\n }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n uniforms,\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n\n this.webgl.scene.add(mesh);\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time: number) {\n this.uTime.value = time;\n }\n}\n\nconst roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\nconst roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n`;\n\nconst roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n`;\n\nconst roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\nconst roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nfunction resizeRendererToDisplaySize(\n renderer: THREE.WebGLRenderer,\n setSize: (width: number, height: number, updateStyle: boolean) => void\n) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n}\n\nclass App {\n container: HTMLElement;\n options: HyperspeedOptions;\n renderer: THREE.WebGLRenderer;\n composer: EffectComposer;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n renderPass!: RenderPass;\n bloomPass!: EffectPass;\n clock: THREE.Clock;\n assets: Record;\n disposed: boolean;\n road: Road;\n leftCarLights: CarLights;\n rightCarLights: CarLights;\n leftSticks: LightsSticks;\n fogUniforms: Record;\n fovTarget: number;\n speedUpTarget: number;\n speedUp: number;\n timeOffset: number;\n\n constructor(container: HTMLElement, options: HyperspeedOptions) {\n this.options = options;\n if (!this.options.distortion) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n\n this.composer = new EffectComposer(this.renderer);\n container.appendChild(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(options.fov, container.offsetWidth / container.offsetHeight, 0.1, 10000);\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n const fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n window.addEventListener('resize', this.onWindowResize.bind(this));\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets(): Promise {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, { passive: true });\n this.container.addEventListener('touchend', this.onTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true });\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev: MouseEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev: MouseEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev: TouchEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev: TouchEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev: MouseEvent) {\n ev.preventDefault();\n }\n\n update(delta: number) {\n const lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n const time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n const fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (typeof this.options.distortion === 'object' && this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n }\n\n render(delta: number) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.renderer) {\n this.renderer.dispose();\n }\n if (this.composer) {\n this.composer.dispose();\n }\n if (this.scene) {\n this.scene.clear();\n }\n\n window.removeEventListener('resize', this.onWindowResize.bind(this));\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width: number, height: number, updateStyles: boolean) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n}\n\nconst DEFAULT_EFFECT_OPTIONS: Partial = {};\n\nconst Hyperspeed: FC = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n\n const container = hyperspeed.current;\n if (!container) return;\n\n const options: HyperspeedOptions = { ...defaultOptions, ...effectOptions, colors: { ...defaultOptions.colors, ...effectOptions.colors } };\n if (typeof options.distortion === 'string') {\n options.distortion = distortions[options.distortion];\n }\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" + "content": "import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\nimport { FC, useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nimport './Hyperspeed.css';\n\ninterface Distortion {\n uniforms: Record;\n getDistortion: string;\n getJS?: (progress: number, time: number) => THREE.Vector3;\n}\n\ninterface Distortions {\n [key: string]: Distortion;\n}\n\ninterface Colors {\n roadColor: number;\n islandColor: number;\n background: number;\n shoulderLines: number;\n brokenLines: number;\n leftCars: number[];\n rightCars: number[];\n sticks: number;\n}\n\ninterface HyperspeedOptions {\n onSpeedUp?: (ev: MouseEvent | TouchEvent) => void;\n onSlowDown?: (ev: MouseEvent | TouchEvent) => void;\n distortion?: string | Distortion;\n length: number;\n roadWidth: number;\n islandWidth: number;\n lanesPerRoad: number;\n fov: number;\n fovSpeedUp: number;\n speedUp: number;\n carLightsFade: number;\n totalSideLightSticks: number;\n lightPairsPerRoadWay: number;\n shoulderLinesWidthPercentage: number;\n brokenLinesWidthPercentage: number;\n brokenLinesLengthPercentage: number;\n lightStickWidth: [number, number];\n lightStickHeight: [number, number];\n movingAwaySpeed: [number, number];\n movingCloserSpeed: [number, number];\n carLightsLength: [number, number];\n carLightsRadius: [number, number];\n carWidthPercentage: [number, number];\n carShiftX: [number, number];\n carFloorSeparation: [number, number];\n colors: Colors;\n isHyper?: boolean;\n}\n\ninterface HyperspeedProps {\n effectOptions?: Partial;\n}\n\nconst defaultOptions: HyperspeedOptions = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nfunction nsin(val: number) {\n return Math.sin(val) * 0.5 + 0.5;\n}\n\nconst mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n};\n\nconst xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n};\n\nconst LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n};\n\nconst turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n};\n\nconst deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n};\n\nconst distortions: Distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = mountainUniforms.uFreq.value;\n const uAmp = mountainUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n const lookAtAmp = new THREE.Vector3(2, 2, 2);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = xyUniforms.uFreq.value;\n const uAmp = xyUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n const lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const camProgress = 0.0125;\n const uFreq = LongRaceUniforms.uFreq.value;\n const uAmp = LongRaceUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(1, 1, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = (p: number) =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = (p: number) =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -5, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = (p: number) => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = (p: number) => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -4, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n};\n\nconst distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n};\n\nconst distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n`;\n\nfunction random(base: number | [number, number]): number {\n if (Array.isArray(base)) {\n return Math.random() * (base[1] - base[0]) + base[0];\n }\n return Math.random() * base;\n}\n\nfunction pickRandom(arr: T | T[]): T {\n if (Array.isArray(arr)) {\n return arr[Math.floor(Math.random() * arr.length)];\n }\n return arr;\n}\n\nfunction lerp(current: number, target: number, speed = 0.1, limit = 0.001): number {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n}\n\nclass CarLights {\n webgl: App;\n options: HyperspeedOptions;\n colors: number[] | THREE.Color;\n speed: [number, number];\n fade: THREE.Vector2;\n mesh!: THREE.Mesh;\n\n constructor(\n webgl: App,\n options: HyperspeedOptions,\n colors: number[] | THREE.Color,\n speed: [number, number],\n fade: THREE.Vector2\n ) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n const curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n const geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n const laneWidth = options.roadWidth / options.lanesPerRoad;\n\n const aOffset: number[] = [];\n const aMetrics: number[] = [];\n const aColor: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(this.colors)) {\n colorArray = this.colors.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(this.colors)];\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n const radius = random(options.carLightsRadius);\n const length = random(options.carLightsLength);\n const spd = random(this.speed);\n\n const carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n const carWidth = random(options.carWidthPercentage) * laneWidth;\n const carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n const offsetY = random(options.carFloorSeparation) + radius * 1.3;\n const offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n (typeof this.options.distortion === 'object' ? this.options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nclass LightsSticks {\n webgl: App;\n options: HyperspeedOptions;\n mesh!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n const totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n const stickoffset = options.length / (totalSticks - 1);\n const aOffset: number[] = [];\n const aColor: number[] = [];\n const aMetrics: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(options.colors.sticks)) {\n colorArray = options.colors.sticks.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(options.colors.sticks)];\n }\n\n for (let i = 0; i < totalSticks; i++) {\n const width = random(options.lightStickWidth);\n const height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\n cos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t 1.0,\t0,\t\t\t0,\n -sin(angle),\t 0,\t\tcos(angle),\t0,\n 0, \t\t 0,\t\t0,\t\t\t1\n );\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nconst sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nclass Road {\n webgl: App;\n options: HyperspeedOptions;\n uTime: { value: number };\n leftRoadWay!: THREE.Mesh;\n rightRoadWay!: THREE.Mesh;\n island!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side: number, width: number, isRoad: boolean) {\n const options = this.options;\n const segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n\n let uniforms: Record = {\n uTravelLength: { value: options.length },\n uColor: {\n value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor)\n },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: {\n value: new THREE.Color(options.colors.brokenLines)\n },\n uShoulderLinesColor: {\n value: new THREE.Color(options.colors.shoulderLines)\n },\n uShoulderLinesWidthPercentage: {\n value: options.shoulderLinesWidthPercentage\n },\n uBrokenLinesLengthPercentage: {\n value: options.brokenLinesLengthPercentage\n },\n uBrokenLinesWidthPercentage: {\n value: options.brokenLinesWidthPercentage\n }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n uniforms,\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n\n this.webgl.scene.add(mesh);\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time: number) {\n this.uTime.value = time;\n }\n}\n\nconst roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\nconst roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n`;\n\nconst roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n`;\n\nconst roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\nconst roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nfunction resizeRendererToDisplaySize(\n renderer: THREE.WebGLRenderer,\n setSize: (width: number, height: number, updateStyle: boolean) => void\n) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n}\n\nclass App {\n container: HTMLElement;\n options: HyperspeedOptions;\n renderer: THREE.WebGLRenderer;\n composer: EffectComposer;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n renderPass!: RenderPass;\n bloomPass!: EffectPass;\n clock: THREE.Clock;\n assets: Record;\n disposed: boolean;\n road: Road;\n leftCarLights: CarLights;\n rightCarLights: CarLights;\n leftSticks: LightsSticks;\n fogUniforms: Record;\n fovTarget: number;\n speedUpTarget: number;\n speedUp: number;\n timeOffset: number;\n\n constructor(container: HTMLElement, options: HyperspeedOptions) {\n this.options = options;\n if (!this.options.distortion) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n\n this.composer = new EffectComposer(this.renderer);\n container.appendChild(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(options.fov, container.offsetWidth / container.offsetHeight, 0.1, 10000);\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n const fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n this.onWindowResize = this.onWindowResize.bind(this);\n window.addEventListener('resize', this.onWindowResize);\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets(): Promise {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, { passive: true });\n this.container.addEventListener('touchend', this.onTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true });\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev: MouseEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev: MouseEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev: TouchEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev: TouchEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev: MouseEvent) {\n ev.preventDefault();\n }\n\n update(delta: number) {\n const lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n const time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n const fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (typeof this.options.distortion === 'object' && this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n }\n\n render(delta: number) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.scene) {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n\n if (obj.geometry) obj.geometry.dispose();\n\n if (obj.material) {\n if (Array.isArray(obj.material)) {\n obj.material.forEach(material => material.dispose());\n } else {\n obj.material.dispose();\n }\n }\n });\n this.scene.clear();\n }\n\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n if (this.renderer.domElement && this.renderer.domElement.parentNode) {\n this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);\n }\n }\n if (this.composer) {\n this.composer.dispose();\n }\n\n window.removeEventListener('resize', this.onWindowResize);\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width: number, height: number, updateStyles: boolean) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n}\n\nconst DEFAULT_EFFECT_OPTIONS: Partial = {};\n\nconst Hyperspeed: FC = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n\n const container = hyperspeed.current;\n if (!container) return;\n\n const options: HyperspeedOptions = {\n ...defaultOptions,\n ...effectOptions,\n colors: { ...defaultOptions.colors, ...effectOptions.colors }\n };\n if (typeof options.distortion === 'string') {\n options.distortion = distortions[options.distortion];\n }\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/Hyperspeed-TS-TW.json b/public/r/Hyperspeed-TS-TW.json index 29bd3224..568dc8b1 100644 --- a/public/r/Hyperspeed-TS-TW.json +++ b/public/r/Hyperspeed-TS-TW.json @@ -13,12 +13,12 @@ { "type": "registry:component", "path": "Hyperspeed/Hyperspeed.tsx", - "content": "import { useEffect, useRef, FC } from 'react';\nimport * as THREE from 'three';\nimport { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\n\ninterface Distortion {\n uniforms: Record;\n getDistortion: string;\n getJS?: (progress: number, time: number) => THREE.Vector3;\n}\n\ninterface Distortions {\n [key: string]: Distortion;\n}\n\ninterface Colors {\n roadColor: number;\n islandColor: number;\n background: number;\n shoulderLines: number;\n brokenLines: number;\n leftCars: number[];\n rightCars: number[];\n sticks: number;\n}\n\ninterface HyperspeedOptions {\n onSpeedUp?: (ev: MouseEvent | TouchEvent) => void;\n onSlowDown?: (ev: MouseEvent | TouchEvent) => void;\n distortion?: string | Distortion;\n length: number;\n roadWidth: number;\n islandWidth: number;\n lanesPerRoad: number;\n fov: number;\n fovSpeedUp: number;\n speedUp: number;\n carLightsFade: number;\n totalSideLightSticks: number;\n lightPairsPerRoadWay: number;\n shoulderLinesWidthPercentage: number;\n brokenLinesWidthPercentage: number;\n brokenLinesLengthPercentage: number;\n lightStickWidth: [number, number];\n lightStickHeight: [number, number];\n movingAwaySpeed: [number, number];\n movingCloserSpeed: [number, number];\n carLightsLength: [number, number];\n carLightsRadius: [number, number];\n carWidthPercentage: [number, number];\n carShiftX: [number, number];\n carFloorSeparation: [number, number];\n colors: Colors;\n isHyper?: boolean;\n}\n\ninterface HyperspeedProps {\n effectOptions?: Partial;\n}\n\nconst defaultOptions: HyperspeedOptions = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nfunction nsin(val: number) {\n return Math.sin(val) * 0.5 + 0.5;\n}\n\nconst mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n};\n\nconst xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n};\n\nconst LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n};\n\nconst turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n};\n\nconst deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n};\n\nconst distortions: Distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = mountainUniforms.uFreq.value;\n const uAmp = mountainUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n const lookAtAmp = new THREE.Vector3(2, 2, 2);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = xyUniforms.uFreq.value;\n const uAmp = xyUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n const lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const camProgress = 0.0125;\n const uFreq = LongRaceUniforms.uFreq.value;\n const uAmp = LongRaceUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(1, 1, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = (p: number) =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = (p: number) =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -5, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = (p: number) => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = (p: number) => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -4, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n};\n\nconst distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n};\n\nconst distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n`;\n\nfunction random(base: number | [number, number]): number {\n if (Array.isArray(base)) {\n return Math.random() * (base[1] - base[0]) + base[0];\n }\n return Math.random() * base;\n}\n\nfunction pickRandom(arr: T | T[]): T {\n if (Array.isArray(arr)) {\n return arr[Math.floor(Math.random() * arr.length)];\n }\n return arr;\n}\n\nfunction lerp(current: number, target: number, speed = 0.1, limit = 0.001): number {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n}\n\nclass CarLights {\n webgl: App;\n options: HyperspeedOptions;\n colors: number[] | THREE.Color;\n speed: [number, number];\n fade: THREE.Vector2;\n mesh!: THREE.Mesh;\n\n constructor(\n webgl: App,\n options: HyperspeedOptions,\n colors: number[] | THREE.Color,\n speed: [number, number],\n fade: THREE.Vector2\n ) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n const curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n const geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n const laneWidth = options.roadWidth / options.lanesPerRoad;\n\n const aOffset: number[] = [];\n const aMetrics: number[] = [];\n const aColor: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(this.colors)) {\n colorArray = this.colors.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(this.colors)];\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n const radius = random(options.carLightsRadius);\n const length = random(options.carLightsLength);\n const spd = random(this.speed);\n\n const carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n const carWidth = random(options.carWidthPercentage) * laneWidth;\n const carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n const offsetY = random(options.carFloorSeparation) + radius * 1.3;\n const offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n (typeof this.options.distortion === 'object' ? this.options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nclass LightsSticks {\n webgl: App;\n options: HyperspeedOptions;\n mesh!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n const totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n const stickoffset = options.length / (totalSticks - 1);\n const aOffset: number[] = [];\n const aColor: number[] = [];\n const aMetrics: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(options.colors.sticks)) {\n colorArray = options.colors.sticks.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(options.colors.sticks)];\n }\n\n for (let i = 0; i < totalSticks; i++) {\n const width = random(options.lightStickWidth);\n const height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\n cos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t 1.0,\t0,\t\t\t0,\n -sin(angle),\t 0,\t\tcos(angle),\t0,\n 0, \t\t 0,\t\t0,\t\t\t1\n );\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nconst sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nclass Road {\n webgl: App;\n options: HyperspeedOptions;\n uTime: { value: number };\n leftRoadWay!: THREE.Mesh;\n rightRoadWay!: THREE.Mesh;\n island!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side: number, width: number, isRoad: boolean) {\n const options = this.options;\n const segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n\n let uniforms: Record = {\n uTravelLength: { value: options.length },\n uColor: {\n value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor)\n },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: {\n value: new THREE.Color(options.colors.brokenLines)\n },\n uShoulderLinesColor: {\n value: new THREE.Color(options.colors.shoulderLines)\n },\n uShoulderLinesWidthPercentage: {\n value: options.shoulderLinesWidthPercentage\n },\n uBrokenLinesLengthPercentage: {\n value: options.brokenLinesLengthPercentage\n },\n uBrokenLinesWidthPercentage: {\n value: options.brokenLinesWidthPercentage\n }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n uniforms,\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n\n this.webgl.scene.add(mesh);\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time: number) {\n this.uTime.value = time;\n }\n}\n\nconst roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\nconst roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n`;\n\nconst roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n`;\n\nconst roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\nconst roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nfunction resizeRendererToDisplaySize(\n renderer: THREE.WebGLRenderer,\n setSize: (width: number, height: number, updateStyle: boolean) => void\n) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n}\n\nclass App {\n container: HTMLElement;\n options: HyperspeedOptions;\n renderer: THREE.WebGLRenderer;\n composer: EffectComposer;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n renderPass!: RenderPass;\n bloomPass!: EffectPass;\n clock: THREE.Clock;\n assets: Record;\n disposed: boolean;\n road: Road;\n leftCarLights: CarLights;\n rightCarLights: CarLights;\n leftSticks: LightsSticks;\n fogUniforms: Record;\n fovTarget: number;\n speedUpTarget: number;\n speedUp: number;\n timeOffset: number;\n\n constructor(container: HTMLElement, options: HyperspeedOptions) {\n this.options = options;\n if (!this.options.distortion) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n\n this.composer = new EffectComposer(this.renderer);\n container.appendChild(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(options.fov, container.offsetWidth / container.offsetHeight, 0.1, 10000);\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n const fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n window.addEventListener('resize', this.onWindowResize.bind(this));\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets(): Promise {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, {\n passive: true\n });\n this.container.addEventListener('touchend', this.onTouchEnd, {\n passive: true\n });\n this.container.addEventListener('touchcancel', this.onTouchEnd, {\n passive: true\n });\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev: MouseEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev: MouseEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev: TouchEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev: TouchEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev: MouseEvent) {\n ev.preventDefault();\n }\n\n update(delta: number) {\n const lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n const time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n const fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (typeof this.options.distortion === 'object' && this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n }\n\n render(delta: number) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.renderer) {\n this.renderer.dispose();\n }\n if (this.composer) {\n this.composer.dispose();\n }\n if (this.scene) {\n this.scene.clear();\n }\n\n window.removeEventListener('resize', this.onWindowResize.bind(this));\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width: number, height: number, updateStyles: boolean) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n}\n\nconst DEFAULT_EFFECT_OPTIONS: Partial = {};\n\nconst Hyperspeed: FC = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n\n const container = hyperspeed.current;\n if (!container) return;\n\n const options: HyperspeedOptions = { ...defaultOptions, ...effectOptions, colors: { ...defaultOptions.colors, ...effectOptions.colors } };\n if (typeof options.distortion === 'string') {\n options.distortion = distortions[options.distortion];\n }\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" + "content": "import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';\nimport { FC, useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\ninterface Distortion {\n uniforms: Record;\n getDistortion: string;\n getJS?: (progress: number, time: number) => THREE.Vector3;\n}\n\ninterface Distortions {\n [key: string]: Distortion;\n}\n\ninterface Colors {\n roadColor: number;\n islandColor: number;\n background: number;\n shoulderLines: number;\n brokenLines: number;\n leftCars: number[];\n rightCars: number[];\n sticks: number;\n}\n\ninterface HyperspeedOptions {\n onSpeedUp?: (ev: MouseEvent | TouchEvent) => void;\n onSlowDown?: (ev: MouseEvent | TouchEvent) => void;\n distortion?: string | Distortion;\n length: number;\n roadWidth: number;\n islandWidth: number;\n lanesPerRoad: number;\n fov: number;\n fovSpeedUp: number;\n speedUp: number;\n carLightsFade: number;\n totalSideLightSticks: number;\n lightPairsPerRoadWay: number;\n shoulderLinesWidthPercentage: number;\n brokenLinesWidthPercentage: number;\n brokenLinesLengthPercentage: number;\n lightStickWidth: [number, number];\n lightStickHeight: [number, number];\n movingAwaySpeed: [number, number];\n movingCloserSpeed: [number, number];\n carLightsLength: [number, number];\n carLightsRadius: [number, number];\n carWidthPercentage: [number, number];\n carShiftX: [number, number];\n carFloorSeparation: [number, number];\n colors: Colors;\n isHyper?: boolean;\n}\n\ninterface HyperspeedProps {\n effectOptions?: Partial;\n}\n\nconst defaultOptions: HyperspeedOptions = {\n onSpeedUp: () => {},\n onSlowDown: () => {},\n distortion: 'turbulentDistortion',\n length: 400,\n roadWidth: 10,\n islandWidth: 2,\n lanesPerRoad: 4,\n fov: 90,\n fovSpeedUp: 150,\n speedUp: 2,\n carLightsFade: 0.4,\n totalSideLightSticks: 20,\n lightPairsPerRoadWay: 40,\n shoulderLinesWidthPercentage: 0.05,\n brokenLinesWidthPercentage: 0.1,\n brokenLinesLengthPercentage: 0.5,\n lightStickWidth: [0.12, 0.5],\n lightStickHeight: [1.3, 1.7],\n movingAwaySpeed: [60, 80],\n movingCloserSpeed: [-120, -160],\n carLightsLength: [400 * 0.03, 400 * 0.2],\n carLightsRadius: [0.05, 0.14],\n carWidthPercentage: [0.3, 0.5],\n carShiftX: [-0.8, 0.8],\n carFloorSeparation: [0, 5],\n colors: {\n roadColor: 0x080808,\n islandColor: 0x0a0a0a,\n background: 0x000000,\n shoulderLines: 0xffffff,\n brokenLines: 0xffffff,\n leftCars: [0xd856bf, 0x6750a2, 0xc247ac],\n rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],\n sticks: 0x03b3c3\n }\n};\n\nfunction nsin(val: number) {\n return Math.sin(val) * 0.5 + 0.5;\n}\n\nconst mountainUniforms = {\n uFreq: { value: new THREE.Vector3(3, 6, 10) },\n uAmp: { value: new THREE.Vector3(30, 30, 20) }\n};\n\nconst xyUniforms = {\n uFreq: { value: new THREE.Vector2(5, 2) },\n uAmp: { value: new THREE.Vector2(25, 15) }\n};\n\nconst LongRaceUniforms = {\n uFreq: { value: new THREE.Vector2(2, 3) },\n uAmp: { value: new THREE.Vector2(35, 10) }\n};\n\nconst turbulentUniforms = {\n uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },\n uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }\n};\n\nconst deepUniforms = {\n uFreq: { value: new THREE.Vector2(4, 8) },\n uAmp: { value: new THREE.Vector2(10, 20) },\n uPowY: { value: new THREE.Vector2(20, 2) }\n};\n\nconst distortions: Distortions = {\n mountainDistortion: {\n uniforms: mountainUniforms,\n getDistortion: `\n uniform vec3 uAmp;\n uniform vec3 uFreq;\n #define PI 3.14159265358979\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,\n nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = mountainUniforms.uFreq.value;\n const uAmp = mountainUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,\n nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -\n nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z\n );\n const lookAtAmp = new THREE.Vector3(2, 2, 2);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n xyDistortion: {\n uniforms: xyUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float movementProgressFix = 0.02;\n return vec3( \n cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const movementProgressFix = 0.02;\n const uFreq = xyUniforms.uFreq.value;\n const uAmp = xyUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -\n Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(2, 0.4, 1);\n const lookAtOffset = new THREE.Vector3(0, 0, -3);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n LongRaceDistortion: {\n uniforms: LongRaceUniforms,\n getDistortion: `\n uniform vec2 uFreq;\n uniform vec2 uAmp;\n #define PI 3.14159265358979\n vec3 getDistortion(float progress){\n float camProgress = 0.0125;\n return vec3( \n sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,\n sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const camProgress = 0.0125;\n const uFreq = LongRaceUniforms.uFreq.value;\n const uAmp = LongRaceUniforms.uAmp.value;\n const distortion = new THREE.Vector3(\n Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -\n Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,\n Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -\n Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,\n 0\n );\n const lookAtAmp = new THREE.Vector3(1, 1, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -5);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortion: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r + uTime) * uAmp.r +\n pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b + uTime) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.0125),\n getDistortionY(progress) - getDistortionY(0.0125),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = turbulentUniforms.uFreq.value;\n const uAmp = turbulentUniforms.uAmp.value;\n\n const getX = (p: number) =>\n Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +\n Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;\n\n const getY = (p: number) =>\n -nsin(Math.PI * p * uFreq.z + time) * uAmp.z -\n Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.007),\n getY(progress) - getY(progress + 0.007),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -5, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n },\n turbulentDistortionStill: {\n uniforms: turbulentUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n cos(PI * progress * uFreq.r) * uAmp.r +\n pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g\n );\n }\n float getDistortionY(float progress){\n return (\n -nsin(PI * progress * uFreq.b) * uAmp.b +\n -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `\n },\n deepDistortionStill: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x) * uAmp.x * 2.\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.05),\n 0.\n );\n }\n `\n },\n deepDistortion: {\n uniforms: deepUniforms,\n getDistortion: `\n uniform vec4 uFreq;\n uniform vec4 uAmp;\n uniform vec2 uPowY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n #define PI 3.14159265358979\n float getDistortionX(float progress){\n return (\n sin(progress * PI * uFreq.x + uTime) * uAmp.x\n );\n }\n float getDistortionY(float progress){\n return (\n pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y\n );\n }\n vec3 getDistortion(float progress){\n return vec3(\n getDistortionX(progress) - getDistortionX(0.02),\n getDistortionY(progress) - getDistortionY(0.02),\n 0.\n );\n }\n `,\n getJS: (progress: number, time: number) => {\n const uFreq = deepUniforms.uFreq.value;\n const uAmp = deepUniforms.uAmp.value;\n const uPowY = deepUniforms.uPowY.value;\n\n const getX = (p: number) => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;\n const getY = (p: number) => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;\n\n const distortion = new THREE.Vector3(\n getX(progress) - getX(progress + 0.01),\n getY(progress) - getY(progress + 0.01),\n 0\n );\n const lookAtAmp = new THREE.Vector3(-2, -4, 0);\n const lookAtOffset = new THREE.Vector3(0, 0, -10);\n return distortion.multiply(lookAtAmp).add(lookAtOffset);\n }\n }\n};\n\nconst distortion_uniforms = {\n uDistortionX: { value: new THREE.Vector2(80, 3) },\n uDistortionY: { value: new THREE.Vector2(-40, 2.5) }\n};\n\nconst distortion_vertex = `\n #define PI 3.14159265358979\n uniform vec2 uDistortionX;\n uniform vec2 uDistortionY;\n float nsin(float val){\n return sin(val) * 0.5 + 0.5;\n }\n vec3 getDistortion(float progress){\n progress = clamp(progress, 0., 1.);\n float xAmp = uDistortionX.r;\n float xFreq = uDistortionX.g;\n float yAmp = uDistortionY.r;\n float yFreq = uDistortionY.g;\n return vec3( \n xAmp * nsin(progress * PI * xFreq - PI / 2.),\n yAmp * nsin(progress * PI * yFreq - PI / 2.),\n 0.\n );\n }\n`;\n\nfunction random(base: number | [number, number]): number {\n if (Array.isArray(base)) {\n return Math.random() * (base[1] - base[0]) + base[0];\n }\n return Math.random() * base;\n}\n\nfunction pickRandom(arr: T | T[]): T {\n if (Array.isArray(arr)) {\n return arr[Math.floor(Math.random() * arr.length)];\n }\n return arr;\n}\n\nfunction lerp(current: number, target: number, speed = 0.1, limit = 0.001): number {\n let change = (target - current) * speed;\n if (Math.abs(change) < limit) {\n change = target - current;\n }\n return change;\n}\n\nclass CarLights {\n webgl: App;\n options: HyperspeedOptions;\n colors: number[] | THREE.Color;\n speed: [number, number];\n fade: THREE.Vector2;\n mesh!: THREE.Mesh;\n\n constructor(\n webgl: App,\n options: HyperspeedOptions,\n colors: number[] | THREE.Color,\n speed: [number, number],\n fade: THREE.Vector2\n ) {\n this.webgl = webgl;\n this.options = options;\n this.colors = colors;\n this.speed = speed;\n this.fade = fade;\n }\n\n init() {\n const options = this.options;\n const curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));\n const geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);\n\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n instanced.instanceCount = options.lightPairsPerRoadWay * 2;\n\n const laneWidth = options.roadWidth / options.lanesPerRoad;\n\n const aOffset: number[] = [];\n const aMetrics: number[] = [];\n const aColor: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(this.colors)) {\n colorArray = this.colors.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(this.colors)];\n }\n\n for (let i = 0; i < options.lightPairsPerRoadWay; i++) {\n const radius = random(options.carLightsRadius);\n const length = random(options.carLightsLength);\n const spd = random(this.speed);\n\n const carLane = i % options.lanesPerRoad;\n let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;\n\n const carWidth = random(options.carWidthPercentage) * laneWidth;\n const carShiftX = random(options.carShiftX) * laneWidth;\n laneX += carShiftX;\n\n const offsetY = random(options.carFloorSeparation) + radius * 1.3;\n const offsetZ = -random(options.length);\n\n aOffset.push(laneX - carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aOffset.push(laneX + carWidth / 2);\n aOffset.push(offsetY);\n aOffset.push(offsetZ);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n aMetrics.push(radius);\n aMetrics.push(length);\n aMetrics.push(spd);\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: carLightsFragment,\n vertexShader: carLightsVertex,\n transparent: true,\n uniforms: Object.assign(\n {\n uTime: { value: 0 },\n uTravelLength: { value: options.length },\n uFade: { value: this.fade }\n },\n this.webgl.fogUniforms,\n (typeof this.options.distortion === 'object' ? this.options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst carLightsFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n varying vec2 vUv; \n uniform vec2 uFade;\n void main() {\n vec3 color = vec3(vColor);\n float alpha = smoothstep(uFade.x, uFade.y, vUv.x);\n gl_FragColor = vec4(color, alpha);\n if (gl_FragColor.a < 0.0001) discard;\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst carLightsVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute vec3 aOffset;\n attribute vec3 aMetrics;\n attribute vec3 aColor;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec2 vUv; \n varying vec3 vColor; \n #include \n void main() {\n vec3 transformed = position.xyz;\n float radius = aMetrics.r;\n float myLength = aMetrics.g;\n float speed = aMetrics.b;\n\n transformed.xy *= radius;\n transformed.z *= myLength;\n\n transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);\n transformed.xy += aOffset.xy;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nclass LightsSticks {\n webgl: App;\n options: HyperspeedOptions;\n mesh!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n }\n\n init() {\n const options = this.options;\n const geometry = new THREE.PlaneGeometry(1, 1);\n const instanced = new THREE.InstancedBufferGeometry().copy(geometry as any) as THREE.InstancedBufferGeometry;\n const totalSticks = options.totalSideLightSticks;\n instanced.instanceCount = totalSticks;\n\n const stickoffset = options.length / (totalSticks - 1);\n const aOffset: number[] = [];\n const aColor: number[] = [];\n const aMetrics: number[] = [];\n\n let colorArray: THREE.Color[];\n if (Array.isArray(options.colors.sticks)) {\n colorArray = options.colors.sticks.map(c => new THREE.Color(c));\n } else {\n colorArray = [new THREE.Color(options.colors.sticks)];\n }\n\n for (let i = 0; i < totalSticks; i++) {\n const width = random(options.lightStickWidth);\n const height = random(options.lightStickHeight);\n aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());\n\n const color = pickRandom(colorArray);\n aColor.push(color.r);\n aColor.push(color.g);\n aColor.push(color.b);\n\n aMetrics.push(width);\n aMetrics.push(height);\n }\n\n instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));\n instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));\n instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: sideSticksFragment,\n vertexShader: sideSticksVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n {\n uTravelLength: { value: options.length },\n uTime: { value: 0 }\n },\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(instanced, material);\n mesh.frustumCulled = false;\n this.webgl.scene.add(mesh);\n this.mesh = mesh;\n }\n\n update(time: number) {\n if (this.mesh.material.uniforms.uTime) {\n this.mesh.material.uniforms.uTime.value = time;\n }\n }\n}\n\nconst sideSticksVertex = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n attribute float aOffset;\n attribute vec3 aColor;\n attribute vec2 aMetrics;\n uniform float uTravelLength;\n uniform float uTime;\n varying vec3 vColor;\n mat4 rotationY( in float angle ) {\n return mat4(\n cos(angle),\t\t0,\t\tsin(angle),\t0,\n 0,\t\t 1.0,\t0,\t\t\t0,\n -sin(angle),\t 0,\t\tcos(angle),\t0,\n 0, \t\t 0,\t\t0,\t\t\t1\n );\n }\n #include \n void main(){\n vec3 transformed = position.xyz;\n float width = aMetrics.x;\n float height = aMetrics.y;\n\n transformed.xy *= vec2(width, height);\n float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);\n\n transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;\n transformed.z += - uTravelLength + time;\n\n float progress = abs(transformed.z / uTravelLength);\n transformed.xyz += getDistortion(progress);\n\n transformed.y += height / 2.;\n transformed.x += -width / 2.;\n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vColor = aColor;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nconst sideSticksFragment = `\n #define USE_FOG;\n ${THREE.ShaderChunk['fog_pars_fragment']}\n varying vec3 vColor;\n void main(){\n vec3 color = vec3(vColor);\n gl_FragColor = vec4(color,1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nclass Road {\n webgl: App;\n options: HyperspeedOptions;\n uTime: { value: number };\n leftRoadWay!: THREE.Mesh;\n rightRoadWay!: THREE.Mesh;\n island!: THREE.Mesh;\n\n constructor(webgl: App, options: HyperspeedOptions) {\n this.webgl = webgl;\n this.options = options;\n this.uTime = { value: 0 };\n }\n\n createPlane(side: number, width: number, isRoad: boolean) {\n const options = this.options;\n const segments = 100;\n const geometry = new THREE.PlaneGeometry(\n isRoad ? options.roadWidth : options.islandWidth,\n options.length,\n 20,\n segments\n );\n\n let uniforms: Record = {\n uTravelLength: { value: options.length },\n uColor: {\n value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor)\n },\n uTime: this.uTime\n };\n\n if (isRoad) {\n uniforms = Object.assign(uniforms, {\n uLanes: { value: options.lanesPerRoad },\n uBrokenLinesColor: {\n value: new THREE.Color(options.colors.brokenLines)\n },\n uShoulderLinesColor: {\n value: new THREE.Color(options.colors.shoulderLines)\n },\n uShoulderLinesWidthPercentage: {\n value: options.shoulderLinesWidthPercentage\n },\n uBrokenLinesLengthPercentage: {\n value: options.brokenLinesLengthPercentage\n },\n uBrokenLinesWidthPercentage: {\n value: options.brokenLinesWidthPercentage\n }\n });\n }\n\n const material = new THREE.ShaderMaterial({\n fragmentShader: isRoad ? roadFragment : islandFragment,\n vertexShader: roadVertex,\n side: THREE.DoubleSide,\n uniforms: Object.assign(\n uniforms,\n this.webgl.fogUniforms,\n (typeof options.distortion === 'object' ? options.distortion.uniforms : {}) || {}\n )\n });\n\n material.onBeforeCompile = shader => {\n shader.vertexShader = shader.vertexShader.replace(\n '#include ',\n typeof this.options.distortion === 'object' ? this.options.distortion.getDistortion : ''\n );\n };\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.rotation.x = -Math.PI / 2;\n mesh.position.z = -options.length / 2;\n mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;\n\n this.webgl.scene.add(mesh);\n return mesh;\n }\n\n init() {\n this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);\n this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);\n this.island = this.createPlane(0, this.options.islandWidth, false);\n }\n\n update(time: number) {\n this.uTime.value = time;\n }\n}\n\nconst roadBaseFragment = `\n #define USE_FOG;\n varying vec2 vUv; \n uniform vec3 uColor;\n uniform float uTime;\n #include \n ${THREE.ShaderChunk['fog_pars_fragment']}\n void main() {\n vec2 uv = vUv;\n vec3 color = vec3(uColor);\n #include \n gl_FragColor = vec4(color, 1.);\n ${THREE.ShaderChunk['fog_fragment']}\n }\n`;\n\nconst islandFragment = roadBaseFragment\n .replace('#include ', '')\n .replace('#include ', '');\n\nconst roadMarkings_vars = `\n uniform float uLanes;\n uniform vec3 uBrokenLinesColor;\n uniform vec3 uShoulderLinesColor;\n uniform float uShoulderLinesWidthPercentage;\n uniform float uBrokenLinesWidthPercentage;\n uniform float uBrokenLinesLengthPercentage;\n highp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, 3.14);\n return fract(sin(sn) * c);\n }\n`;\n\nconst roadMarkings_fragment = `\n uv.y = mod(uv.y + uTime * 0.05, 1.);\n float laneWidth = 1.0 / uLanes;\n float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;\n float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;\n\n float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));\n float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);\n\n brokenLines = mix(brokenLines, sideLines, uv.x);\n`;\n\nconst roadFragment = roadBaseFragment\n .replace('#include ', roadMarkings_fragment)\n .replace('#include ', roadMarkings_vars);\n\nconst roadVertex = `\n #define USE_FOG;\n uniform float uTime;\n ${THREE.ShaderChunk['fog_pars_vertex']}\n uniform float uTravelLength;\n varying vec2 vUv; \n #include \n void main() {\n vec3 transformed = position.xyz;\n vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);\n transformed.x += distortion.x;\n transformed.z += distortion.y;\n transformed.y += -1. * distortion.z; \n \n vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);\n gl_Position = projectionMatrix * mvPosition;\n vUv = uv;\n ${THREE.ShaderChunk['fog_vertex']}\n }\n`;\n\nfunction resizeRendererToDisplaySize(\n renderer: THREE.WebGLRenderer,\n setSize: (width: number, height: number, updateStyle: boolean) => void\n) {\n const canvas = renderer.domElement;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n const needResize = canvas.width !== width || canvas.height !== height;\n if (needResize) {\n setSize(width, height, false);\n }\n return needResize;\n}\n\nclass App {\n container: HTMLElement;\n options: HyperspeedOptions;\n renderer: THREE.WebGLRenderer;\n composer: EffectComposer;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n renderPass!: RenderPass;\n bloomPass!: EffectPass;\n clock: THREE.Clock;\n assets: Record;\n disposed: boolean;\n road: Road;\n leftCarLights: CarLights;\n rightCarLights: CarLights;\n leftSticks: LightsSticks;\n fogUniforms: Record;\n fovTarget: number;\n speedUpTarget: number;\n speedUp: number;\n timeOffset: number;\n\n constructor(container: HTMLElement, options: HyperspeedOptions) {\n this.options = options;\n if (!this.options.distortion) {\n this.options.distortion = {\n uniforms: distortion_uniforms,\n getDistortion: distortion_vertex\n };\n }\n this.container = container;\n\n this.renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: true\n });\n this.renderer.setSize(container.offsetWidth, container.offsetHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n\n this.composer = new EffectComposer(this.renderer);\n container.appendChild(this.renderer.domElement);\n\n this.camera = new THREE.PerspectiveCamera(options.fov, container.offsetWidth / container.offsetHeight, 0.1, 10000);\n this.camera.position.z = -5;\n this.camera.position.y = 8;\n this.camera.position.x = 0;\n\n this.scene = new THREE.Scene();\n this.scene.background = null;\n\n const fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);\n this.scene.fog = fog;\n\n this.fogUniforms = {\n fogColor: { value: fog.color },\n fogNear: { value: fog.near },\n fogFar: { value: fog.far }\n };\n\n this.clock = new THREE.Clock();\n this.assets = {};\n this.disposed = false;\n\n this.road = new Road(this, options);\n this.leftCarLights = new CarLights(\n this,\n options,\n options.colors.leftCars,\n options.movingAwaySpeed,\n new THREE.Vector2(0, 1 - options.carLightsFade)\n );\n this.rightCarLights = new CarLights(\n this,\n options,\n options.colors.rightCars,\n options.movingCloserSpeed,\n new THREE.Vector2(1, 0 + options.carLightsFade)\n );\n this.leftSticks = new LightsSticks(this, options);\n\n this.fovTarget = options.fov;\n this.speedUpTarget = 0;\n this.speedUp = 0;\n this.timeOffset = 0;\n\n this.tick = this.tick.bind(this);\n this.init = this.init.bind(this);\n this.setSize = this.setSize.bind(this);\n this.onMouseDown = this.onMouseDown.bind(this);\n this.onMouseUp = this.onMouseUp.bind(this);\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onContextMenu = this.onContextMenu.bind(this);\n\n this.onWindowResize = this.onWindowResize.bind(this);\n window.addEventListener('resize', this.onWindowResize);\n }\n\n onWindowResize() {\n const width = this.container.offsetWidth;\n const height = this.container.offsetHeight;\n\n this.renderer.setSize(width, height);\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.composer.setSize(width, height);\n }\n\n initPasses() {\n this.renderPass = new RenderPass(this.scene, this.camera);\n this.bloomPass = new EffectPass(\n this.camera,\n new BloomEffect({\n luminanceThreshold: 0.2,\n luminanceSmoothing: 0,\n resolutionScale: 1\n })\n );\n\n const smaaPass = new EffectPass(\n this.camera,\n new SMAAEffect({\n preset: SMAAPreset.MEDIUM\n })\n );\n this.renderPass.renderToScreen = false;\n this.bloomPass.renderToScreen = false;\n smaaPass.renderToScreen = true;\n\n this.composer.addPass(this.renderPass);\n this.composer.addPass(this.bloomPass);\n this.composer.addPass(smaaPass);\n }\n\n loadAssets(): Promise {\n const assets = this.assets;\n return new Promise(resolve => {\n const manager = new THREE.LoadingManager(resolve);\n\n const searchImage = new Image();\n const areaImage = new Image();\n assets.smaa = {};\n\n searchImage.addEventListener('load', function () {\n assets.smaa.search = this;\n manager.itemEnd('smaa-search');\n });\n\n areaImage.addEventListener('load', function () {\n assets.smaa.area = this;\n manager.itemEnd('smaa-area');\n });\n\n manager.itemStart('smaa-search');\n manager.itemStart('smaa-area');\n\n searchImage.src = SMAAEffect.searchImageDataURL;\n areaImage.src = SMAAEffect.areaImageDataURL;\n });\n }\n\n init() {\n this.initPasses();\n const options = this.options;\n this.road.init();\n this.leftCarLights.init();\n this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);\n\n this.rightCarLights.init();\n this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);\n\n this.leftSticks.init();\n this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));\n\n this.container.addEventListener('mousedown', this.onMouseDown);\n this.container.addEventListener('mouseup', this.onMouseUp);\n this.container.addEventListener('mouseout', this.onMouseUp);\n\n this.container.addEventListener('touchstart', this.onTouchStart, {\n passive: true\n });\n this.container.addEventListener('touchend', this.onTouchEnd, {\n passive: true\n });\n this.container.addEventListener('touchcancel', this.onTouchEnd, {\n passive: true\n });\n this.container.addEventListener('contextmenu', this.onContextMenu);\n\n this.tick();\n }\n\n onMouseDown(ev: MouseEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onMouseUp(ev: MouseEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onTouchStart(ev: TouchEvent) {\n if (this.options.onSpeedUp) this.options.onSpeedUp(ev);\n this.fovTarget = this.options.fovSpeedUp;\n this.speedUpTarget = this.options.speedUp;\n }\n\n onTouchEnd(ev: TouchEvent) {\n if (this.options.onSlowDown) this.options.onSlowDown(ev);\n this.fovTarget = this.options.fov;\n this.speedUpTarget = 0;\n }\n\n onContextMenu(ev: MouseEvent) {\n ev.preventDefault();\n }\n\n update(delta: number) {\n const lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);\n this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);\n this.timeOffset += this.speedUp * delta;\n const time = this.clock.elapsedTime + this.timeOffset;\n\n this.rightCarLights.update(time);\n this.leftCarLights.update(time);\n this.leftSticks.update(time);\n this.road.update(time);\n\n let updateCamera = false;\n const fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);\n if (fovChange !== 0) {\n this.camera.fov += fovChange * delta * 6;\n updateCamera = true;\n }\n\n if (typeof this.options.distortion === 'object' && this.options.distortion.getJS) {\n const distortion = this.options.distortion.getJS(0.025, time);\n this.camera.lookAt(\n new THREE.Vector3(\n this.camera.position.x + distortion.x,\n this.camera.position.y + distortion.y,\n this.camera.position.z + distortion.z\n )\n );\n updateCamera = true;\n }\n\n if (updateCamera) {\n this.camera.updateProjectionMatrix();\n }\n }\n\n render(delta: number) {\n this.composer.render(delta);\n }\n\n dispose() {\n this.disposed = true;\n\n if (this.scene) {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n\n if (obj.geometry) obj.geometry.dispose();\n\n if (obj.material) {\n if (Array.isArray(obj.material)) {\n obj.material.forEach(material => material.dispose());\n } else {\n obj.material.dispose();\n }\n }\n });\n this.scene.clear();\n }\n\n if (this.renderer) {\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n if (this.renderer.domElement && this.renderer.domElement.parentNode) {\n this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);\n }\n }\n if (this.composer) {\n this.composer.dispose();\n }\n\n window.removeEventListener('resize', this.onWindowResize);\n if (this.container) {\n this.container.removeEventListener('mousedown', this.onMouseDown);\n this.container.removeEventListener('mouseup', this.onMouseUp);\n this.container.removeEventListener('mouseout', this.onMouseUp);\n\n this.container.removeEventListener('touchstart', this.onTouchStart);\n this.container.removeEventListener('touchend', this.onTouchEnd);\n this.container.removeEventListener('touchcancel', this.onTouchEnd);\n this.container.removeEventListener('contextmenu', this.onContextMenu);\n }\n }\n\n setSize(width: number, height: number, updateStyles: boolean) {\n this.composer.setSize(width, height, updateStyles);\n }\n\n tick() {\n if (this.disposed || !this) return;\n if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {\n const canvas = this.renderer.domElement;\n this.camera.aspect = canvas.clientWidth / canvas.clientHeight;\n this.camera.updateProjectionMatrix();\n }\n const delta = this.clock.getDelta();\n this.render(delta);\n this.update(delta);\n requestAnimationFrame(this.tick);\n }\n}\n\nconst DEFAULT_EFFECT_OPTIONS: Partial = {};\n\nconst Hyperspeed: FC = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {\n const hyperspeed = useRef(null);\n const appRef = useRef(null);\n\n useEffect(() => {\n if (appRef.current) {\n appRef.current.dispose();\n const container = document.getElementById('lights');\n if (container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n }\n\n const container = hyperspeed.current;\n if (!container) return;\n\n const options: HyperspeedOptions = {\n ...defaultOptions,\n ...effectOptions,\n colors: { ...defaultOptions.colors, ...effectOptions.colors }\n };\n if (typeof options.distortion === 'string') {\n options.distortion = distortions[options.distortion];\n }\n\n const myApp = new App(container, options);\n appRef.current = myApp;\n myApp.loadAssets().then(myApp.init);\n\n return () => {\n if (appRef.current) {\n appRef.current.dispose();\n }\n };\n }, [effectOptions]);\n\n return
;\n};\n\nexport default Hyperspeed;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/LaserFlow-JS-CSS.json b/public/r/LaserFlow-JS-CSS.json index b81cf06f..36ac3223 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 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 renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/LaserFlow-JS-TW.json b/public/r/LaserFlow-JS-TW.json index f99b2178..60d7bab8 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 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 renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/LaserFlow-TS-CSS.json b/public/r/LaserFlow-TS-CSS.json index 81c7a699..05072940 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 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 renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/LaserFlow-TS-TW.json b/public/r/LaserFlow-TS-TW.json index 3d692559..850db13b 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 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\nfunction hexToRGB(hex: string) {\n let c = hex.trim();\n if (c[0] === '#') c = c.slice(1);\n if (c.length === 3)\n c = c\n .split('')\n .map(x => x + x)\n .join('');\n const n = parseInt(c, 16) || 0xffffff;\n return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };\n}\n\nexport const LaserFlow: React.FC = ({\n className,\n style,\n wispDensity = 1,\n dpr,\n mouseSmoothTime = 0.0,\n mouseTiltStrength = 0.01,\n horizontalBeamOffset = 0.1,\n verticalBeamOffset = 0.0,\n flowSpeed = 0.35,\n verticalSizing = 2.0,\n horizontalSizing = 0.5,\n fogIntensity = 0.45,\n fogScale = 0.3,\n wispSpeed = 15.0,\n wispIntensity = 5.0,\n flowStrength = 0.25,\n decay = 1.1,\n falloffStart = 1.2,\n fogFallSpeed = 0.6,\n color = '#FF79C6'\n}) => {\n const mountRef = useRef(null);\n const rendererRef = useRef(null);\n const uniformsRef = useRef(null);\n const hasFadedRef = useRef(false);\n const rectRef = useRef(null);\n const baseDprRef = useRef(1);\n const currentDprRef = useRef(1);\n const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });\n const fpsSamplesRef = useRef([]);\n const lastFpsCheckRef = useRef(performance.now());\n const emaDtRef = useRef(16.7);\n const pausedRef = useRef(false);\n const inViewRef = useRef(true);\n\n const mouseSmoothTimeRef = useRef(mouseSmoothTime);\n useEffect(() => {\n mouseSmoothTimeRef.current = mouseSmoothTime;\n }, [mouseSmoothTime]);\n\n useEffect(() => {\n const mount = mountRef.current!;\n const renderer = new THREE.WebGLRenderer({\n antialias: false,\n alpha: false,\n depth: false,\n stencil: false,\n powerPreference: 'high-performance',\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n failIfMajorPerformanceCaveat: false,\n logarithmicDepthBuffer: false\n });\n rendererRef.current = renderer;\n\n baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);\n currentDprRef.current = baseDprRef.current;\n\n renderer.setPixelRatio(currentDprRef.current);\n renderer.shadowMap.enabled = false;\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 1);\n const canvas = renderer.domElement;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n mount.appendChild(canvas);\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));\n\n const uniforms = {\n iTime: { value: 0 },\n iResolution: { value: new THREE.Vector3(1, 1, 1) },\n iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },\n uWispDensity: { value: wispDensity },\n uTiltScale: { value: mouseTiltStrength },\n uFlowTime: { value: 0 },\n uFogTime: { value: 0 },\n uBeamXFrac: { value: horizontalBeamOffset },\n uBeamYFrac: { value: verticalBeamOffset },\n uFlowSpeed: { value: flowSpeed },\n uVLenFactor: { value: verticalSizing },\n uHLenFactor: { value: horizontalSizing },\n uFogIntensity: { value: fogIntensity },\n uFogScale: { value: fogScale },\n uWSpeed: { value: wispSpeed },\n uWIntensity: { value: wispIntensity },\n uFlowStrength: { value: flowStrength },\n uDecay: { value: decay },\n uFalloffStart: { value: falloffStart },\n uFogFallSpeed: { value: fogFallSpeed },\n uColor: { value: new THREE.Vector3(1, 1, 1) },\n uFade: { value: hasFadedRef.current ? 1 : 0 }\n };\n uniformsRef.current = uniforms;\n\n const material = new THREE.RawShaderMaterial({\n vertexShader: VERT,\n fragmentShader: FRAG,\n uniforms,\n transparent: false,\n depthTest: false,\n depthWrite: false,\n blending: THREE.NormalBlending\n });\n\n const mesh = new THREE.Mesh(geometry, material);\n mesh.frustumCulled = false;\n scene.add(mesh);\n\n const clock = new THREE.Clock();\n let prevTime = 0;\n let fade = hasFadedRef.current ? 1 : 0;\n\n const mouseTarget = new THREE.Vector2(0, 0);\n const mouseSmooth = new THREE.Vector2(0, 0);\n\n const setSizeNow = () => {\n const w = mount.clientWidth || 1;\n const h = mount.clientHeight || 1;\n const pr = currentDprRef.current;\n\n const last = lastSizeRef.current;\n const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;\n const dprChanged = Math.abs(pr - last.dpr) > 0.01;\n if (!sizeChanged && !dprChanged) return;\n\n lastSizeRef.current = { width: w, height: h, dpr: pr };\n renderer.setPixelRatio(pr);\n renderer.setSize(w, h, false);\n uniforms.iResolution.value.set(w * pr, h * pr, pr);\n rectRef.current = canvas.getBoundingClientRect();\n\n if (!pausedRef.current) {\n renderer.render(scene, camera);\n }\n };\n\n let resizeRaf = 0;\n const scheduleResize = () => {\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n resizeRaf = requestAnimationFrame(setSizeNow);\n };\n\n setSizeNow();\n const ro = new ResizeObserver(scheduleResize);\n ro.observe(mount);\n\n const io = new IntersectionObserver(\n entries => {\n inViewRef.current = entries[0]?.isIntersecting ?? true;\n },\n { root: null, threshold: 0 }\n );\n io.observe(mount);\n\n const onVis = () => {\n pausedRef.current = document.hidden;\n };\n document.addEventListener('visibilitychange', onVis, { passive: true });\n\n const updateMouse = (clientX: number, clientY: number) => {\n const rect = rectRef.current;\n if (!rect) return;\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const ratio = currentDprRef.current;\n const hb = rect.height * ratio;\n mouseTarget.set(x * ratio, hb - y * ratio);\n };\n const onMove = (ev: PointerEvent | MouseEvent) => updateMouse(ev.clientX, ev.clientY);\n const onLeave = () => mouseTarget.set(0, 0);\n canvas.addEventListener('pointermove', onMove as any, { passive: true });\n canvas.addEventListener('pointerdown', onMove as any, { passive: true });\n canvas.addEventListener('pointerenter', onMove as any, { passive: true });\n canvas.addEventListener('pointerleave', onLeave as any, { passive: true });\n\n const onCtxLost = (e: Event) => {\n e.preventDefault();\n pausedRef.current = true;\n };\n const onCtxRestored = () => {\n pausedRef.current = false;\n scheduleResize();\n };\n canvas.addEventListener('webglcontextlost', onCtxLost, false);\n canvas.addEventListener('webglcontextrestored', onCtxRestored, false);\n\n let raf = 0;\n\n const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\n const dprFloor = 0.6;\n const lowerThresh = 50;\n const upperThresh = 58;\n let lastDprChange = 0;\n const dprChangeCooldown = 2000;\n\n const adjustDprIfNeeded = (now: number) => {\n const elapsed = now - lastFpsCheckRef.current;\n if (elapsed < 750) return;\n\n const samples = fpsSamplesRef.current;\n if (samples.length === 0) {\n lastFpsCheckRef.current = now;\n return;\n }\n const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;\n\n let next = currentDprRef.current;\n const base = baseDprRef.current;\n\n if (avgFps < lowerThresh) {\n next = clamp(currentDprRef.current * 0.85, dprFloor, base);\n } else if (avgFps > upperThresh && currentDprRef.current < base) {\n next = clamp(currentDprRef.current * 1.1, dprFloor, base);\n }\n\n if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChange > dprChangeCooldown) {\n currentDprRef.current = next;\n lastDprChange = now;\n setSizeNow();\n }\n\n fpsSamplesRef.current = [];\n lastFpsCheckRef.current = now;\n };\n\n const animate = () => {\n raf = requestAnimationFrame(animate);\n if (pausedRef.current || !inViewRef.current) return;\n\n const t = clock.getElapsedTime();\n const dt = Math.max(0, t - prevTime);\n prevTime = t;\n\n const dtMs = dt * 1000;\n emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;\n const instFps = 1000 / Math.max(1, emaDtRef.current);\n fpsSamplesRef.current.push(instFps);\n\n uniforms.iTime.value = t;\n\n const cdt = Math.min(0.033, Math.max(0.001, dt));\n (uniforms.uFlowTime.value as number) += cdt;\n (uniforms.uFogTime.value as number) += cdt;\n\n if (!hasFadedRef.current) {\n const fadeDur = 1.0;\n fade = Math.min(1, fade + cdt / fadeDur);\n uniforms.uFade.value = fade;\n if (fade >= 1) hasFadedRef.current = true;\n }\n\n const tau = Math.max(1e-3, mouseSmoothTimeRef.current);\n const alpha = 1 - Math.exp(-cdt / tau);\n mouseSmooth.lerp(mouseTarget, alpha);\n uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);\n\n renderer.render(scene, camera);\n\n adjustDprIfNeeded(performance.now());\n };\n\n animate();\n\n return () => {\n cancelAnimationFrame(raf);\n if (resizeRaf) cancelAnimationFrame(resizeRaf);\n\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVis);\n canvas.removeEventListener('pointermove', onMove as any);\n canvas.removeEventListener('pointerdown', onMove as any);\n canvas.removeEventListener('pointerenter', onMove as any);\n canvas.removeEventListener('pointerleave', onLeave as any);\n canvas.removeEventListener('webglcontextlost', onCtxLost);\n canvas.removeEventListener('webglcontextrestored', onCtxRestored);\n\n scene.clear();\n geometry.dispose();\n material.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n if (mount.contains(canvas)) mount.removeChild(canvas);\n };\n }, [dpr]);\n\n useEffect(() => {\n const uniforms = uniformsRef.current;\n if (!uniforms) return;\n\n uniforms.uWispDensity.value = wispDensity;\n uniforms.uTiltScale.value = mouseTiltStrength;\n uniforms.uBeamXFrac.value = horizontalBeamOffset;\n uniforms.uBeamYFrac.value = verticalBeamOffset;\n uniforms.uFlowSpeed.value = flowSpeed;\n uniforms.uVLenFactor.value = verticalSizing;\n uniforms.uHLenFactor.value = horizontalSizing;\n uniforms.uFogIntensity.value = fogIntensity;\n uniforms.uFogScale.value = fogScale;\n uniforms.uWSpeed.value = wispSpeed;\n uniforms.uWIntensity.value = wispIntensity;\n uniforms.uFlowStrength.value = flowStrength;\n uniforms.uDecay.value = decay;\n uniforms.uFalloffStart.value = falloffStart;\n uniforms.uFogFallSpeed.value = fogFallSpeed;\n\n const { r, g, b } = hexToRGB(color || '#FFFFFF');\n uniforms.uColor.value.set(r, g, b);\n }, [\n wispDensity,\n mouseTiltStrength,\n horizontalBeamOffset,\n verticalBeamOffset,\n flowSpeed,\n verticalSizing,\n horizontalSizing,\n fogIntensity,\n fogScale,\n wispSpeed,\n wispIntensity,\n flowStrength,\n decay,\n falloffStart,\n fogFallSpeed,\n color\n ]);\n\n return
;\n};\n\nexport default LaserFlow;\n" } ], "registryDependencies": [], diff --git a/public/r/LiquidEther-JS-CSS.json b/public/r/LiquidEther-JS-CSS.json index b31a1858..1b99309d 100644 --- a/public/r/LiquidEther-JS-CSS.json +++ b/public/r/LiquidEther-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "LiquidEther/LiquidEther.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LiquidEther.css';\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = ['#5227FF', '#FF9FFC', '#B19EEF'],\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}) {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops) {\n let arr;\n if (Array.isArray(stops) && stops.length > 0) {\n if (stops.length === 1) {\n arr = [stops[0], stops[0]];\n } else {\n arr = stops;\n }\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0); // always transparent\n\n class CommonClass {\n constructor() {\n this.width = 0;\n this.height = 0;\n this.aspect = 1;\n this.pixelRatio = 1;\n this.isMobile = false;\n this.breakpoint = 768;\n this.fboWidth = null;\n this.fboHeight = null;\n this.time = 0;\n this.delta = 0;\n this.container = null;\n this.renderer = null;\n this.clock = null;\n }\n init(container) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n this.renderer.domElement.style.width = '100%';\n this.renderer.domElement.style.height = '100%';\n this.renderer.domElement.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n constructor() {\n this.mouseMoved = false;\n this.coords = new THREE.Vector2();\n this.coords_old = new THREE.Vector2();\n this.diff = new THREE.Vector2();\n this.timer = null;\n this.container = null;\n this.docTarget = null;\n this.listenerTarget = null;\n this.isHoverInside = false;\n this.hasUserControl = false;\n this.isAutoActive = false;\n this.autoIntensity = 2.0;\n this.takeoverActive = false;\n this.takeoverStartTime = 0;\n this.takeoverDuration = 0.25;\n this.takeoverFrom = new THREE.Vector2();\n this.takeoverTo = new THREE.Vector2();\n this.onInteract = null;\n this._onMouseMove = this.onDocumentMouseMove.bind(this);\n this._onTouchStart = this.onDocumentTouchStart.bind(this);\n this._onTouchMove = this.onDocumentTouchMove.bind(this);\n this._onTouchEnd = this.onTouchEnd.bind(this);\n this._onDocumentLeave = this.onDocumentLeave.bind(this);\n }\n init(container) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView =\n (this.docTarget && this.docTarget.defaultView) || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { passive: true });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { passive: true });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n if (this.docTarget) {\n this.docTarget.addEventListener('mouseleave', this._onDocumentLeave);\n }\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n isPointInside(clientX, clientY) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n updateHoverState(clientX, clientY) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x, y) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx, ny) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n constructor(mouse, manager, opts) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed; // normalized units/sec\n this.resumeDelay = opts.resumeDelay || 3000; // ms\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.active = false;\n this.current = new THREE.Vector2(0, 0);\n this.target = new THREE.Vector2();\n this.lastTime = performance.now();\n this.activationTime = 0;\n this.margin = 0.2;\n this._tmpDir = new THREE.Vector2(); // reuse temp vector to avoid per-frame alloc\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n attribute vec3 position;\n uniform vec2 px;\n uniform vec2 boundarySpace;\n varying vec2 uv;\n precision highp float;\n void main(){\n vec3 pos = position;\n vec2 scale = 1.0 - boundarySpace * 2.0;\n pos.xy = pos.xy * scale;\n uv = vec2(0.5)+(pos.xy)*0.5;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n attribute vec3 position;\n uniform vec2 px;\n precision highp float;\n varying vec2 uv;\n void main(){\n vec3 pos = position;\n uv = 0.5 + pos.xy * 0.5;\n vec2 n = sign(pos.xy);\n pos.xy = abs(pos.xy) - px * 1.0;\n pos.xy *= n;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n precision highp float;\n attribute vec3 position;\n attribute vec2 uv;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 pos = position.xy * scale * 2.0 * px + center;\n vUv = uv;\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform bool isBFECC;\n uniform vec2 fboSize;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n if(isBFECC == false){\n vec2 vel = texture2D(velocity, uv).xy;\n vec2 uv2 = uv - vel * dt * ratio;\n vec2 newVel = texture2D(velocity, uv2).xy;\n gl_FragColor = vec4(newVel, 0.0, 0.0);\n } else {\n vec2 spot_new = uv;\n vec2 vel_old = texture2D(velocity, uv).xy;\n vec2 spot_old = spot_new - vel_old * dt * ratio;\n vec2 vel_new1 = texture2D(velocity, spot_old).xy;\n vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n vec2 error = spot_new2 - spot_new;\n vec2 spot_new3 = spot_new - error / 2.0;\n vec2 vel_2 = texture2D(velocity, spot_new3).xy;\n vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n vec2 newVel2 = texture2D(velocity, spot_old2).xy; \n gl_FragColor = vec4(newVel2, 0.0, 0.0);\n }\n}\n`;\n const color_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D palette;\n uniform vec4 bgColor;\n varying vec2 uv;\n void main(){\n vec2 vel = texture2D(velocity, uv).xy;\n float lenv = clamp(length(vel), 0.0, 1.0);\n vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n vec3 outRGB = mix(bgColor.rgb, c, lenv);\n float outA = mix(bgColor.a, 1.0, lenv);\n gl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n float divergence = (x1 - x0 + y1 - y0) / 2.0;\n gl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n precision highp float;\n uniform vec2 force;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 circle = (vUv - 0.5) * 2.0;\n float d = 1.0 - min(length(circle), 1.0);\n d *= d;\n gl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D divergence;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n float div = texture2D(divergence, uv).r;\n float newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n gl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D velocity;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n float step = 1.0;\n float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n vec2 v = texture2D(velocity, uv).xy;\n vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n v = v - gradP * dt;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D velocity_new;\n uniform float v;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n vec2 old = texture2D(velocity, uv).xy;\n vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n newv /= 4.0 * (1.0 + v * dt);\n gl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n class ShaderPass {\n constructor(props) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n this.scene = null;\n this.camera = null;\n this.material = null;\n this.geometry = null;\n this.plane = null;\n }\n init() {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2.0, 2.0);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update() {\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene.add(this.line);\n }\n update({ dt, isBounce, BFECC }) {\n this.uniforms.dt.value = dt;\n this.line.visible = isBounce;\n this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n constructor(simProps) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0.0, 0.0) },\n center: { value: new THREE.Vector2(0.0, 0.0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene.add(this.mouse);\n }\n update(props) {\n const forceX = (Mouse.diff.x / 2) * props.mouse_force;\n const forceY = (Mouse.diff.y / 2) * props.mouse_force;\n const cursorSizeX = props.cursor_size * props.cellScale.x;\n const cursorSizeY = props.cursor_size * props.cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + props.cellScale.x * 2),\n 1 - cursorSizeX - props.cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + props.cellScale.y * 2),\n 1 - cursorSizeY - props.cellScale.y * 2\n );\n const uniforms = this.mouse.material.uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(props.cursor_size, props.cursor_size);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ viscous, iterations, dt }) {\n let fbo_in, fbo_out;\n this.uniforms.v.value = viscous;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel }) {\n this.uniforms.velocity.value = vel.texture;\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ iterations }) {\n let p_in, p_out;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel, pressure }) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n super.update();\n }\n }\n\n class Simulation {\n constructor(options) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.fbos = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n this.fboSize = new THREE.Vector2();\n this.cellScale = new THREE.Vector2();\n this.boundarySpace = new THREE.Vector2();\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n };\n for (let key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n const px_x = 1.0 / width;\n const px_y = 1.0 / height;\n this.cellScale.set(px_x, px_y);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (let key in this.fbos) {\n this.fbos[key].setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) {\n this.boundarySpace.set(0, 0);\n } else {\n this.boundarySpace.copy(this.cellScale);\n }\n this.advection.update({\n dt: this.options.dt,\n isBounce: this.options.isBounce,\n BFECC: this.options.BFECC\n });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({\n iterations: this.options.iterations_poisson\n });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n constructor() {\n this.init();\n }\n init() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n addScene(mesh) {\n this.scene.add(mesh);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager {\n constructor(props) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n this.lastUserInteraction = performance.now();\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n this._loop = this.loop.bind(this);\n this._resize = this.resize.bind(this);\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n this.running = false;\n }\n init() {\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return; // safety\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n }\n } catch (e) {\n void 0;\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) {\n sim.resize();\n }\n };\n applyOptionsFromProps();\n\n webgl.start();\n\n // IntersectionObserver to pause rendering when not visible\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) {\n sim.resize();\n }\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return
;\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LiquidEther.css';\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = ['#5227FF', '#FF9FFC', '#B19EEF'],\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}) {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops) {\n let arr;\n if (Array.isArray(stops) && stops.length > 0) {\n if (stops.length === 1) {\n arr = [stops[0], stops[0]];\n } else {\n arr = stops;\n }\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0); // always transparent\n\n class CommonClass {\n constructor() {\n this.width = 0;\n this.height = 0;\n this.aspect = 1;\n this.pixelRatio = 1;\n this.isMobile = false;\n this.breakpoint = 768;\n this.fboWidth = null;\n this.fboHeight = null;\n this.time = 0;\n this.delta = 0;\n this.container = null;\n this.renderer = null;\n this.clock = null;\n }\n init(container) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n this.renderer.domElement.style.width = '100%';\n this.renderer.domElement.style.height = '100%';\n this.renderer.domElement.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n constructor() {\n this.mouseMoved = false;\n this.coords = new THREE.Vector2();\n this.coords_old = new THREE.Vector2();\n this.diff = new THREE.Vector2();\n this.timer = null;\n this.container = null;\n this.docTarget = null;\n this.listenerTarget = null;\n this.isHoverInside = false;\n this.hasUserControl = false;\n this.isAutoActive = false;\n this.autoIntensity = 2.0;\n this.takeoverActive = false;\n this.takeoverStartTime = 0;\n this.takeoverDuration = 0.25;\n this.takeoverFrom = new THREE.Vector2();\n this.takeoverTo = new THREE.Vector2();\n this.onInteract = null;\n this._onMouseMove = this.onDocumentMouseMove.bind(this);\n this._onTouchStart = this.onDocumentTouchStart.bind(this);\n this._onTouchMove = this.onDocumentTouchMove.bind(this);\n this._onTouchEnd = this.onTouchEnd.bind(this);\n this._onDocumentLeave = this.onDocumentLeave.bind(this);\n }\n init(container) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView =\n (this.docTarget && this.docTarget.defaultView) || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { passive: true });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { passive: true });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n if (this.docTarget) {\n this.docTarget.addEventListener('mouseleave', this._onDocumentLeave);\n }\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n isPointInside(clientX, clientY) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n updateHoverState(clientX, clientY) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x, y) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx, ny) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n constructor(mouse, manager, opts) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed; // normalized units/sec\n this.resumeDelay = opts.resumeDelay || 3000; // ms\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.active = false;\n this.current = new THREE.Vector2(0, 0);\n this.target = new THREE.Vector2();\n this.lastTime = performance.now();\n this.activationTime = 0;\n this.margin = 0.2;\n this._tmpDir = new THREE.Vector2(); // reuse temp vector to avoid per-frame alloc\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n attribute vec3 position;\n uniform vec2 px;\n uniform vec2 boundarySpace;\n varying vec2 uv;\n precision highp float;\n void main(){\n vec3 pos = position;\n vec2 scale = 1.0 - boundarySpace * 2.0;\n pos.xy = pos.xy * scale;\n uv = vec2(0.5)+(pos.xy)*0.5;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n attribute vec3 position;\n uniform vec2 px;\n precision highp float;\n varying vec2 uv;\n void main(){\n vec3 pos = position;\n uv = 0.5 + pos.xy * 0.5;\n vec2 n = sign(pos.xy);\n pos.xy = abs(pos.xy) - px * 1.0;\n pos.xy *= n;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n precision highp float;\n attribute vec3 position;\n attribute vec2 uv;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 pos = position.xy * scale * 2.0 * px + center;\n vUv = uv;\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform bool isBFECC;\n uniform vec2 fboSize;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n if(isBFECC == false){\n vec2 vel = texture2D(velocity, uv).xy;\n vec2 uv2 = uv - vel * dt * ratio;\n vec2 newVel = texture2D(velocity, uv2).xy;\n gl_FragColor = vec4(newVel, 0.0, 0.0);\n } else {\n vec2 spot_new = uv;\n vec2 vel_old = texture2D(velocity, uv).xy;\n vec2 spot_old = spot_new - vel_old * dt * ratio;\n vec2 vel_new1 = texture2D(velocity, spot_old).xy;\n vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n vec2 error = spot_new2 - spot_new;\n vec2 spot_new3 = spot_new - error / 2.0;\n vec2 vel_2 = texture2D(velocity, spot_new3).xy;\n vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n vec2 newVel2 = texture2D(velocity, spot_old2).xy; \n gl_FragColor = vec4(newVel2, 0.0, 0.0);\n }\n}\n`;\n const color_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D palette;\n uniform vec4 bgColor;\n varying vec2 uv;\n void main(){\n vec2 vel = texture2D(velocity, uv).xy;\n float lenv = clamp(length(vel), 0.0, 1.0);\n vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n vec3 outRGB = mix(bgColor.rgb, c, lenv);\n float outA = mix(bgColor.a, 1.0, lenv);\n gl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n float divergence = (x1 - x0 + y1 - y0) / 2.0;\n gl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n precision highp float;\n uniform vec2 force;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 circle = (vUv - 0.5) * 2.0;\n float d = 1.0 - min(length(circle), 1.0);\n d *= d;\n gl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D divergence;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n float div = texture2D(divergence, uv).r;\n float newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n gl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D velocity;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n float step = 1.0;\n float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n vec2 v = texture2D(velocity, uv).xy;\n vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n v = v - gradP * dt;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D velocity_new;\n uniform float v;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n vec2 old = texture2D(velocity, uv).xy;\n vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n newv /= 4.0 * (1.0 + v * dt);\n gl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n class ShaderPass {\n constructor(props) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n this.scene = null;\n this.camera = null;\n this.material = null;\n this.geometry = null;\n this.plane = null;\n }\n init() {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2.0, 2.0);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update() {\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene.add(this.line);\n }\n update({ dt, isBounce, BFECC }) {\n this.uniforms.dt.value = dt;\n this.line.visible = isBounce;\n this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n constructor(simProps) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0.0, 0.0) },\n center: { value: new THREE.Vector2(0.0, 0.0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene.add(this.mouse);\n }\n update(props) {\n const forceX = (Mouse.diff.x / 2) * props.mouse_force;\n const forceY = (Mouse.diff.y / 2) * props.mouse_force;\n const cursorSizeX = props.cursor_size * props.cellScale.x;\n const cursorSizeY = props.cursor_size * props.cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + props.cellScale.x * 2),\n 1 - cursorSizeX - props.cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + props.cellScale.y * 2),\n 1 - cursorSizeY - props.cellScale.y * 2\n );\n const uniforms = this.mouse.material.uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(props.cursor_size, props.cursor_size);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ viscous, iterations, dt }) {\n let fbo_in, fbo_out;\n this.uniforms.v.value = viscous;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel }) {\n this.uniforms.velocity.value = vel.texture;\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ iterations }) {\n let p_in, p_out;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel, pressure }) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n super.update();\n }\n }\n\n class Simulation {\n constructor(options) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.fbos = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n this.fboSize = new THREE.Vector2();\n this.cellScale = new THREE.Vector2();\n this.boundarySpace = new THREE.Vector2();\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n };\n for (let key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n const px_x = 1.0 / width;\n const px_y = 1.0 / height;\n this.cellScale.set(px_x, px_y);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (let key in this.fbos) {\n this.fbos[key].setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) {\n this.boundarySpace.set(0, 0);\n } else {\n this.boundarySpace.copy(this.cellScale);\n }\n this.advection.update({\n dt: this.options.dt,\n isBounce: this.options.isBounce,\n BFECC: this.options.BFECC\n });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({\n iterations: this.options.iterations_poisson\n });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n constructor() {\n this.init();\n }\n init() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n addScene(mesh) {\n this.scene.add(mesh);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager {\n constructor(props) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n this.lastUserInteraction = performance.now();\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n this._loop = this.loop.bind(this);\n this._resize = this.resize.bind(this);\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n this.running = false;\n }\n init() {\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return; // safety\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n Common.renderer.forceContextLoss();\n }\n } catch (e) {\n void 0;\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) {\n sim.resize();\n }\n };\n applyOptionsFromProps();\n\n webgl.start();\n\n // IntersectionObserver to pause rendering when not visible\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) {\n sim.resize();\n }\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/LiquidEther-JS-TW.json b/public/r/LiquidEther-JS-TW.json index 1692797f..fa86e3af 100644 --- a/public/r/LiquidEther-JS-TW.json +++ b/public/r/LiquidEther-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "LiquidEther/LiquidEther.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = ['#5227FF', '#FF9FFC', '#B19EEF'],\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}) {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops) {\n let arr;\n if (Array.isArray(stops) && stops.length > 0) {\n if (stops.length === 1) {\n arr = [stops[0], stops[0]];\n } else {\n arr = stops;\n }\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0); // always transparent\n\n class CommonClass {\n constructor() {\n this.width = 0;\n this.height = 0;\n this.aspect = 1;\n this.pixelRatio = 1;\n this.isMobile = false;\n this.breakpoint = 768;\n this.fboWidth = null;\n this.fboHeight = null;\n this.time = 0;\n this.delta = 0;\n this.container = null;\n this.renderer = null;\n this.clock = null;\n }\n init(container) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n this.renderer.domElement.style.width = '100%';\n this.renderer.domElement.style.height = '100%';\n this.renderer.domElement.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n constructor() {\n this.mouseMoved = false;\n this.coords = new THREE.Vector2();\n this.coords_old = new THREE.Vector2();\n this.diff = new THREE.Vector2();\n this.timer = null;\n this.container = null;\n this.docTarget = null;\n this.listenerTarget = null;\n this.isHoverInside = false;\n this.hasUserControl = false;\n this.isAutoActive = false;\n this.autoIntensity = 2.0;\n this.takeoverActive = false;\n this.takeoverStartTime = 0;\n this.takeoverDuration = 0.25;\n this.takeoverFrom = new THREE.Vector2();\n this.takeoverTo = new THREE.Vector2();\n this.onInteract = null;\n this._onMouseMove = this.onDocumentMouseMove.bind(this);\n this._onTouchStart = this.onDocumentTouchStart.bind(this);\n this._onTouchMove = this.onDocumentTouchMove.bind(this);\n this._onTouchEnd = this.onTouchEnd.bind(this);\n this._onDocumentLeave = this.onDocumentLeave.bind(this);\n }\n init(container) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView =\n (this.docTarget && this.docTarget.defaultView) || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { passive: true });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { passive: true });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n if (this.docTarget) {\n this.docTarget.addEventListener('mouseleave', this._onDocumentLeave);\n }\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n isPointInside(clientX, clientY) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n updateHoverState(clientX, clientY) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x, y) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx, ny) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n constructor(mouse, manager, opts) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed; // normalized units/sec\n this.resumeDelay = opts.resumeDelay || 3000; // ms\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.active = false;\n this.current = new THREE.Vector2(0, 0);\n this.target = new THREE.Vector2();\n this.lastTime = performance.now();\n this.activationTime = 0;\n this.margin = 0.2;\n this._tmpDir = new THREE.Vector2(); // reuse temp vector to avoid per-frame alloc\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n attribute vec3 position;\n uniform vec2 px;\n uniform vec2 boundarySpace;\n varying vec2 uv;\n precision highp float;\n void main(){\n vec3 pos = position;\n vec2 scale = 1.0 - boundarySpace * 2.0;\n pos.xy = pos.xy * scale;\n uv = vec2(0.5)+(pos.xy)*0.5;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n attribute vec3 position;\n uniform vec2 px;\n precision highp float;\n varying vec2 uv;\n void main(){\n vec3 pos = position;\n uv = 0.5 + pos.xy * 0.5;\n vec2 n = sign(pos.xy);\n pos.xy = abs(pos.xy) - px * 1.0;\n pos.xy *= n;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n precision highp float;\n attribute vec3 position;\n attribute vec2 uv;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 pos = position.xy * scale * 2.0 * px + center;\n vUv = uv;\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform bool isBFECC;\n uniform vec2 fboSize;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n if(isBFECC == false){\n vec2 vel = texture2D(velocity, uv).xy;\n vec2 uv2 = uv - vel * dt * ratio;\n vec2 newVel = texture2D(velocity, uv2).xy;\n gl_FragColor = vec4(newVel, 0.0, 0.0);\n } else {\n vec2 spot_new = uv;\n vec2 vel_old = texture2D(velocity, uv).xy;\n vec2 spot_old = spot_new - vel_old * dt * ratio;\n vec2 vel_new1 = texture2D(velocity, spot_old).xy;\n vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n vec2 error = spot_new2 - spot_new;\n vec2 spot_new3 = spot_new - error / 2.0;\n vec2 vel_2 = texture2D(velocity, spot_new3).xy;\n vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n vec2 newVel2 = texture2D(velocity, spot_old2).xy; \n gl_FragColor = vec4(newVel2, 0.0, 0.0);\n }\n}\n`;\n const color_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D palette;\n uniform vec4 bgColor;\n varying vec2 uv;\n void main(){\n vec2 vel = texture2D(velocity, uv).xy;\n float lenv = clamp(length(vel), 0.0, 1.0);\n vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n vec3 outRGB = mix(bgColor.rgb, c, lenv);\n float outA = mix(bgColor.a, 1.0, lenv);\n gl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n float divergence = (x1 - x0 + y1 - y0) / 2.0;\n gl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n precision highp float;\n uniform vec2 force;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 circle = (vUv - 0.5) * 2.0;\n float d = 1.0 - min(length(circle), 1.0);\n d *= d;\n gl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D divergence;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n float div = texture2D(divergence, uv).r;\n float newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n gl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D velocity;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n float step = 1.0;\n float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n vec2 v = texture2D(velocity, uv).xy;\n vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n v = v - gradP * dt;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D velocity_new;\n uniform float v;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n vec2 old = texture2D(velocity, uv).xy;\n vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n newv /= 4.0 * (1.0 + v * dt);\n gl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n class ShaderPass {\n constructor(props) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n this.scene = null;\n this.camera = null;\n this.material = null;\n this.geometry = null;\n this.plane = null;\n }\n init() {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2.0, 2.0);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update() {\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene.add(this.line);\n }\n update({ dt, isBounce, BFECC }) {\n this.uniforms.dt.value = dt;\n this.line.visible = isBounce;\n this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n constructor(simProps) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0.0, 0.0) },\n center: { value: new THREE.Vector2(0.0, 0.0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene.add(this.mouse);\n }\n update(props) {\n const forceX = (Mouse.diff.x / 2) * props.mouse_force;\n const forceY = (Mouse.diff.y / 2) * props.mouse_force;\n const cursorSizeX = props.cursor_size * props.cellScale.x;\n const cursorSizeY = props.cursor_size * props.cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + props.cellScale.x * 2),\n 1 - cursorSizeX - props.cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + props.cellScale.y * 2),\n 1 - cursorSizeY - props.cellScale.y * 2\n );\n const uniforms = this.mouse.material.uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(props.cursor_size, props.cursor_size);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ viscous, iterations, dt }) {\n let fbo_in, fbo_out;\n this.uniforms.v.value = viscous;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel }) {\n this.uniforms.velocity.value = vel.texture;\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ iterations }) {\n let p_in, p_out;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel, pressure }) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n super.update();\n }\n }\n\n class Simulation {\n constructor(options) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.fbos = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n this.fboSize = new THREE.Vector2();\n this.cellScale = new THREE.Vector2();\n this.boundarySpace = new THREE.Vector2();\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n };\n for (let key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n const px_x = 1.0 / width;\n const px_y = 1.0 / height;\n this.cellScale.set(px_x, px_y);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (let key in this.fbos) {\n this.fbos[key].setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) {\n this.boundarySpace.set(0, 0);\n } else {\n this.boundarySpace.copy(this.cellScale);\n }\n this.advection.update({\n dt: this.options.dt,\n isBounce: this.options.isBounce,\n BFECC: this.options.BFECC\n });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({\n iterations: this.options.iterations_poisson\n });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n constructor() {\n this.init();\n }\n init() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n addScene(mesh) {\n this.scene.add(mesh);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager {\n constructor(props) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n this.lastUserInteraction = performance.now();\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n this._loop = this.loop.bind(this);\n this._resize = this.resize.bind(this);\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n this.running = false;\n }\n init() {\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return; // safety\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n }\n } catch (e) {\n void 0;\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) {\n sim.resize();\n }\n };\n applyOptionsFromProps();\n\n webgl.start();\n\n // IntersectionObserver to pause rendering when not visible\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) {\n sim.resize();\n }\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return (\n \n );\n}\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = ['#5227FF', '#FF9FFC', '#B19EEF'],\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}) {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops) {\n let arr;\n if (Array.isArray(stops) && stops.length > 0) {\n if (stops.length === 1) {\n arr = [stops[0], stops[0]];\n } else {\n arr = stops;\n }\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0); // always transparent\n\n class CommonClass {\n constructor() {\n this.width = 0;\n this.height = 0;\n this.aspect = 1;\n this.pixelRatio = 1;\n this.isMobile = false;\n this.breakpoint = 768;\n this.fboWidth = null;\n this.fboHeight = null;\n this.time = 0;\n this.delta = 0;\n this.container = null;\n this.renderer = null;\n this.clock = null;\n }\n init(container) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n this.renderer.domElement.style.width = '100%';\n this.renderer.domElement.style.height = '100%';\n this.renderer.domElement.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n constructor() {\n this.mouseMoved = false;\n this.coords = new THREE.Vector2();\n this.coords_old = new THREE.Vector2();\n this.diff = new THREE.Vector2();\n this.timer = null;\n this.container = null;\n this.docTarget = null;\n this.listenerTarget = null;\n this.isHoverInside = false;\n this.hasUserControl = false;\n this.isAutoActive = false;\n this.autoIntensity = 2.0;\n this.takeoverActive = false;\n this.takeoverStartTime = 0;\n this.takeoverDuration = 0.25;\n this.takeoverFrom = new THREE.Vector2();\n this.takeoverTo = new THREE.Vector2();\n this.onInteract = null;\n this._onMouseMove = this.onDocumentMouseMove.bind(this);\n this._onTouchStart = this.onDocumentTouchStart.bind(this);\n this._onTouchMove = this.onDocumentTouchMove.bind(this);\n this._onTouchEnd = this.onTouchEnd.bind(this);\n this._onDocumentLeave = this.onDocumentLeave.bind(this);\n }\n init(container) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView =\n (this.docTarget && this.docTarget.defaultView) || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { passive: true });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { passive: true });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n if (this.docTarget) {\n this.docTarget.addEventListener('mouseleave', this._onDocumentLeave);\n }\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n isPointInside(clientX, clientY) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n updateHoverState(clientX, clientY) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x, y) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx, ny) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n constructor(mouse, manager, opts) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed; // normalized units/sec\n this.resumeDelay = opts.resumeDelay || 3000; // ms\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.active = false;\n this.current = new THREE.Vector2(0, 0);\n this.target = new THREE.Vector2();\n this.lastTime = performance.now();\n this.activationTime = 0;\n this.margin = 0.2;\n this._tmpDir = new THREE.Vector2(); // reuse temp vector to avoid per-frame alloc\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n attribute vec3 position;\n uniform vec2 px;\n uniform vec2 boundarySpace;\n varying vec2 uv;\n precision highp float;\n void main(){\n vec3 pos = position;\n vec2 scale = 1.0 - boundarySpace * 2.0;\n pos.xy = pos.xy * scale;\n uv = vec2(0.5)+(pos.xy)*0.5;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n attribute vec3 position;\n uniform vec2 px;\n precision highp float;\n varying vec2 uv;\n void main(){\n vec3 pos = position;\n uv = 0.5 + pos.xy * 0.5;\n vec2 n = sign(pos.xy);\n pos.xy = abs(pos.xy) - px * 1.0;\n pos.xy *= n;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n precision highp float;\n attribute vec3 position;\n attribute vec2 uv;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 pos = position.xy * scale * 2.0 * px + center;\n vUv = uv;\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform bool isBFECC;\n uniform vec2 fboSize;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n if(isBFECC == false){\n vec2 vel = texture2D(velocity, uv).xy;\n vec2 uv2 = uv - vel * dt * ratio;\n vec2 newVel = texture2D(velocity, uv2).xy;\n gl_FragColor = vec4(newVel, 0.0, 0.0);\n } else {\n vec2 spot_new = uv;\n vec2 vel_old = texture2D(velocity, uv).xy;\n vec2 spot_old = spot_new - vel_old * dt * ratio;\n vec2 vel_new1 = texture2D(velocity, spot_old).xy;\n vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n vec2 error = spot_new2 - spot_new;\n vec2 spot_new3 = spot_new - error / 2.0;\n vec2 vel_2 = texture2D(velocity, spot_new3).xy;\n vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n vec2 newVel2 = texture2D(velocity, spot_old2).xy; \n gl_FragColor = vec4(newVel2, 0.0, 0.0);\n }\n}\n`;\n const color_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D palette;\n uniform vec4 bgColor;\n varying vec2 uv;\n void main(){\n vec2 vel = texture2D(velocity, uv).xy;\n float lenv = clamp(length(vel), 0.0, 1.0);\n vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n vec3 outRGB = mix(bgColor.rgb, c, lenv);\n float outA = mix(bgColor.a, 1.0, lenv);\n gl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n float divergence = (x1 - x0 + y1 - y0) / 2.0;\n gl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n precision highp float;\n uniform vec2 force;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 circle = (vUv - 0.5) * 2.0;\n float d = 1.0 - min(length(circle), 1.0);\n d *= d;\n gl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D divergence;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n float div = texture2D(divergence, uv).r;\n float newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n gl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D velocity;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n float step = 1.0;\n float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n vec2 v = texture2D(velocity, uv).xy;\n vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n v = v - gradP * dt;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D velocity_new;\n uniform float v;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n vec2 old = texture2D(velocity, uv).xy;\n vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n newv /= 4.0 * (1.0 + v * dt);\n gl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n class ShaderPass {\n constructor(props) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n this.scene = null;\n this.camera = null;\n this.material = null;\n this.geometry = null;\n this.plane = null;\n }\n init() {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2.0, 2.0);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update() {\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene.add(this.line);\n }\n update({ dt, isBounce, BFECC }) {\n this.uniforms.dt.value = dt;\n this.line.visible = isBounce;\n this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n constructor(simProps) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0.0, 0.0) },\n center: { value: new THREE.Vector2(0.0, 0.0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene.add(this.mouse);\n }\n update(props) {\n const forceX = (Mouse.diff.x / 2) * props.mouse_force;\n const forceY = (Mouse.diff.y / 2) * props.mouse_force;\n const cursorSizeX = props.cursor_size * props.cellScale.x;\n const cursorSizeY = props.cursor_size * props.cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + props.cellScale.x * 2),\n 1 - cursorSizeX - props.cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + props.cellScale.y * 2),\n 1 - cursorSizeY - props.cellScale.y * 2\n );\n const uniforms = this.mouse.material.uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(props.cursor_size, props.cursor_size);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ viscous, iterations, dt }) {\n let fbo_in, fbo_out;\n this.uniforms.v.value = viscous;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel }) {\n this.uniforms.velocity.value = vel.texture;\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update({ iterations }) {\n let p_in, p_out;\n for (let i = 0; i < iterations; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update({ vel, pressure }) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n super.update();\n }\n }\n\n class Simulation {\n constructor(options) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.fbos = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n this.fboSize = new THREE.Vector2();\n this.cellScale = new THREE.Vector2();\n this.boundarySpace = new THREE.Vector2();\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n };\n for (let key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n const px_x = 1.0 / width;\n const px_y = 1.0 / height;\n this.cellScale.set(px_x, px_y);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (let key in this.fbos) {\n this.fbos[key].setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) {\n this.boundarySpace.set(0, 0);\n } else {\n this.boundarySpace.copy(this.cellScale);\n }\n this.advection.update({\n dt: this.options.dt,\n isBounce: this.options.isBounce,\n BFECC: this.options.BFECC\n });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({\n iterations: this.options.iterations_poisson\n });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n constructor() {\n this.init();\n }\n init() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n addScene(mesh) {\n this.scene.add(mesh);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager {\n constructor(props) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n this.lastUserInteraction = performance.now();\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n this._loop = this.loop.bind(this);\n this._resize = this.resize.bind(this);\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n this.running = false;\n }\n init() {\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return; // safety\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n Common.renderer.forceContextLoss();\n }\n } catch (e) {\n void 0;\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) {\n sim.resize();\n }\n };\n applyOptionsFromProps();\n\n webgl.start();\n\n // IntersectionObserver to pause rendering when not visible\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch (e) {\n void 0;\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) {\n sim.resize();\n }\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/LiquidEther-TS-CSS.json b/public/r/LiquidEther-TS-CSS.json index 239edd7e..97e3d917 100644 --- a/public/r/LiquidEther-TS-CSS.json +++ b/public/r/LiquidEther-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "LiquidEther/LiquidEther.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LiquidEther.css';\n\nexport interface LiquidEtherProps {\n mouseForce?: number;\n cursorSize?: number;\n isViscous?: boolean;\n viscous?: number;\n iterationsViscous?: number;\n iterationsPoisson?: number;\n dt?: number;\n BFECC?: boolean;\n resolution?: number;\n isBounce?: boolean;\n colors?: string[];\n style?: React.CSSProperties;\n className?: string;\n autoDemo?: boolean;\n autoSpeed?: number;\n autoIntensity?: number;\n takeoverDuration?: number;\n autoResumeDelay?: number;\n autoRampDuration?: number;\n}\n\ninterface SimOptions {\n iterations_poisson: number;\n iterations_viscous: number;\n mouse_force: number;\n resolution: number;\n cursor_size: number;\n viscous: number;\n isBounce: boolean;\n dt: number;\n isViscous: boolean;\n BFECC: boolean;\n}\n\ninterface LiquidEtherWebGL {\n output?: { simulation?: { options: SimOptions; resize: () => void } };\n autoDriver?: {\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n mouse?: { autoIntensity: number; takeoverDuration: number };\n forceStop: () => void;\n };\n resize: () => void;\n start: () => void;\n pause: () => void;\n dispose: () => void;\n}\n\nconst defaultColors = ['#5227FF', '#FF9FFC', '#B19EEF'];\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = defaultColors,\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}: LiquidEtherProps): React.ReactElement {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops: string[]): THREE.DataTexture {\n let arr: string[];\n if (Array.isArray(stops) && stops.length > 0) {\n arr = stops.length === 1 ? [stops[0], stops[0]] : stops;\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n // Hard-code transparent background vector (alpha 0)\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0);\n\n class CommonClass {\n width = 0;\n height = 0;\n aspect = 1;\n pixelRatio = 1;\n isMobile = false;\n breakpoint = 768;\n fboWidth: number | null = null;\n fboHeight: number | null = null;\n time = 0;\n delta = 0;\n container: HTMLElement | null = null;\n renderer: THREE.WebGLRenderer | null = null;\n clock: THREE.Clock | null = null;\n init(container: HTMLElement) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n // Always transparent\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n const el = this.renderer.domElement;\n el.style.width = '100%';\n el.style.height = '100%';\n el.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n if (!this.clock) return;\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n mouseMoved = false;\n coords = new THREE.Vector2();\n coords_old = new THREE.Vector2();\n diff = new THREE.Vector2();\n timer: number | null = null;\n container: HTMLElement | null = null;\n docTarget: Document | null = null;\n listenerTarget: Window | null = null;\n isHoverInside = false;\n hasUserControl = false;\n isAutoActive = false;\n autoIntensity = 2.0;\n takeoverActive = false;\n takeoverStartTime = 0;\n takeoverDuration = 0.25;\n takeoverFrom = new THREE.Vector2();\n takeoverTo = new THREE.Vector2();\n onInteract: (() => void) | null = null;\n private _onMouseMove = this.onDocumentMouseMove.bind(this);\n private _onTouchStart = this.onDocumentTouchStart.bind(this);\n private _onTouchMove = this.onDocumentTouchMove.bind(this);\n private _onTouchEnd = this.onTouchEnd.bind(this);\n private _onDocumentLeave = this.onDocumentLeave.bind(this);\n init(container: HTMLElement) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView = this.docTarget?.defaultView || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n this.docTarget?.addEventListener('mouseleave', this._onDocumentLeave);\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n private isPointInside(clientX: number, clientY: number) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n private updateHoverState(clientX: number, clientY: number) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x: number, y: number) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx: number, ny: number) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event: MouseEvent) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n mouse: MouseClass;\n manager: WebGLManager;\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n active = false;\n current = new THREE.Vector2(0, 0);\n target = new THREE.Vector2();\n lastTime = performance.now();\n activationTime = 0;\n margin = 0.2;\n private _tmpDir = new THREE.Vector2();\n constructor(\n mouse: MouseClass,\n manager: WebGLManager,\n opts: { enabled: boolean; speed: number; resumeDelay: number; rampDuration: number }\n ) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed;\n this.resumeDelay = opts.resumeDelay || 3000;\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n\tattribute vec3 position;\n\tuniform vec2 px;\n\tuniform vec2 boundarySpace;\n\tvarying vec2 uv;\n\tprecision highp float;\n\tvoid main(){\n\tvec3 pos = position;\n\tvec2 scale = 1.0 - boundarySpace * 2.0;\n\tpos.xy = pos.xy * scale;\n\tuv = vec2(0.5)+(pos.xy)*0.5;\n\tgl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n\tattribute vec3 position;\n\tuniform vec2 px;\n\tprecision highp float;\n\tvarying vec2 uv;\n\tvoid main(){\n\tvec3 pos = position;\n\tuv = 0.5 + pos.xy * 0.5;\n\tvec2 n = sign(pos.xy);\n\tpos.xy = abs(pos.xy) - px * 1.0;\n\tpos.xy *= n;\n\tgl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n\t\tprecision highp float;\n\t\tattribute vec3 position;\n\t\tattribute vec2 uv;\n\t\tuniform vec2 center;\n\t\tuniform vec2 scale;\n\t\tuniform vec2 px;\n\t\tvarying vec2 vUv;\n\t\tvoid main(){\n\t\tvec2 pos = position.xy * scale * 2.0 * px + center;\n\t\tvUv = uv;\n\t\tgl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform float dt;\n\t\tuniform bool isBFECC;\n\t\tuniform vec2 fboSize;\n\t\tuniform vec2 px;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tvec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n\t\tif(isBFECC == false){\n\t\t\t\tvec2 vel = texture2D(velocity, uv).xy;\n\t\t\t\tvec2 uv2 = uv - vel * dt * ratio;\n\t\t\t\tvec2 newVel = texture2D(velocity, uv2).xy;\n\t\t\t\tgl_FragColor = vec4(newVel, 0.0, 0.0);\n\t\t} else {\n\t\t\t\tvec2 spot_new = uv;\n\t\t\t\tvec2 vel_old = texture2D(velocity, uv).xy;\n\t\t\t\tvec2 spot_old = spot_new - vel_old * dt * ratio;\n\t\t\t\tvec2 vel_new1 = texture2D(velocity, spot_old).xy;\n\t\t\t\tvec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n\t\t\t\tvec2 error = spot_new2 - spot_new;\n\t\t\t\tvec2 spot_new3 = spot_new - error / 2.0;\n\t\t\t\tvec2 vel_2 = texture2D(velocity, spot_new3).xy;\n\t\t\t\tvec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n\t\t\t\tvec2 newVel2 = texture2D(velocity, spot_old2).xy; \n\t\t\t\tgl_FragColor = vec4(newVel2, 0.0, 0.0);\n\t\t}\n}\n`;\n const color_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform sampler2D palette;\n\t\tuniform vec4 bgColor;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tvec2 vel = texture2D(velocity, uv).xy;\n\t\tfloat lenv = clamp(length(vel), 0.0, 1.0);\n\t\tvec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n\t\tvec3 outRGB = mix(bgColor.rgb, c, lenv);\n\t\tfloat outA = mix(bgColor.a, 1.0, lenv);\n\t\tgl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform float dt;\n\t\tuniform vec2 px;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tfloat x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n\t\tfloat x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n\t\tfloat y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n\t\tfloat y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n\t\tfloat divergence = (x1 - x0 + y1 - y0) / 2.0;\n\t\tgl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n\t\tprecision highp float;\n\t\tuniform vec2 force;\n\t\tuniform vec2 center;\n\t\tuniform vec2 scale;\n\t\tuniform vec2 px;\n\t\tvarying vec2 vUv;\n\t\tvoid main(){\n\t\tvec2 circle = (vUv - 0.5) * 2.0;\n\t\tfloat d = 1.0 - min(length(circle), 1.0);\n\t\td *= d;\n\t\tgl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D pressure;\n\t\tuniform sampler2D divergence;\n\t\tuniform vec2 px;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tfloat p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n\t\tfloat p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n\t\tfloat p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n\t\tfloat p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n\t\tfloat div = texture2D(divergence, uv).r;\n\t\tfloat newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n\t\tgl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D pressure;\n\t\tuniform sampler2D velocity;\n\t\tuniform vec2 px;\n\t\tuniform float dt;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tfloat step = 1.0;\n\t\tfloat p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n\t\tfloat p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n\t\tfloat p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n\t\tfloat p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n\t\tvec2 v = texture2D(velocity, uv).xy;\n\t\tvec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n\t\tv = v - gradP * dt;\n\t\tgl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform sampler2D velocity_new;\n\t\tuniform float v;\n\t\tuniform vec2 px;\n\t\tuniform float dt;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tvec2 old = texture2D(velocity, uv).xy;\n\t\tvec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n\t\tvec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n\t\tvec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n\t\tvec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n\t\tvec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n\t\tnewv /= 4.0 * (1.0 + v * dt);\n\t\tgl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n type Uniforms = Record;\n\n class ShaderPass {\n props: any;\n uniforms?: Uniforms;\n scene: THREE.Scene | null = null;\n camera: THREE.Camera | null = null;\n material: THREE.RawShaderMaterial | null = null;\n geometry: THREE.BufferGeometry | null = null;\n plane: THREE.Mesh | null = null;\n constructor(props: any) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n }\n init(..._args: any[]) {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2, 2);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update(..._args: any[]) {\n if (!Common.renderer || !this.scene || !this.camera) return;\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n line!: THREE.LineSegments;\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms!\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene!.add(this.line);\n }\n update(...args: any[]) {\n const { dt, isBounce, BFECC } = (args[0] || {}) as { dt?: number; isBounce?: boolean; BFECC?: boolean };\n if (!this.uniforms) return;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n if (typeof isBounce === 'boolean') this.line.visible = isBounce;\n if (typeof BFECC === 'boolean') this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n mouse!: THREE.Mesh;\n constructor(simProps: any) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps: any) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0, 0) },\n center: { value: new THREE.Vector2(0, 0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene!.add(this.mouse);\n }\n update(...args: any[]) {\n const props = args[0] || {};\n const forceX = (Mouse.diff.x / 2) * (props.mouse_force || 0);\n const forceY = (Mouse.diff.y / 2) * (props.mouse_force || 0);\n const cellScale = props.cellScale || { x: 1, y: 1 };\n const cursorSize = props.cursor_size || 0;\n const cursorSizeX = cursorSize * cellScale.x;\n const cursorSizeY = cursorSize * cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + cellScale.x * 2),\n 1 - cursorSizeX - cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + cellScale.y * 2),\n 1 - cursorSizeY - cellScale.y * 2\n );\n const uniforms = (this.mouse.material as THREE.RawShaderMaterial).uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(cursorSize, cursorSize);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { viscous, iterations, dt } = (args[0] || {}) as { viscous?: number; iterations?: number; dt?: number };\n if (!this.uniforms) return;\n let fbo_in: any, fbo_out: any;\n if (typeof viscous === 'number') this.uniforms.v.value = viscous;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel } = (args[0] || {}) as { vel?: any };\n if (this.uniforms && vel) {\n this.uniforms.velocity.value = vel.texture;\n }\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { iterations } = (args[0] || {}) as { iterations?: number };\n let p_in: any, p_out: any;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n if (this.uniforms) this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel, pressure } = (args[0] || {}) as { vel?: any; pressure?: any };\n if (this.uniforms && vel && pressure) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n }\n super.update();\n }\n }\n\n class Simulation {\n options: SimOptions;\n fbos: Record = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n fboSize = new THREE.Vector2();\n cellScale = new THREE.Vector2();\n boundarySpace = new THREE.Vector2();\n advection!: Advection;\n externalForce!: ExternalForce;\n viscous!: Viscous;\n divergence!: Divergence;\n poisson!: Poisson;\n pressure!: Pressure;\n constructor(options?: Partial) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n } as const;\n for (const key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n this.cellScale.set(1 / width, 1 / height);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (const key in this.fbos) {\n this.fbos[key]!.setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) this.boundarySpace.set(0, 0);\n else this.boundarySpace.copy(this.cellScale);\n this.advection.update({ dt: this.options.dt, isBounce: this.options.isBounce, BFECC: this.options.BFECC });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel: any = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({ iterations: this.options.iterations_poisson });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n simulation: Simulation;\n scene: THREE.Scene;\n camera: THREE.Camera;\n output: THREE.Mesh;\n constructor() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0!.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n if (!Common.renderer) return;\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager implements LiquidEtherWebGL {\n props: any;\n output!: Output;\n autoDriver?: AutoDriver;\n lastUserInteraction = performance.now();\n running = false;\n private _loop = this.loop.bind(this);\n private _resize = this.resize.bind(this);\n private _onVisibility?: () => void;\n constructor(props: any) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this as any, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n }\n init() {\n if (!Common.renderer) return;\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return;\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n if (this._onVisibility) document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n }\n } catch {\n /* noop */\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) sim.resize();\n };\n applyOptionsFromProps();\n webgl.start();\n\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) sim.resize();\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return
;\n}\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './LiquidEther.css';\n\nexport interface LiquidEtherProps {\n mouseForce?: number;\n cursorSize?: number;\n isViscous?: boolean;\n viscous?: number;\n iterationsViscous?: number;\n iterationsPoisson?: number;\n dt?: number;\n BFECC?: boolean;\n resolution?: number;\n isBounce?: boolean;\n colors?: string[];\n style?: React.CSSProperties;\n className?: string;\n autoDemo?: boolean;\n autoSpeed?: number;\n autoIntensity?: number;\n takeoverDuration?: number;\n autoResumeDelay?: number;\n autoRampDuration?: number;\n}\n\ninterface SimOptions {\n iterations_poisson: number;\n iterations_viscous: number;\n mouse_force: number;\n resolution: number;\n cursor_size: number;\n viscous: number;\n isBounce: boolean;\n dt: number;\n isViscous: boolean;\n BFECC: boolean;\n}\n\ninterface LiquidEtherWebGL {\n output?: { simulation?: { options: SimOptions; resize: () => void } };\n autoDriver?: {\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n mouse?: { autoIntensity: number; takeoverDuration: number };\n forceStop: () => void;\n };\n resize: () => void;\n start: () => void;\n pause: () => void;\n dispose: () => void;\n}\n\nconst defaultColors = ['#5227FF', '#FF9FFC', '#B19EEF'];\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = defaultColors,\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}: LiquidEtherProps): React.ReactElement {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops: string[]): THREE.DataTexture {\n let arr: string[];\n if (Array.isArray(stops) && stops.length > 0) {\n arr = stops.length === 1 ? [stops[0], stops[0]] : stops;\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n // Hard-code transparent background vector (alpha 0)\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0);\n\n class CommonClass {\n width = 0;\n height = 0;\n aspect = 1;\n pixelRatio = 1;\n isMobile = false;\n breakpoint = 768;\n fboWidth: number | null = null;\n fboHeight: number | null = null;\n time = 0;\n delta = 0;\n container: HTMLElement | null = null;\n renderer: THREE.WebGLRenderer | null = null;\n clock: THREE.Clock | null = null;\n init(container: HTMLElement) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n // Always transparent\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n const el = this.renderer.domElement;\n el.style.width = '100%';\n el.style.height = '100%';\n el.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n if (!this.clock) return;\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n mouseMoved = false;\n coords = new THREE.Vector2();\n coords_old = new THREE.Vector2();\n diff = new THREE.Vector2();\n timer: number | null = null;\n container: HTMLElement | null = null;\n docTarget: Document | null = null;\n listenerTarget: Window | null = null;\n isHoverInside = false;\n hasUserControl = false;\n isAutoActive = false;\n autoIntensity = 2.0;\n takeoverActive = false;\n takeoverStartTime = 0;\n takeoverDuration = 0.25;\n takeoverFrom = new THREE.Vector2();\n takeoverTo = new THREE.Vector2();\n onInteract: (() => void) | null = null;\n private _onMouseMove = this.onDocumentMouseMove.bind(this);\n private _onTouchStart = this.onDocumentTouchStart.bind(this);\n private _onTouchMove = this.onDocumentTouchMove.bind(this);\n private _onTouchEnd = this.onTouchEnd.bind(this);\n private _onDocumentLeave = this.onDocumentLeave.bind(this);\n init(container: HTMLElement) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView = this.docTarget?.defaultView || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n this.docTarget?.addEventListener('mouseleave', this._onDocumentLeave);\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n private isPointInside(clientX: number, clientY: number) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n private updateHoverState(clientX: number, clientY: number) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x: number, y: number) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx: number, ny: number) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event: MouseEvent) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n mouse: MouseClass;\n manager: WebGLManager;\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n active = false;\n current = new THREE.Vector2(0, 0);\n target = new THREE.Vector2();\n lastTime = performance.now();\n activationTime = 0;\n margin = 0.2;\n private _tmpDir = new THREE.Vector2();\n constructor(\n mouse: MouseClass,\n manager: WebGLManager,\n opts: { enabled: boolean; speed: number; resumeDelay: number; rampDuration: number }\n ) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed;\n this.resumeDelay = opts.resumeDelay || 3000;\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n\tattribute vec3 position;\n\tuniform vec2 px;\n\tuniform vec2 boundarySpace;\n\tvarying vec2 uv;\n\tprecision highp float;\n\tvoid main(){\n\tvec3 pos = position;\n\tvec2 scale = 1.0 - boundarySpace * 2.0;\n\tpos.xy = pos.xy * scale;\n\tuv = vec2(0.5)+(pos.xy)*0.5;\n\tgl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n\tattribute vec3 position;\n\tuniform vec2 px;\n\tprecision highp float;\n\tvarying vec2 uv;\n\tvoid main(){\n\tvec3 pos = position;\n\tuv = 0.5 + pos.xy * 0.5;\n\tvec2 n = sign(pos.xy);\n\tpos.xy = abs(pos.xy) - px * 1.0;\n\tpos.xy *= n;\n\tgl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n\t\tprecision highp float;\n\t\tattribute vec3 position;\n\t\tattribute vec2 uv;\n\t\tuniform vec2 center;\n\t\tuniform vec2 scale;\n\t\tuniform vec2 px;\n\t\tvarying vec2 vUv;\n\t\tvoid main(){\n\t\tvec2 pos = position.xy * scale * 2.0 * px + center;\n\t\tvUv = uv;\n\t\tgl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform float dt;\n\t\tuniform bool isBFECC;\n\t\tuniform vec2 fboSize;\n\t\tuniform vec2 px;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tvec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n\t\tif(isBFECC == false){\n\t\t\t\tvec2 vel = texture2D(velocity, uv).xy;\n\t\t\t\tvec2 uv2 = uv - vel * dt * ratio;\n\t\t\t\tvec2 newVel = texture2D(velocity, uv2).xy;\n\t\t\t\tgl_FragColor = vec4(newVel, 0.0, 0.0);\n\t\t} else {\n\t\t\t\tvec2 spot_new = uv;\n\t\t\t\tvec2 vel_old = texture2D(velocity, uv).xy;\n\t\t\t\tvec2 spot_old = spot_new - vel_old * dt * ratio;\n\t\t\t\tvec2 vel_new1 = texture2D(velocity, spot_old).xy;\n\t\t\t\tvec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n\t\t\t\tvec2 error = spot_new2 - spot_new;\n\t\t\t\tvec2 spot_new3 = spot_new - error / 2.0;\n\t\t\t\tvec2 vel_2 = texture2D(velocity, spot_new3).xy;\n\t\t\t\tvec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n\t\t\t\tvec2 newVel2 = texture2D(velocity, spot_old2).xy; \n\t\t\t\tgl_FragColor = vec4(newVel2, 0.0, 0.0);\n\t\t}\n}\n`;\n const color_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform sampler2D palette;\n\t\tuniform vec4 bgColor;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tvec2 vel = texture2D(velocity, uv).xy;\n\t\tfloat lenv = clamp(length(vel), 0.0, 1.0);\n\t\tvec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n\t\tvec3 outRGB = mix(bgColor.rgb, c, lenv);\n\t\tfloat outA = mix(bgColor.a, 1.0, lenv);\n\t\tgl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform float dt;\n\t\tuniform vec2 px;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tfloat x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n\t\tfloat x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n\t\tfloat y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n\t\tfloat y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n\t\tfloat divergence = (x1 - x0 + y1 - y0) / 2.0;\n\t\tgl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n\t\tprecision highp float;\n\t\tuniform vec2 force;\n\t\tuniform vec2 center;\n\t\tuniform vec2 scale;\n\t\tuniform vec2 px;\n\t\tvarying vec2 vUv;\n\t\tvoid main(){\n\t\tvec2 circle = (vUv - 0.5) * 2.0;\n\t\tfloat d = 1.0 - min(length(circle), 1.0);\n\t\td *= d;\n\t\tgl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D pressure;\n\t\tuniform sampler2D divergence;\n\t\tuniform vec2 px;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tfloat p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n\t\tfloat p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n\t\tfloat p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n\t\tfloat p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n\t\tfloat div = texture2D(divergence, uv).r;\n\t\tfloat newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n\t\tgl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D pressure;\n\t\tuniform sampler2D velocity;\n\t\tuniform vec2 px;\n\t\tuniform float dt;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tfloat step = 1.0;\n\t\tfloat p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n\t\tfloat p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n\t\tfloat p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n\t\tfloat p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n\t\tvec2 v = texture2D(velocity, uv).xy;\n\t\tvec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n\t\tv = v - gradP * dt;\n\t\tgl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n\t\tprecision highp float;\n\t\tuniform sampler2D velocity;\n\t\tuniform sampler2D velocity_new;\n\t\tuniform float v;\n\t\tuniform vec2 px;\n\t\tuniform float dt;\n\t\tvarying vec2 uv;\n\t\tvoid main(){\n\t\tvec2 old = texture2D(velocity, uv).xy;\n\t\tvec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n\t\tvec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n\t\tvec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n\t\tvec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n\t\tvec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n\t\tnewv /= 4.0 * (1.0 + v * dt);\n\t\tgl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n type Uniforms = Record;\n\n class ShaderPass {\n props: any;\n uniforms?: Uniforms;\n scene: THREE.Scene | null = null;\n camera: THREE.Camera | null = null;\n material: THREE.RawShaderMaterial | null = null;\n geometry: THREE.BufferGeometry | null = null;\n plane: THREE.Mesh | null = null;\n constructor(props: any) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n }\n init(..._args: any[]) {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2, 2);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update(..._args: any[]) {\n if (!Common.renderer || !this.scene || !this.camera) return;\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n line!: THREE.LineSegments;\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms!\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene!.add(this.line);\n }\n update(...args: any[]) {\n const { dt, isBounce, BFECC } = (args[0] || {}) as { dt?: number; isBounce?: boolean; BFECC?: boolean };\n if (!this.uniforms) return;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n if (typeof isBounce === 'boolean') this.line.visible = isBounce;\n if (typeof BFECC === 'boolean') this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n mouse!: THREE.Mesh;\n constructor(simProps: any) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps: any) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0, 0) },\n center: { value: new THREE.Vector2(0, 0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene!.add(this.mouse);\n }\n update(...args: any[]) {\n const props = args[0] || {};\n const forceX = (Mouse.diff.x / 2) * (props.mouse_force || 0);\n const forceY = (Mouse.diff.y / 2) * (props.mouse_force || 0);\n const cellScale = props.cellScale || { x: 1, y: 1 };\n const cursorSize = props.cursor_size || 0;\n const cursorSizeX = cursorSize * cellScale.x;\n const cursorSizeY = cursorSize * cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + cellScale.x * 2),\n 1 - cursorSizeX - cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + cellScale.y * 2),\n 1 - cursorSizeY - cellScale.y * 2\n );\n const uniforms = (this.mouse.material as THREE.RawShaderMaterial).uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(cursorSize, cursorSize);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { viscous, iterations, dt } = (args[0] || {}) as { viscous?: number; iterations?: number; dt?: number };\n if (!this.uniforms) return;\n let fbo_in: any, fbo_out: any;\n if (typeof viscous === 'number') this.uniforms.v.value = viscous;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel } = (args[0] || {}) as { vel?: any };\n if (this.uniforms && vel) {\n this.uniforms.velocity.value = vel.texture;\n }\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { iterations } = (args[0] || {}) as { iterations?: number };\n let p_in: any, p_out: any;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n if (this.uniforms) this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel, pressure } = (args[0] || {}) as { vel?: any; pressure?: any };\n if (this.uniforms && vel && pressure) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n }\n super.update();\n }\n }\n\n class Simulation {\n options: SimOptions;\n fbos: Record = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n fboSize = new THREE.Vector2();\n cellScale = new THREE.Vector2();\n boundarySpace = new THREE.Vector2();\n advection!: Advection;\n externalForce!: ExternalForce;\n viscous!: Viscous;\n divergence!: Divergence;\n poisson!: Poisson;\n pressure!: Pressure;\n constructor(options?: Partial) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n } as const;\n for (const key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n this.cellScale.set(1 / width, 1 / height);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (const key in this.fbos) {\n this.fbos[key]!.setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) this.boundarySpace.set(0, 0);\n else this.boundarySpace.copy(this.cellScale);\n this.advection.update({ dt: this.options.dt, isBounce: this.options.isBounce, BFECC: this.options.BFECC });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel: any = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({ iterations: this.options.iterations_poisson });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n simulation: Simulation;\n scene: THREE.Scene;\n camera: THREE.Camera;\n output: THREE.Mesh;\n constructor() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0!.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n if (!Common.renderer) return;\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager implements LiquidEtherWebGL {\n props: any;\n output!: Output;\n autoDriver?: AutoDriver;\n lastUserInteraction = performance.now();\n running = false;\n private _loop = this.loop.bind(this);\n private _resize = this.resize.bind(this);\n private _onVisibility?: () => void;\n constructor(props: any) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this as any, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n }\n init() {\n if (!Common.renderer) return;\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return;\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n if (this._onVisibility) document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n Common.renderer.forceContextLoss();\n }\n } catch {\n /* noop */\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) sim.resize();\n };\n applyOptionsFromProps();\n webgl.start();\n\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) sim.resize();\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/LiquidEther-TS-TW.json b/public/r/LiquidEther-TS-TW.json index 409ec6cc..89ea3186 100644 --- a/public/r/LiquidEther-TS-TW.json +++ b/public/r/LiquidEther-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "LiquidEther/LiquidEther.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nexport interface LiquidEtherProps {\n mouseForce?: number;\n cursorSize?: number;\n isViscous?: boolean;\n viscous?: number;\n iterationsViscous?: number;\n iterationsPoisson?: number;\n dt?: number;\n BFECC?: boolean;\n resolution?: number;\n isBounce?: boolean;\n colors?: string[];\n style?: React.CSSProperties;\n className?: string;\n autoDemo?: boolean;\n autoSpeed?: number;\n autoIntensity?: number;\n takeoverDuration?: number;\n autoResumeDelay?: number;\n autoRampDuration?: number;\n}\n\ninterface SimOptions {\n iterations_poisson: number;\n iterations_viscous: number;\n mouse_force: number;\n resolution: number;\n cursor_size: number;\n viscous: number;\n isBounce: boolean;\n dt: number;\n isViscous: boolean;\n BFECC: boolean;\n}\n\ninterface LiquidEtherWebGL {\n output?: { simulation?: { options: SimOptions; resize: () => void } };\n autoDriver?: {\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n mouse?: { autoIntensity: number; takeoverDuration: number };\n forceStop: () => void;\n };\n resize: () => void;\n start: () => void;\n pause: () => void;\n dispose: () => void;\n}\n\nconst defaultColors = ['#5227FF', '#FF9FFC', '#B19EEF'];\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = defaultColors,\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}: LiquidEtherProps): React.ReactElement {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops: string[]): THREE.DataTexture {\n let arr: string[];\n if (Array.isArray(stops) && stops.length > 0) {\n arr = stops.length === 1 ? [stops[0], stops[0]] : stops;\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n // Hard-code transparent background vector (alpha 0)\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0);\n\n class CommonClass {\n width = 0;\n height = 0;\n aspect = 1;\n pixelRatio = 1;\n isMobile = false;\n breakpoint = 768;\n fboWidth: number | null = null;\n fboHeight: number | null = null;\n time = 0;\n delta = 0;\n container: HTMLElement | null = null;\n renderer: THREE.WebGLRenderer | null = null;\n clock: THREE.Clock | null = null;\n init(container: HTMLElement) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n // Always transparent\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n const el = this.renderer.domElement;\n el.style.width = '100%';\n el.style.height = '100%';\n el.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n if (!this.clock) return;\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n mouseMoved = false;\n coords = new THREE.Vector2();\n coords_old = new THREE.Vector2();\n diff = new THREE.Vector2();\n timer: number | null = null;\n container: HTMLElement | null = null;\n docTarget: Document | null = null;\n listenerTarget: Window | null = null;\n isHoverInside = false;\n hasUserControl = false;\n isAutoActive = false;\n autoIntensity = 2.0;\n takeoverActive = false;\n takeoverStartTime = 0;\n takeoverDuration = 0.25;\n takeoverFrom = new THREE.Vector2();\n takeoverTo = new THREE.Vector2();\n onInteract: (() => void) | null = null;\n private _onMouseMove = this.onDocumentMouseMove.bind(this);\n private _onTouchStart = this.onDocumentTouchStart.bind(this);\n private _onTouchMove = this.onDocumentTouchMove.bind(this);\n private _onTouchEnd = this.onTouchEnd.bind(this);\n private _onDocumentLeave = this.onDocumentLeave.bind(this);\n init(container: HTMLElement) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView = this.docTarget?.defaultView || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n this.docTarget?.addEventListener('mouseleave', this._onDocumentLeave);\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n private isPointInside(clientX: number, clientY: number) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n private updateHoverState(clientX: number, clientY: number) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x: number, y: number) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx: number, ny: number) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event: MouseEvent) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n mouse: MouseClass;\n manager: WebGLManager;\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n active = false;\n current = new THREE.Vector2(0, 0);\n target = new THREE.Vector2();\n lastTime = performance.now();\n activationTime = 0;\n margin = 0.2;\n private _tmpDir = new THREE.Vector2();\n constructor(\n mouse: MouseClass,\n manager: WebGLManager,\n opts: { enabled: boolean; speed: number; resumeDelay: number; rampDuration: number }\n ) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed;\n this.resumeDelay = opts.resumeDelay || 3000;\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n attribute vec3 position;\n uniform vec2 px;\n uniform vec2 boundarySpace;\n varying vec2 uv;\n precision highp float;\n void main(){\n vec3 pos = position;\n vec2 scale = 1.0 - boundarySpace * 2.0;\n pos.xy = pos.xy * scale;\n uv = vec2(0.5)+(pos.xy)*0.5;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n attribute vec3 position;\n uniform vec2 px;\n precision highp float;\n varying vec2 uv;\n void main(){\n vec3 pos = position;\n uv = 0.5 + pos.xy * 0.5;\n vec2 n = sign(pos.xy);\n pos.xy = abs(pos.xy) - px * 1.0;\n pos.xy *= n;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n precision highp float;\n attribute vec3 position;\n attribute vec2 uv;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 pos = position.xy * scale * 2.0 * px + center;\n vUv = uv;\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform bool isBFECC;\n uniform vec2 fboSize;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n if(isBFECC == false){\n vec2 vel = texture2D(velocity, uv).xy;\n vec2 uv2 = uv - vel * dt * ratio;\n vec2 newVel = texture2D(velocity, uv2).xy;\n gl_FragColor = vec4(newVel, 0.0, 0.0);\n } else {\n vec2 spot_new = uv;\n vec2 vel_old = texture2D(velocity, uv).xy;\n vec2 spot_old = spot_new - vel_old * dt * ratio;\n vec2 vel_new1 = texture2D(velocity, spot_old).xy;\n vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n vec2 error = spot_new2 - spot_new;\n vec2 spot_new3 = spot_new - error / 2.0;\n vec2 vel_2 = texture2D(velocity, spot_new3).xy;\n vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n vec2 newVel2 = texture2D(velocity, spot_old2).xy; \n gl_FragColor = vec4(newVel2, 0.0, 0.0);\n }\n}\n`;\n const color_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D palette;\n uniform vec4 bgColor;\n varying vec2 uv;\n void main(){\n vec2 vel = texture2D(velocity, uv).xy;\n float lenv = clamp(length(vel), 0.0, 1.0);\n vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n vec3 outRGB = mix(bgColor.rgb, c, lenv);\n float outA = mix(bgColor.a, 1.0, lenv);\n gl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n float divergence = (x1 - x0 + y1 - y0) / 2.0;\n gl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n precision highp float;\n uniform vec2 force;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 circle = (vUv - 0.5) * 2.0;\n float d = 1.0 - min(length(circle), 1.0);\n d *= d;\n gl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D divergence;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n float div = texture2D(divergence, uv).r;\n float newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n gl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D velocity;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n float step = 1.0;\n float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n vec2 v = texture2D(velocity, uv).xy;\n vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n v = v - gradP * dt;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D velocity_new;\n uniform float v;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n vec2 old = texture2D(velocity, uv).xy;\n vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n newv /= 4.0 * (1.0 + v * dt);\n gl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n type Uniforms = Record;\n\n class ShaderPass {\n props: any;\n uniforms?: Uniforms;\n scene: THREE.Scene | null = null;\n camera: THREE.Camera | null = null;\n material: THREE.RawShaderMaterial | null = null;\n geometry: THREE.BufferGeometry | null = null;\n plane: THREE.Mesh | null = null;\n constructor(props: any) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n }\n init(..._args: any[]) {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2, 2);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update(..._args: any[]) {\n if (!Common.renderer || !this.scene || !this.camera) return;\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n line!: THREE.LineSegments;\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms!\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene!.add(this.line);\n }\n update(...args: any[]) {\n const { dt, isBounce, BFECC } = (args[0] || {}) as { dt?: number; isBounce?: boolean; BFECC?: boolean };\n if (!this.uniforms) return;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n if (typeof isBounce === 'boolean') this.line.visible = isBounce;\n if (typeof BFECC === 'boolean') this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n mouse!: THREE.Mesh;\n constructor(simProps: any) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps: any) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0, 0) },\n center: { value: new THREE.Vector2(0, 0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene!.add(this.mouse);\n }\n update(...args: any[]) {\n const props = args[0] || {};\n const forceX = (Mouse.diff.x / 2) * (props.mouse_force || 0);\n const forceY = (Mouse.diff.y / 2) * (props.mouse_force || 0);\n const cellScale = props.cellScale || { x: 1, y: 1 };\n const cursorSize = props.cursor_size || 0;\n const cursorSizeX = cursorSize * cellScale.x;\n const cursorSizeY = cursorSize * cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + cellScale.x * 2),\n 1 - cursorSizeX - cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + cellScale.y * 2),\n 1 - cursorSizeY - cellScale.y * 2\n );\n const uniforms = (this.mouse.material as THREE.RawShaderMaterial).uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(cursorSize, cursorSize);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { viscous, iterations, dt } = (args[0] || {}) as { viscous?: number; iterations?: number; dt?: number };\n if (!this.uniforms) return;\n let fbo_in: any, fbo_out: any;\n if (typeof viscous === 'number') this.uniforms.v.value = viscous;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel } = (args[0] || {}) as { vel?: any };\n if (this.uniforms && vel) {\n this.uniforms.velocity.value = vel.texture;\n }\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { iterations } = (args[0] || {}) as { iterations?: number };\n let p_in: any, p_out: any;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n if (this.uniforms) this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel, pressure } = (args[0] || {}) as { vel?: any; pressure?: any };\n if (this.uniforms && vel && pressure) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n }\n super.update();\n }\n }\n\n class Simulation {\n options: SimOptions;\n fbos: Record = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n fboSize = new THREE.Vector2();\n cellScale = new THREE.Vector2();\n boundarySpace = new THREE.Vector2();\n advection!: Advection;\n externalForce!: ExternalForce;\n viscous!: Viscous;\n divergence!: Divergence;\n poisson!: Poisson;\n pressure!: Pressure;\n constructor(options?: Partial) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n } as const;\n for (const key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n this.cellScale.set(1 / width, 1 / height);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (const key in this.fbos) {\n this.fbos[key]!.setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) this.boundarySpace.set(0, 0);\n else this.boundarySpace.copy(this.cellScale);\n this.advection.update({ dt: this.options.dt, isBounce: this.options.isBounce, BFECC: this.options.BFECC });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel: any = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({ iterations: this.options.iterations_poisson });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n simulation: Simulation;\n scene: THREE.Scene;\n camera: THREE.Camera;\n output: THREE.Mesh;\n constructor() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0!.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n if (!Common.renderer) return;\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager implements LiquidEtherWebGL {\n props: any;\n output!: Output;\n autoDriver?: AutoDriver;\n lastUserInteraction = performance.now();\n running = false;\n private _loop = this.loop.bind(this);\n private _resize = this.resize.bind(this);\n private _onVisibility?: () => void;\n constructor(props: any) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this as any, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n }\n init() {\n if (!Common.renderer) return;\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return;\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n if (this._onVisibility) document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n }\n } catch {\n /* noop */\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) sim.resize();\n };\n applyOptionsFromProps();\n webgl.start();\n\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) sim.resize();\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return (\n \n );\n}\n" + "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nexport interface LiquidEtherProps {\n mouseForce?: number;\n cursorSize?: number;\n isViscous?: boolean;\n viscous?: number;\n iterationsViscous?: number;\n iterationsPoisson?: number;\n dt?: number;\n BFECC?: boolean;\n resolution?: number;\n isBounce?: boolean;\n colors?: string[];\n style?: React.CSSProperties;\n className?: string;\n autoDemo?: boolean;\n autoSpeed?: number;\n autoIntensity?: number;\n takeoverDuration?: number;\n autoResumeDelay?: number;\n autoRampDuration?: number;\n}\n\ninterface SimOptions {\n iterations_poisson: number;\n iterations_viscous: number;\n mouse_force: number;\n resolution: number;\n cursor_size: number;\n viscous: number;\n isBounce: boolean;\n dt: number;\n isViscous: boolean;\n BFECC: boolean;\n}\n\ninterface LiquidEtherWebGL {\n output?: { simulation?: { options: SimOptions; resize: () => void } };\n autoDriver?: {\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n mouse?: { autoIntensity: number; takeoverDuration: number };\n forceStop: () => void;\n };\n resize: () => void;\n start: () => void;\n pause: () => void;\n dispose: () => void;\n}\n\nconst defaultColors = ['#5227FF', '#FF9FFC', '#B19EEF'];\n\nexport default function LiquidEther({\n mouseForce = 20,\n cursorSize = 100,\n isViscous = false,\n viscous = 30,\n iterationsViscous = 32,\n iterationsPoisson = 32,\n dt = 0.014,\n BFECC = true,\n resolution = 0.5,\n isBounce = false,\n colors = defaultColors,\n style = {},\n className = '',\n autoDemo = true,\n autoSpeed = 0.5,\n autoIntensity = 2.2,\n takeoverDuration = 0.25,\n autoResumeDelay = 1000,\n autoRampDuration = 0.6\n}: LiquidEtherProps): React.ReactElement {\n const mountRef = useRef(null);\n const webglRef = useRef(null);\n const resizeObserverRef = useRef(null);\n const rafRef = useRef(null);\n const intersectionObserverRef = useRef(null);\n const isVisibleRef = useRef(true);\n const resizeRafRef = useRef(null);\n\n useEffect(() => {\n if (!mountRef.current) return;\n\n function makePaletteTexture(stops: string[]): THREE.DataTexture {\n let arr: string[];\n if (Array.isArray(stops) && stops.length > 0) {\n arr = stops.length === 1 ? [stops[0], stops[0]] : stops;\n } else {\n arr = ['#ffffff', '#ffffff'];\n }\n const w = arr.length;\n const data = new Uint8Array(w * 4);\n for (let i = 0; i < w; i++) {\n const c = new THREE.Color(arr[i]);\n data[i * 4 + 0] = Math.round(c.r * 255);\n data[i * 4 + 1] = Math.round(c.g * 255);\n data[i * 4 + 2] = Math.round(c.b * 255);\n data[i * 4 + 3] = 255;\n }\n const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);\n tex.magFilter = THREE.LinearFilter;\n tex.minFilter = THREE.LinearFilter;\n tex.wrapS = THREE.ClampToEdgeWrapping;\n tex.wrapT = THREE.ClampToEdgeWrapping;\n tex.generateMipmaps = false;\n tex.needsUpdate = true;\n return tex;\n }\n\n const paletteTex = makePaletteTexture(colors);\n // Hard-code transparent background vector (alpha 0)\n const bgVec4 = new THREE.Vector4(0, 0, 0, 0);\n\n class CommonClass {\n width = 0;\n height = 0;\n aspect = 1;\n pixelRatio = 1;\n isMobile = false;\n breakpoint = 768;\n fboWidth: number | null = null;\n fboHeight: number | null = null;\n time = 0;\n delta = 0;\n container: HTMLElement | null = null;\n renderer: THREE.WebGLRenderer | null = null;\n clock: THREE.Clock | null = null;\n init(container: HTMLElement) {\n this.container = container;\n this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n this.resize();\n this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n // Always transparent\n this.renderer.autoClear = false;\n this.renderer.setClearColor(new THREE.Color(0x000000), 0);\n this.renderer.setPixelRatio(this.pixelRatio);\n this.renderer.setSize(this.width, this.height);\n const el = this.renderer.domElement;\n el.style.width = '100%';\n el.style.height = '100%';\n el.style.display = 'block';\n this.clock = new THREE.Clock();\n this.clock.start();\n }\n resize() {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n this.width = Math.max(1, Math.floor(rect.width));\n this.height = Math.max(1, Math.floor(rect.height));\n this.aspect = this.width / this.height;\n if (this.renderer) this.renderer.setSize(this.width, this.height, false);\n }\n update() {\n if (!this.clock) return;\n this.delta = this.clock.getDelta();\n this.time += this.delta;\n }\n }\n const Common = new CommonClass();\n\n class MouseClass {\n mouseMoved = false;\n coords = new THREE.Vector2();\n coords_old = new THREE.Vector2();\n diff = new THREE.Vector2();\n timer: number | null = null;\n container: HTMLElement | null = null;\n docTarget: Document | null = null;\n listenerTarget: Window | null = null;\n isHoverInside = false;\n hasUserControl = false;\n isAutoActive = false;\n autoIntensity = 2.0;\n takeoverActive = false;\n takeoverStartTime = 0;\n takeoverDuration = 0.25;\n takeoverFrom = new THREE.Vector2();\n takeoverTo = new THREE.Vector2();\n onInteract: (() => void) | null = null;\n private _onMouseMove = this.onDocumentMouseMove.bind(this);\n private _onTouchStart = this.onDocumentTouchStart.bind(this);\n private _onTouchMove = this.onDocumentTouchMove.bind(this);\n private _onTouchEnd = this.onTouchEnd.bind(this);\n private _onDocumentLeave = this.onDocumentLeave.bind(this);\n init(container: HTMLElement) {\n this.container = container;\n this.docTarget = container.ownerDocument || null;\n const defaultView = this.docTarget?.defaultView || (typeof window !== 'undefined' ? window : null);\n if (!defaultView) return;\n this.listenerTarget = defaultView;\n this.listenerTarget.addEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.addEventListener('touchstart', this._onTouchStart, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchmove', this._onTouchMove, {\n passive: true\n });\n this.listenerTarget.addEventListener('touchend', this._onTouchEnd);\n this.docTarget?.addEventListener('mouseleave', this._onDocumentLeave);\n }\n dispose() {\n if (this.listenerTarget) {\n this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);\n this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);\n this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);\n this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);\n }\n if (this.docTarget) {\n this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);\n }\n this.listenerTarget = null;\n this.docTarget = null;\n this.container = null;\n }\n private isPointInside(clientX: number, clientY: number) {\n if (!this.container) return false;\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;\n }\n private updateHoverState(clientX: number, clientY: number) {\n this.isHoverInside = this.isPointInside(clientX, clientY);\n return this.isHoverInside;\n }\n setCoords(x: number, y: number) {\n if (!this.container) return;\n if (this.timer) window.clearTimeout(this.timer);\n const rect = this.container.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n const nx = (x - rect.left) / rect.width;\n const ny = (y - rect.top) / rect.height;\n this.coords.set(nx * 2 - 1, -(ny * 2 - 1));\n this.mouseMoved = true;\n this.timer = window.setTimeout(() => {\n this.mouseMoved = false;\n }, 100);\n }\n setNormalized(nx: number, ny: number) {\n this.coords.set(nx, ny);\n this.mouseMoved = true;\n }\n onDocumentMouseMove(event: MouseEvent) {\n if (!this.updateHoverState(event.clientX, event.clientY)) return;\n if (this.onInteract) this.onInteract();\n if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n const nx = (event.clientX - rect.left) / rect.width;\n const ny = (event.clientY - rect.top) / rect.height;\n this.takeoverFrom.copy(this.coords);\n this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));\n this.takeoverStartTime = performance.now();\n this.takeoverActive = true;\n this.hasUserControl = true;\n this.isAutoActive = false;\n return;\n }\n this.setCoords(event.clientX, event.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchStart(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n this.hasUserControl = true;\n }\n onDocumentTouchMove(event: TouchEvent) {\n if (event.touches.length !== 1) return;\n const t = event.touches[0];\n if (!this.updateHoverState(t.clientX, t.clientY)) return;\n if (this.onInteract) this.onInteract();\n this.setCoords(t.clientX, t.clientY);\n }\n onTouchEnd() {\n this.isHoverInside = false;\n }\n onDocumentLeave() {\n this.isHoverInside = false;\n }\n update() {\n if (this.takeoverActive) {\n const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);\n if (t >= 1) {\n this.takeoverActive = false;\n this.coords.copy(this.takeoverTo);\n this.coords_old.copy(this.coords);\n this.diff.set(0, 0);\n } else {\n const k = t * t * (3 - 2 * t);\n this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);\n }\n }\n this.diff.subVectors(this.coords, this.coords_old);\n this.coords_old.copy(this.coords);\n if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);\n if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);\n }\n }\n const Mouse = new MouseClass();\n\n class AutoDriver {\n mouse: MouseClass;\n manager: WebGLManager;\n enabled: boolean;\n speed: number;\n resumeDelay: number;\n rampDurationMs: number;\n active = false;\n current = new THREE.Vector2(0, 0);\n target = new THREE.Vector2();\n lastTime = performance.now();\n activationTime = 0;\n margin = 0.2;\n private _tmpDir = new THREE.Vector2();\n constructor(\n mouse: MouseClass,\n manager: WebGLManager,\n opts: { enabled: boolean; speed: number; resumeDelay: number; rampDuration: number }\n ) {\n this.mouse = mouse;\n this.manager = manager;\n this.enabled = opts.enabled;\n this.speed = opts.speed;\n this.resumeDelay = opts.resumeDelay || 3000;\n this.rampDurationMs = (opts.rampDuration || 0) * 1000;\n this.pickNewTarget();\n }\n pickNewTarget() {\n const r = Math.random;\n this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));\n }\n forceStop() {\n this.active = false;\n this.mouse.isAutoActive = false;\n }\n update() {\n if (!this.enabled) return;\n const now = performance.now();\n const idle = now - this.manager.lastUserInteraction;\n if (idle < this.resumeDelay) {\n if (this.active) this.forceStop();\n return;\n }\n if (this.mouse.isHoverInside) {\n if (this.active) this.forceStop();\n return;\n }\n if (!this.active) {\n this.active = true;\n this.current.copy(this.mouse.coords);\n this.lastTime = now;\n this.activationTime = now;\n }\n if (!this.active) return;\n this.mouse.isAutoActive = true;\n let dtSec = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (dtSec > 0.2) dtSec = 0.016;\n const dir = this._tmpDir.subVectors(this.target, this.current);\n const dist = dir.length();\n if (dist < 0.01) {\n this.pickNewTarget();\n return;\n }\n dir.normalize();\n let ramp = 1;\n if (this.rampDurationMs > 0) {\n const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);\n ramp = t * t * (3 - 2 * t);\n }\n const step = this.speed * dtSec * ramp;\n const move = Math.min(step, dist);\n this.current.addScaledVector(dir, move);\n this.mouse.setNormalized(this.current.x, this.current.y);\n }\n }\n\n const face_vert = `\n attribute vec3 position;\n uniform vec2 px;\n uniform vec2 boundarySpace;\n varying vec2 uv;\n precision highp float;\n void main(){\n vec3 pos = position;\n vec2 scale = 1.0 - boundarySpace * 2.0;\n pos.xy = pos.xy * scale;\n uv = vec2(0.5)+(pos.xy)*0.5;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const line_vert = `\n attribute vec3 position;\n uniform vec2 px;\n precision highp float;\n varying vec2 uv;\n void main(){\n vec3 pos = position;\n uv = 0.5 + pos.xy * 0.5;\n vec2 n = sign(pos.xy);\n pos.xy = abs(pos.xy) - px * 1.0;\n pos.xy *= n;\n gl_Position = vec4(pos, 1.0);\n}\n`;\n const mouse_vert = `\n precision highp float;\n attribute vec3 position;\n attribute vec2 uv;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 pos = position.xy * scale * 2.0 * px + center;\n vUv = uv;\n gl_Position = vec4(pos, 0.0, 1.0);\n}\n`;\n const advection_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform bool isBFECC;\n uniform vec2 fboSize;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;\n if(isBFECC == false){\n vec2 vel = texture2D(velocity, uv).xy;\n vec2 uv2 = uv - vel * dt * ratio;\n vec2 newVel = texture2D(velocity, uv2).xy;\n gl_FragColor = vec4(newVel, 0.0, 0.0);\n } else {\n vec2 spot_new = uv;\n vec2 vel_old = texture2D(velocity, uv).xy;\n vec2 spot_old = spot_new - vel_old * dt * ratio;\n vec2 vel_new1 = texture2D(velocity, spot_old).xy;\n vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;\n vec2 error = spot_new2 - spot_new;\n vec2 spot_new3 = spot_new - error / 2.0;\n vec2 vel_2 = texture2D(velocity, spot_new3).xy;\n vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;\n vec2 newVel2 = texture2D(velocity, spot_old2).xy; \n gl_FragColor = vec4(newVel2, 0.0, 0.0);\n }\n}\n`;\n const color_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D palette;\n uniform vec4 bgColor;\n varying vec2 uv;\n void main(){\n vec2 vel = texture2D(velocity, uv).xy;\n float lenv = clamp(length(vel), 0.0, 1.0);\n vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;\n vec3 outRGB = mix(bgColor.rgb, c, lenv);\n float outA = mix(bgColor.a, 1.0, lenv);\n gl_FragColor = vec4(outRGB, outA);\n}\n`;\n const divergence_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform float dt;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;\n float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;\n float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;\n float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;\n float divergence = (x1 - x0 + y1 - y0) / 2.0;\n gl_FragColor = vec4(divergence / dt);\n}\n`;\n const externalForce_frag = `\n precision highp float;\n uniform vec2 force;\n uniform vec2 center;\n uniform vec2 scale;\n uniform vec2 px;\n varying vec2 vUv;\n void main(){\n vec2 circle = (vUv - 0.5) * 2.0;\n float d = 1.0 - min(length(circle), 1.0);\n d *= d;\n gl_FragColor = vec4(force * d, 0.0, 1.0);\n}\n`;\n const poisson_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D divergence;\n uniform vec2 px;\n varying vec2 uv;\n void main(){\n float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;\n float div = texture2D(divergence, uv).r;\n float newP = (p0 + p1 + p2 + p3) / 4.0 - div;\n gl_FragColor = vec4(newP);\n}\n`;\n const pressure_frag = `\n precision highp float;\n uniform sampler2D pressure;\n uniform sampler2D velocity;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n float step = 1.0;\n float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;\n float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;\n float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;\n float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;\n vec2 v = texture2D(velocity, uv).xy;\n vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;\n v = v - gradP * dt;\n gl_FragColor = vec4(v, 0.0, 1.0);\n}\n`;\n const viscous_frag = `\n precision highp float;\n uniform sampler2D velocity;\n uniform sampler2D velocity_new;\n uniform float v;\n uniform vec2 px;\n uniform float dt;\n varying vec2 uv;\n void main(){\n vec2 old = texture2D(velocity, uv).xy;\n vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;\n vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;\n vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;\n vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;\n vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);\n newv /= 4.0 * (1.0 + v * dt);\n gl_FragColor = vec4(newv, 0.0, 0.0);\n}\n`;\n\n type Uniforms = Record;\n\n class ShaderPass {\n props: any;\n uniforms?: Uniforms;\n scene: THREE.Scene | null = null;\n camera: THREE.Camera | null = null;\n material: THREE.RawShaderMaterial | null = null;\n geometry: THREE.BufferGeometry | null = null;\n plane: THREE.Mesh | null = null;\n constructor(props: any) {\n this.props = props || {};\n this.uniforms = this.props.material?.uniforms;\n }\n init(..._args: any[]) {\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n if (this.uniforms) {\n this.material = new THREE.RawShaderMaterial(this.props.material);\n this.geometry = new THREE.PlaneGeometry(2, 2);\n this.plane = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.plane);\n }\n }\n update(..._args: any[]) {\n if (!Common.renderer || !this.scene || !this.camera) return;\n Common.renderer.setRenderTarget(this.props.output || null);\n Common.renderer.render(this.scene, this.camera);\n Common.renderer.setRenderTarget(null);\n }\n }\n\n class Advection extends ShaderPass {\n line!: THREE.LineSegments;\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: advection_frag,\n uniforms: {\n boundarySpace: { value: simProps.cellScale },\n px: { value: simProps.cellScale },\n fboSize: { value: simProps.fboSize },\n velocity: { value: simProps.src.texture },\n dt: { value: simProps.dt },\n isBFECC: { value: true }\n }\n },\n output: simProps.dst\n });\n this.uniforms = this.props.material.uniforms;\n this.init();\n }\n init() {\n super.init();\n this.createBoundary();\n }\n createBoundary() {\n const boundaryG = new THREE.BufferGeometry();\n const vertices_boundary = new Float32Array([\n -1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0\n ]);\n boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));\n const boundaryM = new THREE.RawShaderMaterial({\n vertexShader: line_vert,\n fragmentShader: advection_frag,\n uniforms: this.uniforms!\n });\n this.line = new THREE.LineSegments(boundaryG, boundaryM);\n this.scene!.add(this.line);\n }\n update(...args: any[]) {\n const { dt, isBounce, BFECC } = (args[0] || {}) as { dt?: number; isBounce?: boolean; BFECC?: boolean };\n if (!this.uniforms) return;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n if (typeof isBounce === 'boolean') this.line.visible = isBounce;\n if (typeof BFECC === 'boolean') this.uniforms.isBFECC.value = BFECC;\n super.update();\n }\n }\n\n class ExternalForce extends ShaderPass {\n mouse!: THREE.Mesh;\n constructor(simProps: any) {\n super({ output: simProps.dst });\n this.init(simProps);\n }\n init(simProps: any) {\n super.init();\n const mouseG = new THREE.PlaneGeometry(1, 1);\n const mouseM = new THREE.RawShaderMaterial({\n vertexShader: mouse_vert,\n fragmentShader: externalForce_frag,\n blending: THREE.AdditiveBlending,\n depthWrite: false,\n uniforms: {\n px: { value: simProps.cellScale },\n force: { value: new THREE.Vector2(0, 0) },\n center: { value: new THREE.Vector2(0, 0) },\n scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }\n }\n });\n this.mouse = new THREE.Mesh(mouseG, mouseM);\n this.scene!.add(this.mouse);\n }\n update(...args: any[]) {\n const props = args[0] || {};\n const forceX = (Mouse.diff.x / 2) * (props.mouse_force || 0);\n const forceY = (Mouse.diff.y / 2) * (props.mouse_force || 0);\n const cellScale = props.cellScale || { x: 1, y: 1 };\n const cursorSize = props.cursor_size || 0;\n const cursorSizeX = cursorSize * cellScale.x;\n const cursorSizeY = cursorSize * cellScale.y;\n const centerX = Math.min(\n Math.max(Mouse.coords.x, -1 + cursorSizeX + cellScale.x * 2),\n 1 - cursorSizeX - cellScale.x * 2\n );\n const centerY = Math.min(\n Math.max(Mouse.coords.y, -1 + cursorSizeY + cellScale.y * 2),\n 1 - cursorSizeY - cellScale.y * 2\n );\n const uniforms = (this.mouse.material as THREE.RawShaderMaterial).uniforms;\n uniforms.force.value.set(forceX, forceY);\n uniforms.center.value.set(centerX, centerY);\n uniforms.scale.value.set(cursorSize, cursorSize);\n super.update();\n }\n }\n\n class Viscous extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: viscous_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n velocity_new: { value: simProps.dst_.texture },\n v: { value: simProps.viscous },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { viscous, iterations, dt } = (args[0] || {}) as { viscous?: number; iterations?: number; dt?: number };\n if (!this.uniforms) return;\n let fbo_in: any, fbo_out: any;\n if (typeof viscous === 'number') this.uniforms.v.value = viscous;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n fbo_in = this.props.output0;\n fbo_out = this.props.output1;\n } else {\n fbo_in = this.props.output1;\n fbo_out = this.props.output0;\n }\n this.uniforms.velocity_new.value = fbo_in.texture;\n this.props.output = fbo_out;\n if (typeof dt === 'number') this.uniforms.dt.value = dt;\n super.update();\n }\n return fbo_out;\n }\n }\n\n class Divergence extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: divergence_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n velocity: { value: simProps.src.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel } = (args[0] || {}) as { vel?: any };\n if (this.uniforms && vel) {\n this.uniforms.velocity.value = vel.texture;\n }\n super.update();\n }\n }\n\n class Poisson extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: poisson_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.dst_.texture },\n divergence: { value: simProps.src.texture },\n px: { value: simProps.cellScale }\n }\n },\n output: simProps.dst,\n output0: simProps.dst_,\n output1: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { iterations } = (args[0] || {}) as { iterations?: number };\n let p_in: any, p_out: any;\n const iter = iterations ?? 0;\n for (let i = 0; i < iter; i++) {\n if (i % 2 === 0) {\n p_in = this.props.output0;\n p_out = this.props.output1;\n } else {\n p_in = this.props.output1;\n p_out = this.props.output0;\n }\n if (this.uniforms) this.uniforms.pressure.value = p_in.texture;\n this.props.output = p_out;\n super.update();\n }\n return p_out;\n }\n }\n\n class Pressure extends ShaderPass {\n constructor(simProps: any) {\n super({\n material: {\n vertexShader: face_vert,\n fragmentShader: pressure_frag,\n uniforms: {\n boundarySpace: { value: simProps.boundarySpace },\n pressure: { value: simProps.src_p.texture },\n velocity: { value: simProps.src_v.texture },\n px: { value: simProps.cellScale },\n dt: { value: simProps.dt }\n }\n },\n output: simProps.dst\n });\n this.init();\n }\n update(...args: any[]) {\n const { vel, pressure } = (args[0] || {}) as { vel?: any; pressure?: any };\n if (this.uniforms && vel && pressure) {\n this.uniforms.velocity.value = vel.texture;\n this.uniforms.pressure.value = pressure.texture;\n }\n super.update();\n }\n }\n\n class Simulation {\n options: SimOptions;\n fbos: Record = {\n vel_0: null,\n vel_1: null,\n vel_viscous0: null,\n vel_viscous1: null,\n div: null,\n pressure_0: null,\n pressure_1: null\n };\n fboSize = new THREE.Vector2();\n cellScale = new THREE.Vector2();\n boundarySpace = new THREE.Vector2();\n advection!: Advection;\n externalForce!: ExternalForce;\n viscous!: Viscous;\n divergence!: Divergence;\n poisson!: Poisson;\n pressure!: Pressure;\n constructor(options?: Partial) {\n this.options = {\n iterations_poisson: 32,\n iterations_viscous: 32,\n mouse_force: 20,\n resolution: 0.5,\n cursor_size: 100,\n viscous: 30,\n isBounce: false,\n dt: 0.014,\n isViscous: false,\n BFECC: true,\n ...options\n };\n this.init();\n }\n init() {\n this.calcSize();\n this.createAllFBO();\n this.createShaderPass();\n }\n getFloatType() {\n const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);\n return isIOS ? THREE.HalfFloatType : THREE.FloatType;\n }\n createAllFBO() {\n const type = this.getFloatType();\n const opts = {\n type,\n depthBuffer: false,\n stencilBuffer: false,\n minFilter: THREE.LinearFilter,\n magFilter: THREE.LinearFilter,\n wrapS: THREE.ClampToEdgeWrapping,\n wrapT: THREE.ClampToEdgeWrapping\n } as const;\n for (const key in this.fbos) {\n this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);\n }\n }\n createShaderPass() {\n this.advection = new Advection({\n cellScale: this.cellScale,\n fboSize: this.fboSize,\n dt: this.options.dt,\n src: this.fbos.vel_0,\n dst: this.fbos.vel_1\n });\n this.externalForce = new ExternalForce({\n cellScale: this.cellScale,\n cursor_size: this.options.cursor_size,\n dst: this.fbos.vel_1\n });\n this.viscous = new Viscous({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n viscous: this.options.viscous,\n src: this.fbos.vel_1,\n dst: this.fbos.vel_viscous1,\n dst_: this.fbos.vel_viscous0,\n dt: this.options.dt\n });\n this.divergence = new Divergence({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.vel_viscous0,\n dst: this.fbos.div,\n dt: this.options.dt\n });\n this.poisson = new Poisson({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src: this.fbos.div,\n dst: this.fbos.pressure_1,\n dst_: this.fbos.pressure_0\n });\n this.pressure = new Pressure({\n cellScale: this.cellScale,\n boundarySpace: this.boundarySpace,\n src_p: this.fbos.pressure_0,\n src_v: this.fbos.vel_viscous0,\n dst: this.fbos.vel_0,\n dt: this.options.dt\n });\n }\n calcSize() {\n const width = Math.max(1, Math.round(this.options.resolution * Common.width));\n const height = Math.max(1, Math.round(this.options.resolution * Common.height));\n this.cellScale.set(1 / width, 1 / height);\n this.fboSize.set(width, height);\n }\n resize() {\n this.calcSize();\n for (const key in this.fbos) {\n this.fbos[key]!.setSize(this.fboSize.x, this.fboSize.y);\n }\n }\n update() {\n if (this.options.isBounce) this.boundarySpace.set(0, 0);\n else this.boundarySpace.copy(this.cellScale);\n this.advection.update({ dt: this.options.dt, isBounce: this.options.isBounce, BFECC: this.options.BFECC });\n this.externalForce.update({\n cursor_size: this.options.cursor_size,\n mouse_force: this.options.mouse_force,\n cellScale: this.cellScale\n });\n let vel: any = this.fbos.vel_1;\n if (this.options.isViscous) {\n vel = this.viscous.update({\n viscous: this.options.viscous,\n iterations: this.options.iterations_viscous,\n dt: this.options.dt\n });\n }\n this.divergence.update({ vel });\n const pressure = this.poisson.update({ iterations: this.options.iterations_poisson });\n this.pressure.update({ vel, pressure });\n }\n }\n\n class Output {\n simulation: Simulation;\n scene: THREE.Scene;\n camera: THREE.Camera;\n output: THREE.Mesh;\n constructor() {\n this.simulation = new Simulation();\n this.scene = new THREE.Scene();\n this.camera = new THREE.Camera();\n this.output = new THREE.Mesh(\n new THREE.PlaneGeometry(2, 2),\n new THREE.RawShaderMaterial({\n vertexShader: face_vert,\n fragmentShader: color_frag,\n transparent: true,\n depthWrite: false,\n uniforms: {\n velocity: { value: this.simulation.fbos.vel_0!.texture },\n boundarySpace: { value: new THREE.Vector2() },\n palette: { value: paletteTex },\n bgColor: { value: bgVec4 }\n }\n })\n );\n this.scene.add(this.output);\n }\n resize() {\n this.simulation.resize();\n }\n render() {\n if (!Common.renderer) return;\n Common.renderer.setRenderTarget(null);\n Common.renderer.render(this.scene, this.camera);\n }\n update() {\n this.simulation.update();\n this.render();\n }\n }\n\n class WebGLManager implements LiquidEtherWebGL {\n props: any;\n output!: Output;\n autoDriver?: AutoDriver;\n lastUserInteraction = performance.now();\n running = false;\n private _loop = this.loop.bind(this);\n private _resize = this.resize.bind(this);\n private _onVisibility?: () => void;\n constructor(props: any) {\n this.props = props;\n Common.init(props.$wrapper);\n Mouse.init(props.$wrapper);\n Mouse.autoIntensity = props.autoIntensity;\n Mouse.takeoverDuration = props.takeoverDuration;\n Mouse.onInteract = () => {\n this.lastUserInteraction = performance.now();\n if (this.autoDriver) this.autoDriver.forceStop();\n };\n this.autoDriver = new AutoDriver(Mouse, this as any, {\n enabled: props.autoDemo,\n speed: props.autoSpeed,\n resumeDelay: props.autoResumeDelay,\n rampDuration: props.autoRampDuration\n });\n this.init();\n window.addEventListener('resize', this._resize);\n this._onVisibility = () => {\n const hidden = document.hidden;\n if (hidden) {\n this.pause();\n } else if (isVisibleRef.current) {\n this.start();\n }\n };\n document.addEventListener('visibilitychange', this._onVisibility);\n }\n init() {\n if (!Common.renderer) return;\n this.props.$wrapper.prepend(Common.renderer.domElement);\n this.output = new Output();\n }\n resize() {\n Common.resize();\n this.output.resize();\n }\n render() {\n if (this.autoDriver) this.autoDriver.update();\n Mouse.update();\n Common.update();\n this.output.update();\n }\n loop() {\n if (!this.running) return;\n this.render();\n rafRef.current = requestAnimationFrame(this._loop);\n }\n start() {\n if (this.running) return;\n this.running = true;\n this._loop();\n }\n pause() {\n this.running = false;\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }\n dispose() {\n try {\n window.removeEventListener('resize', this._resize);\n if (this._onVisibility) document.removeEventListener('visibilitychange', this._onVisibility);\n Mouse.dispose();\n if (Common.renderer) {\n const canvas = Common.renderer.domElement;\n if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);\n Common.renderer.dispose();\n Common.renderer.forceContextLoss();\n }\n } catch {\n /* noop */\n }\n }\n }\n\n const container = mountRef.current;\n container.style.position = container.style.position || 'relative';\n container.style.overflow = container.style.overflow || 'hidden';\n\n const webgl = new WebGLManager({\n $wrapper: container,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n });\n webglRef.current = webgl;\n\n const applyOptionsFromProps = () => {\n if (!webglRef.current) return;\n const sim = webglRef.current.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (resolution !== prevRes) sim.resize();\n };\n applyOptionsFromProps();\n webgl.start();\n\n const io = new IntersectionObserver(\n entries => {\n const entry = entries[0];\n const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;\n isVisibleRef.current = isVisible;\n if (!webglRef.current) return;\n if (isVisible && !document.hidden) {\n webglRef.current.start();\n } else {\n webglRef.current.pause();\n }\n },\n { threshold: [0, 0.01, 0.1] }\n );\n io.observe(container);\n intersectionObserverRef.current = io;\n\n const ro = new ResizeObserver(() => {\n if (!webglRef.current) return;\n if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);\n resizeRafRef.current = requestAnimationFrame(() => {\n if (!webglRef.current) return;\n webglRef.current.resize();\n });\n });\n ro.observe(container);\n resizeObserverRef.current = ro;\n\n return () => {\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\n if (resizeObserverRef.current) {\n try {\n resizeObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (intersectionObserverRef.current) {\n try {\n intersectionObserverRef.current.disconnect();\n } catch {\n /* noop */\n }\n }\n if (webglRef.current) {\n webglRef.current.dispose();\n }\n webglRef.current = null;\n };\n }, [\n BFECC,\n cursorSize,\n dt,\n isBounce,\n isViscous,\n iterationsPoisson,\n iterationsViscous,\n mouseForce,\n resolution,\n viscous,\n colors,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n useEffect(() => {\n const webgl = webglRef.current;\n if (!webgl) return;\n const sim = webgl.output?.simulation;\n if (!sim) return;\n const prevRes = sim.options.resolution;\n Object.assign(sim.options, {\n mouse_force: mouseForce,\n cursor_size: cursorSize,\n isViscous,\n viscous,\n iterations_viscous: iterationsViscous,\n iterations_poisson: iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce\n });\n if (webgl.autoDriver) {\n webgl.autoDriver.enabled = autoDemo;\n webgl.autoDriver.speed = autoSpeed;\n webgl.autoDriver.resumeDelay = autoResumeDelay;\n webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;\n if (webgl.autoDriver.mouse) {\n webgl.autoDriver.mouse.autoIntensity = autoIntensity;\n webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;\n }\n }\n if (resolution !== prevRes) sim.resize();\n }, [\n mouseForce,\n cursorSize,\n isViscous,\n viscous,\n iterationsViscous,\n iterationsPoisson,\n dt,\n BFECC,\n resolution,\n isBounce,\n autoDemo,\n autoSpeed,\n autoIntensity,\n takeoverDuration,\n autoResumeDelay,\n autoRampDuration\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/PixelBlast-JS-CSS.json b/public/r/PixelBlast-JS-CSS.json index 5d2e1c83..43edceb1 100644 --- a/public/r/PixelBlast-JS-CSS.json +++ b/public/r/PixelBlast-JS-CSS.json @@ -13,12 +13,12 @@ { "type": "registry:component", "path": "PixelBlast/PixelBlast.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer, EffectPass, RenderPass, Effect } from 'postprocessing';\nimport './PixelBlast.css';\n\nconst createTouchTexture = () => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail = [];\n let last = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = p => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = t => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = t => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = norm => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture, opts) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys = ['antialias', 'liquid', 'noiseAmount'];\n const cfg = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = () => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer;\n let touch;\n let liquidEffect;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) composer.passes.forEach(p => (p.renderToScreen = false));\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = e => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = e => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = e => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) liquidEffect.uniforms.get('uTime').value = uniforms.uTime.value;\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const effs = p.effects;\n if (effs)\n effs.forEach(eff => {\n const u = eff.uniforms?.get('uTime');\n if (u) u.value = uniforms.uTime.value;\n });\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const uStrength = t.liquidEffect;\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = t.liquidEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" + "content": "import { Effect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './PixelBlast.css';\n\nconst createTouchTexture = () => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail = [];\n let last = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = p => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = t => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = t => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = norm => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture, opts) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys = ['antialias', 'liquid', 'noiseAmount'];\n const cfg = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = () => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer;\n let touch;\n let liquidEffect;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) composer.passes.forEach(p => (p.renderToScreen = false));\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = e => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = e => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = e => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) liquidEffect.uniforms.get('uTime').value = uniforms.uTime.value;\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const effs = p.effects;\n if (effs)\n effs.forEach(eff => {\n const u = eff.uniforms?.get('uTime');\n if (u) u.value = uniforms.uTime.value;\n });\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const uStrength = t.liquidEffect;\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = t.liquidEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/PixelBlast-JS-TW.json b/public/r/PixelBlast-JS-TW.json index 4bb120c6..13a40c73 100644 --- a/public/r/PixelBlast-JS-TW.json +++ b/public/r/PixelBlast-JS-TW.json @@ -8,12 +8,12 @@ { "type": "registry:component", "path": "PixelBlast/PixelBlast.jsx", - "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer, EffectPass, RenderPass, Effect } from 'postprocessing';\n\nconst createTouchTexture = () => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail = [];\n let last = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = p => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = t => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = t => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = norm => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture, opts) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys = ['antialias', 'liquid', 'noiseAmount'];\n const cfg = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = () => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer;\n let touch;\n let liquidEffect;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) composer.passes.forEach(p => (p.renderToScreen = false));\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = e => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = e => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = e => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) liquidEffect.uniforms.get('uTime').value = uniforms.uTime.value;\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const effs = p.effects;\n if (effs)\n effs.forEach(eff => {\n const u = eff.uniforms?.get('uTime');\n if (u) u.value = uniforms.uTime.value;\n });\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const uStrength = t.liquidEffect;\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = t.liquidEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" + "content": "import { Effect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst createTouchTexture = () => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail = [];\n let last = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = p => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = t => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = t => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = norm => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture, opts) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys = ['antialias', 'liquid', 'noiseAmount'];\n const cfg = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = () => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer;\n let touch;\n let liquidEffect;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) composer.passes.forEach(p => (p.renderToScreen = false));\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = e => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = e => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = e => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) liquidEffect.uniforms.get('uTime').value = uniforms.uTime.value;\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const effs = p.effects;\n if (effs)\n effs.forEach(eff => {\n const u = eff.uniforms?.get('uTime');\n if (u) u.value = uniforms.uTime.value;\n });\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const uStrength = t.liquidEffect;\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = t.liquidEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/PixelBlast-TS-CSS.json b/public/r/PixelBlast-TS-CSS.json index 229fb713..10dbf88a 100644 --- a/public/r/PixelBlast-TS-CSS.json +++ b/public/r/PixelBlast-TS-CSS.json @@ -13,12 +13,12 @@ { "type": "registry:component", "path": "PixelBlast/PixelBlast.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer, EffectPass, RenderPass, Effect } from 'postprocessing';\nimport './PixelBlast.css';\n\ntype PixelBlastVariant = 'square' | 'circle' | 'triangle' | 'diamond';\n\ninterface TouchPoint {\n x: number;\n y: number;\n vx: number;\n vy: number;\n force: number;\n age: number;\n}\n\ninterface TouchTexture {\n canvas: HTMLCanvasElement;\n texture: THREE.Texture;\n addTouch: (norm: { x: number; y: number }) => void;\n update: () => void;\n radiusScale: number;\n size: number;\n}\n\ninterface ReinitConfig {\n antialias: boolean;\n liquid: boolean;\n noiseAmount: number;\n}\n\ntype PixelBlastProps = {\n variant?: PixelBlastVariant;\n pixelSize?: number;\n color?: string;\n className?: string;\n style?: React.CSSProperties;\n antialias?: boolean;\n patternScale?: number;\n patternDensity?: number;\n liquid?: boolean;\n liquidStrength?: number;\n liquidRadius?: number;\n pixelSizeJitter?: number;\n enableRipples?: boolean;\n rippleIntensityScale?: number;\n rippleThickness?: number;\n rippleSpeed?: number;\n liquidWobbleSpeed?: number;\n autoPauseOffscreen?: boolean;\n speed?: number;\n transparent?: boolean;\n edgeFade?: number;\n noiseAmount?: number;\n};\n\nconst createTouchTexture = (): TouchTexture => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail: TouchPoint[] = [];\n let last: { x: number; y: number } | null = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = (p: TouchPoint) => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = (t: number) => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = (t: number) => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = (norm: { x: number; y: number }) => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v: number) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture: THREE.Texture, opts?: { strength?: number; freq?: number }) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP: Record = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast: React.FC = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef<{\n renderer: THREE.WebGLRenderer;\n scene: THREE.Scene;\n camera: THREE.OrthographicCamera;\n material: THREE.ShaderMaterial;\n clock: THREE.Clock;\n clickIx: number;\n uniforms: {\n uResolution: { value: THREE.Vector2 };\n uTime: { value: number };\n uColor: { value: THREE.Color };\n uClickPos: { value: THREE.Vector2[] };\n uClickTimes: { value: Float32Array };\n uShapeType: { value: number };\n uPixelSize: { value: number };\n uScale: { value: number };\n uDensity: { value: number };\n uPixelJitter: { value: number };\n uEnableRipples: { value: number };\n uRippleSpeed: { value: number };\n uRippleThickness: { value: number };\n uRippleIntensity: { value: number };\n uEdgeFade: { value: number };\n };\n resizeObserver?: ResizeObserver;\n raf?: number;\n quad?: THREE.Mesh;\n timeOffset?: number;\n composer?: EffectComposer;\n touch?: ReturnType;\n liquidEffect?: Effect;\n } | null>(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys: (keyof ReinitConfig)[] = ['antialias', 'liquid', 'noiseAmount'];\n const cfg: ReinitConfig = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = (): number => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer: EffectComposer | undefined;\n let touch: ReturnType | undefined;\n let liquidEffect: Effect | undefined;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) {\n composer.passes.forEach(p => {\n const pass = p as { renderToScreen?: boolean };\n pass.renderToScreen = false;\n });\n }\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = (e: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = (e: PointerEvent) => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = (e: PointerEvent) => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) {\n const liqEffect = liquidEffect as Effect & { uniforms: Map };\n const timeUniform = liqEffect.uniforms.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n }\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const pass = p as { effects?: Array }> };\n if (pass.effects) {\n pass.effects.forEach(eff => {\n const timeUniform = eff.uniforms?.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n });\n }\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current!;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const liqEffect = t.liquidEffect as Effect & { uniforms: Map };\n const uStrength = liqEffect.uniforms.get('uStrength');\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = liqEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" + "content": "import { Effect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport './PixelBlast.css';\n\ntype PixelBlastVariant = 'square' | 'circle' | 'triangle' | 'diamond';\n\ninterface TouchPoint {\n x: number;\n y: number;\n vx: number;\n vy: number;\n force: number;\n age: number;\n}\n\ninterface TouchTexture {\n canvas: HTMLCanvasElement;\n texture: THREE.Texture;\n addTouch: (norm: { x: number; y: number }) => void;\n update: () => void;\n radiusScale: number;\n size: number;\n}\n\ninterface ReinitConfig {\n antialias: boolean;\n liquid: boolean;\n noiseAmount: number;\n}\n\ntype PixelBlastProps = {\n variant?: PixelBlastVariant;\n pixelSize?: number;\n color?: string;\n className?: string;\n style?: React.CSSProperties;\n antialias?: boolean;\n patternScale?: number;\n patternDensity?: number;\n liquid?: boolean;\n liquidStrength?: number;\n liquidRadius?: number;\n pixelSizeJitter?: number;\n enableRipples?: boolean;\n rippleIntensityScale?: number;\n rippleThickness?: number;\n rippleSpeed?: number;\n liquidWobbleSpeed?: number;\n autoPauseOffscreen?: boolean;\n speed?: number;\n transparent?: boolean;\n edgeFade?: number;\n noiseAmount?: number;\n};\n\nconst createTouchTexture = (): TouchTexture => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail: TouchPoint[] = [];\n let last: { x: number; y: number } | null = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = (p: TouchPoint) => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = (t: number) => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = (t: number) => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = (norm: { x: number; y: number }) => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v: number) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture: THREE.Texture, opts?: { strength?: number; freq?: number }) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP: Record = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast: React.FC = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef<{\n renderer: THREE.WebGLRenderer;\n scene: THREE.Scene;\n camera: THREE.OrthographicCamera;\n material: THREE.ShaderMaterial;\n clock: THREE.Clock;\n clickIx: number;\n uniforms: {\n uResolution: { value: THREE.Vector2 };\n uTime: { value: number };\n uColor: { value: THREE.Color };\n uClickPos: { value: THREE.Vector2[] };\n uClickTimes: { value: Float32Array };\n uShapeType: { value: number };\n uPixelSize: { value: number };\n uScale: { value: number };\n uDensity: { value: number };\n uPixelJitter: { value: number };\n uEnableRipples: { value: number };\n uRippleSpeed: { value: number };\n uRippleThickness: { value: number };\n uRippleIntensity: { value: number };\n uEdgeFade: { value: number };\n };\n resizeObserver?: ResizeObserver;\n raf?: number;\n quad?: THREE.Mesh;\n timeOffset?: number;\n composer?: EffectComposer;\n touch?: ReturnType;\n liquidEffect?: Effect;\n } | null>(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys: (keyof ReinitConfig)[] = ['antialias', 'liquid', 'noiseAmount'];\n const cfg: ReinitConfig = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = (): number => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer: EffectComposer | undefined;\n let touch: ReturnType | undefined;\n let liquidEffect: Effect | undefined;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) {\n composer.passes.forEach(p => {\n const pass = p as { renderToScreen?: boolean };\n pass.renderToScreen = false;\n });\n }\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = (e: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = (e: PointerEvent) => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = (e: PointerEvent) => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) {\n const liqEffect = liquidEffect as Effect & { uniforms: Map };\n const timeUniform = liqEffect.uniforms.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n }\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const pass = p as { effects?: Array }> };\n if (pass.effects) {\n pass.effects.forEach(eff => {\n const timeUniform = eff.uniforms?.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n });\n }\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current!;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const liqEffect = t.liquidEffect as Effect & { uniforms: Map };\n const uStrength = liqEffect.uniforms.get('uStrength');\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = liqEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/PixelBlast-TS-TW.json b/public/r/PixelBlast-TS-TW.json index b83ed944..2ac239f5 100644 --- a/public/r/PixelBlast-TS-TW.json +++ b/public/r/PixelBlast-TS-TW.json @@ -8,12 +8,12 @@ { "type": "registry:component", "path": "PixelBlast/PixelBlast.tsx", - "content": "import React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\nimport { EffectComposer, EffectPass, RenderPass, Effect } from 'postprocessing';\n\ntype PixelBlastVariant = 'square' | 'circle' | 'triangle' | 'diamond';\n\ninterface TouchPoint {\n x: number;\n y: number;\n vx: number;\n vy: number;\n force: number;\n age: number;\n}\n\ninterface TouchTexture {\n canvas: HTMLCanvasElement;\n texture: THREE.Texture;\n addTouch: (norm: { x: number; y: number }) => void;\n update: () => void;\n radiusScale: number;\n size: number;\n}\n\ninterface ReinitConfig {\n antialias: boolean;\n liquid: boolean;\n noiseAmount: number;\n}\n\ntype PixelBlastProps = {\n variant?: PixelBlastVariant;\n pixelSize?: number;\n color?: string;\n className?: string;\n style?: React.CSSProperties;\n antialias?: boolean;\n patternScale?: number;\n patternDensity?: number;\n liquid?: boolean;\n liquidStrength?: number;\n liquidRadius?: number;\n pixelSizeJitter?: number;\n enableRipples?: boolean;\n rippleIntensityScale?: number;\n rippleThickness?: number;\n rippleSpeed?: number;\n liquidWobbleSpeed?: number;\n autoPauseOffscreen?: boolean;\n speed?: number;\n transparent?: boolean;\n edgeFade?: number;\n noiseAmount?: number;\n};\n\nconst createTouchTexture = (): TouchTexture => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail: TouchPoint[] = [];\n let last: { x: number; y: number } | null = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = (p: TouchPoint) => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = (t: number) => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = (t: number) => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = (norm: { x: number; y: number }) => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v: number) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture: THREE.Texture, opts?: { strength?: number; freq?: number }) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP: Record = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast: React.FC = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef<{\n renderer: THREE.WebGLRenderer;\n scene: THREE.Scene;\n camera: THREE.OrthographicCamera;\n material: THREE.ShaderMaterial;\n clock: THREE.Clock;\n clickIx: number;\n uniforms: {\n uResolution: { value: THREE.Vector2 };\n uTime: { value: number };\n uColor: { value: THREE.Color };\n uClickPos: { value: THREE.Vector2[] };\n uClickTimes: { value: Float32Array };\n uShapeType: { value: number };\n uPixelSize: { value: number };\n uScale: { value: number };\n uDensity: { value: number };\n uPixelJitter: { value: number };\n uEnableRipples: { value: number };\n uRippleSpeed: { value: number };\n uRippleThickness: { value: number };\n uRippleIntensity: { value: number };\n uEdgeFade: { value: number };\n };\n resizeObserver?: ResizeObserver;\n raf?: number;\n quad?: THREE.Mesh;\n timeOffset?: number;\n composer?: EffectComposer;\n touch?: ReturnType;\n liquidEffect?: Effect;\n } | null>(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys: (keyof ReinitConfig)[] = ['antialias', 'liquid', 'noiseAmount'];\n const cfg: ReinitConfig = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = (): number => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer: EffectComposer | undefined;\n let touch: ReturnType | undefined;\n let liquidEffect: Effect | undefined;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) {\n composer.passes.forEach(p => {\n const pass = p as { renderToScreen?: boolean };\n pass.renderToScreen = false;\n });\n }\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = (e: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = (e: PointerEvent) => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = (e: PointerEvent) => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) {\n const liqEffect = liquidEffect as Effect & { uniforms: Map };\n const timeUniform = liqEffect.uniforms.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n }\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const pass = p as { effects?: Array }> };\n if (pass.effects) {\n pass.effects.forEach(eff => {\n const timeUniform = eff.uniforms?.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n });\n }\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current!;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const liqEffect = t.liquidEffect as Effect & { uniforms: Map };\n const uStrength = liqEffect.uniforms.get('uStrength');\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = liqEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" + "content": "import { Effect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport React, { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\ntype PixelBlastVariant = 'square' | 'circle' | 'triangle' | 'diamond';\n\ninterface TouchPoint {\n x: number;\n y: number;\n vx: number;\n vy: number;\n force: number;\n age: number;\n}\n\ninterface TouchTexture {\n canvas: HTMLCanvasElement;\n texture: THREE.Texture;\n addTouch: (norm: { x: number; y: number }) => void;\n update: () => void;\n radiusScale: number;\n size: number;\n}\n\ninterface ReinitConfig {\n antialias: boolean;\n liquid: boolean;\n noiseAmount: number;\n}\n\ntype PixelBlastProps = {\n variant?: PixelBlastVariant;\n pixelSize?: number;\n color?: string;\n className?: string;\n style?: React.CSSProperties;\n antialias?: boolean;\n patternScale?: number;\n patternDensity?: number;\n liquid?: boolean;\n liquidStrength?: number;\n liquidRadius?: number;\n pixelSizeJitter?: number;\n enableRipples?: boolean;\n rippleIntensityScale?: number;\n rippleThickness?: number;\n rippleSpeed?: number;\n liquidWobbleSpeed?: number;\n autoPauseOffscreen?: boolean;\n speed?: number;\n transparent?: boolean;\n edgeFade?: number;\n noiseAmount?: number;\n};\n\nconst createTouchTexture = (): TouchTexture => {\n const size = 64;\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('2D context not available');\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const texture = new THREE.Texture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.generateMipmaps = false;\n const trail: TouchPoint[] = [];\n let last: { x: number; y: number } | null = null;\n const maxAge = 64;\n let radius = 0.1 * size;\n const speed = 1 / maxAge;\n const clear = () => {\n ctx.fillStyle = 'black';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n };\n const drawPoint = (p: TouchPoint) => {\n const pos = { x: p.x * size, y: (1 - p.y) * size };\n let intensity = 1;\n const easeOutSine = (t: number) => Math.sin((t * Math.PI) / 2);\n const easeOutQuad = (t: number) => -t * (t - 2);\n if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));\n else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;\n intensity *= p.force;\n const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;\n const offset = size * 5;\n ctx.shadowOffsetX = offset;\n ctx.shadowOffsetY = offset;\n ctx.shadowBlur = radius;\n ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;\n ctx.beginPath();\n ctx.fillStyle = 'rgba(255,0,0,1)';\n ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);\n ctx.fill();\n };\n const addTouch = (norm: { x: number; y: number }) => {\n let force = 0;\n let vx = 0;\n let vy = 0;\n if (last) {\n const dx = norm.x - last.x;\n const dy = norm.y - last.y;\n if (dx === 0 && dy === 0) return;\n const dd = dx * dx + dy * dy;\n const d = Math.sqrt(dd);\n vx = dx / (d || 1);\n vy = dy / (d || 1);\n force = Math.min(dd * 10000, 1);\n }\n last = { x: norm.x, y: norm.y };\n trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });\n };\n const update = () => {\n clear();\n for (let i = trail.length - 1; i >= 0; i--) {\n const point = trail[i];\n const f = point.force * speed * (1 - point.age / maxAge);\n point.x += point.vx * f;\n point.y += point.vy * f;\n point.age++;\n if (point.age > maxAge) trail.splice(i, 1);\n }\n for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);\n texture.needsUpdate = true;\n };\n return {\n canvas,\n texture,\n addTouch,\n update,\n set radiusScale(v: number) {\n radius = 0.1 * size * v;\n },\n get radiusScale() {\n return radius / (0.1 * size);\n },\n size\n };\n};\n\nconst createLiquidEffect = (texture: THREE.Texture, opts?: { strength?: number; freq?: number }) => {\n const fragment = `\n uniform sampler2D uTexture;\n uniform float uStrength;\n uniform float uTime;\n uniform float uFreq;\n\n void mainUv(inout vec2 uv) {\n vec4 tex = texture2D(uTexture, uv);\n float vx = tex.r * 2.0 - 1.0;\n float vy = tex.g * 2.0 - 1.0;\n float intensity = tex.b;\n\n float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);\n\n float amt = uStrength * intensity * wave;\n\n uv += vec2(vx, vy) * amt;\n }\n `;\n return new Effect('LiquidEffect', fragment, {\n uniforms: new Map([\n ['uTexture', new THREE.Uniform(texture)],\n ['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],\n ['uTime', new THREE.Uniform(0)],\n ['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]\n ])\n });\n};\n\nconst SHAPE_MAP: Record = {\n square: 0,\n circle: 1,\n triangle: 2,\n diamond: 3\n};\n\nconst VERTEX_SRC = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\nconst FRAGMENT_SRC = `\nprecision highp float;\n\nuniform vec3 uColor;\nuniform vec2 uResolution;\nuniform float uTime;\nuniform float uPixelSize;\nuniform float uScale;\nuniform float uDensity;\nuniform float uPixelJitter;\nuniform int uEnableRipples;\nuniform float uRippleSpeed;\nuniform float uRippleThickness;\nuniform float uRippleIntensity;\nuniform float uEdgeFade;\n\nuniform int uShapeType;\nconst int SHAPE_SQUARE = 0;\nconst int SHAPE_CIRCLE = 1;\nconst int SHAPE_TRIANGLE = 2;\nconst int SHAPE_DIAMOND = 3;\n\nconst int MAX_CLICKS = 10;\n\nuniform vec2 uClickPos [MAX_CLICKS];\nuniform float uClickTimes[MAX_CLICKS];\n\nout vec4 fragColor;\n\nfloat Bayer2(vec2 a) {\n a = floor(a);\n return fract(a.x / 2. + a.y * a.y * .75);\n}\n#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))\n#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))\n\n#define FBM_OCTAVES 5\n#define FBM_LACUNARITY 1.25\n#define FBM_GAIN 1.0\n\nfloat hash11(float n){ return fract(sin(n)*43758.5453); }\n\nfloat vnoise(vec3 p){\n vec3 ip = floor(p);\n vec3 fp = fract(p);\n float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));\n float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));\n float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));\n float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));\n vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);\n float x00 = mix(n000, n100, w.x);\n float x10 = mix(n010, n110, w.x);\n float x01 = mix(n001, n101, w.x);\n float x11 = mix(n011, n111, w.x);\n float y0 = mix(x00, x10, w.y);\n float y1 = mix(x01, x11, w.y);\n return mix(y0, y1, w.z) * 2.0 - 1.0;\n}\n\nfloat fbm2(vec2 uv, float t){\n vec3 p = vec3(uv * uScale, t);\n float amp = 1.0;\n float freq = 1.0;\n float sum = 1.0;\n for (int i = 0; i < FBM_OCTAVES; ++i){\n sum += amp * vnoise(p * freq);\n freq *= FBM_LACUNARITY;\n amp *= FBM_GAIN;\n }\n return sum * 0.5 + 0.5;\n}\n\nfloat maskCircle(vec2 p, float cov){\n float r = sqrt(cov) * .25;\n float d = length(p - 0.5) - r;\n float aa = 0.5 * fwidth(d);\n return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));\n}\n\nfloat maskTriangle(vec2 p, vec2 id, float cov){\n bool flip = mod(id.x + id.y, 2.0) > 0.5;\n if (flip) p.x = 1.0 - p.x;\n float r = sqrt(cov);\n float d = p.y - r*(1.0 - p.x);\n float aa = fwidth(d);\n return cov * clamp(0.5 - d/aa, 0.0, 1.0);\n}\n\nfloat maskDiamond(vec2 p, float cov){\n float r = sqrt(cov) * 0.564;\n return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);\n}\n\nvoid main(){\n float pixelSize = uPixelSize;\n vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;\n float aspectRatio = uResolution.x / uResolution.y;\n\n vec2 pixelId = floor(fragCoord / pixelSize);\n vec2 pixelUV = fract(fragCoord / pixelSize);\n\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cellId = floor(fragCoord / cellPixelSize);\n vec2 cellCoord = cellId * cellPixelSize;\n vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);\n\n float base = fbm2(uv, uTime * 0.05);\n base = base * 0.5 - 0.65;\n\n float feed = base + (uDensity - 0.5) * 0.3;\n\n float speed = uRippleSpeed;\n float thickness = uRippleThickness;\n const float dampT = 1.0;\n const float dampR = 10.0;\n\n if (uEnableRipples == 1) {\n for (int i = 0; i < MAX_CLICKS; ++i){\n vec2 pos = uClickPos[i];\n if (pos.x < 0.0) continue;\n float cellPixelSize = 8.0 * pixelSize;\n vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);\n float t = max(uTime - uClickTimes[i], 0.0);\n float r = distance(uv, cuv);\n float waveR = speed * t;\n float ring = exp(-pow((r - waveR) / thickness, 2.0));\n float atten = exp(-dampT * t) * exp(-dampR * r);\n feed = max(feed, ring * atten * uRippleIntensity);\n }\n }\n\n float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;\n float bw = step(0.5, feed + bayer);\n\n float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);\n float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;\n float coverage = bw * jitterScale;\n float M;\n if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);\n else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);\n else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);\n else M = coverage;\n\n if (uEdgeFade > 0.0) {\n vec2 norm = gl_FragCoord.xy / uResolution;\n float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));\n float fade = smoothstep(0.0, uEdgeFade, edge);\n M *= fade;\n }\n\n vec3 color = uColor;\n\n // sRGB gamma correction - convert linear to sRGB for accurate color output\n vec3 srgbColor = mix(\n color * 12.92,\n 1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,\n step(0.0031308, color)\n );\n\n fragColor = vec4(srgbColor, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst PixelBlast: React.FC = ({\n variant = 'square',\n pixelSize = 3,\n color = '#B19EEF',\n className,\n style,\n antialias = true,\n patternScale = 2,\n patternDensity = 1,\n liquid = false,\n liquidStrength = 0.1,\n liquidRadius = 1,\n pixelSizeJitter = 0,\n enableRipples = true,\n rippleIntensityScale = 1,\n rippleThickness = 0.1,\n rippleSpeed = 0.3,\n liquidWobbleSpeed = 4.5,\n autoPauseOffscreen = true,\n speed = 0.5,\n transparent = true,\n edgeFade = 0.5,\n noiseAmount = 0\n}) => {\n const containerRef = useRef(null);\n const visibilityRef = useRef({ visible: true });\n const speedRef = useRef(speed);\n\n const threeRef = useRef<{\n renderer: THREE.WebGLRenderer;\n scene: THREE.Scene;\n camera: THREE.OrthographicCamera;\n material: THREE.ShaderMaterial;\n clock: THREE.Clock;\n clickIx: number;\n uniforms: {\n uResolution: { value: THREE.Vector2 };\n uTime: { value: number };\n uColor: { value: THREE.Color };\n uClickPos: { value: THREE.Vector2[] };\n uClickTimes: { value: Float32Array };\n uShapeType: { value: number };\n uPixelSize: { value: number };\n uScale: { value: number };\n uDensity: { value: number };\n uPixelJitter: { value: number };\n uEnableRipples: { value: number };\n uRippleSpeed: { value: number };\n uRippleThickness: { value: number };\n uRippleIntensity: { value: number };\n uEdgeFade: { value: number };\n };\n resizeObserver?: ResizeObserver;\n raf?: number;\n quad?: THREE.Mesh;\n timeOffset?: number;\n composer?: EffectComposer;\n touch?: ReturnType;\n liquidEffect?: Effect;\n } | null>(null);\n const prevConfigRef = useRef(null);\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n speedRef.current = speed;\n const needsReinitKeys: (keyof ReinitConfig)[] = ['antialias', 'liquid', 'noiseAmount'];\n const cfg: ReinitConfig = { antialias, liquid, noiseAmount };\n let mustReinit = false;\n if (!threeRef.current) mustReinit = true;\n else if (prevConfigRef.current) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.current[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.current) {\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n }\n const canvas = document.createElement('canvas');\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias,\n alpha: true,\n powerPreference: 'high-performance'\n });\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n container.appendChild(renderer.domElement);\n if (transparent) renderer.setClearAlpha(0);\n else renderer.setClearColor(0x000000, 1);\n const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(color) },\n uClickPos: {\n value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))\n },\n uClickTimes: { value: new Float32Array(MAX_CLICKS) },\n uShapeType: { value: SHAPE_MAP[variant] ?? 0 },\n uPixelSize: { value: pixelSize * renderer.getPixelRatio() },\n uScale: { value: patternScale },\n uDensity: { value: patternDensity },\n uPixelJitter: { value: pixelSizeJitter },\n uEnableRipples: { value: enableRipples ? 1 : 0 },\n uRippleSpeed: { value: rippleSpeed },\n uRippleThickness: { value: rippleThickness },\n uRippleIntensity: { value: rippleIntensityScale },\n uEdgeFade: { value: edgeFade }\n };\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader: VERTEX_SRC,\n fragmentShader: FRAGMENT_SRC,\n uniforms,\n transparent: true,\n depthTest: false,\n depthWrite: false,\n glslVersion: THREE.GLSL3\n });\n const quadGeom = new THREE.PlaneGeometry(2, 2);\n const quad = new THREE.Mesh(quadGeom, material);\n scene.add(quad);\n const clock = new THREE.Clock();\n const setSize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h, false);\n uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);\n if (threeRef.current?.composer)\n threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();\n };\n setSize();\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n const randomFloat = (): number => {\n if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {\n const u32 = new Uint32Array(1);\n window.crypto.getRandomValues(u32);\n return u32[0] / 0xffffffff;\n }\n return Math.random();\n };\n const timeOffset = randomFloat() * 1000;\n let composer: EffectComposer | undefined;\n let touch: ReturnType | undefined;\n let liquidEffect: Effect | undefined;\n if (liquid) {\n touch = createTouchTexture();\n touch.radiusScale = liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: liquidStrength,\n freq: liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (noiseAmount > 0) {\n if (!composer) {\n composer = new EffectComposer(renderer);\n composer.addPass(new RenderPass(scene, camera));\n }\n const noiseEffect = new Effect(\n 'NoiseEffect',\n `uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,\n {\n uniforms: new Map([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(noiseAmount)]\n ])\n }\n );\n const noisePass = new EffectPass(camera, noiseEffect);\n noisePass.renderToScreen = true;\n if (composer && composer.passes.length > 0) {\n composer.passes.forEach(p => {\n const pass = p as { renderToScreen?: boolean };\n pass.renderToScreen = false;\n });\n }\n composer.addPass(noisePass);\n }\n if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);\n const mapToPixels = (e: PointerEvent) => {\n const rect = renderer.domElement.getBoundingClientRect();\n const scaleX = renderer.domElement.width / rect.width;\n const scaleY = renderer.domElement.height / rect.height;\n const fx = (e.clientX - rect.left) * scaleX;\n const fy = (rect.height - (e.clientY - rect.top)) * scaleY;\n return {\n fx,\n fy,\n w: renderer.domElement.width,\n h: renderer.domElement.height\n };\n };\n const onPointerDown = (e: PointerEvent) => {\n const { fx, fy } = mapToPixels(e);\n const ix = threeRef.current?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;\n };\n const onPointerMove = (e: PointerEvent) => {\n if (!touch) return;\n const { fx, fy, w, h } = mapToPixels(e);\n touch.addTouch({ x: fx / w, y: fy / h });\n };\n renderer.domElement.addEventListener('pointerdown', onPointerDown, {\n passive: true\n });\n renderer.domElement.addEventListener('pointermove', onPointerMove, {\n passive: true\n });\n let raf = 0;\n const animate = () => {\n if (autoPauseOffscreen && !visibilityRef.current.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;\n if (liquidEffect) {\n const liqEffect = liquidEffect as Effect & { uniforms: Map };\n const timeUniform = liqEffect.uniforms.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n }\n if (composer) {\n if (touch) touch.update();\n composer.passes.forEach(p => {\n const pass = p as { effects?: Array }> };\n if (pass.effects) {\n pass.effects.forEach(eff => {\n const timeUniform = eff.uniforms?.get('uTime');\n if (timeUniform) timeUniform.value = uniforms.uTime.value;\n });\n }\n });\n composer.render();\n } else renderer.render(scene, camera);\n raf = requestAnimationFrame(animate);\n };\n raf = requestAnimationFrame(animate);\n threeRef.current = {\n renderer,\n scene,\n camera,\n material,\n clock,\n clickIx: 0,\n uniforms,\n resizeObserver: ro,\n raf,\n quad,\n timeOffset,\n composer,\n touch,\n liquidEffect\n };\n } else {\n const t = threeRef.current!;\n t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;\n t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(color);\n t.uniforms.uScale.value = patternScale;\n t.uniforms.uDensity.value = patternDensity;\n t.uniforms.uPixelJitter.value = pixelSizeJitter;\n t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = rippleIntensityScale;\n t.uniforms.uRippleThickness.value = rippleThickness;\n t.uniforms.uRippleSpeed.value = rippleSpeed;\n t.uniforms.uEdgeFade.value = edgeFade;\n if (transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const liqEffect = t.liquidEffect as Effect & { uniforms: Map };\n const uStrength = liqEffect.uniforms.get('uStrength');\n if (uStrength) uStrength.value = liquidStrength;\n const uFreq = liqEffect.uniforms.get('uFreq');\n if (uFreq) uFreq.value = liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = liquidRadius;\n }\n prevConfigRef.current = cfg;\n return () => {\n if (threeRef.current && mustReinit) return;\n if (!threeRef.current) return;\n const t = threeRef.current;\n t.resizeObserver?.disconnect();\n cancelAnimationFrame(t.raf!);\n t.quad?.geometry.dispose();\n t.material.dispose();\n t.composer?.dispose();\n t.renderer.dispose();\n t.renderer.forceContextLoss();\n if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);\n threeRef.current = null;\n };\n }, [\n antialias,\n liquid,\n noiseAmount,\n pixelSize,\n patternScale,\n patternDensity,\n enableRipples,\n rippleIntensityScale,\n rippleThickness,\n rippleSpeed,\n pixelSizeJitter,\n edgeFade,\n transparent,\n liquidStrength,\n liquidRadius,\n liquidWobbleSpeed,\n autoPauseOffscreen,\n variant,\n color,\n speed\n ]);\n\n return (\n \n );\n};\n\nexport default PixelBlast;\n" } ], "registryDependencies": [], "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/PixelSnow-JS-CSS.json b/public/r/PixelSnow-JS-CSS.json index 94082a35..6fc42653 100644 --- a/public/r/PixelSnow-JS-CSS.json +++ b/public/r/PixelSnow-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "PixelSnow/PixelSnow.jsx", - "content": "import { useEffect, useRef, useMemo, useCallback } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n ShaderMaterial,\n Mesh,\n Vector2,\n Vector3,\n Color\n} from 'three';\n\nimport './PixelSnow.css';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return
;\n}\n" + "content": "import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport {\n Color,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nimport './PixelSnow.css';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n renderer.forceContextLoss();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/PixelSnow-JS-TW.json b/public/r/PixelSnow-JS-TW.json index ede39e33..4f982f14 100644 --- a/public/r/PixelSnow-JS-TW.json +++ b/public/r/PixelSnow-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "PixelSnow/PixelSnow.jsx", - "content": "import { useEffect, useRef, useMemo, useCallback } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n ShaderMaterial,\n Mesh,\n Vector2,\n Vector3,\n Color\n} from 'three';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return (\n \n );\n}\n" + "content": "import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport {\n Color,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n renderer.forceContextLoss();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/PixelSnow-TS-CSS.json b/public/r/PixelSnow-TS-CSS.json index 37af2e5a..f1b4b918 100644 --- a/public/r/PixelSnow-TS-CSS.json +++ b/public/r/PixelSnow-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "PixelSnow/PixelSnow.tsx", - "content": "import { useEffect, useRef, useMemo, useCallback } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n ShaderMaterial,\n Mesh,\n Vector2,\n Vector3,\n Color\n} from 'three';\n\nimport './PixelSnow.css';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\ninterface PixelSnowProps {\n color?: string;\n flakeSize?: number;\n minFlakeSize?: number;\n pixelResolution?: number;\n speed?: number;\n depthFade?: number;\n farPlane?: number;\n brightness?: number;\n gamma?: number;\n density?: number;\n variant?: 'square' | 'round' | 'snowflake';\n direction?: number;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}: PixelSnowProps) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return
;\n}\n" + "content": "import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport {\n Color,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nimport './PixelSnow.css';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\ninterface PixelSnowProps {\n color?: string;\n flakeSize?: number;\n minFlakeSize?: number;\n pixelResolution?: number;\n speed?: number;\n depthFade?: number;\n farPlane?: number;\n brightness?: number;\n gamma?: number;\n density?: number;\n variant?: 'square' | 'round' | 'snowflake';\n direction?: number;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}: PixelSnowProps) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n renderer.forceContextLoss();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return
;\n}\n" } ], "registryDependencies": [], diff --git a/public/r/PixelSnow-TS-TW.json b/public/r/PixelSnow-TS-TW.json index 6cb2ff34..c0831fe7 100644 --- a/public/r/PixelSnow-TS-TW.json +++ b/public/r/PixelSnow-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "PixelSnow/PixelSnow.tsx", - "content": "import { useEffect, useRef, useMemo, useCallback } from 'react';\nimport {\n Scene,\n OrthographicCamera,\n WebGLRenderer,\n PlaneGeometry,\n ShaderMaterial,\n Mesh,\n Vector2,\n Vector3,\n Color\n} from 'three';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\ninterface PixelSnowProps {\n color?: string;\n flakeSize?: number;\n minFlakeSize?: number;\n pixelResolution?: number;\n speed?: number;\n depthFade?: number;\n farPlane?: number;\n brightness?: number;\n gamma?: number;\n density?: number;\n variant?: 'square' | 'round' | 'snowflake';\n direction?: number;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}: PixelSnowProps) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return (\n \n );\n}\n" + "content": "import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport {\n Color,\n Mesh,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n ShaderMaterial,\n Vector2,\n Vector3,\n WebGLRenderer\n} from 'three';\n\nconst vertexShader = `\nvoid main() {\n gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n float r = length(p);\n float a = atan(p.y, p.x);\n a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n vec2 q = r * vec2(cos(a), sin(a));\n float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n // Precompute reciprocals to avoid division\n float invPixelRes = 1.0 / uPixelResolution;\n float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n float invPixelSize = 1.0 / pixelSize;\n \n vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n vec2 res = uResolution * invPixelSize;\n float invResX = 1.0 / res.x;\n\n vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n // Precompute time-based values\n float timeSpeed = uTime * uSpeed;\n float windX = cos(uDirection) * 0.4;\n float windY = sin(uDirection) * 0.4;\n vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n vec3 pos = camPos;\n\n // Precompute ray reciprocal for strides\n vec3 absRay = max(abs(ray), vec3(0.001));\n vec3 strides = 1.0 / absRay;\n vec3 raySign = step(ray, vec3(0.0));\n vec3 phase = fract(pos) * strides;\n phase = mix(strides - phase, phase, raySign);\n\n // Precompute for intersection test\n float rayDotCamK = dot(ray, camK);\n float invRayDotCamK = 1.0 / rayDotCamK;\n float invDepthFade = 1.0 / uDepthFade;\n float halfInvResX = 0.5 * invResX;\n vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n float t = 0.0;\n for (int i = 0; i < 128; i++) {\n if (t >= uFarPlane) break;\n \n vec3 fpos = floor(pos);\n uint cellCoord = coord3(fpos);\n float cellHash = hash3(cellCoord).x;\n\n if (cellHash < uDensity) {\n vec3 h = hash3(cellCoord);\n \n // Optimized flake position calculation\n vec3 sinArg1 = fpos.yzx * 0.073;\n vec3 sinArg2 = fpos.zxy * 0.27;\n vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n \n if (toIntersection > 0.0) {\n vec3 testPos = pos + ray * toIntersection - flakePos;\n float testX = dot(testPos, camI);\n float testY = dot(testPos, camJ);\n vec2 testUV = abs(vec2(testX, testY));\n \n float depth = dot(flakePos - camPos, camK);\n float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n \n // Avoid branching with step functions where possible\n float dist;\n if (uVariant < 0.5) {\n dist = max(testUV.x, testUV.y);\n } else if (uVariant < 1.5) {\n dist = length(testUV);\n } else {\n float invFlakeSize = 1.0 / flakeSize;\n dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n }\n\n if (dist < flakeSize) {\n float flakeSizeRatio = uFlakeSize / flakeSize;\n float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n return;\n }\n }\n }\n\n float nextStep = min(min(phase.x, phase.y), phase.z);\n vec3 sel = step(phase, vec3(nextStep));\n phase = phase - nextStep + strides * sel;\n t += nextStep;\n pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n }\n\n gl_FragColor = vec4(0.0);\n}\n`;\n\ninterface PixelSnowProps {\n color?: string;\n flakeSize?: number;\n minFlakeSize?: number;\n pixelResolution?: number;\n speed?: number;\n depthFade?: number;\n farPlane?: number;\n brightness?: number;\n gamma?: number;\n density?: number;\n variant?: 'square' | 'round' | 'snowflake';\n direction?: number;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport default function PixelSnow({\n color = '#ffffff',\n flakeSize = 0.01,\n minFlakeSize = 1.25,\n pixelResolution = 200,\n speed = 1.25,\n depthFade = 8,\n farPlane = 20,\n brightness = 1,\n gamma = 0.4545,\n density = 0.3,\n variant = 'square',\n direction = 125,\n className = '',\n style = {}\n}: PixelSnowProps) {\n const containerRef = useRef(null);\n const animationRef = useRef(0);\n const isVisibleRef = useRef(true);\n const rendererRef = useRef(null);\n const materialRef = useRef(null);\n const resizeTimeoutRef = useRef(null);\n\n // Memoize shader variant value\n const variantValue = useMemo(() => {\n return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n }, [variant]);\n\n // Memoize color conversion\n const colorVector = useMemo(() => {\n const threeColor = new Color(color);\n return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n }, [color]);\n\n // Debounced resize handler\n const handleResize = useCallback(() => {\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n resizeTimeoutRef.current = window.setTimeout(() => {\n const container = containerRef.current;\n const renderer = rendererRef.current;\n const material = materialRef.current;\n if (!container || !renderer || !material) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n renderer.setSize(w, h);\n material.uniforms.uResolution.value.set(w, h);\n }, 100);\n }, []);\n\n // Visibility observer\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n isVisibleRef.current = entry.isIntersecting;\n },\n { threshold: 0 }\n );\n\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Main Three.js setup - only runs once\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n const renderer = new WebGLRenderer({\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n powerPreference: 'high-performance',\n stencil: false,\n depth: false\n });\n\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n renderer.setClearColor(0x000000, 0);\n container.appendChild(renderer.domElement);\n rendererRef.current = renderer;\n\n const material = new ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n uTime: { value: 0 },\n uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n uFlakeSize: { value: flakeSize },\n uMinFlakeSize: { value: minFlakeSize },\n uPixelResolution: { value: pixelResolution },\n uSpeed: { value: speed },\n uDepthFade: { value: depthFade },\n uFarPlane: { value: farPlane },\n uColor: { value: colorVector.clone() },\n uBrightness: { value: brightness },\n uGamma: { value: gamma },\n uDensity: { value: density },\n uVariant: { value: variantValue },\n uDirection: { value: (direction * Math.PI) / 180 }\n },\n transparent: true\n });\n materialRef.current = material;\n\n const geometry = new PlaneGeometry(2, 2);\n scene.add(new Mesh(geometry, material));\n\n window.addEventListener('resize', handleResize);\n\n const startTime = performance.now();\n const animate = () => {\n animationRef.current = requestAnimationFrame(animate);\n\n // Only render if visible\n if (isVisibleRef.current) {\n material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n renderer.render(scene, camera);\n }\n };\n animate();\n\n return () => {\n cancelAnimationFrame(animationRef.current);\n window.removeEventListener('resize', handleResize);\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n if (container.contains(renderer.domElement)) {\n container.removeChild(renderer.domElement);\n }\n renderer.dispose();\n renderer.forceContextLoss();\n geometry.dispose();\n material.dispose();\n rendererRef.current = null;\n materialRef.current = null;\n };\n }, [handleResize]); // Only recreate scene when handleResize changes\n\n // Update material uniforms when props change\n useEffect(() => {\n const material = materialRef.current;\n if (!material) return;\n\n material.uniforms.uFlakeSize.value = flakeSize;\n material.uniforms.uMinFlakeSize.value = minFlakeSize;\n material.uniforms.uPixelResolution.value = pixelResolution;\n material.uniforms.uSpeed.value = speed;\n material.uniforms.uDepthFade.value = depthFade;\n material.uniforms.uFarPlane.value = farPlane;\n material.uniforms.uBrightness.value = brightness;\n material.uniforms.uGamma.value = gamma;\n material.uniforms.uDensity.value = density;\n material.uniforms.uVariant.value = variantValue;\n material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n material.uniforms.uColor.value.copy(colorVector);\n }, [\n flakeSize,\n minFlakeSize,\n pixelResolution,\n speed,\n depthFade,\n farPlane,\n brightness,\n gamma,\n density,\n variantValue,\n direction,\n colorVector\n ]);\n\n return (\n \n );\n}\n" } ], "registryDependencies": [], diff --git a/public/r/PixelTrail-TS-TW.json b/public/r/PixelTrail-TS-TW.json index ff0aaf34..4048c0f0 100644 --- a/public/r/PixelTrail-TS-TW.json +++ b/public/r/PixelTrail-TS-TW.json @@ -8,13 +8,13 @@ { "type": "registry:component", "path": "PixelTrail/PixelTrail.tsx", - "content": "/* eslint-disable react/no-unknown-property */\nimport React, { useMemo } from 'react';\nimport { Canvas, useThree, CanvasProps, ThreeEvent } from '@react-three/fiber';\nimport { shaderMaterial, useTrailTexture } from '@react-three/drei';\nimport * as THREE from 'three';\n\ninterface GooeyFilterProps {\n id?: string;\n strength?: number;\n}\n\ninterface DotMaterialUniforms {\n resolution: THREE.Vector2;\n mouseTrail: THREE.Texture | null;\n gridSize: number;\n pixelColor: THREE.Color;\n}\n\ninterface SceneProps {\n gridSize: number;\n trailSize: number;\n maxAge: number;\n interpolate: number;\n easingFunction: (x: number) => number;\n pixelColor: string;\n}\n\ninterface PixelTrailProps {\n gridSize?: number;\n trailSize?: number;\n maxAge?: number;\n interpolate?: number;\n easingFunction?: (x: number) => number;\n canvasProps?: Partial;\n glProps?: WebGLContextAttributes & { powerPreference?: string };\n gooeyFilter?: { id: string; strength: number };\n color?: string;\n className?: string;\n}\n\nconst GooeyFilter: React.FC = ({ id = 'goo-filter', strength = 10 }) => {\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nconst DotMaterial = shaderMaterial(\n {\n resolution: new THREE.Vector2(),\n mouseTrail: null,\n gridSize: 100,\n pixelColor: new THREE.Color('#ffffff')\n },\n /* glsl vertex shader */ `\n varying vec2 vUv;\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n `,\n /* glsl fragment shader */ `\n uniform vec2 resolution;\n uniform sampler2D mouseTrail;\n uniform float gridSize;\n uniform vec3 pixelColor;\n\n vec2 coverUv(vec2 uv) {\n vec2 s = resolution.xy / max(resolution.x, resolution.y);\n vec2 newUv = (uv - 0.5) * s + 0.5;\n return clamp(newUv, 0.0, 1.0);\n }\n\n float sdfCircle(vec2 p, float r) {\n return length(p - 0.5) - r;\n }\n\n void main() {\n vec2 screenUv = gl_FragCoord.xy / resolution;\n vec2 uv = coverUv(screenUv);\n\n vec2 gridUv = fract(uv * gridSize);\n vec2 gridUvCenter = (floor(uv * gridSize) + 0.5) / gridSize;\n\n float trail = texture2D(mouseTrail, gridUvCenter).r;\n\n gl_FragColor = vec4(pixelColor, trail);\n }\n `\n);\n\nfunction Scene({ gridSize, trailSize, maxAge, interpolate, easingFunction, pixelColor }: SceneProps) {\n const size = useThree(s => s.size);\n const viewport = useThree(s => s.viewport);\n\n const dotMaterial = useMemo(() => new DotMaterial(), []);\n dotMaterial.uniforms.pixelColor.value = new THREE.Color(pixelColor);\n\n const [trail, onMove] = useTrailTexture({\n size: 512,\n radius: trailSize,\n maxAge: maxAge,\n interpolate: interpolate || 0.1,\n ease: easingFunction || ((x: number) => x)\n }) as [THREE.Texture | null, (e: ThreeEvent) => void];\n\n if (trail) {\n trail.minFilter = THREE.NearestFilter;\n trail.magFilter = THREE.NearestFilter;\n trail.wrapS = THREE.ClampToEdgeWrapping;\n trail.wrapT = THREE.ClampToEdgeWrapping;\n }\n\n const scale = Math.max(viewport.width, viewport.height) / 2;\n\n return (\n \n \n \n \n );\n}\n\nexport default function PixelTrail({\n gridSize = 40,\n trailSize = 0.1,\n maxAge = 250,\n interpolate = 5,\n easingFunction = (x: number) => x,\n canvasProps = {},\n glProps = {\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n },\n gooeyFilter,\n color = '#ffffff',\n className = ''\n}: PixelTrailProps) {\n return (\n <>\n {gooeyFilter && }\n \n \n \n \n );\n}\n" + "content": "import { shaderMaterial, useTrailTexture } from '@react-three/drei';\nimport { Canvas, CanvasProps, ThreeEvent, useThree } from '@react-three/fiber';\nimport React, { useEffect, useMemo } from 'react';\nimport * as THREE from 'three';\n\ninterface GooeyFilterProps {\n id?: string;\n strength?: number;\n}\n\ninterface DotMaterialUniforms {\n resolution: THREE.Vector2;\n mouseTrail: THREE.Texture | null;\n gridSize: number;\n pixelColor: THREE.Color;\n}\n\ninterface SceneProps {\n gridSize: number;\n trailSize: number;\n maxAge: number;\n interpolate: number;\n easingFunction: (x: number) => number;\n pixelColor: string;\n}\n\ninterface PixelTrailProps {\n gridSize?: number;\n trailSize?: number;\n maxAge?: number;\n interpolate?: number;\n easingFunction?: (x: number) => number;\n canvasProps?: Partial;\n glProps?: WebGLContextAttributes & { powerPreference?: string };\n gooeyFilter?: { id: string; strength: number };\n color?: string;\n className?: string;\n}\n\nconst GooeyFilter: React.FC = ({ id = 'goo-filter', strength = 10 }) => {\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nconst DotMaterial = shaderMaterial(\n {\n resolution: new THREE.Vector2(),\n mouseTrail: null,\n gridSize: 100,\n pixelColor: new THREE.Color('#ffffff')\n },\n /* glsl vertex shader */ `\n varying vec2 vUv;\n void main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n }\n `,\n /* glsl fragment shader */ `\n uniform vec2 resolution;\n uniform sampler2D mouseTrail;\n uniform float gridSize;\n uniform vec3 pixelColor;\n\n vec2 coverUv(vec2 uv) {\n vec2 s = resolution.xy / max(resolution.x, resolution.y);\n vec2 newUv = (uv - 0.5) * s + 0.5;\n return clamp(newUv, 0.0, 1.0);\n }\n\n float sdfCircle(vec2 p, float r) {\n return length(p - 0.5) - r;\n }\n\n void main() {\n vec2 screenUv = gl_FragCoord.xy / resolution;\n vec2 uv = coverUv(screenUv);\n\n vec2 gridUv = fract(uv * gridSize);\n vec2 gridUvCenter = (floor(uv * gridSize) + 0.5) / gridSize;\n\n float trail = texture2D(mouseTrail, gridUvCenter).r;\n\n gl_FragColor = vec4(pixelColor, trail);\n }\n `\n);\n\nconst identityEase = (x: number) => x;\n\nfunction Scene({ gridSize, trailSize, maxAge, interpolate, easingFunction, pixelColor }: SceneProps) {\n const size = useThree(s => s.size);\n const viewport = useThree(s => s.viewport);\n\n const dotMaterial = useMemo(() => new DotMaterial(), []);\n useEffect(() => {\n return () => {\n dotMaterial.dispose();\n };\n }, [dotMaterial]);\n\n useEffect(() => {\n (dotMaterial.uniforms.pixelColor.value as THREE.Color).set(pixelColor);\n }, [dotMaterial, pixelColor]);\n\n const [trail, onMove] = useTrailTexture({\n size: 512,\n radius: trailSize,\n maxAge: maxAge,\n interpolate: interpolate || 0.1,\n ease: easingFunction || identityEase\n }) as [THREE.Texture | null, (e: ThreeEvent) => void];\n\n useEffect(() => {\n if (!trail) return;\n trail.minFilter = THREE.NearestFilter;\n trail.magFilter = THREE.NearestFilter;\n trail.wrapS = THREE.ClampToEdgeWrapping;\n trail.wrapT = THREE.ClampToEdgeWrapping;\n }, [trail]);\n\n const scale = Math.max(viewport.width, viewport.height) / 2;\n\n return (\n \n \n \n \n );\n}\n\nexport default function PixelTrail({\n gridSize = 40,\n trailSize = 0.1,\n maxAge = 250,\n interpolate = 5,\n easingFunction = identityEase,\n canvasProps = {},\n glProps = {\n antialias: false,\n powerPreference: 'high-performance',\n alpha: true\n },\n gooeyFilter,\n color = '#ffffff',\n className = ''\n}: PixelTrailProps) {\n return (\n <>\n {gooeyFilter && }\n \n \n \n \n );\n}\n" } ], "registryDependencies": [], "dependencies": [ - "@react-three/fiber@^9.3.0", "@react-three/drei@^10.7.4", + "@react-three/fiber@^9.3.0", "three@^0.167.1" ] } \ No newline at end of file diff --git a/public/r/ShapeBlur-JS-CSS.json b/public/r/ShapeBlur-JS-CSS.json index 776f1aff..9c959c18 100644 --- a/public/r/ShapeBlur-JS-CSS.json +++ b/public/r/ShapeBlur-JS-CSS.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ShapeBlur/ShapeBlur.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\nconst ShapeBlur = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef();\n\n useEffect(() => {\n const mount = mountRef.current;\n let animationFrameId;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = e => {\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n const container = mountRef.current;\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n if (mountRef.current) ro.observe(mountRef.current);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n\n ['x', 'y'].forEach(k => {\n vMouseDamp[k] = THREE.MathUtils.damp(vMouseDamp[k], vMouse[k], 8, dt);\n });\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n if (ro) ro.disconnect();\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n };\n }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\nconst ShapeBlur = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef();\n\n useEffect(() => {\n const mount = mountRef.current;\n let animationFrameId;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = e => {\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n const container = mountRef.current;\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n if (mountRef.current) ro.observe(mountRef.current);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n\n ['x', 'y'].forEach(k => {\n vMouseDamp[k] = THREE.MathUtils.damp(vMouseDamp[k], vMouse[k], 8, dt);\n });\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n if (ro) ro.disconnect();\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n renderer.forceContextLoss();\n };\n }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" } ], "registryDependencies": [], diff --git a/public/r/ShapeBlur-JS-TW.json b/public/r/ShapeBlur-JS-TW.json index 709ca76f..675c6f7f 100644 --- a/public/r/ShapeBlur-JS-TW.json +++ b/public/r/ShapeBlur-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ShapeBlur/ShapeBlur.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\nconst ShapeBlur = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef();\n\n useEffect(() => {\n const mount = mountRef.current;\n let animationFrameId;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = e => {\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n const container = mountRef.current;\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n if (mountRef.current) ro.observe(mountRef.current);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n\n ['x', 'y'].forEach(k => {\n vMouseDamp[k] = THREE.MathUtils.damp(vMouseDamp[k], vMouse[k], 8, dt);\n });\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n if (ro) ro.disconnect();\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n };\n }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\nconst ShapeBlur = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef();\n\n useEffect(() => {\n const mount = mountRef.current;\n let animationFrameId;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = e => {\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n const container = mountRef.current;\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n if (mountRef.current) ro.observe(mountRef.current);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n\n ['x', 'y'].forEach(k => {\n vMouseDamp[k] = THREE.MathUtils.damp(vMouseDamp[k], vMouse[k], 8, dt);\n });\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n if (ro) ro.disconnect();\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n renderer.forceContextLoss();\n };\n }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" } ], "registryDependencies": [], diff --git a/public/r/ShapeBlur-TS-CSS.json b/public/r/ShapeBlur-TS-CSS.json index ecfe5ba8..d2748c06 100644 --- a/public/r/ShapeBlur-TS-CSS.json +++ b/public/r/ShapeBlur-TS-CSS.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ShapeBlur/ShapeBlur.tsx", - "content": "import { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\nconst ShapeBlur = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef(null);\n\n useEffect(() => {\n const mount = mountRef.current;\n let animationFrameId: number;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n if (!mount) return;\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = (e: PointerEvent | MouseEvent) => {\n if (!mount) return;\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n const container = mountRef.current;\n if (!container) return;\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n if (mountRef.current) ro.observe(mountRef.current);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n vMouseDamp.x = THREE.MathUtils.damp(vMouseDamp.x, vMouse.x, 8, dt);\n vMouseDamp.y = THREE.MathUtils.damp(vMouseDamp.y, vMouse.y, 8, dt);\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n if (ro) ro.disconnect();\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n };\n }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" + "content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\nconst ShapeBlur = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef(null);\n\n useEffect(() => {\n const mount = mountRef.current;\n let animationFrameId: number;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n if (!mount) return;\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = (e: PointerEvent | MouseEvent) => {\n if (!mount) return;\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n const container = mountRef.current;\n if (!container) return;\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n if (mountRef.current) ro.observe(mountRef.current);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n vMouseDamp.x = THREE.MathUtils.damp(vMouseDamp.x, vMouse.x, 8, dt);\n vMouseDamp.y = THREE.MathUtils.damp(vMouseDamp.y, vMouse.y, 8, dt);\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n if (ro) ro.disconnect();\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n renderer.forceContextLoss();\n };\n }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" } ], "registryDependencies": [], diff --git a/public/r/ShapeBlur-TS-TW.json b/public/r/ShapeBlur-TS-TW.json index 0095afea..c9696a91 100644 --- a/public/r/ShapeBlur-TS-TW.json +++ b/public/r/ShapeBlur-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "ShapeBlur/ShapeBlur.tsx", - "content": "import React, { useRef, useEffect, FC } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\ninterface ShapeBlurProps {\n className?: string;\n variation?: number;\n pixelRatioProp?: number;\n shapeSize?: number;\n roundness?: number;\n borderSize?: number;\n circleSize?: number;\n circleEdge?: number;\n}\n\nconst ShapeBlur: FC = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef(null);\n\n useEffect(() => {\n const mount = mountRef.current;\n if (!mount) return;\n\n let animationFrameId: number;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = (e: PointerEvent | MouseEvent) => {\n if (!mount) return;\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n if (!mountRef.current) return;\n const container = mountRef.current;\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n ro.observe(mountRef.current as Element);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n\n vMouseDamp.x = THREE.MathUtils.damp(vMouseDamp.x, vMouse.x, 8, dt);\n vMouseDamp.y = THREE.MathUtils.damp(vMouseDamp.y, vMouse.y, 8, dt);\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n ro.disconnect();\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n };\n }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" + "content": "import { FC, useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\ninterface ShapeBlurProps {\n className?: string;\n variation?: number;\n pixelRatioProp?: number;\n shapeSize?: number;\n roundness?: number;\n borderSize?: number;\n circleSize?: number;\n circleEdge?: number;\n}\n\nconst ShapeBlur: FC = ({\n className = '',\n variation = 0,\n pixelRatioProp = 2,\n shapeSize = 1.2,\n roundness = 0.4,\n borderSize = 0.05,\n circleSize = 0.3,\n circleEdge = 0.5\n}) => {\n const mountRef = useRef(null);\n const materialRef = useRef(null);\n\n useEffect(() => {\n const mount = mountRef.current;\n if (!mount) return;\n\n let animationFrameId: number;\n let time = 0,\n lastTime = 0;\n\n const vMouse = new THREE.Vector2();\n const vMouseDamp = new THREE.Vector2();\n const vResolution = new THREE.Vector2();\n\n let w = 1,\n h = 1;\n\n const scene = new THREE.Scene();\n const camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n const renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n const material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: pixelRatioProp },\n u_shapeSize: { value: shapeSize },\n u_roundness: { value: roundness },\n u_borderSize: { value: borderSize },\n u_circleSize: { value: circleSize },\n u_circleEdge: { value: circleEdge }\n },\n defines: { VAR: variation },\n transparent: true\n });\n materialRef.current = material;\n\n const quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n const onPointerMove = (e: PointerEvent | MouseEvent) => {\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n };\n\n mount.addEventListener('mousemove', onPointerMove);\n mount.addEventListener('pointermove', onPointerMove);\n\n const resize = () => {\n w = mount.clientWidth;\n h = mount.clientHeight;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n renderer.setSize(w, h);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n };\n\n resize();\n window.addEventListener('resize', resize);\n\n const ro = new ResizeObserver(() => resize());\n ro.observe(mount);\n\n const update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n\n vMouseDamp.x = THREE.MathUtils.damp(vMouseDamp.x, vMouse.x, 8, dt);\n vMouseDamp.y = THREE.MathUtils.damp(vMouseDamp.y, vMouse.y, 8, dt);\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n };\n update();\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n ro.disconnect();\n mount.removeEventListener('mousemove', onPointerMove);\n mount.removeEventListener('pointermove', onPointerMove);\n if (mount.contains(renderer.domElement)) mount.removeChild(renderer.domElement);\n\n scene.clear();\n geo.dispose();\n material.dispose();\n renderer.dispose();\n materialRef.current = null;\n renderer.forceContextLoss();\n };\n }, [variation]);\n\n useEffect(() => {\n if (materialRef.current) materialRef.current.uniforms.u_shapeSize.value = shapeSize;\n }, [shapeSize]);\n\n useEffect(() => {\n if (materialRef.current) materialRef.current.uniforms.u_roundness.value = roundness;\n }, [roundness]);\n\n useEffect(() => {\n if (materialRef.current) materialRef.current.uniforms.u_borderSize.value = borderSize;\n }, [borderSize]);\n\n useEffect(() => {\n if (materialRef.current) materialRef.current.uniforms.u_circleSize.value = circleSize;\n }, [circleSize]);\n\n useEffect(() => {\n if (materialRef.current) materialRef.current.uniforms.u_circleEdge.value = circleEdge;\n }, [circleEdge]);\n\n useEffect(() => {\n if (materialRef.current) materialRef.current.uniforms.u_pixelRatio.value = pixelRatioProp;\n }, [pixelRatioProp]);\n\n return
;\n};\n\nexport default ShapeBlur;\n" } ], "registryDependencies": [], diff --git a/public/r/registry.json b/public/r/registry.json index b6390e15..1985cfd6 100644 --- a/public/r/registry.json +++ b/public/r/registry.json @@ -1191,8 +1191,8 @@ "description": "Pixelated cursor trail emitting fading squares with retro digital feel.", "type": "registry:component", "dependencies": [ - "@react-three/fiber@^9.3.0", "@react-three/drei@^10.7.4", + "@react-three/fiber@^9.3.0", "three@^0.167.1" ], "registryDependencies": [], @@ -6129,8 +6129,8 @@ "description": "Physics ball pit simulation with bouncing colorful spheres.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "gsap@^3.13.0" + "gsap@^3.13.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -6146,8 +6146,8 @@ "description": "Physics ball pit simulation with bouncing colorful spheres.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "gsap@^3.13.0" + "gsap@^3.13.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -6831,9 +6831,9 @@ "description": "Animated grid room 3D scan effect and cool interactions.", "type": "registry:component", "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -6853,9 +6853,9 @@ "description": "Animated grid room 3D scan effect and cool interactions.", "type": "registry:component", "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -6871,9 +6871,9 @@ "description": "Animated grid room 3D scan effect and cool interactions.", "type": "registry:component", "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -6893,9 +6893,9 @@ "description": "Animated grid room 3D scan effect and cool interactions.", "type": "registry:component", "dependencies": [ + "face-api.js@^0.22.2", "postprocessing@^6.36.0", - "three@^0.167.1", - "face-api.js@^0.22.2" + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7055,8 +7055,8 @@ "description": "Animated lines continuously moving to simulate hyperspace travel on click hold.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7080,8 +7080,8 @@ "description": "Animated lines continuously moving to simulate hyperspace travel on click hold.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7101,8 +7101,8 @@ "description": "Animated lines continuously moving to simulate hyperspace travel on click hold.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7126,8 +7126,8 @@ "description": "Animated lines continuously moving to simulate hyperspace travel on click hold.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7627,8 +7627,8 @@ "description": "Exploding pixel particle bursts with optional liquid postprocessing.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7648,8 +7648,8 @@ "description": "Exploding pixel particle bursts with optional liquid postprocessing.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7665,8 +7665,8 @@ "description": "Exploding pixel particle bursts with optional liquid postprocessing.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ @@ -7686,8 +7686,8 @@ "description": "Exploding pixel particle bursts with optional liquid postprocessing.", "type": "registry:component", "dependencies": [ - "three@^0.167.1", - "postprocessing@^6.36.0" + "postprocessing@^6.36.0", + "three@^0.167.1" ], "registryDependencies": [], "files": [ diff --git a/src/content/Animations/GhostCursor/GhostCursor.jsx b/src/content/Animations/GhostCursor/GhostCursor.jsx index c6d801a8..1df65fa3 100644 --- a/src/content/Animations/GhostCursor/GhostCursor.jsx +++ b/src/content/Animations/GhostCursor/GhostCursor.jsx @@ -2,8 +2,8 @@ import { useEffect, useMemo, useRef } from 'react'; import * as THREE from 'three'; import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; -import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'; import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; +import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'; import './GhostCursor.css'; const GhostCursor = ({ @@ -427,6 +427,7 @@ const GhostCursor = ({ material.dispose(); composer.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (renderer.domElement && renderer.domElement.parentElement) { renderer.domElement.parentElement.removeChild(renderer.domElement); diff --git a/src/content/Animations/LaserFlow/LaserFlow.jsx b/src/content/Animations/LaserFlow/LaserFlow.jsx index e9ca895b..40a1c768 100644 --- a/src/content/Animations/LaserFlow/LaserFlow.jsx +++ b/src/content/Animations/LaserFlow/LaserFlow.jsx @@ -531,6 +531,7 @@ export const LaserFlow = ({ geometry.dispose(); material.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (mount.contains(canvas)) mount.removeChild(canvas); }; // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/content/Animations/ShapeBlur/ShapeBlur.jsx b/src/content/Animations/ShapeBlur/ShapeBlur.jsx index 1b1662e2..b6697c10 100644 --- a/src/content/Animations/ShapeBlur/ShapeBlur.jsx +++ b/src/content/Animations/ShapeBlur/ShapeBlur.jsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import * as THREE from 'three'; const vertexShader = /* glsl */ ` @@ -235,6 +235,7 @@ const ShapeBlur = ({ document.removeEventListener('pointermove', onPointerMove); mount.removeChild(renderer.domElement); renderer.dispose(); + renderer.forceContextLoss(); }; }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]); diff --git a/src/content/Backgrounds/Ballpit/Ballpit.jsx b/src/content/Backgrounds/Ballpit/Ballpit.jsx index 6ee02546..a7d844d0 100644 --- a/src/content/Backgrounds/Ballpit/Ballpit.jsx +++ b/src/content/Backgrounds/Ballpit/Ballpit.jsx @@ -1,25 +1,25 @@ -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { - Clock as e, - PerspectiveCamera as t, - Scene as i, - WebGLRenderer as s, - SRGBColorSpace as n, - MathUtils as o, - Vector2 as r, Vector3 as a, MeshPhysicalMaterial as c, + InstancedMesh as d, + Clock as e, + AmbientLight as f, + SphereGeometry as g, ShaderChunk as h, + Scene as i, Color as l, Object3D as m, - InstancedMesh as d, + SRGBColorSpace as n, + MathUtils as o, PMREMGenerator as p, - SphereGeometry as g, - AmbientLight as f, + Vector2 as r, + WebGLRenderer as s, + PerspectiveCamera as t, PointLight as u, ACESFilmicToneMapping as v, - Raycaster as y, - Plane as w + Plane as w, + Raycaster as y } from 'three'; import { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js'; @@ -227,6 +227,7 @@ class x { this.clear(); this.#t?.dispose(); this.renderer.dispose(); + this.renderer.forceContextLoss(); this.isDisposed = true; } } diff --git a/src/content/Backgrounds/ColorBends/ColorBends.jsx b/src/content/Backgrounds/ColorBends/ColorBends.jsx index b11ca9b5..536129a5 100644 --- a/src/content/Backgrounds/ColorBends/ColorBends.jsx +++ b/src/content/Backgrounds/ColorBends/ColorBends.jsx @@ -217,6 +217,7 @@ export default function ColorBends({ geometry.dispose(); material.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (renderer.domElement && renderer.domElement.parentElement === container) { container.removeChild(renderer.domElement); } diff --git a/src/content/Backgrounds/FloatingLines/FloatingLines.jsx b/src/content/Backgrounds/FloatingLines/FloatingLines.jsx index bc7be031..f9ef4dec 100644 --- a/src/content/Backgrounds/FloatingLines/FloatingLines.jsx +++ b/src/content/Backgrounds/FloatingLines/FloatingLines.jsx @@ -1,14 +1,14 @@ import { useEffect, useRef } from 'react'; import { - Scene, + Clock, + Mesh, OrthographicCamera, - WebGLRenderer, PlaneGeometry, - Mesh, + Scene, ShaderMaterial, - Vector3, Vector2, - Clock + Vector3, + WebGLRenderer } from 'three'; import './FloatingLines.css'; @@ -447,6 +447,7 @@ export default function FloatingLines({ geometry.dispose(); material.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (renderer.domElement.parentElement) { renderer.domElement.parentElement.removeChild(renderer.domElement); } diff --git a/src/content/Backgrounds/GridDistortion/GridDistortion.jsx b/src/content/Backgrounds/GridDistortion/GridDistortion.jsx index 4ab78302..f20c1910 100644 --- a/src/content/Backgrounds/GridDistortion/GridDistortion.jsx +++ b/src/content/Backgrounds/GridDistortion/GridDistortion.jsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import * as THREE from 'three'; import './GridDistortion.css'; @@ -227,6 +227,7 @@ const GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = if (renderer) { renderer.dispose(); + renderer.forceContextLoss(); if (container.contains(renderer.domElement)) { container.removeChild(renderer.domElement); } diff --git a/src/content/Backgrounds/GridScan/GridScan.jsx b/src/content/Backgrounds/GridScan/GridScan.jsx index d61f22b2..6f046b9f 100644 --- a/src/content/Backgrounds/GridScan/GridScan.jsx +++ b/src/content/Backgrounds/GridScan/GridScan.jsx @@ -1,7 +1,7 @@ +import * as faceapi from 'face-api.js'; +import { BloomEffect, ChromaticAberrationEffect, EffectComposer, EffectPass, RenderPass } from 'postprocessing'; import { useEffect, useRef, useState } from 'react'; -import { EffectComposer, RenderPass, EffectPass, BloomEffect, ChromaticAberrationEffect } from 'postprocessing'; import * as THREE from 'three'; -import * as faceapi from 'face-api.js'; import './GridScan.css'; const vert = ` @@ -566,6 +566,7 @@ export const GridScan = ({ composerRef.current = null; } renderer.dispose(); + renderer.forceContextLoss(); container.removeChild(renderer.domElement); }; }, [ diff --git a/src/content/Backgrounds/Hyperspeed/Hyperspeed.jsx b/src/content/Backgrounds/Hyperspeed/Hyperspeed.jsx index d3539af4..f9eda3e4 100644 --- a/src/content/Backgrounds/Hyperspeed/Hyperspeed.jsx +++ b/src/content/Backgrounds/Hyperspeed/Hyperspeed.jsx @@ -1,6 +1,6 @@ +import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing'; import { useEffect, useRef } from 'react'; import * as THREE from 'three'; -import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing'; import './Hyperspeed.css'; @@ -414,7 +414,8 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => { this.onTouchEnd = this.onTouchEnd.bind(this); this.onContextMenu = this.onContextMenu.bind(this); - window.addEventListener('resize', this.onWindowResize.bind(this)); + this.onWindowResize = this.onWindowResize.bind(this); + window.addEventListener('resize', this.onWindowResize); } onWindowResize() { @@ -566,7 +567,6 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => { if (updateCamera) { this.camera.updateProjectionMatrix(); } - } render(delta) { @@ -576,17 +576,36 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => { dispose() { this.disposed = true; + if (this.scene) { + this.scene.traverse(object => { + const obj = object; + if (!obj.isMesh) return; + + if (obj.geometry) obj.geometry.dispose(); + + if (obj.material) { + if (Array.isArray(obj.material)) { + obj.material.forEach(material => material.dispose()); + } else { + obj.material.dispose(); + } + } + }); + this.scene.clear(); + } + if (this.renderer) { this.renderer.dispose(); + this.renderer.forceContextLoss(); + if (this.renderer.domElement && this.renderer.domElement.parentNode) { + this.renderer.domElement.parentNode.removeChild(this.renderer.domElement); + } } if (this.composer) { this.composer.dispose(); } - if (this.scene) { - this.scene.clear(); - } - window.removeEventListener('resize', this.onWindowResize.bind(this)); + window.removeEventListener('resize', this.onWindowResize); if (this.container) { this.container.removeEventListener('mousedown', this.onMouseDown); this.container.removeEventListener('mouseup', this.onMouseUp); @@ -1099,7 +1118,11 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => { (function () { const container = document.getElementById('lights'); - const options = { ...DEFAULT_EFFECT_OPTIONS, ...effectOptions, colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors } }; + const options = { + ...DEFAULT_EFFECT_OPTIONS, + ...effectOptions, + colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors } + }; options.distortion = distortions[options.distortion]; const myApp = new App(container, options); diff --git a/src/content/Backgrounds/LiquidEther/LiquidEther.jsx b/src/content/Backgrounds/LiquidEther/LiquidEther.jsx index 393ca31f..befddcea 100644 --- a/src/content/Backgrounds/LiquidEther/LiquidEther.jsx +++ b/src/content/Backgrounds/LiquidEther/LiquidEther.jsx @@ -996,6 +996,7 @@ export default function LiquidEther({ const canvas = Common.renderer.domElement; if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas); Common.renderer.dispose(); + Common.renderer.forceContextLoss(); } } catch (e) { void 0; diff --git a/src/content/Backgrounds/PixelBlast/PixelBlast.jsx b/src/content/Backgrounds/PixelBlast/PixelBlast.jsx index bca79ea2..84e5c50b 100644 --- a/src/content/Backgrounds/PixelBlast/PixelBlast.jsx +++ b/src/content/Backgrounds/PixelBlast/PixelBlast.jsx @@ -1,6 +1,6 @@ +import { Effect, EffectComposer, EffectPass, RenderPass } from 'postprocessing'; import { useEffect, useRef } from 'react'; import * as THREE from 'three'; -import { EffectComposer, EffectPass, RenderPass, Effect } from 'postprocessing'; import './PixelBlast.css'; const createTouchTexture = () => { @@ -355,6 +355,7 @@ const PixelBlast = ({ t.material.dispose(); t.composer?.dispose(); t.renderer.dispose(); + t.renderer.forceContextLoss(); if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement); threeRef.current = null; } @@ -568,6 +569,7 @@ const PixelBlast = ({ t.material.dispose(); t.composer?.dispose(); t.renderer.dispose(); + t.renderer.forceContextLoss(); if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement); threeRef.current = null; }; diff --git a/src/content/Backgrounds/PixelSnow/PixelSnow.jsx b/src/content/Backgrounds/PixelSnow/PixelSnow.jsx index 09b665f8..8fe75b79 100644 --- a/src/content/Backgrounds/PixelSnow/PixelSnow.jsx +++ b/src/content/Backgrounds/PixelSnow/PixelSnow.jsx @@ -1,14 +1,14 @@ -import { useEffect, useRef, useMemo, useCallback } from 'react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { - Scene, + Color, + Mesh, OrthographicCamera, - WebGLRenderer, PlaneGeometry, + Scene, ShaderMaterial, - Mesh, Vector2, Vector3, - Color + WebGLRenderer } from 'three'; import './PixelSnow.css'; @@ -311,6 +311,7 @@ export default function PixelSnow({ container.removeChild(renderer.domElement); } renderer.dispose(); + renderer.forceContextLoss(); geometry.dispose(); material.dispose(); rendererRef.current = null; diff --git a/src/content/TextAnimations/ASCIIText/ASCIIText.jsx b/src/content/TextAnimations/ASCIIText/ASCIIText.jsx index 8f540339..2526a2c7 100644 --- a/src/content/TextAnimations/ASCIIText/ASCIIText.jsx +++ b/src/content/TextAnimations/ASCIIText/ASCIIText.jsx @@ -1,6 +1,6 @@ // Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import * as THREE from 'three'; const vertexShader = ` @@ -395,6 +395,7 @@ class CanvAscii { this.clear(); if (this.renderer) { this.renderer.dispose(); + this.renderer.forceContextLoss(); } } } diff --git a/src/tailwind/Animations/GhostCursor/GhostCursor.jsx b/src/tailwind/Animations/GhostCursor/GhostCursor.jsx index 5fe6d5d2..c0906587 100644 --- a/src/tailwind/Animations/GhostCursor/GhostCursor.jsx +++ b/src/tailwind/Animations/GhostCursor/GhostCursor.jsx @@ -2,8 +2,8 @@ import { useEffect, useMemo, useRef } from 'react'; import * as THREE from 'three'; import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; -import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'; import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; +import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'; const GhostCursor = ({ className, @@ -431,6 +431,7 @@ const GhostCursor = ({ material.dispose(); composer.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (renderer.domElement && renderer.domElement.parentElement) { renderer.domElement.parentElement.removeChild(renderer.domElement); diff --git a/src/tailwind/Animations/LaserFlow/LaserFlow.jsx b/src/tailwind/Animations/LaserFlow/LaserFlow.jsx index 6354ff72..157e40c7 100644 --- a/src/tailwind/Animations/LaserFlow/LaserFlow.jsx +++ b/src/tailwind/Animations/LaserFlow/LaserFlow.jsx @@ -530,6 +530,7 @@ export const LaserFlow = ({ geometry.dispose(); material.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (mount.contains(canvas)) mount.removeChild(canvas); }; // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/tailwind/Animations/ShapeBlur/ShapeBlur.jsx b/src/tailwind/Animations/ShapeBlur/ShapeBlur.jsx index 96ca5a91..74b8b7f9 100644 --- a/src/tailwind/Animations/ShapeBlur/ShapeBlur.jsx +++ b/src/tailwind/Animations/ShapeBlur/ShapeBlur.jsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import * as THREE from 'three'; const vertexShader = /* glsl */ ` @@ -235,6 +235,7 @@ const ShapeBlur = ({ document.removeEventListener('pointermove', onPointerMove); mount.removeChild(renderer.domElement); renderer.dispose(); + renderer.forceContextLoss(); }; }, [variation, pixelRatioProp, shapeSize, roundness, borderSize, circleSize, circleEdge]); diff --git a/src/tailwind/Backgrounds/Ballpit/Ballpit.jsx b/src/tailwind/Backgrounds/Ballpit/Ballpit.jsx index f18f398d..ae20a33d 100644 --- a/src/tailwind/Backgrounds/Ballpit/Ballpit.jsx +++ b/src/tailwind/Backgrounds/Ballpit/Ballpit.jsx @@ -1,25 +1,25 @@ -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { - Clock as e, - PerspectiveCamera as t, - Scene as i, - WebGLRenderer as s, - SRGBColorSpace as n, - MathUtils as o, - Vector2 as r, Vector3 as a, MeshPhysicalMaterial as c, + InstancedMesh as d, + Clock as e, + AmbientLight as f, + SphereGeometry as g, ShaderChunk as h, + Scene as i, Color as l, Object3D as m, - InstancedMesh as d, + SRGBColorSpace as n, + MathUtils as o, PMREMGenerator as p, - SphereGeometry as g, - AmbientLight as f, + Vector2 as r, + WebGLRenderer as s, + PerspectiveCamera as t, PointLight as u, ACESFilmicToneMapping as v, - Raycaster as y, - Plane as w + Plane as w, + Raycaster as y } from 'three'; import { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js'; @@ -227,6 +227,7 @@ class x { this.clear(); this.#t?.dispose(); this.renderer.dispose(); + this.renderer.forceContextLoss(); this.isDisposed = true; } } diff --git a/src/tailwind/Backgrounds/ColorBends/ColorBends.jsx b/src/tailwind/Backgrounds/ColorBends/ColorBends.jsx index a89b74c4..e09654cb 100644 --- a/src/tailwind/Backgrounds/ColorBends/ColorBends.jsx +++ b/src/tailwind/Backgrounds/ColorBends/ColorBends.jsx @@ -217,6 +217,7 @@ export default function ColorBends({ geometry.dispose(); material.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (renderer.domElement && renderer.domElement.parentElement === container) { container.removeChild(renderer.domElement); } diff --git a/src/tailwind/Backgrounds/FloatingLines/FloatingLines.jsx b/src/tailwind/Backgrounds/FloatingLines/FloatingLines.jsx index aee596c9..7060e300 100644 --- a/src/tailwind/Backgrounds/FloatingLines/FloatingLines.jsx +++ b/src/tailwind/Backgrounds/FloatingLines/FloatingLines.jsx @@ -1,14 +1,14 @@ import { useEffect, useRef } from 'react'; import { - Scene, + Clock, + Mesh, OrthographicCamera, - WebGLRenderer, PlaneGeometry, - Mesh, + Scene, ShaderMaterial, - Vector3, Vector2, - Clock + Vector3, + WebGLRenderer } from 'three'; const vertexShader = ` @@ -445,6 +445,7 @@ export default function FloatingLines({ geometry.dispose(); material.dispose(); renderer.dispose(); + renderer.forceContextLoss(); if (renderer.domElement.parentElement) { renderer.domElement.parentElement.removeChild(renderer.domElement); } @@ -470,7 +471,7 @@ export default function FloatingLines({ return (
{showPreview && ( -
+