
if(!window._impulseGyro){window._impulseGyro=(function(){var granted=false,listeners=[],requested=false,needsPerm=typeof DeviceOrientationEvent!=='undefined'&&typeof DeviceOrientationEvent.requestPermission==='function';function activate(){if(granted)return;granted=true;window._impulseGyro.granted=true;for(var i=0;i<listeners.length;i++)window.addEventListener('deviceorientation',listeners[i],{passive:true});}function reqGesture(){if(requested)return;requested=true;document.addEventListener('touchstart',function f(){document.removeEventListener('touchstart',f);DeviceOrientationEvent.requestPermission().then(function(s){if(s==='granted')activate();}).catch(function(){});},{once:true,passive:true});}return{granted:false,register:function(fn){listeners.push(fn);if(granted)window.addEventListener('deviceorientation',fn,{passive:true});else if(needsPerm)reqGesture();else if(typeof DeviceOrientationEvent!=='undefined')activate();},unregister:function(fn){window.removeEventListener('deviceorientation',fn);var i=listeners.indexOf(fn);if(i!==-1)listeners.splice(i,1);}};})();}

(function() {
'use strict';

function hexToVec3(hex) {
    hex = String(hex == null ? '' : hex).trim();

    

    

    
    if (hex.indexOf('rgb') === 0) {
        var m = hex.match(/\d+(?:\.\d+)?/g);
        if (m && m.length >= 3) {
            return [
                Math.min(255, parseFloat(m[0])) / 255,
                Math.min(255, parseFloat(m[1])) / 255,
                Math.min(255, parseFloat(m[2])) / 255
            ];
        }
    }
    hex = hex.replace('#','');

    if (hex.length === 3) hex = hex.split('').map(function(c){ return c+c; }).join('');
    if (hex.length !== 6) return [0.5, 0.5, 0.5]; 
    var r = parseInt(hex.substring(0,2), 16);
    var g = parseInt(hex.substring(2,4), 16);
    var b = parseInt(hex.substring(4,6), 16);
    if (isNaN(r) || isNaN(g) || isNaN(b)) return [0.5, 0.5, 0.5];
    return [r / 255, g / 255, b / 255];
}

var NOISE_GLSL = [
'vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}',
'vec4 mod289(vec4 x){return x-floor(x*(1.0/289.0))*289.0;}',
'vec4 permute(vec4 x){return mod289(((x*34.0)+1.0)*x);}',
'vec4 taylorInvSqrt(vec4 r){return 1.79284291400159-0.85373472095314*r;}',
'float snoise(vec3 v){',
'  const vec2 C=vec2(1.0/6.0,1.0/3.0);',
'  const vec4 D=vec4(0.0,0.5,1.0,2.0);',
'  vec3 i=floor(v+dot(v,C.yyy));',
'  vec3 x0=v-i+dot(i,C.xxx);',
'  vec3 g=step(x0.yzx,x0.xyz);',
'  vec3 l=1.0-g;',
'  vec3 i1=min(g.xyz,l.zxy);',
'  vec3 i2=max(g.xyz,l.zxy);',
'  vec3 x1=x0-i1+C.xxx;',
'  vec3 x2=x0-i2+C.yyy;',
'  vec3 x3=x0-D.yyy;',
'  i=mod289(i);',
'  vec4 p=permute(permute(permute(',
'    i.z+vec4(0.0,i1.z,i2.z,1.0))',
'    +i.y+vec4(0.0,i1.y,i2.y,1.0))',
'    +i.x+vec4(0.0,i1.x,i2.x,1.0));',
'  float n_=0.142857142857;',
'  vec3 ns=n_*D.wyz-D.xzx;',
'  vec4 j=p-49.0*floor(p*ns.z*ns.z);',
'  vec4 x_=floor(j*ns.z);',
'  vec4 y_=floor(j-7.0*x_);',
'  vec4 x=x_*ns.x+ns.yyyy;',
'  vec4 y=y_*ns.x+ns.yyyy;',
'  vec4 h=1.0-abs(x)-abs(y);',
'  vec4 b0=vec4(x.xy,y.xy);',
'  vec4 b1=vec4(x.zw,y.zw);',
'  vec4 s0=floor(b0)*2.0+1.0;',
'  vec4 s1=floor(b1)*2.0+1.0;',
'  vec4 sh=-step(h,vec4(0.0));',
'  vec4 a0=b0.xzyw+s0.xzyw*sh.xxyy;',
'  vec4 a1=b1.xzyw+s1.xzyw*sh.zzww;',
'  vec3 p0=vec3(a0.xy,h.x);',
'  vec3 p1=vec3(a0.zw,h.y);',
'  vec3 p2=vec3(a1.xy,h.z);',
'  vec3 p3=vec3(a1.zw,h.w);',
'  vec4 norm=taylorInvSqrt(vec4(dot(p0,p0),dot(p1,p1),dot(p2,p2),dot(p3,p3)));',
'  p0*=norm.x; p1*=norm.y; p2*=norm.z; p3*=norm.w;',
'  vec4 m=max(0.6-vec4(dot(x0,x0),dot(x1,x1),dot(x2,x2),dot(x3,x3)),0.0);',
'  m=m*m;',
'  return 42.0*dot(m*m,vec4(dot(p0,x0),dot(p1,x1),dot(p2,x2),dot(p3,x3)));',
'}'
].join('\n');

var BLEND_GLSL = [
'vec3 blendNormal(vec3 base, vec3 blend, float opacity){',
'  return blend*opacity+base*(1.0-opacity);',
'}'
].join('\n');

var VERT_SRC = [
'precision highp float;',
'attribute vec4 position;',
'attribute vec2 uv;',
'attribute vec2 uvNorm;',
'uniform mat4 u_projMatrix;',
'uniform vec2 u_resolution;',
'uniform float u_time;',
'uniform float u_noiseAmp;',
'uniform float u_noiseSeed;',
'uniform float u_noiseFreq;', 
'uniform vec3 u_baseColor;',
'uniform vec3 u_waveColor1;',
'uniform vec3 u_waveColor2;',
'uniform vec3 u_waveColor3;',
'uniform float u_waveActive1;',
'uniform float u_waveActive2;',
'uniform float u_waveActive3;',
'uniform vec2 u_mousePosition;',
'uniform float u_edgeSharpness;',
'varying vec3 v_color;',
'',
NOISE_GLSL,
'',
BLEND_GLSL,
'',
'void main(){',
'  float time = u_time * 5e-6;',
'  vec2 noiseCoord = u_resolution * uvNorm * vec2(14e-5, 29e-5) * u_noiseFreq;',
'',
'  // Plane tilt',
'  float tilt = u_resolution.y * 0.5 * uvNorm.y;',
'',
'  // Vertex noise displacement',
'  float n = snoise(vec3(',
'    noiseCoord.x * 3.0 + time * 3.0,',
'    noiseCoord.y * 4.0,',
'    time * 10.0 + u_noiseSeed',
'  )) * u_noiseAmp;',
'  n *= 1.0 - pow(abs(uvNorm.y), 2.0);',
'  n = max(0.0, n);',
'',
'  vec3 pos = vec3(position.x, position.y + tilt + n, position.z);',
'',
'  // Mouse interaction — warp noise coords near cursor',
'  vec2 mUv = uvNorm * 0.5 + 0.5;',
'  vec2 mDiff = mUv - u_mousePosition;',
'  float mDist = length(mDiff);',
'  float mInf = smoothstep(0.35, 0.0, mDist) * u_noiseAmp * 0.4;',
'  pos.y += mInf;',
'',
'  v_color = u_baseColor;',
'',
'  if(u_waveActive1 > 0.5){',
'    float w1 = smoothstep(0.1, 0.63, snoise(vec3(',
'      noiseCoord.x*2.33+time*6.5,',
'      noiseCoord.y*3.33,',
'      time*11.3+u_noiseSeed+10.0',
'    ))*0.5+0.5);',
'    v_color = blendNormal(v_color, u_waveColor1, pow(w1, u_edgeSharpness));',
'  }',
'',
'  if(u_waveActive2 > 0.5){',
'    float w2 = smoothstep(0.1, 0.7, snoise(vec3(',
'      noiseCoord.x*2.66+time*6.8,',
'      noiseCoord.y*3.66,',
'      time*11.6+u_noiseSeed+20.0',
'    ))*0.5+0.5);',
'    v_color = blendNormal(v_color, u_waveColor2, pow(w2, u_edgeSharpness));',
'  }',
'',
'  if(u_waveActive3 > 0.5){',
'    float w3 = smoothstep(0.1, 0.77, snoise(vec3(',
'      noiseCoord.x*3.0+time*7.1,',
'      noiseCoord.y*4.0,',
'      time*11.9+u_noiseSeed+30.0',
'    ))*0.5+0.5);',
'    v_color = blendNormal(v_color, u_waveColor3, pow(w3, u_edgeSharpness));',
'  }',
'',
'  gl_Position = u_projMatrix * vec4(pos, 1.0);',
'}'
].join('\n');

var FRAG_SRC = [
'precision highp float;',
'uniform vec2 u_resolution;',
'uniform float u_shadowPower;',
'uniform float u_shadowScale;',
'varying vec3 v_color;',
'void main(){',
'  vec3 color = v_color;',
'  vec2 st = gl_FragCoord.xy / u_resolution;',
'  float shadowVal = clamp(st.y + sin(-12.0)*st.x, 0.0, 1.0);',
'  // Proportional darken (hue-preserving). The original code did',
'  // `color.g -= shadow` — subtracting ONLY the green channel. On the',
'  // dark blue/purple default palette that happened to read as warm',
'  // depth, but it is a hue shift, not a darken: on a light palette',
'  // (e.g. all-white lava) subtracting green turns white into magenta',
'  // — the "pink in the top-right corner" report. Multiplying every',
'  // channel by (1 - shadow) darkens any colour toward grey/black',
'  // without shifting hue, so white lava shades to grey and the',
'  // default palette still gains depth in the shadow region.',
'  float shadow = pow(shadowVal, u_shadowPower) * 0.4 * u_shadowScale;',
'  color *= (1.0 - shadow);',
'  gl_FragColor = vec4(color, 1.0);',
'}'
].join('\n');

function createShader(gl, type, src) {
    var s = gl.createShader(type);
    gl.shaderSource(s, src);
    gl.compileShader(s);
    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
        console.error('Shader compile error:', gl.getShaderInfoLog(s));
        gl.deleteShader(s);
        return null;
    }
    return s;
}

function createProgram(gl, vSrc, fSrc) {
    var vs = createShader(gl, gl.VERTEX_SHADER, vSrc);
    var fs = createShader(gl, gl.FRAGMENT_SHADER, fSrc);
    if (!vs || !fs) return null;
    var prog = gl.createProgram();
    gl.attachShader(prog, vs);
    gl.attachShader(prog, fs);
    gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
        console.error('Program link error:', gl.getProgramInfoLog(prog));
        return null;
    }
    return prog;
}

