{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "LiquidEther-JS-TW",
	"title": "LiquidEther",
	"description": "Interactive liquid shader with flowing distortion and customizable colors.",
	"type": "registry:component",
	"files": [
		{
			"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', '#B497CF'],\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  backgroundColor = '#FFFFFF',\n  lightMode = false\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 bg = new THREE.Color(backgroundColor);\n    const bgVec4 = lightMode ? new THREE.Vector4(bg.r, bg.g, bg.b, 1) : new THREE.Vector4(0, 0, 0, 0);\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    uniform bool lightMode;\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    float peak = max(c.r, max(c.g, c.b));\n    vec3 chroma = clamp(c / max(peak, 0.0001), 0.0, 1.0);\n    chroma = pow(chroma, vec3(1.25));\n    vec3 ink = lightMode ? chroma : c;\n    vec3 outRGB = mix(bgColor.rgb, ink, 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              lightMode: { value: lightMode }\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    backgroundColor,\n    lightMode\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    <div\n      ref={mountRef}\n      className={`w-full h-full relative overflow-hidden pointer-events-none touch-none ${className || ''}`}\n      style={style}\n    />\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}