
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';
if(typeof THREE==='undefined'){window.ImpulseWave=function(){};return;}
function hexToColor(h){
    if(!h||typeof h!=='string')return new THREE.Color(0);
    var rm=h.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[0-9.]+)?\s*\)/);
    if(rm)return new THREE.Color('rgb('+rm[1]+','+rm[2]+','+rm[3]+')');
    var hm=h.match(/^#([0-9a-fA-F]{8})$/);
    if(hm)return new THREE.Color('#'+hm[1].slice(0,6));
    return new THREE.Color(h);
}

var PRESETS={
    flow:{

        twistFreqX:-0.42, twistFreqY:0.26, twistFreqZ:-0.36,
        twistPowX:2.4, twistPowY:0.9, twistPowZ:2.7,
        displaceFreqX:0.0038, displaceFreqZ:0.0115, displaceAmt:-5.2,
        posX:620, posY:-800, posZ:-11.1,
        rotX:-0.4496, rotY:-0.1176, rotZ:1.8744,
        scaleX:14, scaleY:15, scaleZ:8,
        glowAmt:1.98, glowPow:0.806, glowRamp:0.834,
        colors:['#6b8eff','#e256a0','#ff8054','#9333ea']
    },
    storm:{

        

        
        
        twistFreqX:-0.38, twistFreqY:0.22, twistFreqZ:-0.31,
        twistPowX:2.6, twistPowY:1.4, twistPowZ:3.2,
        displaceFreqX:0.0055, displaceFreqZ:0.015, displaceAmt:-9.2,
        posX:540, posY:-420, posZ:-11.1,
        rotX:-0.44, rotY:-0.06, rotZ:1.82,
        scaleX:13, scaleY:14, scaleZ:8,
        glowAmt:2.1, glowPow:0.72, glowRamp:0.82,
        colors:['#ffffff','#cccccc','#555555','#1a1a1a']
    },
    helix:{
        
        twistFreqX:0.059, twistFreqY:0.077, twistFreqZ:-0.518,
        twistPowX:3.95, twistPowY:5.85, twistPowZ:6.33,
        displaceFreqX:0.003234, displaceFreqZ:0.00799, displaceAmt:6.051,
        posX:-540, posY:-360, posZ:-11.1,
        rotX:-0.35, rotY:3.34, rotZ:2.0,
        scaleX:15, scaleY:14, scaleZ:10,
        glowAmt:0.6, glowPow:0.589, glowRamp:1.0,
        colors:['#041e42','#0077b6','#00b4d8','#caf0f8']
    },
    vortex:{
        
        twistFreqX:-0.48, twistFreqY:0.32, twistFreqZ:-0.42,
        twistPowX:2.8, twistPowY:0.9, twistPowZ:3.2,
        displaceFreqX:0.0052, displaceFreqZ:0.014, displaceAmt:-8.5,
        posX:380, posY:-400, posZ:-11.1,
        rotX:-0.28, rotY:-0.08, rotZ:1.72,
        scaleX:15, scaleY:16, scaleZ:9,
        glowAmt:4.2, glowPow:0.60, glowRamp:0.78,
        colors:['#ffdd00','#ff6b35','#d90429','#6a040f']
    },
    aurora:{
        
        twistFreqX:-0.65, twistFreqY:0.41, twistFreqZ:-0.58,
        twistPowX:3.63, twistPowY:0.7, twistPowZ:3.95,
        displaceFreqX:0.005831, displaceFreqZ:0.016001, displaceAmt:-7.821,
        posX:-620, posY:-800, posZ:-11.1,
        rotX:-0.4496, rotY:0.1176, rotZ:1.2672,
        scaleX:14, scaleY:15, scaleZ:8,
        glowAmt:1.98, glowPow:0.806, glowRamp:0.834,
        colors:['#1b1b4e','#762c9e','#06d6a0','#ffd166']
    }
}