function buildPlane(w, h, xSegs, ySegs) {
    var vCount = (xSegs + 1) * (ySegs + 1);
    var positions = new Float32Array(vCount * 3);
    var uvs = new Float32Array(vCount * 2);
    var uvNorms = new Float32Array(vCount * 2);
    var indices = new Uint16Array(xSegs * ySegs * 6);
    var halfW = w / 2, halfH = h / 2;
    var segW = w / xSegs, segH = h / ySegs;
    var idx = 0;

    for (var iy = 0; iy <= ySegs; iy++) {
        for (var ix = 0; ix <= xSegs; ix++) {
            var vi = iy * (xSegs + 1) + ix;
            positions[vi * 3]     = -halfW + ix * segW;
            positions[vi * 3 + 1] = halfH - iy * segH;
            positions[vi * 3 + 2] = 0;
            uvs[vi * 2]     = ix / xSegs;
            uvs[vi * 2 + 1] = 1.0 - iy / ySegs;
            uvNorms[vi * 2]     = (ix / xSegs) * 2.0 - 1.0;
            uvNorms[vi * 2 + 1] = 1.0 - (iy / ySegs) * 2.0;
            if (ix < xSegs && iy < ySegs) {
                var a = vi, b = vi + 1, c = vi + xSegs + 1, d = vi + xSegs + 2;
                indices[idx++] = a; indices[idx++] = c; indices[idx++] = b;
                indices[idx++] = b; indices[idx++] = c; indices[idx++] = d;
            }
        }
    }
    return { positions: positions, uvs: uvs, uvNorms: uvNorms, indices: indices, indexCount: idx };
}

