Complete website overhaul #28

Merged
Patrick_Pluto merged 7 commits from sageTheDm/pages:main into main 2025-10-21 20:44:30 +02:00
5 changed files with 480 additions and 25 deletions
Showing only changes of commit fd57d47956 - Show all commits

View file

@ -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.
</p>
@ -481,14 +481,16 @@
</footer>
</main>
<!-- JavaScript Files -->
<!-- General scripts -->
<script src="src/js/main.js"></script>
<script src="src/js/stars.js"></script>
<script src="src/js/navigation.js"></script>
<script src="src/js/form.js"></script>
<script src="src/js/portfolioCard.js"></script>
<script src="src/js/portfolioFilter.js"></script>
<script src="src/js/overview.js"></script>
<script src="src/js/sectionTracker.js"></script>
<!-- Animation scripts -->
<script type="module" src="src/js/animation/starBackground.js"></script>
</body>
</html>

319
src/js/animation/Meteor.js Normal file
View file

@ -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;

64
src/js/animation/Star.js Normal file
View file

@ -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;

View file

@ -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();
}

View file

@ -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);
}
}