function buildFoldedGeometry(w,h,sx,sy){
    var g=new THREE.PlaneGeometry(w,h,sx,sy);
    var pos=g.attributes.position,uv=g.attributes.uv;
    var xA=new THREE.Vector3(1,0,0),yA=new THREE.Vector3(0,1,0);
    for(var i=0;i<pos.count;i++){
        var v=new THREE.Vector3().fromBufferAttribute(pos,i);
        var uvY=new THREE.Vector2().fromBufferAttribute(uv,i).y;
        var fold=4-2*Math.pow(4*uvY*(1-uvY),9.5);
        if(v.x<-16){v.z+=fold;}
        else if(v.x<16){
            v.z=Math.cos(THREE.MathUtils.mapLinear(v.x,-16,16,0,Math.PI))*fold;
            v.x=Math.cos(THREE.MathUtils.mapLinear(v.x,-16,16,-Math.PI/2,Math.PI/2))*fold-16;
        }else{v.z-=fold;v.x=-v.x;}
        v.x+=w/4;
        v.applyAxisAngle(xA,-Math.PI/2);
        v.applyAxisAngle(yA,-Math.PI/2);
        pos.setXYZ(i,v.x,v.y,v.z);
    }
    g.computeVertexNormals();
    return g;
}

function createPalette(colors){
    var s=512,cv=document.createElement('canvas');
    cv.width=s;cv.height=s;
    var ctx=cv.getContext('2d');
    var c=colors;
    
    var g1=ctx.createLinearGradient(0,0,s,s);
    g1.addColorStop(0,'#'+c[0].getHexString());
    g1.addColorStop(0.4,'#'+c[1].getHexString());
    g1.addColorStop(0.65,'#'+c[2].getHexString());
    g1.addColorStop(1,'#'+c[3].getHexString());
    ctx.fillStyle=g1;ctx.fillRect(0,0,s,s);
    
    ctx.globalCompositeOperation='multiply';
    var g2=ctx.createLinearGradient(s,0,0,s);
    g2.addColorStop(0,'#'+c[1].getHexString());
    g2.addColorStop(0.35,'#'+c[0].getHexString());
    g2.addColorStop(0.65,'#'+c[3].getHexString());
    g2.addColorStop(1,'#'+c[2].getHexString());
    ctx.fillStyle=g2;ctx.fillRect(0,0,s,s);
    ctx.globalCompositeOperation='source-over';
    var t=new THREE.CanvasTexture(cv);
    t.minFilter=THREE.LinearFilter;t.magFilter=THREE.LinearFilter;
    t.wrapS=THREE.RepeatWrapping;t.wrapT=THREE.RepeatWrapping;
    return t;
}

var NOISE_GLSL=[
'vec3 mod289(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}',
'vec2 mod289v2(vec2 x){return x-floor(x*(1.0/289.0))*289.0;}',
'vec3 permute(vec3 x){return mod289(((x*34.0)+1.0)*x);}',
'float snoise(vec2 v){',
'  const vec4 C=vec4(0.211324865,0.366025404,-0.577350269,0.024390244);',
'  vec2 i=floor(v+dot(v,C.yy));',
'  vec2 x0=v-i+dot(i,C.xx);',
'  vec2 i1=(x0.x>x0.y)?vec2(1.0,0.0):vec2(0.0,1.0);',
'  vec4 x12=x0.xyxy+C.xxzz;x12.xy-=i1;',
'  i=mod289v2(i);',
'  vec3 p=permute(permute(i.y+vec3(0.0,i1.y,1.0))+i.x+vec3(0.0,i1.x,1.0));',
'  vec3 m=max(0.5-vec3(dot(x0,x0),dot(x12.xy,x12.xy),dot(x12.zw,x12.zw)),0.0);',
'  m=m*m;m=m*m;',
'  vec3 x=2.0*fract(p*C.www)-1.0;',
'  vec3 h=abs(x)-0.5;',
'  vec3 ox=floor(x+0.5);',
'  vec3 a0=x-ox;',
'  m*=1.79284291400159-0.85373472095314*(a0*a0+h*h);',
'  vec3 g;',
'  g.x=a0.x*x0.x+h.x*x0.y;',
'  g.yz=a0.yz*x12.xz+h.yz*x12.yw;',
'  return 130.0*dot(m,g);',
'}',
'float expStep(float x,float n){return exp2(-exp2(n)*pow(max(x,0.0001),n));}',
'float mapLinear(float v,float a,float b,float c,float d){return c+(v-a)*(d-c)/(b-a);}',
'float parabola(float x,float k){return pow(4.0*x*(1.0-x),k);}',
'mat4 rotMat(vec3 ax,float a){',
'  ax=normalize(ax);float s=sin(a),c=cos(a),oc=1.0-c;',
'  return mat4(',
'    oc*ax.x*ax.x+c,     oc*ax.x*ax.y-ax.z*s, oc*ax.z*ax.x+ax.y*s, 0.0,',
'    oc*ax.x*ax.y+ax.z*s, oc*ax.y*ax.y+c,     oc*ax.y*ax.z-ax.x*s, 0.0,',
'    oc*ax.z*ax.x-ax.y*s, oc*ax.y*ax.z+ax.x*s, oc*ax.z*ax.z+c,     0.0,',
'    0.0, 0.0, 0.0, 1.0);',
'}'
].join('\n');