function MotionLava(canvas, opts) {
    opts = opts || {};
    this.canvas = canvas;
    this.playing = false;
    this.rafId = null;
    this.speed = opts.speed !== undefined ? opts.speed : 1;
    this.seed = opts.seed !== undefined ? opts.seed : 5;
    this.amp = opts.amp !== undefined ? opts.amp : 320;
    this.noiseFreq = opts.noiseFreq !== undefined ? opts.noiseFreq : 1.0;
    this.shadowScale = opts.shadowScale !== undefined ? opts.shadowScale : 1.0;

    
    this.edgeSharpness = opts.edgeSharpness !== undefined ? opts.edgeSharpness : 4.0;
    this.colors = (opts.colors || ['#ef008f','#6ec3f4','#7038ff','#ffba27']).map(hexToVec3);

    

    this.mouseSensitivity = (opts.mouseSensitivity !== undefined) ? opts.mouseSensitivity : 1;
    this.t = 1253106;
    this.lastFrame = 0;

    var _lvMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
    var gl = canvas.getContext('webgl', { antialias: !_lvMobile, alpha: false, preserveDrawingBuffer: true });
    if (!gl) { console.warn('ImpulseMotionLava: WebGL not available'); return; }
    this.gl = gl;

    this.program = createProgram(gl, VERT_SRC, FRAG_SRC);
    if (!this.program) return;

    this.uLoc = {};
    var uNames = [
        'u_projMatrix','u_resolution','u_time','u_noiseAmp','u_noiseSeed','u_noiseFreq','u_shadowScale',
        'u_baseColor','u_waveColor1','u_waveColor2','u_waveColor3',
        'u_waveActive1','u_waveActive2','u_waveActive3','u_shadowPower',
        'u_mousePosition','u_edgeSharpness'
    ];
    for (var i = 0; i < uNames.length; i++) {
        this.uLoc[uNames[i]] = gl.getUniformLocation(this.program, uNames[i]);
    }

    this.aPos = gl.getAttribLocation(this.program, 'position');
    this.aUv = gl.getAttribLocation(this.program, 'uv');
    this.aUvNorm = gl.getAttribLocation(this.program, 'uvNorm');

    this.posBuf = gl.createBuffer();
    this.uvBuf = gl.createBuffer();
    this.uvNormBuf = gl.createBuffer();
    this.idxBuf = gl.createBuffer();
    this.indexCount = 0;

    this._onResize = this.resize.bind(this);
    window.addEventListener('resize', this._onResize);

    this.mouseX = 0.5; this.mouseY = 0.5;
    this._targetMX = 0.5; this._targetMY = 0.5;
    var self = this;
    this._onMouse = function(e) {
        var r = canvas.getBoundingClientRect();
        if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) {
            self._targetMX = (e.clientX - r.left) / r.width;
            self._targetMY = 1 - (e.clientY - r.top) / r.height;
        }
    };
    document.addEventListener('mousemove', this._onMouse);
    
    if (!window._impulseScreenMode) {
    this._onTouch = function(e) {
        if (e.touches.length === 1) {
            var r = canvas.getBoundingClientRect();
            self._targetMX = (e.touches[0].clientX - r.left) / r.width;
            self._targetMY = 1 - (e.touches[0].clientY - r.top) / r.height;
        }
    };
    this._onTouchEnd = function() { self._targetMX = 0.5; self._targetMY = 0.5; };
    document.addEventListener('touchmove', this._onTouch, {passive: true});
    document.addEventListener('touchend', this._onTouchEnd, {passive: true});
    }
    this._lvMobile = _lvMobile;

    this._gyroBaseB = null; this._gyroBaseG = null;
    this._onGyro = function(e) {
        if (e.beta === null && e.gamma === null) return;
        if (self._gyroBaseB === null) { self._gyroBaseB = e.beta || 0; self._gyroBaseG = e.gamma || 0; }
        self._targetMX = 0.5 + Math.max(-1, Math.min(1, ((e.gamma || 0) - self._gyroBaseG) / 15)) * 0.4;
        self._targetMY = 0.5 + Math.max(-1, Math.min(1, ((e.beta || 0) - self._gyroBaseB) / 15)) * 0.4;
    };
    if (window._impulseGyro) window._impulseGyro.register(this._onGyro);
    else window.addEventListener('deviceorientation', this._onGyro, {passive: true});
    this.resize();
}

