(() => {
    "use strict";

    // ============================================================
    // 1) PASTE ORIGINAL BASE64 ASSETS HERE (do not change them)
    // ============================================================
    const DROP_ALPHA_SRC = "data:image/png;base64, ";
    const DROP_COLOR_SRC = "data:image/png;base64, ";
    const REFLECTION_JPEG_SRC = "data:image/jpeg;base64, ";

    // ============================================================
    // 2) Small helpers (modern browsers only)
    // ============================================================
    const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
    const easeOutQuint = t => 1 - Math.pow(1 - t, 5);

    function times(n, fn) {
        for (let i = 0; i < n; i++) fn.call(null, i);
    }

    // core'daki random modülünün aynısı
    function random(min = null, max = null, ease = null) {
        if (min == null) { min = 0; max = 1; }
        else if (min != null && max == null) { max = min; min = 0; }
        const range = max - min;
        if (ease == null) ease = t => t;
        return min + ease(Math.random()) * range;
    }

    function createCanvas(w, h) {
        const c = document.createElement("canvas");
        c.width = w; c.height = h;
        return c;
    }

    function loadImages(list) {
        return Promise.all(list.map((item, idx) => new Promise((resolve, reject) => {
            const img = new Image();
            img.crossOrigin = "anonymous";
            img.onload = () => resolve({ name: item.name, img });
            img.onerror = () => reject(new Error("Image load failed: " + item.name));
            img.src = item.src;
        }))).then(arr => {
            const out = {};
            arr.forEach(x => out[x.name] = x.img);
            return out;
        });
    }

    // ============================================================
    // 3) WebGL minimal (same behavior as original)
    // ============================================================
    function getGL(canvas, opts) {
        for (const n of ["webgl", "experimental-webgl"]) {
            try { const gl = canvas.getContext(n, opts); if (gl) return gl; } catch (_) { }
        }
        return null;
    }
    function compileShader(gl, src, type) {
        const sh = gl.createShader(type);
        gl.shaderSource(sh, src);
        gl.compileShader(sh);
        if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
            console.error(gl.getShaderInfoLog(sh));
            gl.deleteShader(sh);
            return null;
        }
        return sh;
    }
    function createProgram(gl, vsSrc, fsSrc) {
        const vs = compileShader(gl, vsSrc, gl.VERTEX_SHADER);
        const fs = compileShader(gl, fsSrc, gl.FRAGMENT_SHADER);
        const p = gl.createProgram();
        gl.attachShader(p, vs);
        gl.attachShader(p, fs);
        gl.linkProgram(p);
        if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
            console.error(gl.getProgramInfoLog(p));
            gl.deleteProgram(p);
            return null;
        }

        // a_texCoord buffer
        const a_tex = gl.getAttribLocation(p, "a_texCoord");
        const texBuf = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texBuf);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0, 0, 1, 0, 0, 1,
            0, 1, 1, 0, 1, 1
        ]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(a_tex);
        gl.vertexAttribPointer(a_tex, 2, gl.FLOAT, false, 0, 0);

        // a_position buffer (will be updated each frame)
        const a_pos = gl.getAttribLocation(p, "a_position");
        const posBuf = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
        gl.enableVertexAttribArray(a_pos);
        gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0);

        return p;
    }
    function setRectangle(gl, x, y, w, h) {
        const x1 = x, x2 = x + w, y1 = y, y2 = y + h;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            x1, y1, x2, y1, x1, y2,
            x1, y2, x2, y1, x2, y2
        ]), gl.STATIC_DRAW);
    }
    function createTexture(gl, unit, source) {
        gl.activeTexture(gl["TEXTURE" + unit]);
        const tex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
        return tex;
    }
    function updateTexture(gl, unit, tex, source) {
        gl.activeTexture(gl["TEXTURE" + unit]);
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
    }

    // ============================================================
    // 4) GLObj wrapper (like original)
    // ============================================================
    class GLObj {
        constructor(canvas, glOpts, vs, fs) {
            this.canvas = canvas;
            this.gl = getGL(canvas, glOpts || {});
            if (!this.gl) throw new Error("WebGL not supported");
            this.program = createProgram(this.gl, vs, fs);
            this.gl.useProgram(this.program);
            this.width = canvas.width;
            this.height = canvas.height;
        }
        uniform2f(name, a, b) {
            const loc = this.gl.getUniformLocation(this.program, "u_" + name);
            this.gl.uniform2f(loc, a, b);
        }
        uniform1f(name, a) {
            const loc = this.gl.getUniformLocation(this.program, "u_" + name);
            this.gl.uniform1f(loc, a);
        }
        uniform1i(name, a) {
            const loc = this.gl.getUniformLocation(this.program, "u_" + name);
            this.gl.uniform1i(loc, a);
        }
        draw() {
            setRectangle(this.gl, 0, 0, this.width, this.height);
            this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
        }
    }

    // ============================================================
    // 5) RainRenderer (same shader logic as original 206)
    // ============================================================
    class RainRenderer {
        constructor(canvas, canvasLiquid, imageFg, imageBg, imageShine = null, opts = {}) {
            this.canvas = canvas;
            this.canvasLiquid = canvasLiquid;
            this.imageFg = imageFg;
            this.imageBg = imageBg;
            this.imageShine = imageShine;
            this.options = Object.assign({
                renderShadow: false,
                minRefraction: 256,
                maxRefraction: 512,
                brightness: 1,
                alphaMultiply: 20,
                alphaSubtract: 5
            }, opts);

            this.parallaxX = 0;
            this.parallaxY = 0;

            this._init();
        }

        _init() {
            const vs = `#define GLSLIFY 1
precision mediump float;
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
varying vec2 v_resolution;
void main() {
  vec2 zeroToOne = a_position / u_resolution;
  vec2 zeroToTwo = zeroToOne * 2.0;
  vec2 clipSpace = zeroToTwo - 1.0;
  gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
  v_texCoord = a_texCoord;
  v_resolution = u_resolution;
}`;

            // shader is kept compatible with original (shine/shadow uniforms exist)
            const fs = `#define GLSLIFY 1
precision mediump float;
uniform sampler2D u_waterMap;
uniform sampler2D u_textureShine;
uniform sampler2D u_textureFg;
uniform sampler2D u_textureBg;
varying vec2 v_texCoord;
varying vec2 v_resolution;
uniform vec2 u_parallax;
uniform float u_textureRatio;
uniform int u_renderShine;
uniform int u_renderShadow;
uniform float u_minRefraction;
uniform float u_refractionDelta;
uniform float u_brightness;
uniform float u_alphaMultiply;
uniform float u_alphaSubtract;

vec4 blend(vec4 bg,vec4 fg){
  vec3 bgm=bg.rgb*bg.a;
  vec3 fgm=fg.rgb*fg.a;
  float ia=1.-fg.a;
  float a=(fg.a + bg.a * ia);
  vec3 rgb;
  if(a!=0.){
    rgb=(fgm + bgm * ia) / a;
  }else{
    rgb=vec3(0.,0.,0.);
  }
  return vec4(rgb,a);
}
vec2 scaledTexCoord(){
  float ratio=v_resolution.x/v_resolution.y;
  vec2 scale=vec2(1.,1.);
  vec2 offset=vec2(0.,0.);
  float ratioDelta=ratio-u_textureRatio;
  if(ratioDelta>=0.){
    scale.y=(1.+ratioDelta);
    offset.y=ratioDelta/2.;
  }else{
    scale.x=(1.-ratioDelta);
    offset.x=-ratioDelta/2.;
  }
  return (v_texCoord+offset)/scale;
}
vec2 pixel(){ return vec2(1.,1.)/v_resolution; }
vec2 parallax(float v){ return u_parallax*pixel()*v; }

vec4 offset(float x, float y){
  vec2 scale=vec2( (v_resolution.x+40.)/v_resolution.x, (v_resolution.y+40.)/v_resolution.y);
  vec2 scaledTexCoord=v_texCoord/scale;
  vec2 scaleOffset=vec2((1.-(1./scale.x))/2.,(1.-(1./scale.y))/2.);
  return texture2D(u_waterMap, (scaledTexCoord+scaleOffset)+(pixel()*vec2(x,y))+parallax(20.));
}

void main() {
  vec4 center = offset(0.,0.);
  float d=center.b;
  float x=center.g;
  float y=center.r;

  float a=clamp(center.a*u_alphaMultiply-u_alphaSubtract, 0.,1.);
  vec2 refraction=(vec2(x,y)-0.5)*2.;
  vec2 texturePos=scaledTexCoord() +( pixel()*refraction*(u_minRefraction+(d*u_refractionDelta)))+parallax(-15.);
  vec4 tex=texture2D(u_textureFg,texturePos);

  if(u_renderShine==1){
    float maxShine=490.;
    float minShine=maxShine*0.18;
    vec4 shine=texture2D(u_textureShine,vec2(0.5,0.5) + ((1./512.)*refraction)*-(minShine+((maxShine-minShine)*d)));
    tex=blend(tex,shine);
  }

  vec4 bg=texture2D(u_textureBg,scaledTexCoord()+parallax(5.));
  vec4 fg=vec4(tex.rgb*u_brightness,a);

  if(u_renderShadow==1){
    float borderAlpha = offset(0.,0.-(d*6.)).a;
    borderAlpha=borderAlpha*u_alphaMultiply-(u_alphaSubtract+0.5);
    borderAlpha=clamp(borderAlpha,0.,1.);
    borderAlpha*=0.2;
    vec4 border=vec4(0.,0.,0.,borderAlpha);
    fg=blend(border,fg);
  }

  gl_FragColor = blend(bg,fg);
}`;

            this.glObj = new GLObj(this.canvas, { alpha: false }, vs, fs);
            const gl = this.glObj.gl;

            // uniforms
            this.glObj.uniform2f("resolution", this.canvas.width, this.canvas.height);
            this.glObj.uniform1f("textureRatio", this.imageBg.width / this.imageBg.height);
            this.glObj.uniform1i("renderShine", this.imageShine ? 1 : 0);
            this.glObj.uniform1i("renderShadow", this.options.renderShadow ? 1 : 0);
            this.glObj.uniform1f("minRefraction", this.options.minRefraction);
            this.glObj.uniform1f("refractionDelta", this.options.maxRefraction - this.options.minRefraction);
            this.glObj.uniform1f("brightness", this.options.brightness);
            this.glObj.uniform1f("alphaMultiply", this.options.alphaMultiply);
            this.glObj.uniform1f("alphaSubtract", this.options.alphaSubtract);

            // textures
            this.texWater = createTexture(gl, 0, this.canvasLiquid);

            // textureShine (dummy 2x2 canvas if null)
            const shineCanvas = this.imageShine || createCanvas(2, 2);
            this.texShine = createTexture(gl, 1, shineCanvas);
            this.texFg = createTexture(gl, 2, this.imageFg);
            this.texBg = createTexture(gl, 3, this.imageBg);

            // sampler uniforms
            this.glObj.uniform1i("waterMap", 0);
            this.glObj.uniform1i("textureShine", 1);
            this.glObj.uniform1i("textureFg", 2);
            this.glObj.uniform1i("textureBg", 3);

            this._raf = requestAnimationFrame(this._draw.bind(this));
        }

        _draw() {
            // update parallax uniform
            const gl = this.glObj.gl;
            const loc = gl.getUniformLocation(this.glObj.program, "u_parallax");
            gl.uniform2f(loc, this.parallaxX, this.parallaxY);

            // update water texture
            updateTexture(gl, 0, this.texWater, this.canvasLiquid);

            this.glObj.draw();
            this._raf = requestAnimationFrame(this._draw.bind(this));
        }
    }

    // ============================================================
    // 6) Raindrops (same logic as original 207, kept intact)
    // ============================================================
    const GFX_SIZE = 64;
    const dropProto = {
        x: 0, y: 0, r: 0, spreadX: 0, spreadY: 0, momentum: 0, momentumX: 0,
        lastSpawn: 0, nextSpawn: 0, parent: null, isNew: true, killed: false, tension: 0, shrink: 0
    };

    class Raindrops {
        constructor(width, height, scale, dropAlpha, dropColor) {
            this.width = width; this.height = height; this.scale = scale;
            this.dropAlpha = dropAlpha; this.dropColor = dropColor;

            this.minR = 10; this.maxR = 40; this.maxDrops = 800;
            this.texturePixelDensity = 1;

            this.rainChance = 0.3; this.rainLimit = 3;
            this.drizzleCounter = 0; this.drizzle = 50;
            this.drizzleSize = [2, 4];
            this.drizzleCleaningRadiusMultiplier = 0.45;

            this.globalTimeScale = 1;
            this.trailRate = 1;
            this.autoShrink = true;
            this.spawnArea = [-0.1, 0.95];
            this.trailScaleRange = [0.2, 0.45];
            this.collisionRadius = 0.65;
            this.collisionRadiusIncrease = 0.01;
            this.dropFallMultiplier = 1;

            this.raining = true;
            this.lastRender = null;
            this.textureCleaningIterations = 0;

            this.canvas = createCanvas(width, height);
            this.ctx = this.canvas.getContext("2d");
            this.texture = createCanvas(width * this.texturePixelDensity, height * this.texturePixelDensity);
            this.textureCtx = this.texture.getContext("2d");
            this.drops = [];
            this.dropsGfx = [];
            this._buildGfx();
        }

        get deltaR() { return this.maxR - this.minR; }
        get area() { return this.width * this.height / this.scale; }
        get areaMultiplier() { return this.area / 786432; }

        _buildGfx() {
            const base = createCanvas(GFX_SIZE, GFX_SIZE);
            const i = base.getContext("2d");

            this.dropsGfx = Array.from({ length: 255 }, (_, blue) => {
                const sprite = createCanvas(GFX_SIZE, GFX_SIZE);
                const a = sprite.getContext("2d");

                i.clearRect(0, 0, GFX_SIZE, GFX_SIZE);
                i.globalCompositeOperation = "source-over";
                i.drawImage(this.dropColor, 0, 0, GFX_SIZE, GFX_SIZE);
                i.globalCompositeOperation = "screen";
                i.fillStyle = `rgba(0,0,${blue},1)`;
                i.fillRect(0, 0, GFX_SIZE, GFX_SIZE);

                a.globalCompositeOperation = "source-over";
                a.drawImage(this.dropAlpha, 0, 0, GFX_SIZE, GFX_SIZE);
                a.globalCompositeOperation = "source-in";
                a.drawImage(base, 0, 0, GFX_SIZE, GFX_SIZE);

                return sprite;
            });

            this.paintGfx = createCanvas(128, 128);
            const p = this.paintGfx.getContext("2d");
            p.fillStyle = "#000";
            p.beginPath(); p.arc(64, 64, 64, 0, 2 * Math.PI); p.fill();

            this._update();
        }

        _drawDrop(ctx, d) {
            if (!this.dropsGfx.length) return;

            let a = Math.max(0, Math.min(1, (d.r - this.minR) / this.deltaR * 0.9));
            a *= 1 / (0.5 * (d.spreadX + d.spreadY) + 1);
            a = Math.floor(a * (this.dropsGfx.length - 1));

            ctx.globalAlpha = 1;
            ctx.globalCompositeOperation = "source-over";
            ctx.drawImage(this.dropsGfx[a],
                (d.x - 1 * d.r * (d.spreadX + 1)) * this.scale,
                (d.y - 1.5 * d.r * (d.spreadY + 1)) * this.scale,
                (2 * d.r * 1 * (d.spreadX + 1)) * this.scale,
                (2 * d.r * 1.5 * (d.spreadY + 1)) * this.scale
            );
        }

        _paint(x, y, r = 30) {
            const ctx = this.textureCtx;
            ctx.globalCompositeOperation = "destination-out";
            ctx.drawImage(this.paintGfx,
                (x - r) * this.texturePixelDensity * this.scale,
                (y - r) * this.texturePixelDensity * this.scale,
                (2 * r) * this.texturePixelDensity * this.scale,
                (2 * r) * this.texturePixelDensity * this.scale * 1.5
            );
        }

        _createDrop(props) {
            if (this.drops.length >= this.maxDrops) return null;
            return Object.assign(Object.create(dropProto), props);
        }

        _updateRain(dt) {
            const out = [];
            if (!this.raining) return out;

            const limit = this.rainLimit * dt * this.areaMultiplier;
            let i = 0;
            while (Math.random() <= this.rainChance * dt * this.areaMultiplier && i < limit) {
                i++;
                const rr = random(this.minR, this.maxR, t => Math.pow(t, 3));
                const d = this._createDrop({
                    x: random(this.width / this.scale),
                    y: random(this.height / this.scale * this.spawnArea[0], this.height / this.scale * this.spawnArea[1]),
                    r: rr,
                    momentum: 1 + 0.3 * (rr - this.minR) + random(0.5),
                    spreadX: 1.5,
                    spreadY: 1.5
                });
                if (d) out.push(d);
            }
            return out;
        }

        _updateTexture(dt) {
            if (this.textureCleaningIterations > 0) {
                this.textureCleaningIterations -= 1 * dt;
                this.textureCtx.globalCompositeOperation = "destination-out";
                this.textureCtx.fillStyle = `rgba(0,0,0,${0.05 * dt})`;
                this.textureCtx.fillRect(0, 0, this.width * this.texturePixelDensity, this.height * this.texturePixelDensity);
            }

            if (this.raining) {
                this.drizzleCounter += this.drizzle * dt * this.areaMultiplier;
                times(this.drizzleCounter | 0, () => {
                    this.drizzleCounter--;
                    this._drawDrop(this.textureCtx, Object.assign(Object.create(dropProto), {
                        x: random(this.width / this.scale),
                        y: random(this.height / this.scale),
                        r: random(this.drizzleSize[0], this.drizzleSize[1], t => t * t)
                    }));
                });
            }
            this.ctx.drawImage(this.texture, 0, 0, this.width, this.height);
        }

        _updateDrops(dt) {
            const next = [];
            this._updateTexture(dt);
            next.push(...this._updateRain(dt));

            const W = this.width / this.scale;
            this.drops.sort((a, b) => (a.y * W + a.x) - (b.y * W + b.x));

            this.drops.forEach((d, idx) => {
                if (d.killed) return;

                if (Math.random() <= (d.r - this.minR * this.dropFallMultiplier) * (0.1 / this.deltaR) * dt) {
                    d.momentum += random(d.r / this.maxR * 4);
                }

                if (this.autoShrink && d.r <= this.minR && Math.random() <= 0.05 * dt) {
                    d.shrink += 0.01;
                }

                d.r -= d.shrink * dt;
                if (d.r <= 0) { d.killed = true; return; }

                if (this.raining) {
                    d.lastSpawn += d.momentum * dt * this.trailRate;
                    if (d.lastSpawn > d.nextSpawn) {
                        const child = this._createDrop({
                            x: d.x + 0.1 * random(-d.r, d.r),
                            y: d.y - 0.01 * d.r,
                            r: d.r * random(this.trailScaleRange[0], this.trailScaleRange[1]),
                            spreadY: 0.15 * d.momentum,
                            parent: d
                        });
                        if (child) {
                            next.push(child);
                            d.r *= Math.pow(0.97, dt);
                            d.lastSpawn = 0;
                            d.nextSpawn = random(this.minR, this.maxR) - 2 * d.momentum * this.trailRate + (this.maxR - d.r);
                        }
                    }
                }

                d.spreadX *= Math.pow(0.4, dt);
                d.spreadY *= Math.pow(0.7, dt);

                const falling = d.momentum > 0;
                if (falling && !d.killed) {
                    d.y += d.momentum;
                    d.x += d.momentumX;
                    if (d.y > this.height / this.scale + d.r) d.killed = true;
                }

                const mayCollide = (falling || d.isNew) && !d.killed;
                d.isNew = false;

                if (mayCollide) {
                    this.drops.slice(idx + 1, idx + 70).forEach(o => {
                        if (d === o || d.r <= o.r || d.parent === o || o.parent === d || o.killed) return;
                        const dx = o.x - d.x, dy = o.y - d.y;
                        const dist = Math.sqrt(dx * dx + dy * dy);
                        const cr = (d.r + o.r) * (this.collisionRadius + d.momentum * this.collisionRadiusIncrease * dt);
                        if (dist < cr) {
                            const PI = Math.PI;
                            let nr = Math.sqrt((PI * d.r * d.r + 0.8 * PI * o.r * o.r) / PI);
                            nr = Math.min(this.maxR, nr);
                            d.r = nr;
                            d.momentumX += 0.1 * dx;
                            d.spreadX = 0; d.spreadY = 0;
                            o.killed = true;
                            d.momentum = Math.max(o.momentum, Math.min(40, d.momentum + 0.04 * nr + 1));
                        }
                    });
                }

                d.momentum -= 0.1 * Math.max(1, 0.5 * this.minR - d.momentum) * dt;
                if (d.momentum < 0) d.momentum = 0;
                d.momentumX *= Math.pow(0.7, dt);

                if (!d.killed) {
                    next.push(d);
                    if (falling && this.drizzle > 0) this._paint(d.x, d.y, d.r * this.drizzleCleaningRadiusMultiplier);
                    this._drawDrop(this.ctx, d);
                }
            });

            this.drops = next;
        }

        _update() {
            this.ctx.clearRect(0, 0, this.width, this.height);

            const now = Date.now();
            if (this.lastRender == null) this.lastRender = now;

            let dt = (now - this.lastRender) / (1000 / 60);
            dt = Math.min(2, dt) * this.globalTimeScale;
            this.lastRender = now;

            this._updateDrops(dt);
            requestAnimationFrame(this._update.bind(this));
        }
    }

    // ============================================================
    // 7) Boot (same params as original)
    // ============================================================
    const canvas = document.getElementById("bg-canvas");
    const dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
    canvas.width = Math.floor(window.innerWidth * dpr);
    canvas.height = Math.floor(window.innerHeight * dpr);

    // load original assets
    loadImages([
        { name: "dropAlpha", src: DROP_ALPHA_SRC },
        { name: "dropColor", src: DROP_COLOR_SRC },
        { name: "tex", src: REFLECTION_JPEG_SRC }
    ]).then(({ dropAlpha, dropColor, tex }) => {

        // create raindrops
        const drops = new Raindrops(canvas.width, canvas.height, dpr, dropAlpha, dropColor);
        drops.trailRate = 1;
        drops.trailScaleRange = [0.2, 0.45];

        // fg/bg texture canvases (same as original sizes)
        const fg = createCanvas(192, 128);
        const bg = createCanvas(384, 256);
        fg.getContext("2d").drawImage(tex, 0, 0, 192, 128);
        bg.getContext("2d").drawImage(tex, 0, 0, 384, 256);

        const renderer = new RainRenderer(
            canvas,
            drops.canvas,
            fg,
            bg,
            null,
            { brightness: 1.04, alphaMultiply: 10, alphaSubtract: 3, minRefraction: 128, maxRefraction: 512 }
        );

        // minimal GSAP replacement: 1s easeOutQuint tween to parallax target
        let tween = null;
        const T = { x: 0, y: 0 };

        function tweenTo(nx, ny) {
            const sx = T.x, sy = T.y;
            const t0 = performance.now(), dur = 1000;
            function step(now) {
                const t = clamp((now - t0) / dur, 0, 1);
                const k = easeOutQuint(t);
                T.x = sx + (nx - sx) * k;
                T.y = sy + (ny - sy) * k;
                renderer.parallaxX = T.x;
                renderer.parallaxY = T.y;
                if (t < 1) tween = requestAnimationFrame(step);
            }
            if (tween) cancelAnimationFrame(tween);
            tween = requestAnimationFrame(step);
        }

        document.addEventListener("mousemove", (ev) => {
            // intentionally same style as original (pageX / canvas.width)
            const px = (ev.pageX ?? ev.clientX ?? 0);
            const py = (ev.pageY ?? ev.clientY ?? 0);
            const x = px / canvas.width * 2 - 1;
            const y = py / canvas.height * 2 - 1;
            tweenTo(x, y);
        }, { passive: true });

    }).catch(err => {
        console.error(err);
    });

})();