var WAVE_VERT=[
'uniform float u_time;',
'uniform float u_speed;',
'uniform float u_displaceFreqX;',
'uniform float u_displaceFreqZ;',
'uniform float u_displaceAmt;',
'uniform float u_twistFreqX;',
'uniform float u_twistFreqY;',
'uniform float u_twistFreqZ;',
'uniform float u_twistPowX;',
'uniform float u_twistPowY;',
'uniform float u_twistPowZ;',
'uniform vec2 u_resolution;',
'varying vec2 v_uv;',
'varying vec3 v_pos;',
'varying vec2 v_resolution;',
'',
NOISE_GLSL,
'',
'void main(){',
'  v_uv=uv;',
'  v_resolution=u_resolution;',
'  float t=u_time*u_speed;',
'  mat4 rA=rotMat(vec3(0.5,0.0,0.5), u_twistFreqY*expStep(uv.x,u_twistPowY));',
'  mat4 rB=rotMat(vec3(0.0,0.5,0.5), u_twistFreqX*expStep(uv.y,u_twistPowX));',
'  mat4 rC=rotMat(vec3(0.5,0.0,0.5), u_twistFreqZ*expStep(uv.y,u_twistPowZ));',
'  float noise=snoise(vec2(position.x*u_displaceFreqX+t,position.z*u_displaceFreqZ+t));',
'  vec3 p=position;',
'  p.y+=u_displaceAmt*noise;',
'  p=(vec4(p,1.0)*rA).xyz;',
'  p=(vec4(p,1.0)*rB).xyz;',
'  p=(vec4(p,1.0)*rC).xyz;',
'  v_pos=p;',
'  gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);',
'}'
].join('\n');

var WAVE_FRAG=[
'uniform sampler2D u_palette;',
'uniform float u_colorContrast;',
'uniform float u_colorSaturation;',
'uniform float u_colorHueShift;',
'uniform float u_glowAmt;',
'uniform float u_glowPow;',
'uniform float u_glowRamp;',
'uniform vec3 u_clearColor;',
'varying vec2 v_uv;',
'varying vec3 v_pos;',
'varying vec2 v_resolution;',
'',
NOISE_GLSL,
'',
'vec3 desaturate(vec3 c,float a){vec3 g=vec3(dot(c,vec3(0.299,0.587,0.114)));return mix(c,g,a);}',
'vec3 contrast(vec3 c,float a){return 0.5+(c-0.5)*a;}',
'vec3 hueShift(vec3 c,float a){',
'  float cosA=cos(a),sinA=sin(a);',
'  vec3 k=vec3(0.57735);',
'  return c*cosA+cross(k,c)*sinA+k*dot(k,c)*(1.0-cosA);',
'}',
'vec3 surfaceColor(vec2 uv,vec3 pos,float pdy){',
'  vec3 color=texture2D(u_palette,vec2(uv.x,uv.y)).rgb;',
'  float p=1.0-parabola(uv.x,3.0);',
'  float n0=snoise(vec2(v_uv.x*0.1,v_uv.y*0.5));',
'  float n1=snoise(vec2(v_uv.x*(600.0+(300.0*n0)),v_uv.y*4.0*n0));',
'  n1=mapLinear(n1,-1.0,1.0,0.0,1.0);',
'  vec3 texColor=color;',
'  texColor+=(n1*0.2*(1.0-texColor.b*0.9)*pdy*p);',
'  return texColor;',
'}',
'void main(){',
'  vec2 dy=dFdy(v_uv);',
'  float pdy=dy.y*v_resolution.y*u_glowAmt;',
'  pdy=mapLinear(pdy,-1.0,1.0,0.0,1.0);',
'  pdy=clamp(pdy,0.0,1.0);',
'  pdy=pow(pdy,u_glowPow);',
'  pdy=smoothstep(0.0,u_glowRamp,pdy);',
'  pdy=clamp(pdy,0.0,1.0);',
'  vec4 color=vec4(surfaceColor(v_uv,v_pos,pdy),1.0);',
'  color.rgb=contrast(color.rgb,u_colorContrast);',
'  color.rgb=desaturate(color.rgb,1.0-u_colorSaturation);',
'  color.rgb=hueShift(color.rgb,u_colorHueShift);',
'  gl_FragColor=clamp(color,0.0,1.0);',
'}'
].join('\n');