MotionLava.prototype.resize = function() {
    if (!this.gl) return;
    var gl = this.gl;
    var w = this.canvas.clientWidth || window.innerWidth;
    var h = this.canvas.clientHeight || window.innerHeight;
    var dpr = Math.min((window.devicePixelRatio || 1) * (this._lvMobile ? 0.5 : 1), 2);
    this.canvas.width = w * dpr;
    this.canvas.height = h * dpr;
    this.width = w;
    this.height = h;
    gl.viewport(0, 0, this.canvas.width, this.canvas.height);

    var xSegs = Math.ceil(w * 0.06);
    var ySegs = Math.ceil(h * 0.16);
    var plane = buildPlane(w, h, xSegs, ySegs);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuf);
    gl.bufferData(gl.ARRAY_BUFFER, plane.positions, gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuf);
    gl.bufferData(gl.ARRAY_BUFFER, plane.uvs, gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvNormBuf);
    gl.bufferData(gl.ARRAY_BUFFER, plane.uvNorms, gl.STATIC_DRAW);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.idxBuf);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, plane.indices, gl.STATIC_DRAW);
    this.indexCount = plane.indexCount;
};

MotionLava.prototype.play = function() {
    if (this.playing || !this.gl) return;
    this.playing = true;
    this.lastFrame = 0;
    var self = this;
    function tick(ts) {
        if (!self.playing) return;
        if (self.lastFrame !== 0) {
            self.t += Math.min(ts - self.lastFrame, 1000 / 15) * self.speed;
        }
        self.lastFrame = ts;
        self._render();
        self.rafId = requestAnimationFrame(tick);
    }
    this.rafId = requestAnimationFrame(tick);
};

