import Star from "./Star.js"; import Meteor from "./Meteor.js"; const starInstances = []; const meteorInstances = []; let lastTime = performance.now(); let isTabVisible = true; let animationFrameId = null; // Track tab visibility to prevent spawning when hidden document.addEventListener("visibilitychange", () => { isTabVisible = !document.hidden; // If tab becomes visible after being hidden, clean up excess meteors if (isTabVisible && meteorInstances.length > 3) { // Remove excess meteors const excess = meteorInstances.length - 3; for (let i = 0; i < excess; i++) { if (meteorInstances[i]) { meteorInstances[i].remove(); } } meteorInstances.splice(0, excess); } // Resume animation loop if it stopped if (isTabVisible && !animationFrameId) { lastTime = performance.now(); animate(); } }); function createStars() { const stars = document.getElementById("stars"); const count = 400; stars.innerHTML = ""; starInstances.length = 0; for (let i = 0; i < count; i++) { const star = new Star(stars); starInstances.push(star); } } function injectStarCSS() { const style = document.createElement("style"); style.textContent = ` .star { position: absolute; border-radius: 50%; animation: twinkle ease-in-out infinite; } @keyframes twinkle { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } `; document.head.appendChild(style); } function trySpawnMeteor() { // Only spawn if tab is visible and not too many meteors if (!isTabVisible || meteorInstances.length >= 2) { return; } const spawnChance = 0.15; if (Math.random() < spawnChance) { const stars = document.getElementById("stars"); const newMeteor = new Meteor(stars); meteorInstances.push(newMeteor); } } function animate() { const currentTime = performance.now(); const deltaTime = currentTime - lastTime; lastTime = currentTime; // Cap deltaTime to prevent huge jumps when tab is hidden const cappedDelta = Math.min(deltaTime, 100); // Update stars with delta time starInstances.forEach((star) => star.update()); // Update meteors and remove dead ones for (let i = meteorInstances.length - 1; i >= 0; i--) { if (!meteorInstances[i].update(cappedDelta)) { meteorInstances.splice(i, 1); } } animationFrameId = requestAnimationFrame(animate); } function createMeteorBurst() { // Only burst if tab is visible if (!isTabVisible) return; // Limit burst size const burstSize = Math.min(2, 3 - meteorInstances.length); for (let i = 0; i < burstSize; i++) { setTimeout(() => { if (isTabVisible && meteorInstances.length < 3) { const stars = document.getElementById("stars"); const newMeteor = new Meteor(stars); meteorInstances.push(newMeteor); } }, i * 300); } } let spawnInterval = null; let burstInterval = null; function init() { injectStarCSS(); createStars(); lastTime = performance.now(); animate(); // Longer interval for spawning spawnInterval = setInterval(() => { if (isTabVisible) { trySpawnMeteor(); } }, 3000); // Every 3 seconds instead of 2 // Longer interval for bursts burstInterval = setInterval(() => { if (isTabVisible) { createMeteorBurst(); } }, 20000); // Every 20 seconds instead of 15 } // Cleanup function function cleanup() { if (animationFrameId) { cancelAnimationFrame(animationFrameId); } if (spawnInterval) { clearInterval(spawnInterval); } if (burstInterval) { clearInterval(burstInterval); } } // Handle page unload window.addEventListener("beforeunload", cleanup); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } export { cleanup };