var PP_VERT='varying vec2 v_uv;void main(){v_uv=uv;gl_Position=vec4(position,1.0);}';
var PP_FRAG=[
'uniform sampler2D u_scene;',
'uniform float u_blurAmount;',
'uniform float u_grainAmount;',
'uniform vec2 u_resolution;',
'varying vec2 v_uv;',
'float random(vec2 st){return fract(sin(dot(st,vec2(12.9898,78.233)))*43758.5453);}',
'vec3 grain(vec3 color,float amount){',
'  float g=random(gl_FragCoord.xy*0.01);',
'  vec3 d=vec3(4.0/255.0);',
'  return color+mix(amount*d,-amount*d,g);',
'}',
'vec4 blurAngular(sampler2D tex,vec2 uv,float angle,int samples){',
'  vec4 total=vec4(0.0);',
'  vec2 coord=uv-0.5;',
'  float d=1.0/float(samples);',
'  float ca=cos(angle*d),sa=sin(angle*d);',
'  mat2 rot=mat2(ca,sa,-sa,ca);',
'  for(int i=0;i<12;i++){',
'    if(i>=samples)break;',
'    total+=texture2D(tex,coord+0.5);',
'    coord=rot*coord;',
'  }',
'  return total*d;',
'}',
'void main(){',
'  vec4 sceneColor=texture2D(u_scene,v_uv);',
'  vec4 blurColor=blurAngular(u_scene,v_uv,u_blurAmount,6);',
'  float blurPower=smoothstep(0.0,0.7,v_uv.y)-smoothstep(0.2,1.0,v_uv.y);',
'  vec4 finalColor=mix(blurColor,sceneColor,blurPower);',
'  finalColor.rgb=grain(finalColor.rgb,u_grainAmount);',
'  gl_FragColor=vec4(min(finalColor.rgb,1.0),finalColor.a);',
'}'
].join('\n');

