
(function() {
    'use strict';

    var sharedCanvas = null;    
    var gl           = null;    
    var program      = null;    
    var aPosLoc      = -1;      
    var uLoc         = {};      
    var quadBuffer   = null;    
    var instances    = new Map(); 
    var rafId        = 0;       
    var io           = null;    
    var startTime    = (typeof performance !== 'undefined' && performance.now)
                         ? performance.now() / 1000
                         : Date.now() / 1000;

    
    var SWIRL = [
        [ 1.0,  1.0],
        [-0.6,  0.8],
        [ 0.7, -0.5],
        [-1.0, -1.0]
    ];

    var VS_SRC = [
        'attribute vec2 a_pos;',
        'varying vec2 v_uv;',
        'void main(){',
        '  v_uv = a_pos * 0.5 + 0.5;',
        '  gl_Position = vec4(a_pos, 0.0, 1.0);',
        '}'
    ].join('\n');

    var FS_SRC = [
        'precision mediump float;',
        'varying vec2 v_uv;',
        'uniform vec4 u_c0;',
        'uniform vec4 u_c1;',
        'uniform vec4 u_c2;',
        'uniform vec4 u_c3;',
        'uniform vec2 u_p0;',
        'uniform vec2 u_p1;',
        'uniform vec2 u_p2;',
        'uniform vec2 u_p3;',
        'uniform float u_time;',
        'uniform float u_grain;',
        'uniform float u_opacity;',
        
        'float hash21(vec2 p){return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);}',
        'void main(){',
        '  vec2 p = v_uv;',

        

        '  float d0 = distance(p, u_p0) + 0.005;',
        '  float d1 = distance(p, u_p1) + 0.005;',
        '  float d2 = distance(p, u_p2) + 0.005;',
        '  float d3 = distance(p, u_p3) + 0.005;',
        '  float w0 = 1.0 / pow(d0, 2.5);',
        '  float w1 = 1.0 / pow(d1, 2.5);',
        '  float w2 = 1.0 / pow(d2, 2.5);',
        '  float w3 = 1.0 / pow(d3, 2.5);',
        '  float wsum = w0 + w1 + w2 + w3;',

        

        
        '  vec4 pc0 = vec4(u_c0.rgb * u_c0.a, u_c0.a);',
        '  vec4 pc1 = vec4(u_c1.rgb * u_c1.a, u_c1.a);',
        '  vec4 pc2 = vec4(u_c2.rgb * u_c2.a, u_c2.a);',
        '  vec4 pc3 = vec4(u_c3.rgb * u_c3.a, u_c3.a);',
        '  vec4 col = (pc0 * w0 + pc1 * w1 + pc2 * w2 + pc3 * w3) / wsum;',
        '  if (u_grain > 0.0001) {',
        '    float g = (hash21(p * 1000.0 + u_time * 13.0) - 0.5) * u_grain * 0.06;',
        '    col.rgb += vec3(g) * col.a;',
        '  }',
        '  gl_FragColor = col * u_opacity;',
        '}'
    ].join('\n');

    

    
    
    function parseColor(str) {
        if (!str) return [0, 0, 0, 0];
        str = String(str).trim().toLowerCase();
        if (str === 'transparent') return [0, 0, 0, 0];
        var m = str.match(/^#([0-9a-f]{6})$/);
        if (m) {
            var n = parseInt(m[1], 16);
            return [((n >> 16) & 0xff) / 255, ((n >> 8) & 0xff) / 255, (n & 0xff) / 255, 1];
        }
        m = str.match(/^#([0-9a-f]{3})$/);
        if (m) {
            var h = m[1];
            return [
                parseInt(h[0] + h[0], 16) / 255,
                parseInt(h[1] + h[1], 16) / 255,
                parseInt(h[2] + h[2], 16) / 255,
                1
            ];
        }
        m = str.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)$/);
        if (m) {
            return [+m[1] / 255, +m[2] / 255, +m[3] / 255, m[4] !== undefined ? +m[4] : 1];
        }
        return [0, 0, 0, 0];
    }

    function compileShader(type, src) {
        var s = gl.createShader(type);
        gl.shaderSource(s, src);
        gl.compileShader(s);
        if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {

            
            try { console.error('[ImpulseMesh] shader compile failed:', gl.getShaderInfoLog(s)); } catch (_) {}
            gl.deleteShader(s);
            return null;
        }
        return s;
    }

    function initGL() {
        if (gl) return true;
        if (gl === false) return false; 

        try {

            if (typeof OffscreenCanvas !== 'undefined') {
                sharedCanvas = new OffscreenCanvas(256, 256);
            } else {
                sharedCanvas = document.createElement('canvas');
                sharedCanvas.width = 256;
                sharedCanvas.height = 256;
                sharedCanvas.style.display = 'none';
            }
            var attrs = {
                alpha: true,
                premultipliedAlpha: true,
                antialias: false,
                preserveDrawingBuffer: false,
                powerPreference: 'low-power'
            };
            gl = sharedCanvas.getContext('webgl', attrs)
              || sharedCanvas.getContext('experimental-webgl', attrs);
            if (!gl) { gl = false; return false; }
        } catch (e) {
            gl = false;
            return false;
        }

        var vs = compileShader(gl.VERTEX_SHADER,   VS_SRC);
        var fs = compileShader(gl.FRAGMENT_SHADER, FS_SRC);
        if (!vs || !fs) { gl = false; return false; }
        program = gl.createProgram();
        gl.attachShader(program, vs);
        gl.attachShader(program, fs);
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            try { console.error('[ImpulseMesh] program link failed:', gl.getProgramInfoLog(program)); } catch (_) {}
            gl = false;
            return false;
        }
        gl.useProgram(program);

        aPosLoc       = gl.getAttribLocation(program, 'a_pos');
        uLoc.c0       = gl.getUniformLocation(program, 'u_c0');
        uLoc.c1       = gl.getUniformLocation(program, 'u_c1');
        uLoc.c2       = gl.getUniformLocation(program, 'u_c2');
        uLoc.c3       = gl.getUniformLocation(program, 'u_c3');
        uLoc.p0       = gl.getUniformLocation(program, 'u_p0');
        uLoc.p1       = gl.getUniformLocation(program, 'u_p1');
        uLoc.p2       = gl.getUniformLocation(program, 'u_p2');
        uLoc.p3       = gl.getUniformLocation(program, 'u_p3');
        uLoc.time     = gl.getUniformLocation(program, 'u_time');
        uLoc.grain    = gl.getUniformLocation(program, 'u_grain');
        uLoc.opacity  = gl.getUniformLocation(program, 'u_opacity');

        quadBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            -1, -1,  1, -1, -1,  1,
            -1,  1,  1, -1,  1,  1
        ]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(aPosLoc);
        gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, 0, 0);

        
        
        gl.enable(gl.BLEND);
        gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
        return true;
    }

    

    function makeInstance(canvas) {
        var inst = {
            canvas: canvas,
            ctx2d: canvas.getContext('2d'),
            visible: true,

            
            phase: Math.random() * Math.PI * 2,
            target: {
                corners: [
                    { x: 0.1, y: 0.1, r: 0, g: 0, b: 0, a: 0 },
                    { x: 0.9, y: 0.1, r: 0, g: 0, b: 0, a: 0 },
                    { x: 0.1, y: 0.9, r: 0, g: 0, b: 0, a: 0 },
                    { x: 0.9, y: 0.9, r: 0, g: 0, b: 0, a: 0 }
                ],
                mouseX: 0.5, mouseY: 0.5,
                mouseSens: 0,
                grain: 0,
                speedSec: 0,
                opacity: 1
            },
            current: null
        };

        inst.current = cloneState(inst.target);
        return inst;
    }

    function cloneState(s) {
        return {
            corners: s.corners.map(function(c){return{x:c.x,y:c.y,r:c.r,g:c.g,b:c.b,a:c.a};}),
            mouseX: s.mouseX, mouseY: s.mouseY,
            mouseSens: s.mouseSens,
            grain: s.grain,
            speedSec: s.speedSec,
            opacity: s.opacity
        };
    }

    function lerp(a, b, t) { return a + (b - a) * t; }

    

    function applyState(inst, state) {
        if (!state) return;
        var t = inst.target;
        if (Array.isArray(state.corners)) {
            for (var i = 0; i < 4; i++) {
                var src = state.corners[i];
                if (!src) {

                    t.corners[i].r = 0;
                    t.corners[i].g = 0;
                    t.corners[i].b = 0;
                    t.corners[i].a = 0;
                    continue;
                }
                var dst = t.corners[i];
                if (src.x !== undefined) dst.x = Math.max(0, Math.min(1, +src.x));
                if (src.y !== undefined) dst.y = Math.max(0, Math.min(1, +src.y));
                if (src.color !== undefined) {
                    var rgba = parseColor(src.color);
                    dst.r = rgba[0]; dst.g = rgba[1]; dst.b = rgba[2]; dst.a = rgba[3];
                }
            }
        }
        if (state.mouseSens !== undefined) t.mouseSens = Math.max(0, +state.mouseSens || 0);
        if (state.grain     !== undefined) t.grain     = Math.max(0, Math.min(1, +state.grain || 0));
        if (state.speedSec  !== undefined) t.speedSec  = Math.max(0, +state.speedSec || 0);
        if (state.opacity   !== undefined) t.opacity   = Math.max(0, Math.min(1, +state.opacity || 0));
    }

    function renderInstance(inst, timeSec) {
        var canvas = inst.canvas;

        
        
        var rect = canvas.getBoundingClientRect();
        var w = rect.width;
        var h = rect.height;
        if (w < 2 || h < 2) return;

        
        
        var dpr = Math.min(window.devicePixelRatio || 1, 2);
        var cw = Math.round(w * dpr);
        var ch = Math.round(h * dpr);

        
        if (canvas.width  !== cw) canvas.width  = cw;
        if (canvas.height !== ch) canvas.height = ch;

        if (sharedCanvas.width  < cw) sharedCanvas.width  = cw;
        if (sharedCanvas.height < ch) sharedCanvas.height = ch;
        gl.viewport(0, 0, cw, ch);

        var cur = inst.current;
        var tgt = inst.target;

        
        var k = 0.10;
        for (var i = 0; i < 4; i++) {
            var cc = cur.corners[i], tc = tgt.corners[i];
            cc.x = lerp(cc.x, tc.x, k);
            cc.y = lerp(cc.y, tc.y, k);
            cc.r = lerp(cc.r, tc.r, k);
            cc.g = lerp(cc.g, tc.g, k);
            cc.b = lerp(cc.b, tc.b, k);
            cc.a = lerp(cc.a, tc.a, k);
        }
        cur.mouseX  = lerp(cur.mouseX,  tgt.mouseX,  k);
        cur.mouseY  = lerp(cur.mouseY,  tgt.mouseY,  k);
        cur.opacity = lerp(cur.opacity, tgt.opacity, k);

        

        var mxRaw = (cur.mouseX - 0.5) * 2;
        var myRaw = (cur.mouseY - 0.5) * 2;
        var mScale = tgt.mouseSens * 0.07;
        var mx = mxRaw * mScale;
        var my = myRaw * mScale;

        var driftMag = (tgt.speedSec > 0) ? 0.025 : 0;
        var driftFreq = (tgt.speedSec > 0) ? (1 / tgt.speedSec) : 0;
        var t = (timeSec - startTime) * driftFreq;

        for (var j = 0; j < 4; j++) {
            var c = cur.corners[j];
            var s = SWIRL[j];
            var dx = driftMag * Math.sin(t * 0.6 + inst.phase + j * 1.3);
            var dy = driftMag * Math.cos(t * 0.47 + inst.phase + j * 0.9);
            var px = c.x + mx * s[0] + dx;
            var py = c.y + my * s[1] + dy;
            gl.uniform2f(uLoc['p' + j], px, py);
            gl.uniform4f(uLoc['c' + j], c.r, c.g, c.b, c.a);
        }
        gl.uniform1f(uLoc.time,    timeSec);
        gl.uniform1f(uLoc.grain,   tgt.grain);
        gl.uniform1f(uLoc.opacity, cur.opacity);

        gl.clearColor(0, 0, 0, 0);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLES, 0, 6);

        

        var ctx = inst.ctx2d;
        ctx.clearRect(0, 0, cw, ch);
        if (typeof sharedCanvas.transferToImageBitmap === 'function') {
            try {
                var bm = sharedCanvas.transferToImageBitmap();
                ctx.drawImage(bm, 0, 0, cw, ch);
                if (bm.close) bm.close();
                return;
            } catch (_) {  }
        }
        ctx.drawImage(sharedCanvas, 0, 0, cw, ch);
    }

    

    

    
    
    function tick() {
        rafId = 0;
        if (!gl || gl === false) return;
        var now = (performance && performance.now ? performance.now() : Date.now()) / 1000;
        var anyRegistered = false;
        instances.forEach(function(inst) {
            anyRegistered = true;

            

            
            if (inst.ioReported && !inst.visible) return;
            try { renderInstance(inst, now); }
            catch (e) {
                
                try { console.error('[ImpulseMesh] render error:', e); } catch (_) {}
            }
        });

        
        if (anyRegistered) rafId = requestAnimationFrame(tick);
    }

    function ensureLoop() {
        if (rafId) return;
        if (instances.size > 0) rafId = requestAnimationFrame(tick);
    }

    

    function ensureIO() {
        if (io || typeof IntersectionObserver === 'undefined') return;
        io = new IntersectionObserver(function(entries) {
            entries.forEach(function(e) {
                var inst = instances.get(e.target);
                if (!inst) return;
                inst.visible    = !!e.isIntersecting;
                inst.ioReported = true;
                if (inst.visible) ensureLoop();
            });
        }, { rootMargin: '200px 0px', threshold: 0 });
    }

    function register(canvas, state) {
        if (!canvas || canvas.nodeName !== 'CANVAS') return false;
        if (!initGL()) return false;
        ensureIO();
        if (instances.has(canvas)) {
            
            if (state) applyState(instances.get(canvas), state);
            return true;
        }
        var inst = makeInstance(canvas);
        instances.set(canvas, inst);
        if (state) {
            applyState(inst, state);

            inst.current = cloneState(inst.target);
        }

        
        var wrap = canvas.parentElement;
        while (wrap && !wrap.classList.contains('impulse-mesh')) wrap = wrap.parentElement;
        if (wrap) wrap.classList.add('has-mesh-gl');
        if (io) io.observe(canvas);
        ensureLoop();
        return true;
    }

    function update(canvas, state) {
        var inst = instances.get(canvas);
        if (!inst) {

            
            
            return register(canvas, state);
        }
        applyState(inst, state);
        if (!rafId && inst.visible) ensureLoop();
        return true;
    }

    function setMouse(canvas, mx, my) {
        var inst = instances.get(canvas);
        if (!inst) return;
        inst.target.mouseX = Math.max(0, Math.min(1, +mx));
        inst.target.mouseY = Math.max(0, Math.min(1, +my));
        if (!rafId && inst.visible) ensureLoop();
    }

    function unregister(canvas) {
        var inst = instances.get(canvas);
        if (!inst) return;
        if (io) io.unobserve(canvas);

        try { inst.ctx2d.clearRect(0, 0, canvas.width, canvas.height); } catch (_) {}
        instances.delete(canvas);
    }

    window.ImpulseMeshRenderer = {
        register:   register,
        update:     update,
        setMouse:   setMouse,
        unregister: unregister
    };

    

    
    function bootstrap() {

        var pending = window._impulseMeshPending || [];
        for (var i = 0; i < pending.length; i++) {
            try {
                var p = pending[i];
                var c = (typeof p.canvas === 'string') ? document.getElementById(p.canvas) : p.canvas;
                if (c) register(c, p.state);
            } catch (_) {}
        }
        window._impulseMeshPending = [];

        
        
        var canvases = document.querySelectorAll('canvas.impulse-mesh-canvas[data-mesh-state]');
        for (var j = 0; j < canvases.length; j++) {
            var cv = canvases[j];
            if (instances.has(cv)) continue;
            try {
                var st = JSON.parse(cv.getAttribute('data-mesh-state'));
                register(cv, st);
            } catch (_) {}
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', bootstrap);
    } else {
        bootstrap();
    }
})();
