<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Feuerwerk Simulator</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #020202;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: white;
user-select: none;
}
canvas {
display: block;
cursor: crosshair;
}
/* Container für alle Controls */
#ui-container {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
z-index: 10;
width: 90%;
max-width: 800px;
transition: opacity 0.5s; /* Für das Ausblenden im Fullscreen */
}
/* Die Leiste für die Varianten */
#controls-variants {
background: rgba(20, 20, 20, 0.8);
backdrop-filter: blur(10px);
padding: 15px 25px;
border-radius: 50px;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.1);
}
/* Controls für Dauerfeuer und Fullscreen */
#controls-utility {
display: flex;
gap: 10px;
}
.btn {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.3);
color: #ccc;
padding: 10px 18px;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
white-space: nowrap;
}
.btn:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
border-color: white;
}
.btn.active {
background: white;
color: black;
border-color: white;
box-shadow: 0 0 15px rgba(255, 255, 255, 0.4);
}
#auto-btn.auto-active {
background: #4CAF50; /* Grün */
color: white;
border-color: #4CAF50;
box-shadow: 0 0 20px rgba(76, 175, 80, 0.6);
}
#fullscreen-btn {
background: #FF9800; /* Orange */
color: white;
border: 1px solid #FF9800;
}
#fullscreen-btn:hover {
background: #E68A00;
}
#instructions {
position: absolute;
top: 20px;
width: 100%;
text-align: center;
pointer-events: none;
opacity: 0.7;
font-weight: 300;
text-shadow: 0 2px 5px black;
transition: opacity 1.5s ease-out; /* Für das Ausfaden */
}
/* Spezifische Anweisung im Fullscreen Modus */
.fullscreen-active #instructions {
opacity: 1;
}
/* Klasse für das Ausfaden */
.fade-out {
opacity: 0 !important;
}
</style>
</head>
<body id="body">
<div id="instructions">Klicke zum Zünden oder aktiviere Dauerfeuer!</div>
<div id="ui-container">
<div id="controls-utility">
<button id="auto-btn" class="btn" onclick="toggleAutoFire()">Dauerfeuer: AUS</button>
<button id="fullscreen-btn" class="btn" onclick="toggleFullscreen()">Full-Screen Modus</button>
</div>
<div id="controls-variants">
<button class="btn active variant-btn" onclick="setMode('bunt', this)">Bunt</button>
<button class="btn variant-btn" onclick="setMode('gold', this)">Goldregen</button>
<button class="btn variant-btn" onclick="setMode('silber', this)">Silber-Blitz</button>
<button class="btn variant-btn" onclick="setMode('neon', this)">Neon</button>
<button class="btn variant-btn" onclick="setMode('palme', this)">Palme</button>
<button class="btn variant-btn" onclick="setMode('geist', this)">Geist</button>
</div>
</div>
<canvas id="canvas"></canvas>
<script>
// --- SETUP ---
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const body = document.getElementById('body');
const instructions = document.getElementById('instructions');
let cw = window.innerWidth;
let ch = window.innerHeight;
let fadeOutTimer = null;
canvas.width = cw;
canvas.height = ch;
window.addEventListener('resize', () => {
cw = window.innerWidth;
ch = window.innerHeight;
canvas.width = cw;
canvas.height = ch;
});
// --- ERWEITERTE KONFIGURATION & NEUE PHYSIK ---
let currentMode = 'bunt';
let dauerfeuerAktiv = false;
let dauerfeuerTimer = null;
let isFullscreen = false;
const modes = {
// [GEÄNDERT] Decay und Speed reduziert, um länger zu halten
bunt: {
hueMin: 0, hueMax: 360, saturation: 100,
friction: 0.94, gravity: 1.2,
countMin: 80, countMax: 180, // Zufällige Größe
decay: 0.008, speed: 5
},
// [GEÄNDERT] Gravity deutlich niedriger für langes Schweben
gold: {
hueMin: 35, hueMax: 45, saturation: 100,
friction: 0.97, gravity: 0.5, // Sehr niedrige Schwerkraft
countMin: 120, countMax: 250,
decay: 0.005, speed: 3.5 // Langsamere Ausbreitung
},
// [GEÄNDERT] Decay und Friction leicht reduziert
silber: {
hueMin: 0, hueMax: 360, saturation: 0,
friction: 0.95, gravity: 1.2,
countMin: 60, countMax: 100,
decay: 0.015, speed: 8, brightnessMin: 80
},
// [GEÄNDERT] Decay reduziert
neon: {
hueMin: 180, hueMax: 320, saturation: 100,
friction: 0.93, gravity: 1,
countMin: 50, countMax: 120,
decay: 0.015, speed: 10
},
// [GEÄNDERT] Decay reduziert, damit die "Blätter" länger sichtbar fallen
palme: {
hueMin: 20, hueMax: 30, saturation: 90,
friction: 0.99,
gravity: 2.5,
countMin: 100, countMax: 200,
decay: 0.008, speed: 4
},
// [GEÄNDERT] Decay stark reduziert, um länger zu verweilen
geist: {
hueMin: 200, hueMax: 260, saturation: 60,
friction: 0.96, gravity: 0.5,
countMin: 40, countMax: 90,
decay: 0.01, speed: 3, brightnessMin: 30
}
};
// --- KLASSEN ---
// 1. Die Rakete
class Firework {
constructor(sx, sy, tx, ty) {
this.x = sx; this.y = sy;
this.sx = sx; this.sy = sy;
this.tx = tx; this.ty = ty;
this.distanceToTarget = calculateDistance(sx, sy, tx, ty);
this.distanceTraveled = 0;
this.coordinates = [];
this.coordinateCount = 3;
while(this.coordinateCount--) { this.coordinates.push([this.x, this.y]); }
this.angle = Math.atan2(ty - sy, tx - sx);
// [GEÄNDERT] Aufstiegsgeschwindigkeit leicht reduziert
this.speed = 1.8;
this.acceleration = 1.04;
const mode = modes[currentMode];
this.brightness = Math.random() * 40 + 60;
this.hue = Math.floor(Math.random() * (mode.hueMax - mode.hueMin + 1)) + mode.hueMin;
this.saturation = mode.saturation;
}
update(index) {
this.coordinates.pop();
this.coordinates.unshift([this.x, this.y]);
this.speed *= this.acceleration;
const vx = Math.cos(this.angle) * this.speed;
const vy = Math.sin(this.angle) * this.speed;
this.distanceTraveled = calculateDistance(this.sx, this.sy, this.x + vx, this.y + vy);
if(this.distanceTraveled >= this.distanceToTarget) {
createParticles(this.tx, this.ty, this.hue);
fireworks.splice(index, 1);
} else {
this.x += vx;
this.y += vy;
}
}
draw() {
ctx.beginPath();
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0], this.coordinates[this.coordinates.length - 1][1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = `hsl(${this.hue}, ${this.saturation}%, ${this.brightness}%)`;
ctx.stroke();
}
}
// 2. Die Partikel
class Particle {
constructor(x, y, hue) {
const mode = modes[currentMode];
this.x = x; this.y = y;
this.coordinates = [];
this.coordinateCount = 6;
while(this.coordinateCount--) { this.coordinates.push([this.x, this.y]); }
this.angle = Math.random() * Math.PI * 2;
// [GEÄNDERT] Anfangsgeschwindigkeit leicht niedriger
this.speed = Math.random() * mode.speed + 0.5;
this.friction = mode.friction;
this.gravity = mode.gravity;
this.hue = Math.floor(Math.random() * (mode.hueMax - mode.hueMin + 1)) + mode.hueMin;
this.saturation = mode.saturation;
let bMin = mode.brightnessMin !== undefined ? mode.brightnessMin : 50;
this.brightness = Math.random() * (100 - bMin) + bMin;
this.alpha = 1;
this.decay = Math.random() * mode.decay + mode.decay/2;
}
update(index) {
this.coordinates.pop();
this.coordinates.unshift([this.x, this.y]);
this.speed *= this.friction;
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed + this.gravity;
this.alpha -= this.decay;
if(this.alpha <= this.decay) { particles.splice(index, 1); }
}
draw() {
ctx.beginPath();
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0], this.coordinates[this.coordinates.length - 1][1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = `hsla(${this.hue}, ${this.saturation}%, ${this.brightness}%, ${this.alpha})`;
ctx.stroke();
}
}
// --- ENGINE LOGIK ---
const fireworks = [];
const particles = [];
function calculateDistance(p1x, p1y, p2x, p2y) {
const xDistance = p1x - p2x;
const yDistance = p1y - p2y;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
// [GEÄNDERT] Erzeugt eine zufällige Anzahl von Partikeln (klein bis groß)
function createParticles(x, y, hue) {
const mode = modes[currentMode];
// Zufällige Partikelanzahl innerhalb des definierten Min/Max-Bereichs
const count = Math.floor(Math.random() * (mode.countMax - mode.countMin + 1)) + mode.countMin;
for(let i = 0; i < count; i++) {
particles.push(new Particle(x, y, hue));
}
}
function loop() {
requestAnimationFrame(loop);
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, cw, ch);
ctx.globalCompositeOperation = 'lighter';
let i = fireworks.length;
while(i--) { fireworks[i].draw(); fireworks[i].update(i); }
let j = particles.length;
while(j--) { particles[j].draw(); particles[j].update(j); }
}
// --- INTERAKTION & MODI (Full-Screen Fade-Out Logik beibehalten) ---
window.setMode = function(modeName, btnElement) {
currentMode = modeName;
document.querySelectorAll('.variant-btn').forEach(btn => btn.classList.remove('active'));
btnElement.classList.add('active');
};
window.toggleAutoFire = function() {
dauerfeuerAktiv = !dauerfeuerAktiv;
updateAutoFireUI();
if(dauerfeuerAktiv) {
autoFireLoop();
} else {
clearTimeout(dauerfeuerTimer);
}
};
function updateAutoFireUI() {
const btn = document.getElementById('auto-btn');
if(dauerfeuerAktiv) {
btn.textContent = "Dauerfeuer: AN";
btn.classList.add('auto-active');
} else {
btn.textContent = "Dauerfeuer: AUS";
btn.classList.remove('auto-active');
}
}
function autoFireLoop() {
if(!dauerfeuerAktiv) return;
let targetX = Math.random() * cw;
let targetY = Math.random() * (ch * 0.6);
let startX = Math.random() * cw;
fireworks.push(new Firework(startX, ch, targetX, targetY));
let randomDelay = Math.random() * 900 + 300;
dauerfeuerTimer = setTimeout(autoFireLoop, randomDelay);
}
// --- FULLSCREEN LOGIK (mit 3s Fade-Out) ---
window.toggleFullscreen = function() {
if (!isFullscreen) {
enterFullscreen();
} else {
exitFullscreen();
}
};
function enterFullscreen() {
isFullscreen = true;
document.getElementById('ui-container').style.opacity = '0';
body.classList.add('fullscreen-active');
instructions.textContent = "Klicke zum Beenden";
instructions.classList.remove('fade-out');
// Timer starten, um den Text nach 3 Sekunden auszublenden
fadeOutTimer = setTimeout(() => {
instructions.classList.add('fade-out');
}, 3000);
if (!dauerfeuerAktiv) {
toggleAutoFire();
}
const elem = document.documentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
}
canvas.removeEventListener('mousedown', handleManualClick);
canvas.addEventListener('mousedown', exitFullscreen, { once: true });
}
function exitFullscreen() {
clearTimeout(fadeOutTimer);
instructions.classList.remove('fade-out');
isFullscreen = false;
document.getElementById('ui-container').style.opacity = '1';
body.classList.remove('fullscreen-active');
document.getElementById('instructions').textContent = "Klicke zum Zünden oder aktiviere Dauerfeuer!";
if (dauerfeuerAktiv) {
toggleAutoFire();
}
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
canvas.removeEventListener('mousedown', exitFullscreen);
canvas.addEventListener('mousedown', handleManualClick);
}
document.addEventListener('fullscreenchange', function() {
if (!document.fullscreenElement && isFullscreen) {
exitFullscreen();
}
});
document.addEventListener('webkitfullscreenchange', function() {
if (!document.webkitFullscreenElement && isFullscreen) {
exitFullscreen();
}
});
function handleManualClick(e) {
e.preventDefault();
fireworks.push(new Firework(cw / 2, ch, e.clientX, e.clientY));
}
canvas.addEventListener('mousedown', handleManualClick);
loop();
</script>
</body>
</html>
Comments
0 B
|👍
/👎
0 B
|0 👍
/0 👎