function applyPreset(wave, p, intensityScale){
    var u=wave.waveMat.uniforms;
    var is=intensityScale||1;
    u.u_twistFreqX.value=p.twistFreqX;
    u.u_twistFreqY.value=p.twistFreqY;
    u.u_twistFreqZ.value=p.twistFreqZ;
    u.u_twistPowX.value=p.twistPowX;
    u.u_twistPowY.value=p.twistPowY;
    u.u_twistPowZ.value=p.twistPowZ;
    u.u_displaceFreqX.value=p.displaceFreqX;
    u.u_displaceFreqZ.value=p.displaceFreqZ;
    
    var clamped = Math.max(0.1, Math.min(2.0, is));
    var norm    = (clamped - 0.1) / 1.9;            
    var scaleF  = 0.55;                             
    var posScaleF = scaleF;                          
    u.u_displaceAmt.value = p.displaceAmt * (0.4 + norm * 1.2); 
    u.u_glowAmt.value=p.glowAmt;
    u.u_glowPow.value=p.glowPow;
    u.u_glowRamp.value=p.glowRamp;
    wave.waveMesh.position.set(p.posX, p.posY*posScaleF, p.posZ);
    wave.waveMesh.rotation.set(p.rotX,p.rotY,p.rotZ);
    wave._baseRotX=p.rotX;
    wave._baseRotY=p.rotY;
    wave._baseRotZ=p.rotZ;
    wave._basePosX=p.posX;
    wave._basePosY=p.posY*posScaleF;
    wave._baseScaleX=p.scaleX*scaleF;
    wave._baseScaleY=p.scaleY*scaleF;
    wave._baseScaleZ=p.scaleZ*scaleF;
    wave.waveMesh.scale.set(p.scaleX*scaleF, p.scaleY*scaleF, p.scaleZ*scaleF);
    
    if(wave.width)wave._adjustForViewport();
}

function Wave(canvas,opts){
    opts=opts||{};
    this.canvas=canvas;
    this.playing=false;this.rafId=null;
    this.speed=opts.speed!==undefined?opts.speed:1;
    this.presetName=opts.preset||'flow';
    this.intensityScale=opts.intensity!==undefined?opts.intensity:1;
    this.colors=(opts.colors||PRESETS[this.presetName].colors).map(hexToColor);
    this.time=0;this.lastTs=0;
    this.frameCount=0;

    var _wvMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
    var _wvQuality = _wvMobile ? 0.6 : 1.5; 
    this.renderer=new THREE.WebGLRenderer({canvas:canvas,antialias:true,alpha:false,preserveDrawingBuffer:true});
    this.renderer.setPixelRatio(Math.min((window.devicePixelRatio||1)*_wvQuality,3));
    this._quality = _wvQuality;
    this.renderer.setClearColor(0xffffff,1);
    this.renderer.autoClear=false;
    this._clearColor=new THREE.Color(0xffffff);

    this.scene=new THREE.Scene();
    this.camera=new THREE.OrthographicCamera(0,0,0,0,1,10000);
    this.camera.position.set(100,0,5000);
    this.camera.rotation.set(0,0.02,0);

    this.ppScene=new THREE.Scene();
    this.ppCamera=new THREE.OrthographicCamera(-1,1,1,-1,0,1);

    this.paletteTex=createPalette(this.colors);

    var dpr=Math.min((window.devicePixelRatio||1)*_wvQuality,2);
    var w=canvas.clientWidth||window.innerWidth;
    var h=canvas.clientHeight||window.innerHeight;

    this.waveMat=new THREE.ShaderMaterial({
        uniforms:{
            u_time:{value:17500},
            u_speed:{value:4e-5},
            u_resolution:{value:new THREE.Vector2(w*dpr,h*dpr)},
            u_displaceFreqX:{value:0},u_displaceFreqZ:{value:0},u_displaceAmt:{value:0},
            u_twistFreqX:{value:0},u_twistFreqY:{value:0},u_twistFreqZ:{value:0},
            u_twistPowX:{value:1},u_twistPowY:{value:1},u_twistPowZ:{value:1},
            u_palette:{value:this.paletteTex},
            u_colorContrast:{value:1.0},
            u_colorSaturation:{value:1.0},
            u_colorHueShift:{value:-0.00159},
            u_glowAmt:{value:1},u_glowPow:{value:0.8},u_glowRamp:{value:0.8},
            u_clearColor:{value:new THREE.Vector3(1,1,1)}
        },
        vertexShader:WAVE_VERT,fragmentShader:WAVE_FRAG,
        extensions:{derivatives:true},
        side:THREE.DoubleSide,transparent:true,depthWrite:true,depthTest:true,
        blending:THREE.CustomBlending,
        blendEquation:THREE.AddEquation,
        blendSrc:THREE.OneFactor,
        blendDst:THREE.OneMinusSrcAlphaFactor
    });

    var _wvSegsX = _wvMobile ? 96 : 280;
    var _wvSegsY = _wvMobile ? 192 : 560;
    var geom=buildFoldedGeometry(400,400,_wvSegsX,_wvSegsY);
    this.waveMesh=new THREE.Mesh(geom,this.waveMat);
    this.waveMesh.frustumCulled=false; 
    this.scene.add(this.waveMesh);

    var preset=PRESETS[this.presetName]||PRESETS.flow;
    applyPreset(this,preset,this.intensityScale);

    this.sceneTarget=null;
    this.ppMat=new THREE.ShaderMaterial({
        uniforms:{
            u_scene:{value:null},
            u_blurAmount:{value:0.02},
            u_grainAmount:{value:0.45},
            u_resolution:{value:new THREE.Vector2(w*dpr,h*dpr)}
        },
        vertexShader:PP_VERT,fragmentShader:PP_FRAG,
        depthTest:false,depthWrite:false
    });
    this.ppQuad=new THREE.Mesh(new THREE.PlaneGeometry(2,2),this.ppMat);
    this.ppScene.add(this.ppQuad);

    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);

    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._onResize=this.resize.bind(this);
    window.addEventListener('resize',this._onResize);
    this.resize();
}