MotionLava.prototype.pause = function() {
    this.playing = false;
    if (this.rafId) { cancelAnimationFrame(this.rafId); this.rafId = null; }
};

MotionLava.prototype._render = function() {
    var gl = this.gl;
    if (!gl) return;
    var w = this.width, h = this.height;

    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(this.program);

    gl.uniformMatrix4fv(this.uLoc.u_projMatrix, false, new Float32Array([
        2/w, 0, 0, 0,   0, 2/h, 0, 0,   0, 0, -0.001, 0,   0, 0, 0, 1
    ]));
    gl.uniform2f(this.uLoc.u_resolution, w, h);
    gl.uniform1f(this.uLoc.u_time, this.t);
    gl.uniform1f(this.uLoc.u_noiseAmp, this.amp);
    gl.uniform1f(this.uLoc.u_noiseSeed, this.seed);
    gl.uniform1f(this.uLoc.u_noiseFreq, this.noiseFreq);
    gl.uniform1f(this.uLoc.u_shadowScale, this.shadowScale);
    gl.uniform1f(this.uLoc.u_shadowPower, w < 600 ? 5.0 : 6.0);
    gl.uniform1f(this.uLoc.u_edgeSharpness, this.edgeSharpness);

    this.mouseX += (this._targetMX - this.mouseX) * 0.05;
    this.mouseY += (this._targetMY - this.mouseY) * 0.05;

    
    
    var _ms = this.mouseSensitivity;
    var _mx = 0.5 + (this.mouseX - 0.5) * _ms;
    var _my = 0.5 + (this.mouseY - 0.5) * _ms;
    gl.uniform2f(this.uLoc.u_mousePosition, _mx, _my);

    var c = this.colors;
    gl.uniform3fv(this.uLoc.u_baseColor, c[0]);
    gl.uniform3fv(this.uLoc.u_waveColor1, c.length > 1 ? c[1] : c[0]);
    gl.uniform3fv(this.uLoc.u_waveColor2, c.length > 2 ? c[2] : c[0]);
    gl.uniform3fv(this.uLoc.u_waveColor3, c.length > 3 ? c[3] : c[0]);
    gl.uniform1f(this.uLoc.u_waveActive1, c.length > 1 ? 1.0 : 0.0);
    gl.uniform1f(this.uLoc.u_waveActive2, c.length > 2 ? 1.0 : 0.0);
    gl.uniform1f(this.uLoc.u_waveActive3, c.length > 3 ? 1.0 : 0.0);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuf);
    if(this.aPos>=0){gl.enableVertexAttribArray(this.aPos);
    gl.vertexAttribPointer(this.aPos, 3, gl.FLOAT, false, 0, 0);}
    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuf);
    if(this.aUv>=0){gl.enableVertexAttribArray(this.aUv);
    gl.vertexAttribPointer(this.aUv, 2, gl.FLOAT, false, 0, 0);}
    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvNormBuf);
    if(this.aUvNorm>=0){gl.enableVertexAttribArray(this.aUvNorm);
    gl.vertexAttribPointer(this.aUvNorm, 2, gl.FLOAT, false, 0, 0);}

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.idxBuf);
    gl.drawElements(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_SHORT, 0);
};

