const EXPLOSION_TIME = 100; const EXPLOSION_SIZE = 100; const NUM_PARTICLES = 12; const SPEED = 2; let OVERRIDE_STATE = null; function HSVtoRGB(h, s, v) { var r, g, b, i, f, p, q, t; if (arguments.length === 1) { s = h.s, v = h.v, h = h.h; } i = Math.floor(h * 6); f = h * 6 - i; p = v * (1 - s); q = v * (1 - f * s); t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } return "rgb(" + (r * 255) + "," + (g * 255) + "," + (b * 255) + ")"; } const canvas = document.getElementById("fireworks-canvas"); const updateSize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; const STATE_NONE = "NONE"; const STATE_COUNTDOWN = "COUNTDOWN"; const STATE_FIREWORKS = "FIREWORKS"; let rockets = []; let state = STATE_NONE; updateSize(); window.onresize = updateSize; const ctx = canvas.getContext("2d"); function leftPad(str, len, pad) { str = "" + str; while (str.length < len) { str = pad + str; } return str; } window.addEventListener("keyup", e => { if (e.key == 'h') { canvas.style.display = canvas.style.display == 'none' ? null : 'none'; } }); setInterval(() => { const theTime = new Date(); if (OVERRIDE_STATE != null) { state = OVERRIDE_STATE; } else if (theTime.getDate() == 1 && theTime.getMonth() == 0 && theTime.getHours() == 0) { // 01.01. 00:00 - 00:59 state = STATE_FIREWORKS; } else { const nextYear = new Date('1970-01-01 00:00'); nextYear.setFullYear(theTime.getFullYear() + 1); const timeUntilMidnight = Math.floor((nextYear - theTime) / 1000); if(timeUntilMidnight < 4 * 60 * 60) { state = STATE_COUNTDOWN; }else { state = STATE_NONE; } } if (state == STATE_FIREWORKS) { rockets.push({ x: Math.random() * canvas.width, y: canvas.height, life: (Math.random() * 0.75 + 0.25) * canvas.height, color: HSVtoRGB(Math.random(), 1, 1) }); return; } else { rockets = []; } }, 300); setInterval(() => { if (state == STATE_NONE) { ctx.clearRect(0, 0, canvas.width, canvas.height); return; } const theTime = new Date(); switch (state) { case STATE_NONE: ctx.clearRect(0, 0, canvas.width, canvas.height); return; case STATE_COUNTDOWN: ctx.fillStyle = "#000044ff"; ctx.fillRect(0, 0, canvas.width, canvas.height); const nextYear = new Date('1970-01-01 00:00'); nextYear.setFullYear(theTime.getFullYear() + 1); const timeUntilMidnight = Math.floor((nextYear - theTime) / 1000); const hours = "" + Math.floor(timeUntilMidnight / (60 * 60)); const minutes = "" + Math.floor((timeUntilMidnight / 60) % 60); const seconds = "" + Math.floor(timeUntilMidnight % 60); ctx.fillStyle = "orangered"; ctx.textAlign = "center"; ctx.font = "bold 50px Comic Mono"; ctx.fillText("T-" + hours.padStart(2, "0") + ":" + minutes.padStart(2, "0") + ":" + seconds.padStart(2, "0"), canvas.width / 2, canvas.height / 2); ctx.fillStyle = "white"; ctx.fillText(theTime.getHours().toString().padStart(2, '0') + ":" + theTime.getMinutes().toString().padStart(2, '0') + ":" + theTime.getSeconds().toString().padStart(2, '0'), canvas.width / 2, canvas.height / 2 - 150); ctx.fillStyle = "lightgray"; ctx.textAlign = "center"; ctx.font = "bold 30px Comic Mono"; let text = "The new year is approaching 👀"; if(hours < 1) { text = "The final hour has started ⏱️"; if(minutes == 0 && seconds < 60) { text = "It's the final countdown 😳"; } } ctx.fillText(text, canvas.width / 2, canvas.height / 2 + 150); break; // Draw help case STATE_FIREWORKS: ctx.fillStyle = "#000044ff"; ctx.fillRect(0, 0, canvas.width, canvas.height); rockets.forEach(rocket => { rocket.life -= SPEED; if (rocket.life <= EXPLOSION_TIME) { if (rocket.life <= 0) { rockets.splice(rockets.indexOf(rocket), 1); return; } const explosionTime = 1 - (rocket.life / EXPLOSION_TIME); const size = explosionTime * EXPLOSION_SIZE; for (let i = 0; i < NUM_PARTICLES; i++) { const angle = i / NUM_PARTICLES * Math.PI * 2; const px = Math.cos(angle) * size; const py = Math.sin(angle) * size; ctx.fillStyle = rocket.color; ctx.beginPath(); ctx.arc(rocket.x + px, rocket.y + py, 5, 0, Math.PI * 2, true); ctx.fill(); } } else { rocket.y -= SPEED; ctx.fillStyle = "black"; ctx.fillRect(rocket.x - 5, rocket.y - 5, 10, 10); } }); ctx.fillStyle = "white"; ctx.textAlign = "center"; ctx.font = "bold 50px Comic Mono"; ctx.fillText("Happy new year! 🥳", canvas.width / 2, canvas.height / 2); ctx.font = "bold 100px Comic Mono"; ctx.fillText(theTime.getHours().toString().padStart(2, '0') + ":" + theTime.getMinutes().toString().padStart(2, '0') + ":" + theTime.getSeconds().toString().padStart(2, '0'), canvas.width / 2, canvas.height / 2 - 150); break; // Draw help } ctx.font = "bold 10px Comic Mono"; ctx.fillStyle = "#0000ff"; ctx.textAlign = "left"; ctx.fillText("Press 'h' to hide", 10, canvas.height - 10); }, 10);