fix fiamme
This commit is contained in:
132
app.js
132
app.js
@@ -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,
|
||||
|
||||
92
styles.css
92
styles.css
@@ -760,50 +760,96 @@ body {
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
/* fiammate dai due angoli in basso */
|
||||
/* canvas del fuoco a particelle (falò vero, disegnato in JS) */
|
||||
.shalom-fire {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1; /* dietro al leone (z-index 2) */
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.shalom-overlay.mode-fire .shalom-fire {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* fuoco che lambisce il bordo inferiore: lingue di fiamma animate.
|
||||
Ora fanno solo da base morbida sotto le particelle canvas. */
|
||||
.shalom-flames {
|
||||
position: absolute;
|
||||
bottom: -6%;
|
||||
width: 50vw;
|
||||
height: 92vh;
|
||||
bottom: 0;
|
||||
height: 40vh;
|
||||
width: 52vw;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
filter: blur(2px);
|
||||
mix-blend-mode: screen;
|
||||
background:
|
||||
radial-gradient(55% 42% at 50% 100%, rgba(255, 235, 140, 0.95), transparent 70%),
|
||||
linear-gradient(0deg, #ff2d00 0%, #ff7a00 26%, #ffd23f 48%, rgba(255, 210, 63, 0) 74%);
|
||||
z-index: 1;
|
||||
}
|
||||
.shalom-flames.left {
|
||||
left: -8%;
|
||||
transform-origin: bottom left;
|
||||
left: 0;
|
||||
}
|
||||
.shalom-flames.right {
|
||||
right: -8%;
|
||||
transform-origin: bottom right;
|
||||
right: 0;
|
||||
transform: scaleX(-1); /* specchia così le due metà non sono identiche */
|
||||
}
|
||||
.shalom-flames::before,
|
||||
.shalom-flames::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transform-origin: bottom center;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom;
|
||||
filter: blur(5px) contrast(1.5) brightness(1.08);
|
||||
-webkit-mask: linear-gradient(0deg, #000 20%, rgba(0, 0, 0, 0.55) 55%, transparent 88%);
|
||||
mask: linear-gradient(0deg, #000 20%, rgba(0, 0, 0, 0.55) 55%, transparent 88%);
|
||||
}
|
||||
.shalom-flames::before {
|
||||
background-image:
|
||||
radial-gradient(22% 58% at 18% 100%, rgba(255, 244, 190, 0.95), rgba(255, 120, 0, 0.55) 46%, transparent 72%),
|
||||
radial-gradient(26% 74% at 42% 100%, rgba(255, 224, 130, 0.9), rgba(255, 80, 0, 0.5) 50%, transparent 74%),
|
||||
radial-gradient(20% 60% at 66% 100%, rgba(255, 240, 170, 0.92), rgba(255, 100, 0, 0.52) 46%, transparent 72%),
|
||||
radial-gradient(24% 76% at 86% 100%, rgba(255, 214, 110, 0.9), rgba(255, 70, 0, 0.5) 50%, transparent 74%);
|
||||
}
|
||||
.shalom-flames::after {
|
||||
background-image:
|
||||
radial-gradient(17% 70% at 30% 100%, rgba(255, 250, 210, 0.95), rgba(255, 140, 0, 0.55) 44%, transparent 70%),
|
||||
radial-gradient(21% 84% at 54% 100%, rgba(255, 232, 150, 0.9), rgba(255, 90, 0, 0.5) 48%, transparent 72%),
|
||||
radial-gradient(17% 66% at 78% 100%, rgba(255, 244, 180, 0.92), rgba(255, 110, 0, 0.52) 44%, transparent 70%);
|
||||
}
|
||||
.shalom-overlay.mode-fire .shalom-flames {
|
||||
opacity: 1;
|
||||
animation: flames 0.26s infinite alternate;
|
||||
opacity: 0.5; /* base morbida: le particelle canvas fanno il grosso */
|
||||
}
|
||||
.shalom-overlay.mode-fire .shalom-flames.right {
|
||||
animation-delay: 0.13s;
|
||||
.shalom-overlay.mode-fire .shalom-flames::before {
|
||||
animation: flameA 0.42s infinite alternate;
|
||||
}
|
||||
@keyframes flames {
|
||||
0% { transform: scaleY(1) scaleX(1) skewX(0deg); }
|
||||
100% { transform: scaleY(1.14) scaleX(0.92) skewX(4deg); }
|
||||
.shalom-overlay.mode-fire .shalom-flames::after {
|
||||
animation: flameB 0.31s infinite alternate;
|
||||
}
|
||||
@keyframes flameA {
|
||||
0% { transform: scaleY(0.94) scaleX(1.02) translateY(0); opacity: 0.85; }
|
||||
100% { transform: scaleY(1.2) scaleX(0.97) translateY(-3%); opacity: 1; }
|
||||
}
|
||||
@keyframes flameB {
|
||||
0% { transform: scaleY(1.14) translateX(-1.5%); opacity: 0.7; }
|
||||
100% { transform: scaleY(0.9) translateX(2%); opacity: 1; }
|
||||
}
|
||||
|
||||
/* leone che irrompe */
|
||||
.shalom-lion {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
top: 48%;
|
||||
left: 50%;
|
||||
width: min(46vw, 520px);
|
||||
border-radius: 16px;
|
||||
width: min(52vw, 580px);
|
||||
z-index: 2;
|
||||
/* sfuma i bordi dell'immagine così il fuoco si fonde col nero invece di
|
||||
mostrare un riquadro rettangolare netto */
|
||||
-webkit-mask: radial-gradient(ellipse 66% 66% at 50% 45%, #000 44%, transparent 74%);
|
||||
mask: radial-gradient(ellipse 66% 66% at 50% 45%, #000 44%, transparent 74%);
|
||||
transform: translate(-50%, -50%) scale(0.2);
|
||||
opacity: 0;
|
||||
filter: drop-shadow(0 0 40px #ff6a00);
|
||||
filter: drop-shadow(0 0 55px #ff6a00);
|
||||
}
|
||||
.shalom-overlay.mode-fire .shalom-lion {
|
||||
animation: lionSlam 0.6s cubic-bezier(0.2, 1.5, 0.4, 1) forwards,
|
||||
|
||||
Reference in New Issue
Block a user