|
|
| Zeile 1.234: |
Zeile 1.234: |
| } | | } |
|
| |
|
| })();
| |
|
| |
|
| |
| /* ============================================================
| |
| ADOS – Feuerwerk mit Raketen + Explosion (sichtbar, ES5)
| |
| - dauerhaft
| |
| - kein Abdunkeln (kein schwarzer Schleier)
| |
| - Raketen mit Trail, Explosion deutlich
| |
| ============================================================ */
| |
| (function () {
| |
| 'use strict';
| |
|
| |
| // true = nur 31.12/01.01, false = immer
| |
| var onlyOnNewYears = false;
| |
|
| |
| // Debug in Konsole
| |
| var DEBUG = false;
| |
|
| |
| function isNewYears() {
| |
| var d = new Date();
| |
| var m = d.getMonth() + 1;
| |
| var day = d.getDate();
| |
| return (m === 12 && day === 31) || (m === 1 && day === 1);
| |
| }
| |
| if (onlyOnNewYears && !isNewYears()) return;
| |
|
| |
| function log() {
| |
| if (!DEBUG || !window.console) return;
| |
| try { console.log.apply(console, arguments); } catch (e) {}
| |
| }
| |
|
| |
| // Canvas anlegen
| |
| function createCanvas() {
| |
| // falls schon existiert (z.B. nach Reload), entfernen
| |
| var old = document.getElementById('ados-fireworks-canvas');
| |
| if (old && old.parentNode) old.parentNode.removeChild(old);
| |
|
| |
| var c = document.createElement('canvas');
| |
| c.id = 'ados-fireworks-canvas';
| |
| c.style.position = 'fixed';
| |
| c.style.left = '0';
| |
| c.style.top = '0';
| |
| c.style.width = '100%';
| |
| c.style.height = '100%';
| |
| c.style.pointerEvents = 'none';
| |
| c.style.zIndex = '9999';
| |
| c.style.opacity = '1';
| |
| document.body.appendChild(c);
| |
| return c;
| |
| }
| |
|
| |
| function fitCanvas(c) {
| |
| var dpr = window.devicePixelRatio || 1;
| |
| c.width = Math.floor(window.innerWidth * dpr);
| |
| c.height = Math.floor(window.innerHeight * dpr);
| |
| var ctx = c.getContext('2d');
| |
|
| |
| // Koordinaten in CSS-Pixeln nutzen
| |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
| |
|
| |
| // Optional: additiver Look für „Glow“
| |
| ctx.globalCompositeOperation = 'lighter';
| |
|
| |
| return ctx;
| |
| }
| |
|
| |
| function rand(min, max) { return min + Math.random() * (max - min); }
| |
| function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
| |
|
| |
| var canvas, ctx;
| |
| var rockets = [];
| |
| var particles = [];
| |
| var last = 0;
| |
| var running = true;
| |
|
| |
| // ---- Rocket Spawn ----
| |
| function spawnRocket() {
| |
| var w = window.innerWidth;
| |
| var h = window.innerHeight;
| |
|
| |
| var x = rand(60, w - 60);
| |
| var y = h + rand(20, 120);
| |
|
| |
| // Explosionshöhe
| |
| var targetY = rand(h * 0.12, h * 0.50);
| |
|
| |
| // Start-Geschwindigkeit nach oben (negativ)
| |
| var vy = rand(-14.0, -18.0);
| |
| var vx = rand(-1.8, 1.8);
| |
|
| |
| // „Glow“-Farbe (gold/weiß)
| |
| var r = Math.floor(rand(220, 255));
| |
| var g = Math.floor(rand(170, 240));
| |
| var b = Math.floor(rand(80, 170));
| |
|
| |
| rockets.push({
| |
| x: x,
| |
| y: y,
| |
| vx: vx,
| |
| vy: vy,
| |
| targetY: targetY,
| |
| age: 0,
| |
| life: rand(2200, 3200),
| |
| r: r, g: g, b: b,
| |
| trail: [] // Liste von Punkten für Spur
| |
| });
| |
| }
| |
|
| |
| // ---- Explosion ----
| |
| function explode(x, y) {
| |
| var count = Math.floor(rand(70, 140));
| |
| var i;
| |
|
| |
| log('[Fireworks] explode @', x, y, 'count', count);
| |
|
| |
| for (i = 0; i < count; i++) {
| |
| var angle = rand(0, Math.PI * 2);
| |
| var speed = rand(2.5, 7.5);
| |
|
| |
| // knalligere Farben
| |
| var rr = Math.floor(rand(120, 255));
| |
| var gg = Math.floor(rand(80, 240));
| |
| var bb = Math.floor(rand(120, 255));
| |
|
| |
| // etwas „Gold“-Bias
| |
| if (Math.random() < 0.40) {
| |
| rr = Math.floor(rand(220, 255));
| |
| gg = Math.floor(rand(150, 230));
| |
| bb = Math.floor(rand(30, 120));
| |
| }
| |
|
| |
| particles.push({
| |
| x: x,
| |
| y: y,
| |
| vx: Math.cos(angle) * speed,
| |
| vy: Math.sin(angle) * speed,
| |
| age: 0,
| |
| life: rand(900, 1600),
| |
| size: rand(1.8, 3.8),
| |
| r: rr, g: gg, b: bb
| |
| });
| |
| }
| |
| }
| |
|
| |
| // ---- Frame ----
| |
| function tick(ts) {
| |
| if (!running) return;
| |
| if (!last) last = ts;
| |
| var dt = ts - last;
| |
| last = ts;
| |
|
| |
| var w = window.innerWidth;
| |
| var h = window.innerHeight;
| |
|
| |
| // KEIN Abdunkeln: wir „faden“ mit transparenter WEISS-Schicht minimal.
| |
| // Dadurch gibt es einen soften Trail-Effekt, aber keinen dunklen Schleier.
| |
| // (Wenn du absolut gar keinen Trail willst: ctx.clearRect(...) statt fillRect)
| |
| ctx.globalCompositeOperation = 'source-over';
| |
| ctx.fillStyle = 'rgba(255,255,255,0.10)'; // kleiner Wert = weniger „Aufhellen“
| |
| ctx.fillRect(0, 0, w, h);
| |
|
| |
| // Glow wieder aktivieren
| |
| ctx.globalCompositeOperation = 'lighter';
| |
|
| |
| // --- Rockets ---
| |
| var i, r;
| |
| for (i = rockets.length - 1; i >= 0; i--) {
| |
| r = rockets[i];
| |
| r.age += dt;
| |
|
| |
| // Trail-Punkt speichern
| |
| r.trail.push({ x: r.x, y: r.y });
| |
| if (r.trail.length > 18) r.trail.shift();
| |
|
| |
| // Physik: Gravity zieht runter (vy wird weniger negativ)
| |
| r.vy += 0.010 * (dt / 16); // stärker, damit es natürlicher wirkt
| |
| r.x += r.vx * (dt / 16);
| |
| r.y += r.vy * (dt / 16);
| |
|
| |
| // Explodieren, wenn Zielhöhe erreicht
| |
| if (r.y <= r.targetY) {
| |
| explode(r.x, r.y);
| |
| rockets.splice(i, 1);
| |
| continue;
| |
| }
| |
|
| |
| // Sicherheit
| |
| if (r.age >= r.life || r.y < -200 || r.x < -200 || r.x > w + 200) {
| |
| rockets.splice(i, 1);
| |
| continue;
| |
| }
| |
|
| |
| // Trail zeichnen
| |
| ctx.beginPath();
| |
| ctx.lineWidth = 3.2;
| |
| ctx.strokeStyle = 'rgba(' + r.r + ',' + r.g + ',' + r.b + ',0.35)';
| |
| var t;
| |
| for (t = 0; t < r.trail.length; t++) {
| |
| var pt = r.trail[t];
| |
| if (t === 0) ctx.moveTo(pt.x, pt.y);
| |
| else ctx.lineTo(pt.x, pt.y);
| |
| }
| |
| ctx.stroke();
| |
|
| |
| // Rocket-Kopf (hell)
| |
| ctx.beginPath();
| |
| ctx.fillStyle = 'rgba(' + r.r + ',' + r.g + ',' + r.b + ',1)';
| |
| ctx.arc(r.x, r.y, 3.0, 0, Math.PI * 2, false);
| |
| ctx.fill();
| |
| }
| |
|
| |
| // --- Particles ---
| |
| var p;
| |
| for (i = particles.length - 1; i >= 0; i--) {
| |
| p = particles[i];
| |
| p.age += dt;
| |
|
| |
| if (p.age >= p.life) {
| |
| particles.splice(i, 1);
| |
| continue;
| |
| }
| |
|
| |
| // Bewegung
| |
| p.vy += 0.020 * (dt / 16);
| |
| p.vx *= Math.pow(0.985, dt / 16);
| |
| p.vy *= Math.pow(0.985, dt / 16);
| |
| p.x += p.vx * (dt / 16);
| |
| p.y += p.vy * (dt / 16);
| |
|
| |
| var alpha = clamp(1 - (p.age / p.life), 0, 1);
| |
|
| |
| ctx.beginPath();
| |
| ctx.fillStyle = 'rgba(' + p.r + ',' + p.g + ',' + p.b + ',' + alpha + ')';
| |
| ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2, false);
| |
| ctx.fill();
| |
| }
| |
|
| |
| requestAnimationFrame(tick);
| |
| }
| |
|
| |
| // ---- Spawner ----
| |
| function scheduleRockets() {
| |
| function loop() {
| |
| if (!running) return;
| |
|
| |
| // 1–3 Raketen pro Tick
| |
| spawnRocket();
| |
| if (Math.random() < 0.55) spawnRocket();
| |
| if (Math.random() < 0.20) spawnRocket();
| |
|
| |
| // Frequenz (kleiner = mehr Feuerwerk)
| |
| setTimeout(loop, Math.floor(rand(650, 1200)));
| |
| }
| |
| loop();
| |
| }
| |
|
| |
| function init() {
| |
| canvas = createCanvas();
| |
| ctx = fitCanvas(canvas);
| |
|
| |
| window.addEventListener('resize', function () {
| |
| if (!canvas) return;
| |
| ctx = fitCanvas(canvas);
| |
| });
| |
|
| |
| log('[Fireworks] init');
| |
| scheduleRockets();
| |
| requestAnimationFrame(tick);
| |
| }
| |
|
| |
| if (document.readyState === 'loading') {
| |
| document.addEventListener('DOMContentLoaded', init, false);
| |
| } else {
| |
| init();
| |
| }
| |
| })(); | | })(); |