Files
vue-bits/public/r/PixelBlast.json
2026-01-21 16:08:55 +05:30

1 line
23 KiB
JSON

{"name":"PixelBlast","title":"PixelBlast","description":"Exploding pixel particle bursts with optional liquid postprocessing.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div\n ref=\"containerRef\"\n :class=\"['w-full h-full relative overflow-hidden', className]\"\n :style=\"style\"\n aria-label=\"PixelBlast interactive background\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { Effect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';\nimport * as THREE from 'three';\nimport { onBeforeUnmount, onMounted, ref, useTemplateRef, watch, type CSSProperties } from 'vue';\n\nexport type PixelBlastVariant = 'square' | 'circle' | 'triangle' | 'diamond';\n\ntype PixelBlastProps = {\n variant?: PixelBlastVariant;\n pixelSize?: number;\n color?: string;\n className?: string;\n style?: 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 = () => {\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 x: number;\n y: number;\n vx: number;\n vy: number;\n force: number;\n age: number;\n }[] = [];\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: { x: number; y: number; vx: number; vy: number; force: number; age: number }) => {\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<string, THREE.Uniform>([\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<PixelBlastVariant, number> = {\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 fragColor = vec4(color, M);\n}\n`;\n\nconst MAX_CLICKS = 10;\n\nconst props = withDefaults(defineProps<PixelBlastProps>(), {\n variant: 'square',\n pixelSize: 3,\n color: '#B19EEF',\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\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\nconst visibilityRef = ref({ visible: true });\nconst speedRef = ref(props.speed);\n\nconst threeRef = ref<{\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<THREE.PlaneGeometry, THREE.ShaderMaterial>;\n timeOffset?: number;\n composer?: EffectComposer;\n touch?: ReturnType<typeof createTouchTexture>;\n liquidEffect?: Effect;\n} | null>(null);\n\ninterface PixelBlastConfig {\n antialias: boolean;\n liquid: boolean;\n noiseAmount: number;\n}\nconst prevConfigRef = ref<PixelBlastConfig | null>(null);\n\nlet cleanup: (() => void) | null = null;\n\nconst setup = () => {\n const container = containerRef.value;\n if (!container) return;\n speedRef.value = props.speed;\n const needsReinitKeys: (keyof PixelBlastConfig)[] = ['antialias', 'liquid', 'noiseAmount'];\n const cfg: PixelBlastConfig = {\n antialias: props.antialias,\n liquid: props.liquid,\n noiseAmount: props.noiseAmount\n };\n let mustReinit = false;\n if (!threeRef.value) mustReinit = true;\n else if (prevConfigRef.value) {\n for (const k of needsReinitKeys)\n if (prevConfigRef.value[k] !== cfg[k]) {\n mustReinit = true;\n break;\n }\n }\n if (mustReinit) {\n if (threeRef.value) {\n const t = threeRef.value;\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.value = null;\n }\n const canvas = document.createElement('canvas');\n const gl = canvas.getContext('webgl2', { antialias: props.antialias, alpha: true });\n if (!gl) return;\n\n const renderer = new THREE.WebGLRenderer({\n canvas,\n antialias: props.antialias,\n alpha: true\n });\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 const uniforms = {\n uResolution: { value: new THREE.Vector2(0, 0) },\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(props.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[props.variant] ?? 0 },\n uPixelSize: { value: props.pixelSize * renderer.getPixelRatio() },\n uScale: { value: props.patternScale },\n uDensity: { value: props.patternDensity },\n uPixelJitter: { value: props.pixelSizeJitter },\n uEnableRipples: { value: props.enableRipples ? 1 : 0 },\n uRippleSpeed: { value: props.rippleSpeed },\n uRippleThickness: { value: props.rippleThickness },\n uRippleIntensity: { value: props.rippleIntensityScale },\n uEdgeFade: { value: props.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 glslVersion: THREE.GLSL3,\n depthTest: false,\n depthWrite: false\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.value?.composer)\n threeRef.value.composer.setSize(renderer.domElement.width, renderer.domElement.height);\n uniforms.uPixelSize.value = props.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: EffectComposer | undefined;\n let touch: ReturnType<typeof createTouchTexture> | undefined;\n let liquidEffect: Effect | undefined;\n if (props.liquid) {\n touch = createTouchTexture();\n touch.radiusScale = props.liquidRadius;\n composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n liquidEffect = createLiquidEffect(touch.texture, {\n strength: props.liquidStrength,\n freq: props.liquidWobbleSpeed\n });\n const effectPass = new EffectPass(camera, liquidEffect);\n effectPass.renderToScreen = true;\n composer.addPass(renderPass);\n composer.addPass(effectPass);\n }\n if (props.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<string, THREE.Uniform>([\n ['uTime', new THREE.Uniform(0)],\n ['uAmount', new THREE.Uniform(props.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 // EffectPass has renderToScreen; ensure we turn it off before adding a new final pass\n if ('renderToScreen' in p) (p as { renderToScreen?: boolean }).renderToScreen = false;\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.value?.clickIx ?? 0;\n uniforms.uClickPos.value[ix].set(fx, fy);\n uniforms.uClickTimes.value[ix] = uniforms.uTime.value;\n if (threeRef.value) threeRef.value.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 (props.autoPauseOffscreen && !visibilityRef.value.visible) {\n raf = requestAnimationFrame(animate);\n return;\n }\n uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.value;\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 if (p instanceof EffectPass) {\n const effs = (p as unknown as { effects?: Effect[] }).effects;\n effs?.forEach(eff => {\n const u = eff.uniforms.get('uTime');\n if (u) u.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.value = {\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.value!;\n t.uniforms.uShapeType.value = SHAPE_MAP[props.variant] ?? 0;\n t.uniforms.uPixelSize.value = props.pixelSize * t.renderer.getPixelRatio();\n t.uniforms.uColor.value.set(props.color);\n t.uniforms.uScale.value = props.patternScale;\n t.uniforms.uDensity.value = props.patternDensity;\n t.uniforms.uPixelJitter.value = props.pixelSizeJitter;\n t.uniforms.uEnableRipples.value = props.enableRipples ? 1 : 0;\n t.uniforms.uRippleIntensity.value = props.rippleIntensityScale;\n t.uniforms.uRippleThickness.value = props.rippleThickness;\n t.uniforms.uRippleSpeed.value = props.rippleSpeed;\n t.uniforms.uEdgeFade.value = props.edgeFade;\n if (props.transparent) t.renderer.setClearAlpha(0);\n else t.renderer.setClearColor(0x000000, 1);\n if (t.liquidEffect) {\n const uStrength = t.liquidEffect?.uniforms.get('uStrength');\n if (uStrength) uStrength.value = props.liquidStrength;\n const uFreq = t.liquidEffect?.uniforms.get('uFreq');\n if (uFreq) uFreq.value = props.liquidWobbleSpeed;\n }\n if (t.touch) t.touch.radiusScale = props.liquidRadius;\n }\n prevConfigRef.value = cfg;\n\n cleanup = () => {\n if (threeRef.value && mustReinit) return;\n if (!threeRef.value) return;\n const t = threeRef.value;\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.value = null;\n };\n};\n\nonMounted(() => {\n setup();\n});\n\nonBeforeUnmount(() => {\n cleanup?.();\n});\n\nwatch(\n props,\n () => {\n cleanup?.();\n setup();\n },\n { deep: true }\n);\n</script>\n","path":"PixelBlast/PixelBlast.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"postprocessing","version":"^6.37.6"},{"ecosystem":"js","name":"three","version":"^0.178.0"}],"devDependencies":[],"categories":["Backgrounds"]}