/* ═══════════════════════════════════════════════════════════════
   LIQUID ETHER — Vanilla JS Port (Multi-Instance Version)
   Fluid Dynamics Simulation with THREE.js
   ─────────────────────────────────────────────────────────────
   Supports multiple instances on the same page by isolating 
   the simulation state per container.
   ═══════════════════════════════════════════════════════════════ */

const LiquidEtherShaders = {
  face_vert: `
    attribute vec3 position;
    uniform vec2 boundarySpace;
    varying vec2 uv;
    precision highp float;
    void main(){
      vec3 pos = position;
      vec2 scale = 1.0 - boundarySpace * 2.0;
      pos.xy = pos.xy * scale;
      uv = vec2(0.5)+(pos.xy)*0.5;
      gl_Position = vec4(pos, 1.0);
    }`,
  line_vert: `
    attribute vec3 position;
    uniform vec2 px;
    precision highp float;
    varying vec2 uv;
    void main(){
      vec3 pos = position;
      uv = 0.5 + pos.xy * 0.5;
      vec2 n = sign(pos.xy);
      pos.xy = abs(pos.xy) - px * 1.0;
      pos.xy *= n;
      gl_Position = vec4(pos, 1.0);
    }`,
  mouse_vert: `
    precision highp float;
    attribute vec3 position;
    attribute vec2 uv;
    uniform vec2 center;
    uniform vec2 scale;
    uniform vec2 px;
    varying vec2 vUv;
    void main(){
      vec2 pos = position.xy * scale * 2.0 * px + center;
      vUv = uv;
      gl_Position = vec4(pos, 0.0, 1.0);
    }`,
  advection_frag: `
    precision highp float;
    uniform sampler2D velocity;
    uniform float dt;
    uniform bool isBFECC;
    uniform vec2 fboSize;
    uniform vec2 px;
    varying vec2 uv;
    void main(){
      vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;
      if(isBFECC == false){
        vec2 vel = texture2D(velocity, uv).xy;
        vec2 uv2 = uv - vel * dt * ratio;
        vec2 newVel = texture2D(velocity, uv2).xy;
        gl_FragColor = vec4(newVel, 0.0, 0.0);
      } else {
        vec2 spot_new = uv;
        vec2 vel_old = texture2D(velocity, uv).xy;
        vec2 spot_old = spot_new - vel_old * dt * ratio;
        vec2 vel_new1 = texture2D(velocity, spot_old).xy;
        vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;
        vec2 error = spot_new2 - spot_new;
        vec2 spot_new3 = spot_new - error / 2.0;
        vec2 vel_2 = texture2D(velocity, spot_new3).xy;
        vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;
        vec2 newVel2 = texture2D(velocity, spot_old2).xy; 
        gl_FragColor = vec4(newVel2, 0.0, 0.0);
      }
    }`,
  color_frag: `
    precision highp float;
    uniform sampler2D velocity;
    uniform sampler2D palette;
    uniform vec4 bgColor;
    varying vec2 uv;
    void main(){
      vec2 vel = texture2D(velocity, uv).xy;
      float lenv = clamp(length(vel), 0.0, 1.0);
      vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;
      vec3 outRGB = mix(bgColor.rgb, c, lenv);
      float outA = mix(bgColor.a, 1.0, lenv);
      gl_FragColor = vec4(outRGB, outA);
    }`,
  divergence_frag: `
    precision highp float;
    uniform sampler2D velocity;
    uniform float dt;
    uniform vec2 px;
    varying vec2 uv;
    void main(){
      float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;
      float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;
      float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;
      float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;
      float divergence = (x1 - x0 + y1 - y0) / 2.0;
      gl_FragColor = vec4(divergence / dt);
    }`,
  externalForce_frag: `
    precision highp float;
    uniform vec2 force;
    uniform vec2 center;
    uniform vec2 scale;
    uniform vec2 px;
    varying vec2 vUv;
    void main(){
      vec2 circle = (vUv - 0.5) * 2.0;
      float d = 1.0 - min(length(circle), 1.0);
      d *= d;
      gl_FragColor = vec4(force * d, 0.0, 1.0);
    }`,
  poisson_frag: `
    precision highp float;
    uniform sampler2D pressure;
    uniform sampler2D divergence;
    uniform vec2 px;
    varying vec2 uv;
    void main(){
      float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;
      float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;
      float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;
      float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;
      float div = texture2D(divergence, uv).r;
      float newP = (p0 + p1 + p2 + p3) / 4.0 - div;
      gl_FragColor = vec4(newP);
    }`,
  pressure_frag: `
    precision highp float;
    uniform sampler2D pressure;
    uniform sampler2D velocity;
    uniform vec2 px;
    uniform float dt;
    varying vec2 uv;
    void main(){
      float step = 1.0;
      float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;
      float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;
      float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;
      float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;
      vec2 v = texture2D(velocity, uv).xy;
      vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;
      v = v - gradP * dt;
      gl_FragColor = vec4(v, 0.0, 1.0);
    }`,
  viscous_frag: `
    precision highp float;
    uniform sampler2D velocity;
    uniform sampler2D velocity_new;
    uniform float v;
    uniform vec2 px;
    uniform float dt;
    varying vec2 uv;
    void main(){
      vec2 old = texture2D(velocity, uv).xy;
      vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;
      vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;
      vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;
      vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;
      vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);
      newv /= 4.0 * (1.0 + v * dt);
      gl_FragColor = vec4(newv, 0.0, 0.0);
    }`
};

