poi cose coriandoli
This commit is contained in:
117
app.js
117
app.js
@@ -122,40 +122,58 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ---------- Coriandoli ---------- */
|
/* ---------- Coriandoli (canvas a schermo intero, dimensionato a mano) ---------- */
|
||||||
|
// IMPORTANTE: niente useWorker + niente resize automatico. Dimensiono io il
|
||||||
|
// canvas alla viewport, altrimenti il buffer resta 300x150 e i coriandoli
|
||||||
|
// finiscono tutti ammassati nell'angolo in alto a sinistra.
|
||||||
|
const confettiCanvas = $("#confetti");
|
||||||
|
function sizeConfetti() {
|
||||||
|
confettiCanvas.width = window.innerWidth;
|
||||||
|
confettiCanvas.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
sizeConfetti();
|
||||||
|
window.addEventListener("resize", sizeConfetti);
|
||||||
const myConfetti =
|
const myConfetti =
|
||||||
typeof confetti !== "undefined"
|
typeof confetti !== "undefined"
|
||||||
? confetti.create($("#confetti"), { resize: true, useWorker: true })
|
? confetti.create(confettiCanvas, { resize: false, useWorker: false })
|
||||||
: () => {};
|
: () => {};
|
||||||
|
|
||||||
const CONFETTI_COLORS = ["#ffd23f", "#ff9e00", "#ff2e88", "#00e5ff", "#29e08a", "#ffffff"];
|
const CONFETTI_COLORS = ["#ffd23f", "#ff9e00", "#ff2e88", "#00e5ff", "#29e08a", "#ff3b5c", "#ffffff"];
|
||||||
|
|
||||||
|
// VALANGA di coriandoli su TUTTO lo schermo
|
||||||
function bigBurst() {
|
function bigBurst() {
|
||||||
// esplosione centrale abbondante
|
const C = CONFETTI_COLORS;
|
||||||
myConfetti({ particleCount: 220, spread: 110, startVelocity: 48, origin: { y: 0.55 }, colors: CONFETTI_COLORS });
|
// esplosione centrale gigante a 360°
|
||||||
myConfetti({ particleCount: 140, spread: 130, startVelocity: 40, decay: 0.92, scalar: 1.1, origin: { y: 0.6 }, colors: CONFETTI_COLORS });
|
myConfetti({ particleCount: 400, spread: 360, startVelocity: 45, scalar: 1.1, origin: { x: 0.5, y: 0.5 }, colors: C });
|
||||||
// due cannoni laterali
|
// fila di esplosioni lungo TUTTA la larghezza
|
||||||
myConfetti({ particleCount: 80, angle: 60, spread: 75, origin: { x: 0, y: 0.7 }, colors: CONFETTI_COLORS });
|
for (let x = 0; x <= 1.0001; x += 0.1) {
|
||||||
myConfetti({ particleCount: 80, angle: 120, spread: 75, origin: { x: 1, y: 0.7 }, colors: CONFETTI_COLORS });
|
myConfetti({ particleCount: 60, spread: 110, startVelocity: 55, origin: { x, y: 0.55 }, colors: C });
|
||||||
|
}
|
||||||
|
// cannoni laterali potenti dal basso
|
||||||
|
myConfetti({ particleCount: 250, angle: 60, spread: 120, startVelocity: 75, origin: { x: 0, y: 0.85 }, colors: C });
|
||||||
|
myConfetti({ particleCount: 250, angle: 120, spread: 120, startVelocity: 75, origin: { x: 1, y: 0.85 }, colors: C });
|
||||||
|
// pioggia dall'alto su tutta la larghezza
|
||||||
|
for (let x = 0; x <= 1.0001; x += 0.1) {
|
||||||
|
myConfetti({ particleCount: 40, spread: 160, startVelocity: 35, gravity: 1, origin: { x, y: -0.1 }, colors: C });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pioggia continua a schermo pieno per N ms
|
||||||
|
function confettiRain(durationMs) {
|
||||||
|
const end = Date.now() + durationMs;
|
||||||
|
(function frame() {
|
||||||
|
for (let x = 0; x <= 1.0001; x += 0.12) {
|
||||||
|
myConfetti({ particleCount: 10, spread: 130, startVelocity: 45, gravity: 1, origin: { x, y: -0.05 }, colors: CONFETTI_COLORS });
|
||||||
|
}
|
||||||
|
if (Date.now() < end) requestAnimationFrame(frame);
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
function celebrate(kind) {
|
function celebrate(kind) {
|
||||||
bigBurst();
|
bigBurst();
|
||||||
if (kind === "jackpot") {
|
// seconda ondata immediata per raddoppiare la densità
|
||||||
// pioggia prolungata extra per il jackpot
|
setTimeout(bigBurst, 180);
|
||||||
const end = Date.now() + 2600;
|
confettiRain(kind === "jackpot" ? 3500 : 1400);
|
||||||
(function frame() {
|
|
||||||
myConfetti({ particleCount: 12, angle: 60, spread: 80, origin: { x: 0 }, colors: CONFETTI_COLORS });
|
|
||||||
myConfetti({ particleCount: 12, angle: 120, spread: 80, origin: { x: 1 }, colors: CONFETTI_COLORS });
|
|
||||||
myConfetti({ particleCount: 10, spread: 140, startVelocity: 60, origin: { y: 0.25 }, colors: CONFETTI_COLORS });
|
|
||||||
if (Date.now() < end) requestAnimationFrame(frame);
|
|
||||||
})();
|
|
||||||
} else {
|
|
||||||
// anche un reveal normale ha una seconda ondata
|
|
||||||
setTimeout(() => {
|
|
||||||
myConfetti({ particleCount: 120, spread: 100, startVelocity: 40, origin: { y: 0.5 }, colors: CONFETTI_COLORS });
|
|
||||||
}, 220);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function screenShake() {
|
function screenShake() {
|
||||||
@@ -307,19 +325,22 @@
|
|||||||
return el ? el.textContent.replace(/[🏠🛁]/g, "").trim() : "";
|
return el ? el.textContent.replace(/[🏠🛁]/g, "").trim() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// GIRO con RAGEBAIT: decelera e "si ferma" su una stanza sbagliata (adiacente),
|
// Probabilità di ragebait (finto stop sulla stanza sbagliata) per giro.
|
||||||
// tiene la suspense, poi scatta di scatto sulla stanza vera.
|
const TEASE_CHANCE = 0.3;
|
||||||
|
|
||||||
|
// GIRO. Nel ~30% dei casi: decelera e "si ferma" su una stanza sbagliata
|
||||||
|
// (adiacente), tiene la suspense, poi scatta sulla stanza vera. Altrimenti
|
||||||
|
// atterra direttamente con un normale assestamento.
|
||||||
async function spinReel(targetId) {
|
async function spinReel(targetId) {
|
||||||
const targetIndex = buildStrip(targetId);
|
const targetIndex = buildStrip(targetId);
|
||||||
const finalY = -(targetIndex - 1) * ITEM_H; // target centrata
|
const finalY = -(targetIndex - 1) * ITEM_H; // target centrata
|
||||||
const targetName = roomById[targetId].nome;
|
const targetName = roomById[targetId].nome;
|
||||||
|
|
||||||
// scegli la direzione del bluff: la "finta" stanza deve essere diversa dalla vera
|
// direzione del bluff: la "finta" stanza deve essere diversa dalla vera
|
||||||
let dir = Math.random() < 0.5 ? -1 : 1;
|
let dir = Math.random() < 0.5 ? -1 : 1;
|
||||||
if (reelNameAt(targetIndex + dir) === targetName) dir = -dir;
|
if (reelNameAt(targetIndex + dir) === targetName) dir = -dir;
|
||||||
if (reelNameAt(targetIndex + dir) === targetName) dir = 0; // fallback: niente bluff
|
if (reelNameAt(targetIndex + dir) === targetName) dir = 0; // niente adiacente valida
|
||||||
const fakeIndex = targetIndex + dir;
|
const wantTease = dir !== 0 && Math.random() < TEASE_CHANCE;
|
||||||
const fakeY = -(fakeIndex - 1) * ITEM_H;
|
|
||||||
|
|
||||||
// --- tick sincronizzati ---
|
// --- tick sincronizzati ---
|
||||||
let ticking = false;
|
let ticking = false;
|
||||||
@@ -339,26 +360,27 @@
|
|||||||
}
|
}
|
||||||
const stopTicks = () => (ticking = false);
|
const stopTicks = () => (ticking = false);
|
||||||
|
|
||||||
const stopRiser = Audio.riser(4.6);
|
const stopRiser = Audio.riser(wantTease ? 4.6 : 4.2);
|
||||||
|
|
||||||
// FASE 1: decelera fino a "fermarsi" sulla stanza SBAGLIATA
|
|
||||||
startTicks();
|
startTicks();
|
||||||
const a1 = reelEl.animate(
|
|
||||||
[{ transform: "translateY(0)" }, { transform: `translateY(${fakeY}px)` }],
|
|
||||||
{ duration: 3600, easing: "cubic-bezier(0.08, 0.85, 0.14, 1)", fill: "forwards" }
|
|
||||||
);
|
|
||||||
await a1.finished;
|
|
||||||
stopTicks();
|
|
||||||
|
|
||||||
if (dir !== 0) {
|
if (wantTease) {
|
||||||
|
const fakeY = -(targetIndex + dir - 1) * ITEM_H;
|
||||||
|
// FASE 1: decelera fino a "fermarsi" sulla stanza SBAGLIATA
|
||||||
|
const a1 = reelEl.animate(
|
||||||
|
[{ transform: "translateY(0)" }, { transform: `translateY(${fakeY}px)` }],
|
||||||
|
{ duration: 3600, easing: "cubic-bezier(0.08, 0.85, 0.14, 1)", fill: "forwards" }
|
||||||
|
);
|
||||||
|
await a1.finished;
|
||||||
|
stopTicks();
|
||||||
|
|
||||||
// FASE 2: gaslighting — sembra tutto finito
|
// FASE 2: gaslighting — sembra tutto finito
|
||||||
Audio.blip(320, 0.18, "sine", 0.16);
|
Audio.blip(320, 0.18, "sine", 0.16);
|
||||||
const win = $(".reel-window");
|
const win = $(".reel-window");
|
||||||
win.classList.add("teasing");
|
win.classList.add("teasing");
|
||||||
mc(`Ferma su ${reelNameAt(fakeIndex)}...? 🤨`);
|
mc(`Ferma su ${reelNameAt(targetIndex + dir)}...? 🤨`);
|
||||||
await sleep(700);
|
await sleep(700);
|
||||||
|
|
||||||
// FASE 3: SCATTO alla stanza vera (rabbia garantita)
|
// FASE 3: SCATTO alla stanza vera
|
||||||
Audio.blip(200, 0.14, "sawtooth", 0.22, 90); // whoosh
|
Audio.blip(200, 0.14, "sawtooth", 0.22, 90); // whoosh
|
||||||
startTicks();
|
startTicks();
|
||||||
const a2 = reelEl.animate(
|
const a2 = reelEl.animate(
|
||||||
@@ -369,7 +391,13 @@
|
|||||||
stopTicks();
|
stopTicks();
|
||||||
win.classList.remove("teasing");
|
win.classList.remove("teasing");
|
||||||
} else {
|
} else {
|
||||||
// nessun bluff possibile: piccolo assestamento
|
// atterraggio diretto + micro-assestamento
|
||||||
|
const a = reelEl.animate(
|
||||||
|
[{ transform: "translateY(0)" }, { transform: `translateY(${finalY}px)` }],
|
||||||
|
{ duration: 4200, easing: "cubic-bezier(0.12, 0.85, 0.18, 1)", fill: "forwards" }
|
||||||
|
);
|
||||||
|
await a.finished;
|
||||||
|
stopTicks();
|
||||||
reelEl.animate(
|
reelEl.animate(
|
||||||
[
|
[
|
||||||
{ transform: `translateY(${finalY}px)` },
|
{ transform: `translateY(${finalY}px)` },
|
||||||
@@ -780,4 +808,7 @@
|
|||||||
initMarquee();
|
initMarquee();
|
||||||
bindControls();
|
bindControls();
|
||||||
newGame(false);
|
newGame(false);
|
||||||
|
|
||||||
|
// hook di debug (per test/verifica; innocuo)
|
||||||
|
window.__celebrate = celebrate;
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ body {
|
|||||||
#confetti {
|
#confetti {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user