const STARCOUNTMOD = 2; const VELOCITYVARX = 40; const VELOCITYVARY = 30; const SMOKEVELVARX = 3; const SMOKEVELVARY = 3; const VELOCITYDOWN = 2; const SMOKEVELDOWN = 1; const RAYINTENSITY = 50; const FRENCHRANDOM = Math.floor(20 * Math.random()); const bgStars = document.getElementById("bgstars"); const planet = document.getElementById("planet"); const canvas = document.getElementById("canvas"); const debris = document.getElementById("debris"); const ray = document.querySelector("#hidden .ray"); const light = document.getElementById("light"); const whiteRect = document.getElementById("whiterect"); const bgCtx = bgStars.getContext("2d"); const ctx = canvas.getContext("2d"); function drawBgStars(count) { for (let i = 0; i < count; ++i) { bgCtx.beginPath(); bgCtx.arc(bgStars.width * Math.random(), bgStars.height * Math.random(), 2 * Math.random(), 0, 2 * Math.PI); bgCtx.fillStyle = `rgba(${255 - 55 * Math.random()}, ${255 - 55 * Math.random()}, ${255 - 55 * Math.random()}, ${Math.random()})`; bgCtx.fill(); } } let heightConstant; let originPosX; let originPosY; function updateCanvas() { const innerWidth = window.innerWidth; const innerHeight = window.innerHeight; bgStars.width = innerWidth; bgStars.height = innerHeight; canvas.width = innerWidth; canvas.height = innerHeight; drawBgStars(innerWidth / STARCOUNTMOD); heightConstant = innerHeight / 1024; originPosX = canvas.width / 2; originPosY = .8 * canvas.height; } updateCanvas(); window.addEventListener("resize", updateCanvas); // KAIKKI MIELUISA TUHO TUOHON ALLE!!! class Particle { constructor(posX, posY, velX, velY, acc, ls, col, time) { this._positionX = posX; this._positionY = posY; this._velocityX = velX; this._velocityY = velY; this._acceleration = acc; this._lifespan = ls; this._color = col; this._birthtime = time; this._birthposX = posX; this._birthposY = posY; this._maxDistance = 0; } getPositionX() { return this._positionX; } getAbsolutePosX() { return originPosX + this.getPositionX(); } getPositionY() { return this._positionY; } getAbsolutePosY() { return originPosY + this.getPositionY(); } getVelocityX() { return this._velocityX; } getVelocityY() { return this._velocityY; } getAcceleration() { return this._acceleration; } getLifespan() { return this._lifespan; } getColor() { return this._color; } getBirthtime() { return this._birthtime; } getBirthposX() { return this._birthposX; } getBirthposY() { return this._birthposY; } getMaxDistance() { return this._maxDistance; } getAge(time) { return (time - this.getBirthtime()) / this.getLifespan(); } getTooOld(time) { return (this.getAge(time) > 1); } updateVelocity() { this._velocityY -= this.getAcceleration(); } updatePosition() { this._positionX += this.getVelocityX(); this._positionY += this.getVelocityY(); const distance = Math.sqrt((this.getPositionX() - this.getBirthposX())**2 + (this.getPositionY() - this.getBirthposY())**2); if (distance > this.getMaxDistance()) this._maxDistance = distance; } renderCircle(radius, time) { ctx.beginPath(); ctx.arc(this.getAbsolutePosX(), this.getAbsolutePosY(), radius, 0, 2 * Math.PI); ctx.fillStyle = `rgb(from ${this.getColor()} r g b / ${Math.sqrt(1 - this.getAge(time)**2)}`; ctx.fill(); } renderTrail(maxLength, time, width) { const length = Math.min(this.getMaxDistance(), maxLength * (1 - this.getAge(time)) * Math.sqrt(this.getVelocityX()**2 + this.getVelocityY()**2) * heightConstant); let atan = Math.atan(this.getVelocityY() / this.getVelocityX()); if (this.getVelocityX() < 0) atan += Math.PI; ctx.beginPath(); ctx.moveTo(this.getAbsolutePosX(), this.getAbsolutePosY()); ctx.lineTo(this.getAbsolutePosX() - length * Math.cos(atan), this.getAbsolutePosY() - length * Math.sin(atan)); ctx.lineWidth = width; ctx.strokeStyle = this.getColor(); ctx.stroke(); // FIX TRAIL BEFORE SPAWN!!! } } const dots = []; const trails = []; function processDots(time) { const tooOld = []; for (let i = 0; i < dots.length; i++) { if (dots[i].getTooOld(time)) { tooOld.push(i); continue; } const dot = dots[i]; dot.renderCircle(2, time); // dot.renderTrail(50, time, 2); dot.updatePosition(); dot.updateVelocity(); } for (let i = tooOld.length-1; i >= 0; i--) { dots.splice(tooOld[i], 1); } } function processTrails(time) { const tooOld = []; for (let i = 0; i < trails.length; i++) { if (trails[i].getTooOld(time)) { tooOld.push(i); continue; } const dot = trails[i]; // dot.renderCircle(2, time); dot.renderTrail(50, time, 2); dot.updatePosition(); dot.updateVelocity(); } for (let i = tooOld.length-1; i >= 0; i--) { trails.splice(tooOld[i], 1); } } let fuseSpawner = 1; let dotSpawner = 0; let trailSpawner = 0; let fuseStrength = .02; const maxTrailSurge = 100; let trailSurge = 100; let phase = 0; const rays = []; let lastFrame = -1000; function main(time) { if (time - lastFrame < 15) { requestAnimationFrame(main); return; } lastFrame = time; const fuseMod = 1.01 - Math.sqrt(1 - fuseStrength**2); for (let i = 0; i < fuseSpawner * (1 + fuseMod * 8); ++i) { // FUSEDOTS: PosXY, VelXY, Acc, Life, Col, time dots.push(new Particle( 0, 0, fuseMod * heightConstant * (SMOKEVELVARX * Math.random() - SMOKEVELVARX/2), fuseMod * heightConstant * (-SMOKEVELVARY * Math.random() + fuseMod * SMOKEVELDOWN), -.001 * fuseMod * heightConstant, 2000 / heightConstant * canvas.height / 1024 / Math.max(10 * fuseMod, .5), `hsl(${60 * Math.random()}, 100%, ${30 + 40 * Math.random()}%)`, time )); } for (let i = 0; i < dotSpawner; ++i) { // DOTS: PosXY, VelXY, Acc, Life, Col, time dots.push(new Particle( 0, 0, heightConstant * (VELOCITYVARX * Math.random() - VELOCITYVARX/2), heightConstant * (-VELOCITYVARY * Math.random() + VELOCITYDOWN), -.05 * heightConstant, 2000 / heightConstant * canvas.height / 1024, `hsl(${180 + 120 * Math.random()}, 100%, ${50 + 50 * Math.random()}%)`, time )); } for (let i = 0; i < trailSpawner; ++i) { // TRAILS: PosXY, VelXY, Acc, Life, Col, time const velX = (VELOCITYVARX * Math.random() - VELOCITYVARX/2); const whiteTreshold = 10; trails.push(new Particle( 0, 0, trailSurge * heightConstant * velX, trailSurge * heightConstant * (-VELOCITYVARY * Math.random() + VELOCITYDOWN * (1 + 2 * trailSurge / maxTrailSurge)), -.1 * heightConstant, Math.min(2000 / heightConstant * canvas.height / 1024 * trailSurge, 10000), (FRENCHRANDOM == 0) ? `hsl(${(velX < -whiteTreshold ? 220 : -20) + 40 * Math.random()}, ${velX >= -whiteTreshold && velX <= whiteTreshold ? 0 : 100}%, ${50 + 50 * Math.random()}%)` : `hsl(${180 + 120 * Math.random()}, 100%, ${50 + 50 * Math.random()}%)` , time )); } ctx.clearRect(0, 0, canvas.width, canvas.height); processDots(time); processTrails(time); fuseStrength = Math.min(fuseStrength + .0025, 1); if (phase == 0 && fuseStrength >= 1) { fuseSpawner = 0; planet.style.backgroundColor = "midnightblue"; light.style.transition = "scale 1s linear, opacity 1s linear"; whiteRect.style.opacity = "1"; setTimeout(() => { whiteRect.style.opacity = "0"; }, 10000); requestAnimationFrame(() => { light.style.scale = "100"; light.style.opacity = "0"; }); for (let i = 0; i < RAYINTENSITY; ++i) { const clone = ray.cloneNode(true); clone.style.background = `linear-gradient(to right, hsl(${180 + 120 * Math.random()}, 100%, 95%), transparent)`; const lifespan = 8 * Math.random(); clone.style.transition = `height ${lifespan}s ease-in, rotate ${lifespan}s linear`; const rotation = -180 * Math.random(); const rotationMod = -180 * Math.random(); clone.style.rotate = `${rotation}deg`; debris.appendChild(clone); requestAnimationFrame(() => { clone.style.rotate = `${rotationMod}deg`; clone.style.height = "0"; }); } ++phase; } if (phase > 0) { trailSpawner = 30 + 20 * (1 - Math.sqrt(1 - (.2 * (trailSurge - 1) / maxTrailSurge)**2)); trailSurge = Math.max(trailSurge - 1, 1); } requestAnimationFrame(main); } main(0);