(function() {
  /**
   * Main initialization function.
   * Creates a closure with its own state for each container.
   */
  window.initLiquidEther = function(container, customOpts = {}) {
    if (!container) return;

    // Default options
    const opts = {
      colors: ['#5227FF', '#FF9FFC', '#B497CF'],
      autoDemo: true,
      autoSpeed: 0.5,
      autoIntensity: 2.2,
      takeoverDuration: 0.25,
      autoResumeDelay: 3000,
      autoRampDuration: 0.6,
      ...customOpts
    };

    // --- Instance State ---
    const State = {
      width: 0,
      height: 0,
      aspect: 1,
      pixelRatio: Math.min(window.devicePixelRatio || 1, 2),
      time: 0,
      delta: 0,
      renderer: null,
      clock: new THREE.Clock(),
      running: true
    };

    const Mouse = {
      mouseMoved: false,
      coords: new THREE.Vector2(),
      coords_old: new THREE.Vector2(),
      diff: new THREE.Vector2(),
      timer: null,
      isHoverInside: false,
      hasUserControl: false,
      isAutoActive: false,
      autoIntensity: opts.autoIntensity,
      takeoverActive: false,
      takeoverStartTime: 0,
      takeoverDuration: opts.takeoverDuration,
      takeoverFrom: new THREE.Vector2(),
      takeoverTo: new THREE.Vector2()
    };

    // --- Core Classes (Closure-Refactored) ---
    class ShaderPass {
      constructor(props) {
        this.props = props || {};
        this.uniforms = this.props.material?.uniforms;
        this.init();
      }
      init() {
        this.scene = new THREE.Scene();
        this.camera = new THREE.Camera();
        if (this.uniforms) {
          this.material = new THREE.RawShaderMaterial(this.props.material);
          this.geometry = new THREE.PlaneGeometry(2.0, 2.0);
          this.plane = new THREE.Mesh(this.geometry, this.material);
          this.scene.add(this.plane);
        }
      }
      update() {
        State.renderer.setRenderTarget(this.props.output || null);
        State.renderer.render(this.scene, this.camera);
        State.renderer.setRenderTarget(null);
      }
    }

    class Advection extends ShaderPass {
      constructor(simProps) {
        super({
          material: {
            vertexShader: LiquidEtherShaders.face_vert,
            fragmentShader: LiquidEtherShaders.advection_frag,
            uniforms: {
              boundarySpace: { value: simProps.cellScale },
              px: { value: simProps.cellScale },
              fboSize: { value: simProps.fboSize },
              velocity: { value: simProps.src.texture },
              dt: { value: simProps.dt },
              isBFECC: { value: true }
            }
          },
          output: simProps.dst
        });
        this.createBoundary();
      }
      createBoundary() {
        const boundaryG = new THREE.BufferGeometry();
        const vertices = new Float32Array([-1,-1,0, -1,1,0, -1,1,0, 1,1,0, 1,1,0, 1,-1,0, 1,-1,0, -1,-1,0]);
        boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
        this.line = new THREE.LineSegments(boundaryG, new THREE.RawShaderMaterial({
          vertexShader: LiquidEtherShaders.line_vert,
          fragmentShader: LiquidEtherShaders.advection_frag,
          uniforms: this.uniforms
        }));
        this.scene.add(this.line);
      }
      update(opts) {
        this.uniforms.dt.value = opts.dt;
        this.line.visible = opts.isBounce;
        this.uniforms.isBFECC.value = opts.BFECC;
        super.update();
      }
    }

    class ExternalForce extends ShaderPass {
      constructor(simProps) {
        super({ output: simProps.dst });
        const mouseG = new THREE.PlaneGeometry(1, 1);
        const mouseM = new THREE.RawShaderMaterial({
          vertexShader: LiquidEtherShaders.mouse_vert,
          fragmentShader: LiquidEtherShaders.externalForce_frag,
          blending: THREE.AdditiveBlending,
          depthWrite: false,
          uniforms: {
            px: { value: simProps.cellScale },
            force: { value: new THREE.Vector2(0, 0) },
            center: { value: new THREE.Vector2(0, 0) },
            scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }
          }
        });
        this.mouse = new THREE.Mesh(mouseG, mouseM);
        this.scene.add(this.mouse);
      }
      update(props) {
        const uniforms = this.mouse.material.uniforms;
        uniforms.force.value.set((Mouse.diff.x / 2) * props.mouse_force, (Mouse.diff.y / 2) * props.mouse_force);
        uniforms.center.value.set(Mouse.coords.x, Mouse.coords.y);
        uniforms.scale.value.set(props.cursor_size, props.cursor_size);
        super.update();
      }
    }

    class Viscous extends ShaderPass {
      constructor(simProps) {
        super({
          material: {
            vertexShader: LiquidEtherShaders.face_vert,
            fragmentShader: LiquidEtherShaders.viscous_frag,
            uniforms: {
              boundarySpace: { value: simProps.boundarySpace },
              velocity: { value: simProps.src.texture },
              velocity_new: { value: simProps.dst_.texture },
              v: { value: simProps.viscous }, px: { value: simProps.cellScale }, dt: { value: simProps.dt }
            }
          },
          output: simProps.dst,
          output0: simProps.dst_,
          output1: simProps.dst
        });
      }
      update({ viscous, iterations, dt }) {
        this.uniforms.v.value = viscous;
        this.uniforms.dt.value = dt;
        let fbo_in, fbo_out;
        for (let i = 0; i < iterations; i++) {
          fbo_in = i % 2 === 0 ? this.props.output0 : this.props.output1;
          fbo_out = i % 2 === 0 ? this.props.output1 : this.props.output0;
          this.uniforms.velocity_new.value = fbo_in.texture;
          this.props.output = fbo_out;
          super.update();
        }
        return fbo_out;
      }
    }

    class Divergence extends ShaderPass {
      constructor(simProps) {
        super({
          material: {
            vertexShader: LiquidEtherShaders.face_vert,
            fragmentShader: LiquidEtherShaders.divergence_frag,
            uniforms: {
              boundarySpace: { value: simProps.boundarySpace },
              velocity: { value: simProps.src.texture }, px: { value: simProps.cellScale }, dt: { value: simProps.dt }
            }
          },
          output: simProps.dst
        });
      }
      update({ vel }) {
        this.uniforms.velocity.value = vel.texture;
        super.update();
      }
    }

    class Poisson extends ShaderPass {
      constructor(simProps) {
        super({
          material: {
            vertexShader: LiquidEtherShaders.face_vert,
            fragmentShader: LiquidEtherShaders.poisson_frag,
            uniforms: {
              boundarySpace: { value: simProps.boundarySpace },
              pressure: { value: simProps.dst_.texture },
              divergence: { value: simProps.src.texture }, px: { value: simProps.cellScale }
            }
          },
          output: simProps.dst,
          output0: simProps.dst_,
          output1: simProps.dst
        });
      }
      update({ iterations }) {
        let p_in, p_out;
        for (let i = 0; i < iterations; i++) {
          p_in = i % 2 === 0 ? this.props.output0 : this.props.output1;
          p_out = i % 2 === 0 ? this.props.output1 : this.props.output0;
          this.uniforms.pressure.value = p_in.texture;
          this.props.output = p_out;
          super.update();
        }
        return p_out;
      }
    }

    class Pressure extends ShaderPass {
      constructor(simProps) {
        super({
          material: {
            vertexShader: LiquidEtherShaders.face_vert,
            fragmentShader: LiquidEtherShaders.pressure_frag,
            uniforms: {
              boundarySpace: { value: simProps.boundarySpace },
              pressure: { value: simProps.src_p.texture },
              velocity: { value: simProps.src_v.texture }, px: { value: simProps.cellScale }, dt: { value: simProps.dt }
            }
          },
          output: simProps.dst
        });
      }
      update({ vel, pressure }) {
        this.uniforms.velocity.value = vel.texture;
        this.uniforms.pressure.value = pressure.texture;
        super.update();
      }
    }

    class Simulation {
      constructor(options) {
        this.options = { 
          iterations_poisson: 32, iterations_viscous: 32, mouse_force: 20, resolution: 0.5, cursor_size: 100,
          viscous: 30, isBounce: false, dt: 0.014, isViscous: false, BFECC: true, ...options 
        };
        this.fbos = {};
        this.fboSize = new THREE.Vector2();
        this.cellScale = new THREE.Vector2();
        this.boundarySpace = new THREE.Vector2();
        this.init();
      }
      init() {
        this.calcSize();
        const type = /(iPad|iPhone|iPod)/i.test(navigator.userAgent) ? THREE.HalfFloatType : THREE.FloatType;
        const opts = { type, depthBuffer: false, stencilBuffer: false, minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter };
        ['vel_0', 'vel_1', 'vel_viscous0', 'vel_viscous1', 'div', 'pressure_0', 'pressure_1'].forEach(k => {
          this.fbos[k] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);
        });
        this.advection = new Advection({ cellScale: this.cellScale, fboSize: this.fboSize, dt: this.options.dt, src: this.fbos.vel_0, dst: this.fbos.vel_1 });
        this.externalForce = new ExternalForce({ cellScale: this.cellScale, cursor_size: this.options.cursor_size, dst: this.fbos.vel_1 });
        this.viscous = new Viscous({ cellScale: this.cellScale, boundarySpace: this.boundarySpace, viscous: this.options.viscous, src: this.fbos.vel_1, dst: this.fbos.vel_viscous1, dst_: this.fbos.vel_viscous0, dt: this.options.dt });
        this.divergence = new Divergence({ cellScale: this.cellScale, boundarySpace: this.boundarySpace, src: this.fbos.vel_viscous0, dst: this.fbos.div, dt: this.options.dt });
        this.poisson = new Poisson({ cellScale: this.cellScale, boundarySpace: this.boundarySpace, src: this.fbos.div, dst: this.fbos.pressure_1, dst_: this.fbos.pressure_0 });
        this.pressure = new Pressure({ cellScale: this.cellScale, boundarySpace: this.boundarySpace, src_p: this.fbos.pressure_0, src_v: this.fbos.vel_viscous0, dst: this.fbos.vel_0, dt: this.options.dt });
      }
      calcSize() {
        const w = Math.max(1, Math.round(this.options.resolution * State.width));
        const h = Math.max(1, Math.round(this.options.resolution * State.height));
        this.cellScale.set(1/w, 1/h);
        this.fboSize.set(w, h);
      }
      resize() {
        this.calcSize();
        Object.values(this.fbos).forEach(f => f.setSize(this.fboSize.x, this.fboSize.y));
      }
      update() {
        this.boundarySpace.copy(this.options.isBounce ? new THREE.Vector2(0,0) : this.cellScale);
        this.advection.update(this.options);
        this.externalForce.update(this.options);
        let vel = this.options.isViscous ? this.viscous.update({ viscous: this.options.viscous, iterations: this.options.iterations_viscous, dt: this.options.dt }) : this.fbos.vel_1;
        this.divergence.update({ vel });
        this.pressure.update({ vel, pressure: this.poisson.update({ iterations: this.options.iterations_poisson }) });
      }
    }

    // --- Interaction Manager ---
    function isPointInside(clientX, clientY) {
      const rect = container.getBoundingClientRect();
      return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
    }

    function setCoords(x, y) {
      if (Mouse.timer) window.clearTimeout(Mouse.timer);
      const rect = container.getBoundingClientRect();
      const nx = (x - rect.left) / rect.width;
      const ny = (y - rect.top) / rect.height;
      Mouse.coords.set(nx * 2 - 1, -(ny * 2 - 1));
      Mouse.mouseMoved = true;
      Mouse.timer = window.setTimeout(() => { Mouse.mouseMoved = false; }, 100);
    }

    let lastInteraction = performance.now();
    function onInteract() {
      lastInteraction = performance.now();
      if (autoDriver) autoDriver.active = false;
    }

    const mouseMoveHandler = (e) => {
      Mouse.isHoverInside = isPointInside(e.clientX, e.clientY);
      if (!Mouse.isHoverInside) return;
      onInteract();
      if (Mouse.isAutoActive && !Mouse.hasUserControl && !Mouse.takeoverActive) {
        const rect = container.getBoundingClientRect();
        const nx = (e.clientX - rect.left) / rect.width;
        const ny = (e.clientY - rect.top) / rect.height;
        Mouse.takeoverFrom.copy(Mouse.coords);
        Mouse.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));
        Mouse.takeoverStartTime = performance.now();
        Mouse.takeoverActive = true;
        Mouse.hasUserControl = true;
        Mouse.isAutoActive = false;
        return;
      }
      setCoords(e.clientX, e.clientY);
      Mouse.hasUserControl = true;
    };

    const touchHandler = (e) => {
      const t = e.touches ? e.touches[0] : e;
      Mouse.isHoverInside = isPointInside(t.clientX, t.clientY);
      if (!Mouse.isHoverInside) return;
      onInteract();
      setCoords(t.clientX, t.clientY);
      Mouse.hasUserControl = true;
    };

    const autoDriver = {
      active: false, current: new THREE.Vector2(), target: new THREE.Vector2(), lastTime: performance.now(),
      pick() { const r = Math.random; this.target.set((r()*2-1)*0.8, (r()*2-1)*0.8); },
      update() {
        if (!opts.autoDemo) return;
        const now = performance.now();
        if (now - lastInteraction < opts.autoResumeDelay || Mouse.isHoverInside) { this.active = false; Mouse.isAutoActive = false; return; }
        if (!this.active) { this.active = true; this.current.copy(Mouse.coords); this.lastTime = now; this.pick(); }
        Mouse.isAutoActive = true;
        let dt = (now - this.lastTime) / 1000; this.lastTime = now;
        const dir = new THREE.Vector2().subVectors(this.target, this.current);
        if (dir.length() < 0.01) { this.pick(); return; }
        this.current.addScaledVector(dir.normalize(), Math.min(opts.autoSpeed * dt, dir.length()));
        Mouse.coords.copy(this.current); Mouse.mouseMoved = true;
      }
    };

    // --- Initialization ---
    const rect = container.getBoundingClientRect();
    State.width = Math.max(1, Math.floor(rect.width));
    State.height = Math.max(1, Math.floor(rect.height));
    State.aspect = State.width / State.height;

    State.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    State.renderer.autoClear = false;
    State.renderer.setClearColor(new THREE.Color(0x000000), 0);
    State.renderer.setPixelRatio(State.pixelRatio);
    State.renderer.setSize(State.width, State.height);
    State.renderer.domElement.style.width = '100%';
    State.renderer.domElement.style.height = '100%';
    State.renderer.domElement.style.display = 'block';

    const sim = new Simulation(opts);
    const scene = new THREE.Scene();
    const bgVec4 = new THREE.Vector4(0,0,0,0);
    const paletteTex = (function(stops) {
      const data = new Uint8Array(stops.length * 4);
      stops.forEach((s, i) => { const c = new THREE.Color(s); data[i*4]=c.r*255; data[i*4+1]=c.g*255; data[i*4+2]=c.b*255; data[i*4+3]=255; });
      const tex = new THREE.DataTexture(data, stops.length, 1, THREE.RGBAFormat);
      tex.magFilter = tex.minFilter = THREE.LinearFilter; tex.needsUpdate = true; return tex;
    })(opts.colors);

    const output = new THREE.Mesh(new THREE.PlaneGeometry(2,2), new THREE.RawShaderMaterial({
      vertexShader: LiquidEtherShaders.face_vert, fragmentShader: LiquidEtherShaders.color_frag, transparent: true,
      uniforms: { velocity: { value: sim.fbos.vel_0.texture }, boundarySpace: { value: new THREE.Vector2() }, palette: { value: paletteTex }, bgColor: { value: bgVec4 } }
    }));
    scene.add(output);
    container.prepend(State.renderer.domElement);

    // Event Listeners
    window.addEventListener('mousemove', mouseMoveHandler);
    window.addEventListener('touchstart', touchHandler, { passive: true });
    window.addEventListener('touchmove', touchHandler, { passive: true });

    const resizeHandler = () => {
      const rect = container.getBoundingClientRect();
      State.width = Math.max(1, Math.floor(rect.width));
      State.height = Math.max(1, Math.floor(rect.height));
      State.aspect = State.width / State.height;
      State.renderer.setSize(State.width, State.height, false);
      sim.resize();
    };
    window.addEventListener('resize', resizeHandler);

    // Loop
    function loop() {
      // Visibility Check: If container is hidden or scrolled far away, we can pause.
      // In ui-kit, we use display: none when a different BG is selected.
      if (container.offsetParent === null) {
        requestAnimationFrame(loop);
        return;
      }

      autoDriver.update();
      
      // Update Mouse Takeover
      if (Mouse.takeoverActive) {
        const t = (performance.now() - Mouse.takeoverStartTime) / (Mouse.takeoverDuration * 1000);
        if (t >= 1) {
          Mouse.takeoverActive = false;
          Mouse.coords.copy(Mouse.takeoverTo);
          Mouse.coords_old.copy(Mouse.coords);
          Mouse.diff.set(0, 0);
        } else {
          const k = t * t * (3 - 2 * t);
          Mouse.coords.copy(Mouse.takeoverFrom).lerp(Mouse.takeoverTo, k);
        }
      }
      Mouse.diff.subVectors(Mouse.coords, Mouse.coords_old);
      Mouse.coords_old.copy(Mouse.coords);
      if (Mouse.coords_old.x === 0 && Mouse.coords_old.y === 0) Mouse.diff.set(0, 0);
      if (Mouse.isAutoActive && !Mouse.takeoverActive) Mouse.diff.multiplyScalar(Mouse.autoIntensity);

      State.delta = State.clock.getDelta();
      State.time += State.delta;
      
      sim.update();

      State.renderer.setRenderTarget(null);
      State.renderer.render(scene, new THREE.Camera());
      requestAnimationFrame(loop);
    }
    loop();
  };
})();