Wave.prototype._adjustForViewport=function(){
    if(!this._basePosX)return;
    var w=this.width||1440, h=this.height||900;
    var aspect=w/h;
    var sf;
    if(aspect<1.0){
        
        sf=Math.max(w/1440, h/900, 0.5)*1.4;
    } else {
        
        sf=Math.max(w/1440, h/900, 1);
    }
    this.waveMesh.position.x=this._basePosX*sf;
    
    var posY=this._basePosY*sf;
    if(aspect<1.0) posY -= h*0.15; 

    

    var _sxF = this._baseScaleX * sf;
    var _syF = this._baseScaleY * sf * (aspect<1.0 ? 1.3 : 1);
    var _szF = this._baseScaleZ * sf;
    var _rz  = this._baseRotZ || 0;
    var _dispAmp = Math.abs(this.waveMat.uniforms.u_displaceAmt.value) * _syF;
    var _foldExt = 8 * _szF;
    var rotatedExtent = _sxF*200*Math.abs(Math.sin(_rz)) + _syF*200*Math.abs(Math.cos(_rz));
    var approxTop = posY + rotatedExtent + _dispAmp + _foldExt;

    var screenTop = (this.camera ? this.camera.top : h/2) * 1.25;
    if(approxTop < screenTop){
        
        posY += (screenTop - approxTop);
    }
    this.waveMesh.position.y=posY;
    this.waveMesh.scale.set(this._baseScaleX*sf, this._baseScaleY*sf*(aspect<1.0?1.3:1), this._baseScaleZ*sf);
};

Wave.prototype.resize=function(){
    if(!this.renderer)return;
    var w=this.canvas.clientWidth||window.innerWidth;
    var h=this.canvas.clientHeight||window.innerHeight;
    var dpr=Math.min((window.devicePixelRatio||1)*(this._quality||1),2);
    this.renderer.setSize(w,h,false);
    this.renderer.setPixelRatio(dpr);
    this.camera.left=-w/2;this.camera.right=w/2;
    this.camera.top=h/2;this.camera.bottom=-h/2;
    this.camera.updateProjectionMatrix();
    this.width=w;this.height=h;
    var rw=Math.floor(w*dpr),rh=Math.floor(h*dpr);
    this.waveMat.uniforms.u_resolution.value.set(rw,rh);
    this.ppMat.uniforms.u_resolution.value.set(rw,rh);
    if(this.sceneTarget)this.sceneTarget.dispose();
    this.sceneTarget=new THREE.WebGLRenderTarget(rw,rh,{minFilter:THREE.LinearFilter,magFilter:THREE.LinearFilter,format:THREE.RGBAFormat});
    this.ppMat.uniforms.u_scene.value=this.sceneTarget.texture;
    this._adjustForViewport();
};

