From 7dcbc9582d59832995ead6f47f6ca3faeede128b Mon Sep 17 00:00:00 2001 From: Alberto Moretti Date: Thu, 2 Jul 2026 15:29:24 +0200 Subject: [PATCH] shalom --- README.md | 6 ++ app.js | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++- styles.css | 168 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 356 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62ce581..244bbf2 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,12 @@ python3 -m http.server 8080 I posti con **jacuzzi** (Argo, Eolo, "Quanto te la rischi") sono i jackpot e vengono rivelati per ultimi, con celebrazione extra. +**Effetto SHALOM:** vincere una stanza con idromassaggio (Argo, Eolo, "Quanto te +la rischi") fa partire un effetto a caso tra 4 — fiammate + leone infuocato, video +a schermo intero con `shalom.mp3`, il combo (flashbang di fuoco poi video), oppure +Gesù che scende dall'alto con luce evangelica e `gesu_song.mp3`. Tap per saltare. +Asset in `image/`, `video/`, `sounds/`. + **Nessun match (~30%):** ogni tanto i 3 rulli si fermano su stanze diverse → effetto pazzo (sirena + strobo + glitch + timbro "NO MATCH"), le puntate del giro vengono annullate e la persona **ri-tira** (senza avanzare). Max 2 no-match di fila. diff --git a/app.js b/app.js index 1503eae..8dba834 100644 --- a/app.js +++ b/app.js @@ -2,6 +2,17 @@ (function () { "use strict"; + /* ===== DEBUG / ANTEPRIMA EFFETTI ===================================== + Per vedere gli effetti senza aspettare le vincite jacuzzi casuali. + Rimetti entrambi a null/false per il gioco normale. */ + // Forza un effetto shalom fisso: "fire" | "video" | "combo" | "gesu" | null + const FORCE_SHALOM = null; + // Forza il risultato del giro su una stanza con idromassaggio (così l'effetto + // parte a ogni giro). true = jacuzzi a caso; oppure l'id di una stanza + // ("argo" | "eolo" | "rischio" | "zeus" | ...); false = risultato normale. + const FORCE_RESULT = false; + /* ==================================================================== */ + const $ = (sel) => document.querySelector(sel); const roomById = Object.fromEntries(ROOMS.map((r) => [r.id, r])); const genderOf = Object.fromEntries( @@ -161,6 +172,8 @@ win: "sounds/win.mp3", // jingle di vincita (match normale) jackpot: "sounds/jackpot.mp3", // sirena di jackpot (idromassaggio) coins: "sounds/coins.mp3", // pioggia di monete + shalom: "sounds/shalom.mp3", // musica dell'effetto "shalom" (vittoria jacuzzi) + gesuSong: "sounds/gesu_song.mp3", // canzone dell'effetto "Gesù" }, el: {}, ready: {}, @@ -375,6 +388,146 @@ }); } + /* ---------- Effetto SHALOM (vittoria stanze con idromassaggio) ---------- */ + let shalomOverlay = null; + function ensureShalomOverlay() { + if (shalomOverlay) return shalomOverlay; + const o = document.createElement("div"); + o.className = "shalom-overlay"; + o.innerHTML = + '
' + + '
' + + '' + + '
' + + '' + + '' + + '
tap per saltare ▸
'; + document.body.appendChild(o); + shalomOverlay = o; + return o; + } + function showShalom(o, mode) { + o.classList.remove("mode-fire", "mode-video"); + o.classList.add("on", mode); + } + function hideShalom(o) { + const v = o.querySelector(".shalom-video"); + try { + v.pause(); + } catch (e) {} + o.classList.remove("on", "mode-fire", "mode-video"); + } + // Attende: fine video, tetto di tempo, oppure tap sull'overlay (skip). + function shalomWait(v, capMs, o) { + return new Promise((resolve) => { + let done = false; + const finish = () => { + if (done) return; + done = true; + clearTimeout(to); + if (v) v.removeEventListener("ended", finish); + o.removeEventListener("pointerdown", finish); + resolve(); + }; + const to = setTimeout(finish, capMs); + if (v) v.addEventListener("ended", finish); + o.addEventListener("pointerdown", finish); + }); + } + + // EFFETTO 1: fiammate dai due angoli in basso + leone (flashbang) ~3.5s + async function shalomFireLion() { + const o = ensureShalomOverlay(); + showShalom(o, "mode-fire"); + screenShake(); + const stop = Samples.play("shalom", { + vol: 0.9, + fallback: () => Audio.jackpot(), + }); + await shalomWait(null, 3500, o); + if (stop) stop(0.4); + hideShalom(o); + } + + // EFFETTO 2: video a schermo intero con la musica shalom + async function shalomVideoOnly() { + const o = ensureShalomOverlay(); + const v = o.querySelector(".shalom-video"); + showShalom(o, "mode-video"); + const stop = Samples.play("shalom", { + vol: 0.9, + fallback: () => Audio.jackpot(), + }); + try { + v.currentTime = 0; + await v.play(); + } catch (e) {} + await shalomWait(v, 14000, o); + if (stop) stop(0.4); + hideShalom(o); + } + + // EFFETTO 3: combo — flashbang fuoco+leone 3s, poi parte il video con la musica + async function shalomCombo() { + const o = ensureShalomOverlay(); + showShalom(o, "mode-fire"); + screenShake(); + await shalomWait(null, 3000, o); + const v = o.querySelector(".shalom-video"); + o.classList.remove("mode-fire"); + o.classList.add("mode-video"); + const stop = Samples.play("shalom", { + vol: 0.9, + fallback: () => Audio.jackpot(), + }); + try { + v.currentTime = 0; + await v.play(); + } catch (e) {} + await shalomWait(v, 14000, o); + if (stop) stop(0.4); + hideShalom(o); + } + + // EFFETTO 4: Gesù scende dall'alto con luce evangelica + canzone + async function shalomGesu() { + const o = ensureShalomOverlay(); + showShalom(o, "mode-gesu"); + const stop = Samples.play("gesuSong", { + vol: 0.9, + fallback: () => Audio.fanfare(), + }); + await shalomWait(null, 14000, o); + if (stop) stop(0.5); + hideShalom(o); + } + + const SHALOM_EFFECTS = [ + shalomFireLion, + shalomVideoOnly, + shalomCombo, + shalomGesu, + ]; + const SHALOM_BY_NAME = { + fire: shalomFireLion, + video: shalomVideoOnly, + combo: shalomCombo, + gesu: shalomGesu, + }; + // "sacchetto" mescolato: a ogni vittoria jacuzzi esce un effetto DIVERSO + // (senza ripetizioni) finché non sono usciti tutti, poi si rimescola. + let shalomBag = []; + function playShalom() { + if (FORCE_SHALOM && SHALOM_BY_NAME[FORCE_SHALOM]) { + return SHALOM_BY_NAME[FORCE_SHALOM](); // effetto fisso (debug) + } + if (shalomBag.length === 0) { + shalomBag = Matching.shuffle(SHALOM_EFFECTS.slice(), Math.random); + } + const fx = shalomBag.shift(); + return fx(); + } + /* ---------- Sfondo three.js: pioggia di monete ---------- */ function initBackground() { if (typeof THREE === "undefined") return; @@ -744,7 +897,10 @@ function buildRevealOrder() { // ordine completamente casuale: ogni persona (e quindi ogni stanza, anche // quelle con idromassaggio) può uscire in qualsiasi momento - return Matching.shuffle(PARTICIPANTS.map((p) => p.nome), Math.random); + return Matching.shuffle( + PARTICIPANTS.map((p) => p.nome), + Math.random, + ); } function newGame(keepChips) { @@ -755,6 +911,7 @@ cursor = 0; currentBet = null; noMatchStreak = 0; + shalomBag = []; if (!keepChips) { players = [ { name: "TU", chips: 100, you: true }, @@ -932,7 +1089,15 @@ Audio.resume(); const person = revealQueue[cursor]; - const roomId = assignment.byPerson[person]; + let roomId = assignment.byPerson[person]; + if (FORCE_RESULT) { + // debug: forza il risultato su una jacuzzi (o su un id specifico) + const jac = ROOMS.filter((r) => r.jacuzzi); + roomId = + typeof FORCE_RESULT === "string" + ? FORCE_RESULT + : jac[Math.floor(Math.random() * jac.length)].id; + } const room = roomById[roomId]; $("#player-name").textContent = person; @@ -952,7 +1117,8 @@ // 30% dei giri: NESSUN MATCH (3 stanze diverse) -> effetto pazzo + respin. // Tetto di 2 no-match di fila per non bloccare nessuno. - const noMatch = noMatchStreak < 2 && Math.random() < NOMATCH_CHANCE; + const noMatch = + !FORCE_RESULT && noMatchStreak < 2 && Math.random() < NOMATCH_CHANCE; if (noMatch) { noMatchStreak++; @@ -1021,6 +1187,12 @@ // la macchina resta zoomata per godersi la vittoria, poi torna al posto setTimeout(zoomMachineOut, isJackpot ? 1200 : 800); + // EFFETTO SHALOM: vittoria di una stanza con idromassaggio -> effetto a caso + if (isJackpot) { + await sleep(600); // lascia vedere un attimo i coriandoli + await playShalom(); + } + cursor++; setProgress(); renderLeaderboard(); @@ -1187,4 +1359,11 @@ // hook di debug (per test/verifica; innocuo) window.__celebrate = celebrate; + window.__shalom = { + fire: shalomFireLion, + video: shalomVideoOnly, + combo: shalomCombo, + gesu: shalomGesu, + random: playShalom, + }; })(); diff --git a/styles.css b/styles.css index 4da050c..57247ad 100644 --- a/styles.css +++ b/styles.css @@ -731,6 +731,174 @@ body { text-shadow: 0 0 16px var(--red); } +/* ===== Effetto SHALOM (vittoria stanze con idromassaggio) ===== */ +.shalom-overlay { + position: fixed; + inset: 0; + z-index: 100; + display: none; + overflow: hidden; + background: rgba(4, 2, 9, 0.94); +} +.shalom-overlay.on { + display: block; + animation: shalomBang 0.5s ease; /* flashbang bianco iniziale */ +} +@keyframes shalomBang { + 0% { background: #fff; } + 35% { background: #fff; } + 100% { background: rgba(4, 2, 9, 0.94); } +} +.shalom-skip { + position: absolute; + bottom: 16px; + right: 22px; + z-index: 3; + font-family: "Bebas Neue", sans-serif; + letter-spacing: 2px; + font-size: 16px; + color: rgba(255, 255, 255, 0.55); +} + +/* fiammate dai due angoli in basso */ +.shalom-flames { + position: absolute; + bottom: -6%; + width: 50vw; + height: 92vh; + 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%); +} +.shalom-flames.left { + left: -8%; + transform-origin: bottom left; +} +.shalom-flames.right { + right: -8%; + transform-origin: bottom right; +} +.shalom-overlay.mode-fire .shalom-flames { + opacity: 1; + animation: flames 0.26s infinite alternate; +} +.shalom-overlay.mode-fire .shalom-flames.right { + animation-delay: 0.13s; +} +@keyframes flames { + 0% { transform: scaleY(1) scaleX(1) skewX(0deg); } + 100% { transform: scaleY(1.14) scaleX(0.92) skewX(4deg); } +} + +/* leone che irrompe */ +.shalom-lion { + position: absolute; + top: 50%; + left: 50%; + width: min(46vw, 520px); + border-radius: 16px; + transform: translate(-50%, -50%) scale(0.2); + opacity: 0; + filter: drop-shadow(0 0 40px #ff6a00); +} +.shalom-overlay.mode-fire .shalom-lion { + animation: lionSlam 0.6s cubic-bezier(0.2, 1.5, 0.4, 1) forwards, + lionPulse 1.1s 0.6s infinite; +} +@keyframes lionSlam { + 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.2) rotate(-7deg); } + 60% { opacity: 1; transform: translate(-50%, -50%) scale(1.12) rotate(2deg); } + 100% { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(0deg); } +} +@keyframes lionPulse { + 0%, 100% { filter: drop-shadow(0 0 40px #ff6a00); } + 50% { filter: drop-shadow(0 0 75px #ff2e00); } +} + +/* video a schermo intero */ +.shalom-video { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0; +} +.shalom-overlay.mode-video .shalom-flames, +.shalom-overlay.mode-video .shalom-lion { + display: none; +} +.shalom-overlay.mode-video .shalom-video { + opacity: 1; +} + +/* effetto GESÙ: raggi di luce + immagine che scende dall'alto */ +.shalom-rays { + position: absolute; + top: 50%; + left: 50%; + width: 180vmax; + height: 180vmax; + transform: translate(-50%, -50%); + opacity: 0; + pointer-events: none; + mix-blend-mode: screen; + background: repeating-conic-gradient( + from 0deg, + rgba(255, 240, 200, 0) 0deg, + rgba(255, 240, 200, 0.3) 3.5deg, + rgba(255, 240, 200, 0) 7deg + ); + -webkit-mask: radial-gradient(closest-side, #000 26%, transparent 72%); + mask: radial-gradient(closest-side, #000 26%, transparent 72%); +} +.shalom-overlay.mode-gesu .shalom-rays { + opacity: 1; + animation: shalomRays 26s linear infinite; +} +@keyframes shalomRays { + to { transform: translate(-50%, -50%) rotate(360deg); } +} +.shalom-gesu { + position: absolute; + top: 50%; + left: 50%; + width: min(42vw, 460px); + border-radius: 14px; + transform: translate(-50%, -160%); + opacity: 0; + filter: drop-shadow(0 0 55px rgba(255, 240, 180, 0.9)); +} +.shalom-overlay.mode-gesu .shalom-gesu { + animation: gesuDescend 1.7s cubic-bezier(0.16, 0.8, 0.3, 1) forwards, + gesuFloat 4.5s 1.7s ease-in-out infinite; +} +@keyframes gesuDescend { + 0% { opacity: 0; transform: translate(-50%, -160%) scale(0.9); } + 55% { opacity: 1; } + 100% { opacity: 1; transform: translate(-50%, -50%) scale(1); } +} +@keyframes gesuFloat { + 0%, 100% { transform: translate(-50%, -50%) translateY(0); } + 50% { transform: translate(-50%, -50%) translateY(-14px); } +} +/* mostra solo gli elementi giusti per ciascuna modalità */ +.shalom-overlay.mode-fire .shalom-rays, +.shalom-overlay.mode-fire .shalom-gesu, +.shalom-overlay.mode-video .shalom-rays, +.shalom-overlay.mode-video .shalom-gesu { + display: none; +} +.shalom-overlay.mode-gesu .shalom-flames, +.shalom-overlay.mode-gesu .shalom-lion, +.shalom-overlay.mode-gesu .shalom-video { + display: none; +} + @media (max-width: 980px) { .stage { grid-template-columns: 1fr;