(()=>{const CONFIG={POINT_INTERVAL:5,FISH_COUNT:3,MAX_INTERVAL_COUNT:50,INIT_HEIGHT_RATE:0.5,THRESHOLD:50,WATCH_INTERVAL:200};class Renderer{constructor(){this.init()}init(){this.setParameters();this.bindMethods();this.setup();this.bindEvent();this.render()}setParameters(){this.container=document.getElementById('jsi-flying-fish-container');this.canvas=document.createElement('canvas');this.ctx=this.canvas.getContext('2d');this.container.appendChild(this.canvas);this.points=[];this.fishes=[];this.resizeTimer=null;this.animationFrameId=null}createSurfacePoints(){const count=Math.round(this.width/CONFIG.POINT_INTERVAL);this.pointInterval=this.width/(count-1);for(let i=0;i0){point.setPreviousPoint(this.points[i-1]);this.points[i-1].setNextPoint(point)}this.points[i]=point}}bindMethods(){['handleResize','render','controlStatus','generateEpicenter'].forEach(method=>{this[method]=this[method].bind(this)})}setup(){const rect=this.container.getBoundingClientRect();this.width=rect.width;this.height=rect.height;this.fishCount=Math.ceil(CONFIG.FISH_COUNT*(this.width*this.height)/250000);this.canvas.width=this.width;this.canvas.height=this.height;this.manageFishInstances();this.createSurfacePoints()}manageFishInstances(){const targetCount=Math.max(this.fishCount,this.fishes.length);while(this.fishes.lengththis.setup(),CONFIG.WATCH_INTERVAL)}bindEvent(){const opts={passive:true};window.addEventListener('resize',this.handleResize,opts);window.addEventListener('beforeunload',()=>{window.removeEventListener('resize',this.handleResize);cancelAnimationFrame(this.animationFrameId)})}generateEpicenter(x,y,velocity){if(Math.abs(y-this.height/2)>CONFIG.THRESHOLD)return;const index=Math.round(x/this.pointInterval)|0;if(index>=0&&indexpoint.render(ctx));ctx.lineTo(width,height);ctx.closePath();ctx.fill();ctx.restore()}}class SurfacePoint{static SPRING=0.03;static FRICTION=0.9;static WAVE_SPREAD=0.3;static ACCELERATION=0.01;constructor(renderer){this.renderer=renderer;this.prev=null;this.next=null}init(x){this.x=x;this.baseHeight=this.renderer.height*CONFIG.INIT_HEIGHT_RATE;this.height=this.baseHeight;this.forceY=0;this.forces={prev:0,next:0}}interfere(y,velocity){const direction=(this.renderer.height-this.height-y>=0)?-1:1;this.forceY=this.renderer.height*SurfacePoint.ACCELERATION*direction*Math.abs(velocity)}updateSelf(){this.forceY+=SurfacePoint.SPRING*(this.baseHeight-this.height);this.forceY*=SurfacePoint.FRICTION;this.height+=this.forceY}updateNeighbors(){if(this.prev){this.forces.prev=SurfacePoint.WAVE_SPREAD*(this.height-this.prev.height)}if(this.next){this.forces.next=SurfacePoint.WAVE_SPREAD*(this.height-this.next.height)}}render(ctx){if(this.prev){this.prev.height+=this.forces.prev;this.prev.forceY+=this.forces.prev}if(this.next){this.next.height+=this.forces.next;this.next.forceY+=this.forces.next}ctx.lineTo(this.x,this.renderer.height-this.height)}setPreviousPoint(point){this.prev=point}setNextPoint(point){this.next=point}}class Fish{static GRAVITY=0.4;constructor(renderer){this.renderer=renderer;this.reset()}reset(){this.direction=Math.random()<0.5;this.x=this.direction?this.renderer.width+CONFIG.THRESHOLD:-CONFIG.THRESHOLD;this.vx=this.random(4,10)*(this.direction?-1:1);this.initMovement()}random(min,max){return min+(max-min)*Math.random()}initMovement(){const baseY=this.renderer.height*CONFIG.INIT_HEIGHT_RATE;this.y=this.random(baseY,baseY+this.renderer.height*0.3);this.vy=this.random(-5,-2);this.ay=this.random(-0.2,-0.05);this.theta=0;this.phi=0}update(){const thresholdY=this.renderer.height*CONFIG.INIT_HEIGHT_RATE;this.x+=this.vx;this.y+=this.vy;this.vy+=this.ay;if(this.y0&&this.x>this.renderer.width+CONFIG.THRESHOLD)||(this.vx<0&&this.x<-CONFIG.THRESHOLD)){this.reset()}this.theta=(this.theta+Math.PI/20)%(Math.PI*2);this.phi=(this.phi+Math.PI/30)%(Math.PI*2)}render(ctx){ctx.save();ctx.translate(this.x,this.y);ctx.rotate(Math.PI+Math.atan2(this.vy,this.vx));ctx.scale(1,this.direction?1:-1);ctx.beginPath();ctx.moveTo(-30,0);ctx.bezierCurveTo(-20,15,15,10,40,0);ctx.bezierCurveTo(15,-10,-20,-15,-30,0);ctx.fill();ctx.save();ctx.translate(40,0);ctx.scale(0.9+0.2*Math.sin(this.theta),1);ctx.beginPath();ctx.moveTo(0,0);ctx.quadraticCurveTo(5,10,20,8);ctx.quadraticCurveTo(12,5,10,0);ctx.quadraticCurveTo(12,-5,20,-8);ctx.quadraticCurveTo(5,-10,0,0);ctx.fill();ctx.restore();ctx.save();ctx.translate(-3,0);ctx.rotate(Math.PI/3+Math.PI/10*Math.sin(this.phi));ctx.beginPath();ctx.moveTo(-5,0);ctx.bezierCurveTo(-10,-10,-10,-30,0,-40);ctx.bezierCurveTo(12,-25,8,-10,0,0);ctx.closePath();ctx.fill();ctx.restore();ctx.restore();this.update()}}document.addEventListener('DOMContentLoaded',()=>new Renderer())})();