Wave.prototype.play=function(){
    if(this.playing||!this.renderer)return;
    this.playing=true;this.lastTs=0;this.frameCount=0;
    var self=this;
    function tick(ts){
        if(!self.playing)return;
        if(self._quality<1){self.frameCount++;if(self.frameCount%2!==0){self.rafId=requestAnimationFrame(tick);return;}}
        var dt=0;
        if(self.lastTs)dt=Math.min((ts-self.lastTs)/1000,0.1)*self.speed;
        self.lastTs=ts;
        self.time+=dt*1000;
        self.mouseX+=(self._targetMX-self.mouseX)*0.05;
        self.mouseY+=(self._targetMY-self.mouseY)*0.05;
        self.waveMat.uniforms.u_time.value=17500+self.time;
        
        var orbitX=(self.mouseY-0.5)*0.15;
        var orbitY=(self.mouseX-0.5)*0.12;
        self.waveMesh.rotation.x=self._baseRotX+orbitX;
        self.waveMesh.rotation.y=self._baseRotY+orbitY;
        var r=self.renderer;
        r.setRenderTarget(self.sceneTarget);
        r.setClearColor(self._clearColor,1);r.clear(true,true,false);
        r.render(self.scene,self.camera);
        r.setRenderTarget(null);
        r.setClearColor(self._clearColor,1);r.clear(true,true,false);
        r.render(self.ppScene,self.ppCamera);
        self.rafId=requestAnimationFrame(tick);
    }
    this.rafId=requestAnimationFrame(tick);
};

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

Wave.prototype.updateColors=function(hx){
    this.colors=hx.map(hexToColor);
    if(this.paletteTex)this.paletteTex.dispose();
    this.paletteTex=createPalette(this.colors);
    this.waveMat.uniforms.u_palette.value=this.paletteTex;
};

Wave.prototype.updateSpeed=function(s){this.speed=s;};
Wave.prototype.updateBgColor=function(hex){
    this._clearColor=new THREE.Color(hex);
    if(this.waveMesh&&this.waveMesh.material.uniforms.u_clearColor)this.waveMesh.material.uniforms.u_clearColor.value.set(this._clearColor.r,this._clearColor.g,this._clearColor.b);
};

Wave.prototype.updatePreset=function(name){
    var p=PRESETS[name];
    if(!p)return;
    this.presetName=name;
    applyPreset(this,p,this.intensityScale);
};

Wave.prototype.updateIntensity=function(val){

    this.intensityScale=val;
    var p=PRESETS[this.presetName]||PRESETS.flow;
    var clamped = Math.max(0.3, Math.min(6.0, val));
    var norm    = (clamped - 0.3) / 5.7;            
    var scaleF  = 0.25 + norm * 0.30;               
    var posScaleF = scaleF;                          
    this._baseScaleX=p.scaleX*scaleF;
    this._baseScaleY=p.scaleY*scaleF;
    this._baseScaleZ=p.scaleZ*scaleF;
    this._basePosY=p.posY*posScaleF;
    this._basePosX=p.posX;
    this._adjustForViewport();
    
    this.waveMat.uniforms.u_displaceAmt.value = p.displaceAmt * (0.5 + norm * 1.0);
};

Wave.prototype.destroy=function(){
    this.pause();
    if(this._onResize)window.removeEventListener('resize',this._onResize);
    if(this._onMouse)document.removeEventListener('mousemove',this._onMouse);
    if(this._onGyro){if(window._impulseGyro)window._impulseGyro.unregister(this._onGyro);else window.removeEventListener('deviceorientation',this._onGyro);}
    if(this.waveMesh){this.waveMesh.geometry.dispose();this.waveMat.dispose();}
    if(this.paletteTex)this.paletteTex.dispose();
    if(this.sceneTarget)this.sceneTarget.dispose();
    if(this.ppMat)this.ppMat.dispose();
    if(this.ppQuad)this.ppQuad.geometry.dispose();
    if(this.renderer){
        this.renderer.dispose();
        
        if(this.renderer.domElement){
            this.renderer.domElement.width=1;
            this.renderer.domElement.height=1;
        }
        this.renderer.forceContextLoss();
        this.renderer.domElement=null;
    }
    this.renderer=null;this.canvas=null;
};

Wave.PRESETS=PRESETS;
window.ImpulseWave=Wave;
})();
