diff --git a/index.html b/index.html index f9484b7..a81af8f 100644 --- a/index.html +++ b/index.html @@ -205,7 +205,7 @@ We began with a vision to create high-quality free software, particularly in gaming where such options are often limited. We recognized that many free software games either lack polish or - simply don't exist, inspiring us to focus on this underserved + simply don't exist, inspiring us to focus on this under-served area.

@@ -481,14 +481,16 @@ - + - + + + diff --git a/src/js/animation/Meteor.js b/src/js/animation/Meteor.js new file mode 100644 index 0000000..0244eb3 --- /dev/null +++ b/src/js/animation/Meteor.js @@ -0,0 +1,319 @@ +class Meteor { + constructor(container) { + this.element = document.createElement("div"); + this.element.style.position = "absolute"; + this.container = container; + this.speed = Math.random() * 3 + 4; + this.life = 1.0; + this.particles = []; + + // Enhanced color palette + this.colorType = Math.floor(Math.random() * 4); + this.colors = this.getMeteorColors(); + + // Get container dimensions for pixel-based positioning + this.containerRect = container.getBoundingClientRect(); + + // Start from random edge with PIXEL-based positioning + const edge = Math.floor(Math.random() * 4); + if (edge === 0) { + // Top + this.x = Math.random() * this.containerRect.width; + this.y = -20; + } else if (edge === 1) { + // Right + this.x = this.containerRect.width + 20; + this.y = Math.random() * this.containerRect.height; + } else if (edge === 2) { + // Bottom + this.x = Math.random() * this.containerRect.width; + this.y = this.containerRect.height + 20; + } else { + // Left + this.x = -20; + this.y = Math.random() * this.containerRect.height; + } + + // More dynamic movement patterns + const centerX = this.containerRect.width / 2 + (Math.random() - 0.5) * 200; + const centerY = this.containerRect.height / 2 + (Math.random() - 0.5) * 200; + this.angle = + Math.atan2(centerY - this.y, centerX - this.x) + + (Math.random() - 0.5) * 0.8; + this.dx = Math.cos(this.angle) * this.speed; + this.dy = Math.sin(this.angle) * this.speed; + + this.init(); + } + + getMeteorColors() { + const colorSchemes = [ + { + head: "rgba(255, 255, 255, 1)", + core: "rgba(255, 230, 150, 0.9)", + mid: "rgba(255, 180, 80, 0.7)", + outer: "rgba(255, 100, 50, 0.5)", + glow: "rgba(255, 200, 100, 0.9)", + }, + { + head: "rgba(255, 255, 255, 1)", + core: "rgba(180, 220, 255, 0.9)", + mid: "rgba(100, 180, 255, 0.7)", + outer: "rgba(50, 100, 255, 0.5)", + glow: "rgba(100, 180, 255, 0.9)", + }, + { + head: "rgba(255, 255, 255, 1)", + core: "rgba(220, 180, 255, 0.9)", + mid: "rgba(200, 100, 255, 0.7)", + outer: "rgba(180, 50, 255, 0.5)", + glow: "rgba(200, 150, 255, 0.9)", + }, + { + head: "rgba(255, 255, 255, 1)", + core: "rgba(180, 255, 200, 0.9)", + mid: "rgba(80, 255, 150, 0.7)", + outer: "rgba(50, 200, 100, 0.5)", + glow: "rgba(150, 255, 180, 0.9)", + }, + ]; + return colorSchemes[this.colorType]; + } + + init() { + const size = Math.random() * 4 + 10; + const colors = this.colors; + + // Create enhanced meteor head with multiple layers + const head = document.createElement("div"); + head.style.width = `${size}px`; + head.style.height = `${size}px`; + head.style.borderRadius = "50%"; + head.style.background = ` + radial-gradient( + circle at 30% 30%, + ${colors.head} 0%, + ${colors.core} 30%, + ${colors.mid} 60%, + ${colors.outer} 100% + ) + `; + head.style.boxShadow = ` + 0 0 ${size * 4}px ${colors.glow}, + 0 0 ${size * 2}px rgba(255, 255, 255, 0.8), + inset 0 0 ${size * 0.5}px rgba(255, 255, 255, 0.9) + `; + head.style.position = "absolute"; + head.style.left = "50%"; + head.style.top = "50%"; + head.style.transform = "translate(-50%, -50%)"; + head.style.zIndex = "3"; + head.style.filter = "blur(0.5px)"; + + // Create multi-layer tail + this.createTailLayer(size * 3, size * 1.2, colors.core, 1, "1px"); + this.createTailLayer(size * 5, size * 2, colors.mid, 0.6, "2px"); + this.createTailLayer(size * 8, size * 3, colors.outer, 0.3, "3px"); + + // Assemble meteor + this.element.appendChild(head); + this.element.style.left = `${this.x}px`; + this.element.style.top = `${this.y}px`; + this.element.style.zIndex = "10"; + this.element.style.filter = "blur(0.5px)"; + + this.head = head; + this.size = size; + + this.container.appendChild(this.element); + + // Add trail particles + this.createTrailParticles(); + } + + createTailLayer(length, width, color, opacity, blur) { + const tail = document.createElement("div"); + const tailAngle = Math.atan2(this.dy, this.dx) + Math.PI; + + tail.style.width = `${length}px`; + tail.style.height = `${width}px`; + tail.style.position = "absolute"; + tail.style.left = "50%"; + tail.style.top = "50%"; + tail.style.transformOrigin = "left center"; + tail.style.transform = `translate(0, -50%) rotate(${tailAngle}rad)`; + tail.style.background = `linear-gradient(to right, + ${color} 0%, + ${color.replace(")", ", 0.8)").replace("rgba", "rgba")} 20%, + ${color.replace(")", ", 0.4)").replace("rgba", "rgba")} 50%, + ${color.replace(")", ", 0.1)").replace("rgba", "rgba")} 80%, + transparent 100%)`; + tail.style.opacity = opacity; + tail.style.filter = `blur(${blur})`; + tail.style.pointerEvents = "none"; + tail.style.zIndex = "2"; + tail.style.borderRadius = "0 50% 50% 0"; + + this.element.appendChild(tail); + this.tail = tail; + } + + createTrailParticles() { + // Create initial trail particles + for (let i = 0; i < 5; i++) { + this.addTrailParticle(); + } + } + + addTrailParticle() { + const particle = document.createElement("div"); + const size = Math.random() * 2 + 1; + const colors = this.colors; + + particle.style.width = `${size}px`; + particle.style.height = `${size}px`; + particle.style.borderRadius = "50%"; + particle.style.background = colors.mid; + particle.style.position = "absolute"; + particle.style.left = `${this.x}px`; + particle.style.top = `${this.y}px`; + particle.style.boxShadow = `0 0 ${size * 3}px ${colors.glow}`; + particle.style.opacity = "0.7"; + particle.style.zIndex = "1"; + particle.style.pointerEvents = "none"; + + this.container.appendChild(particle); + + this.particles.push({ + element: particle, + life: 1.0, + x: this.x, + y: this.y, + vx: (Math.random() - 0.5) * 0.5, + vy: (Math.random() - 0.5) * 0.5, + size: size, + }); + } + + update() { + // Smooth pixel-based movement + this.x += this.dx; + this.y += this.dy; + this.life -= 0.005; + + // Use pixel positioning for smooth movement + this.element.style.left = `${this.x}px`; + this.element.style.top = `${this.y}px`; + this.element.style.opacity = this.life; + + // Update tail direction + const tailAngle = Math.atan2(this.dy, this.dx) + Math.PI; + const tails = this.element.querySelectorAll("div:not(.particle)"); + tails.forEach((tail) => { + if (tail !== this.head) { + tail.style.transform = `translate(0, -50%) rotate(${tailAngle}rad)`; + tail.style.opacity = this.life; + } + }); + + // Add new trail particles periodically + if (Math.random() < 0.3) { + this.addTrailParticle(); + } + + // Update existing particles with pixel positioning + for (let i = this.particles.length - 1; i >= 0; i--) { + const p = this.particles[i]; + p.life -= 0.02; + p.x += p.vx; + p.y += p.vy; + + p.element.style.left = `${p.x}px`; + p.element.style.top = `${p.y}px`; + p.element.style.opacity = p.life; + p.element.style.transform = `scale(${p.life})`; + + if (p.life <= 0) { + p.element.remove(); + this.particles.splice(i, 1); + } + } + + // Enhanced removal condition with fading + if ( + this.x < -50 || + this.x > this.containerRect.width + 50 || + this.y < -50 || + this.y > this.containerRect.height + 50 || + this.life <= 0 + ) { + // Fade out particles + this.particles.forEach((p) => { + p.element.style.transition = "opacity 0.5s"; + p.element.style.opacity = "0"; + setTimeout(() => p.element.remove(), 500); + }); + this.remove(); + return false; + } + return true; + } + + remove() { + // Add explosion effect when meteor dies + if (this.life > 0.3) { + this.createExplosion(); + } + + setTimeout(() => { + this.element.remove(); + }, 300); + } + + createExplosion() { + for (let i = 0; i < 8; i++) { + const particle = document.createElement("div"); + const size = Math.random() * 3 + 2; + const colors = this.colors; + + particle.style.width = `${size}px`; + particle.style.height = `${size}px`; + particle.style.borderRadius = "50%"; + particle.style.background = colors.core; + particle.style.position = "absolute"; + particle.style.left = `${this.x}px`; + particle.style.top = `${this.y}px`; + particle.style.boxShadow = `0 0 ${size * 4}px ${colors.glow}`; + particle.style.opacity = "0.8"; + particle.style.zIndex = "2"; + + this.container.appendChild(particle); + + // Animate explosion + const angle = (i / 8) * Math.PI * 2; + const distance = Math.random() * 20 + 10; + const duration = Math.random() * 500 + 500; + + particle.animate( + [ + { + transform: "translate(0, 0) scale(1)", + opacity: 0.8, + }, + { + transform: `translate(${Math.cos(angle) * distance}px, ${ + Math.sin(angle) * distance + }px) scale(0.1)`, + opacity: 0, + }, + ], + { + duration: duration, + easing: "cubic-bezier(0.4, 0, 0.2, 1)", + } + ).onfinish = () => particle.remove(); + } + } +} + +export default Meteor; diff --git a/src/js/animation/Star.js b/src/js/animation/Star.js new file mode 100644 index 0000000..013bfc7 --- /dev/null +++ b/src/js/animation/Star.js @@ -0,0 +1,64 @@ +class Star { + constructor(container) { + this.element = document.createElement("div"); + this.element.classList.add("star"); + this.container = container; + this.speed = Math.random() * 0.5 + 0.1; + + // Random direction angle (0 to 360 degrees) + this.angle = Math.random() * Math.PI * 2; + this.dx = Math.cos(this.angle) * this.speed; + this.dy = Math.sin(this.angle) * this.speed; + + this.x = Math.random() * 100; + this.y = Math.random() * 100; + + // Add color variation + this.color = this.getRandomStarColor(); + this.init(); + } + + getRandomStarColor() { + const colors = [ + "rgba(255, 255, 255, 0.9)", // Pure white + "rgba(255, 250, 200, 0.9)", // Warm white + "rgba(200, 220, 255, 0.9)", // Cool blue-white + "rgba(255, 220, 180, 0.9)", // Yellow-white + "rgba(180, 200, 255, 0.9)", // Blue-white + ]; + return colors[Math.floor(Math.random() * colors.length)]; + } + + init() { + const size = Math.random() * 3 + 0.5; + this.element.style.width = `${size}px`; + this.element.style.height = `${size}px`; + this.element.style.left = `${this.x}%`; + this.element.style.top = `${this.y}%`; + this.element.style.background = this.color; + this.element.style.boxShadow = `0 0 ${size * 2}px ${this.color}`; + this.element.style.animationDelay = `${Math.random() * 5}s`; + this.element.style.animationDuration = `${3 + Math.random() * 4}s`; + this.container.appendChild(this.element); + } + + update() { + this.x += this.dx * 0.05; + this.y += this.dy * 0.05; + + // Wrap around when star goes off screen + if (this.y > 100) this.y = 0; + if (this.y < 0) this.y = 100; + if (this.x > 100) this.x = 0; + if (this.x < 0) this.x = 100; + + this.element.style.left = `${this.x}%`; + this.element.style.top = `${this.y}%`; + } + + remove() { + this.element.remove(); + } +} + +export default Star; diff --git a/src/js/animation/starBackground.js b/src/js/animation/starBackground.js new file mode 100644 index 0000000..ffb2b8b --- /dev/null +++ b/src/js/animation/starBackground.js @@ -0,0 +1,92 @@ +import Star from "./Star.js"; +import Meteor from "./Meteor.js"; + +// State +const starInstances = []; +const meteorInstances = []; + +// Enhanced star creation with more stars +function createStars() { + const stars = document.getElementById("stars"); + const count = 400; // More stars for richer background + stars.innerHTML = ""; + starInstances.length = 0; + + for (let i = 0; i < count; i++) { + const star = new Star(stars); + starInstances.push(star); + } +} + +// Inject required CSS for star animations +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); +} + +// More frequent meteor spawning with variable rates +function trySpawnMeteor() { + const spawnChance = 0.12; // Increased spawn rate + if (Math.random() < spawnChance) { + const stars = document.getElementById("stars"); + const newMeteor = new Meteor(stars); + meteorInstances.push(newMeteor); + } +} + +// Enhanced animation loop +function animateStars() { + starInstances.forEach((star) => star.update()); + + // Update meteors and remove dead ones + for (let i = meteorInstances.length - 1; i >= 0; i--) { + if (!meteorInstances[i].update()) { + meteorInstances.splice(i, 1); + } + } + + requestAnimationFrame(animateStars); +} + +// Meteor burst effect +function createMeteorBurst() { + for (let i = 0; i < 3; i++) { + setTimeout(() => { + const stars = document.getElementById("stars"); + const newMeteor = new Meteor(stars); + meteorInstances.push(newMeteor); + }, i * 200); + } +} + +// Initialize everything +function init() { + injectStarCSS(); + createStars(); + animateStars(); + + // More frequent meteor spawning + setInterval(trySpawnMeteor, 2000); + + // Add occasional meteor bursts + setInterval(createMeteorBurst, 15000); +} + +// Start when DOM is ready +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); +} else { + init(); +} diff --git a/src/js/stars.js b/src/js/stars.js deleted file mode 100644 index d5c34d0..0000000 --- a/src/js/stars.js +++ /dev/null @@ -1,22 +0,0 @@ -// Create stellar background -function createStars() { - const stars = document.getElementById("stars"); - const count = 250; - stars.innerHTML = ""; // Clear any existing stars - - for (let i = 0; i < count; i++) { - const star = document.createElement("div"); - star.classList.add("star"); - - const size = Math.random() * 3; - star.style.width = `${size}px`; - star.style.height = `${size}px`; - - star.style.left = `${Math.random() * 100}%`; - star.style.top = `${Math.random() * 100}%`; - - star.style.animationDelay = `${Math.random() * 5}s`; - - stars.appendChild(star); - } -}