fix fiamme

This commit is contained in:
2026-07-02 16:02:23 +02:00
parent fbdb192a0d
commit a9d525f938
2 changed files with 201 additions and 23 deletions

132
app.js
View File

@@ -399,6 +399,135 @@
});
}
/* ---------- Fuoco a particelle (canvas 2D additivo) ----------
Un vero falò: particelle emesse dal bordo inferiore che salgono, si
allargano e sfumano, colorate dal nucleo bianco-giallo alla brace rossa.
Blending additivo ("lighter") + sprite pre-renderizzati = fluido e realistico. */
const Fire = {
canvas: null,
ctx: null,
parts: [],
sprites: null,
raf: 0,
running: false,
w: 0,
h: 0,
last: 0,
acc: 0,
// palette giovane -> vecchio (nucleo caldo verso brace scura)
PALETTE: [
[255, 255, 235],
[255, 244, 170],
[255, 200, 70],
[255, 130, 20],
[225, 65, 15],
[130, 35, 18],
],
buildSprites() {
this.sprites = this.PALETTE.map(([r, g, b]) => {
const s = document.createElement("canvas");
s.width = s.height = 64;
const c = s.getContext("2d");
const grad = c.createRadialGradient(32, 32, 0, 32, 32, 32);
grad.addColorStop(0, `rgba(${r},${g},${b},1)`);
grad.addColorStop(0.32, `rgba(${r},${g},${b},0.55)`);
grad.addColorStop(1, `rgba(${r},${g},${b},0)`);
c.fillStyle = grad;
c.fillRect(0, 0, 64, 64);
return s;
});
},
ensure() {
if (!this.canvas) {
this.canvas = document.querySelector(".shalom-fire");
this.ctx = this.canvas.getContext("2d");
}
if (!this.sprites) this.buildSprites();
},
resize() {
this.w = this.canvas.width = window.innerWidth;
this.h = this.canvas.height = window.innerHeight;
},
spawn(n) {
for (let i = 0; i < n; i++) {
// più denso al centro (sotto il leone): due gaussiane sommate
const bias = (Math.random() + Math.random()) / 2;
const x = bias * this.w;
const life = 700 + Math.random() * 1200;
this.parts.push({
x,
y: this.h + 12,
vx: (Math.random() - 0.5) * 36,
vy: -(190 + Math.random() * 240),
life,
max: life,
size: 15 + Math.random() * 30,
wob: Math.random() * 6.283,
wobSpeed: 4 + Math.random() * 6,
});
}
},
start() {
this.ensure();
this.resize();
this.parts.length = 0;
this.last = 0;
this.acc = 0;
this.running = true;
const loop = (t) => {
if (!this.running) return;
if (!this.last) this.last = t;
let dt = (t - this.last) / 1000;
this.last = t;
if (dt > 0.05) dt = 0.05; // niente salti dopo un lag
this.acc += dt * 900; // ~900 particelle/sec
const n = Math.floor(this.acc);
this.acc -= n;
this.spawn(n);
const ctx = this.ctx;
ctx.clearRect(0, 0, this.w, this.h);
ctx.globalCompositeOperation = "lighter";
for (let i = this.parts.length - 1; i >= 0; i--) {
const p = this.parts[i];
p.life -= dt * 1000;
if (p.life <= 0) {
this.parts.splice(i, 1);
continue;
}
const k = p.life / p.max; // 1 -> 0
p.vy -= 70 * dt; // il calore accelera verso l'alto
p.wob += dt * p.wobSpeed;
p.x += (p.vx + Math.sin(p.wob) * 34) * dt;
p.y += p.vy * dt;
const age = 1 - k; // 0 -> 1
const idx = Math.min(
this.sprites.length - 1,
(age * this.sprites.length) | 0,
);
const size = p.size * (0.5 + k * 0.85);
const hs = size * (1.5 + (1 - k) * 1.4); // stiro verticale -> lingua di fuoco
ctx.globalAlpha = Math.min(1, k * 1.6) * 0.28;
ctx.drawImage(
this.sprites[idx],
p.x - size,
p.y - hs * 1.4,
size * 2,
hs * 2,
);
}
ctx.globalAlpha = 1;
this.raf = requestAnimationFrame(loop);
};
this.raf = requestAnimationFrame(loop);
},
stop() {
this.running = false;
cancelAnimationFrame(this.raf);
if (this.ctx) this.ctx.clearRect(0, 0, this.w, this.h);
this.parts.length = 0;
},
};
/* ---------- Effetto SHALOM (vittoria stanze con idromassaggio) ---------- */
let shalomOverlay = null;
function ensureShalomOverlay() {
@@ -408,6 +537,7 @@
o.innerHTML =
'<div class="shalom-flames left"></div>' +
'<div class="shalom-flames right"></div>' +
'<canvas class="shalom-fire"></canvas>' +
'<img class="shalom-lion" src="image/lion.jpg" alt="" />' +
'<div class="shalom-rays"></div>' +
'<img class="shalom-gesu" src="image/gesu.jpeg" alt="" />' +
@@ -426,6 +556,7 @@
try {
v.pause();
} catch (e) {}
Fire.stop();
o.classList.remove("on", "mode-fire", "mode-video");
}
// Attende: fine video, tetto di tempo, oppure tap sull'overlay (skip).
@@ -450,6 +581,7 @@
async function shalomFireLion() {
const o = ensureShalomOverlay();
showShalom(o, "mode-fire");
Fire.start();
screenShake();
const stop = Samples.play("leone", {
vol: 1.0,