MotionLava.prototype.updateColors = function(hexColors) {
    this.colors = hexColors.map(hexToVec3);
};

MotionLava.prototype.updateSpeed = function(speed) {
    this.speed = speed;
};

MotionLava.prototype.setMouseSensitivity = function(v) {

    
    var n = parseFloat(v);
    if (isNaN(n)) n = 1;
    this.mouseSensitivity = Math.max(0, Math.min(2, n));
};

MotionLava.prototype.setShadowScale = function(v) {

    

    var n = parseFloat(v);
    if (isNaN(n)) n = 1;
    this.shadowScale = Math.max(0, Math.min(1, n));
};

MotionLava.prototype.setEdgeSharpness = function(v) {

    
    
    var n = parseFloat(v);
    if (isNaN(n)) n = 4;
    this.edgeSharpness = Math.max(1, Math.min(12, n));
};

MotionLava.prototype.destroy = function() {
    this.pause();
    if (this._onResize) window.removeEventListener('resize', this._onResize);
    if (this._onMouse) document.removeEventListener('mousemove', this._onMouse);
    if (this._onTouch) document.removeEventListener('touchmove', this._onTouch);
    if (this._onTouchEnd) document.removeEventListener('touchend', this._onTouchEnd);
    if (this._onGyro) { if (window._impulseGyro) window._impulseGyro.unregister(this._onGyro); else window.removeEventListener('deviceorientation', this._onGyro); }
    if (this.gl) {
        var gl = this.gl;
        if (this.program) gl.deleteProgram(this.program);
        if (this.posBuf) gl.deleteBuffer(this.posBuf);
        if (this.uvBuf) gl.deleteBuffer(this.uvBuf);
        if (this.uvNormBuf) gl.deleteBuffer(this.uvNormBuf);
        if (this.idxBuf) gl.deleteBuffer(this.idxBuf);
        
    }
    this.gl = null;
    this.canvas = null;
};

window.ImpulseMotionLava = MotionLava;
})();
