new ragebait 3 col
This commit is contained in:
100
app.js
100
app.js
@@ -346,8 +346,46 @@
|
|||||||
).finished;
|
).finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rullo che atterra dritto sulla target
|
// Atterraggio di un rullo sulla target. Se teaseMs > 0 fa il RAGEBAIT:
|
||||||
async function reelLand(el, finalY, duration) {
|
// si ferma su una stanza adiacente sbagliata, tiene la suspense (teaseMs),
|
||||||
|
// poi scatta sulla vera. Funziona su QUALSIASI colonna.
|
||||||
|
async function reelLand(el, targetIndex, finalY, duration, teaseMs) {
|
||||||
|
// eventuale bluff: scegli una stanza adiacente diversa dalla vera
|
||||||
|
if (teaseMs > 0) {
|
||||||
|
const targetRoom = roomAtIndex(el, targetIndex);
|
||||||
|
let dir = Math.random() < 0.5 ? -1 : 1;
|
||||||
|
if (roomAtIndex(el, targetIndex + dir)?.id === targetRoom.id) dir = -dir;
|
||||||
|
if (roomAtIndex(el, targetIndex + dir)?.id === targetRoom.id) teaseMs = 0; // no adiacente
|
||||||
|
if (teaseMs > 0) {
|
||||||
|
const fakeRoom = roomAtIndex(el, targetIndex + dir);
|
||||||
|
const fakeY = -(targetIndex + dir - 1) * ITEM_H;
|
||||||
|
const col = el.parentElement;
|
||||||
|
|
||||||
|
// decelera e "si ferma" sulla stanza SBAGLIATA
|
||||||
|
await el.animate(
|
||||||
|
[{ transform: "translateY(0)" }, { transform: `translateY(${fakeY}px)` }],
|
||||||
|
{ duration, easing: "cubic-bezier(0.08, 0.85, 0.14, 1)", fill: "forwards" }
|
||||||
|
).finished;
|
||||||
|
Audio.reelStop();
|
||||||
|
|
||||||
|
// suspense su quella colonna
|
||||||
|
Audio.blip(330, 0.13, "sine", 0.14);
|
||||||
|
col.classList.add("teasing");
|
||||||
|
mc(`${fakeRoom.nome}...? 🤨`);
|
||||||
|
await sleep(teaseMs);
|
||||||
|
|
||||||
|
// SCATTO alla stanza vera
|
||||||
|
Audio.blip(200, 0.12, "sawtooth", 0.2, 90); // whoosh
|
||||||
|
await el.animate(
|
||||||
|
[{ transform: `translateY(${fakeY}px)` }, { transform: `translateY(${finalY}px)` }],
|
||||||
|
{ duration: 480, easing: "cubic-bezier(0.34, 1.56, 0.64, 1)", fill: "forwards" }
|
||||||
|
).finished;
|
||||||
|
col.classList.remove("teasing");
|
||||||
|
Audio.reelStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// atterraggio diretto + micro-assestamento
|
||||||
await el.animate(
|
await el.animate(
|
||||||
[{ transform: "translateY(0)" }, { transform: `translateY(${finalY}px)` }],
|
[{ transform: "translateY(0)" }, { transform: `translateY(${finalY}px)` }],
|
||||||
{ duration, easing: "cubic-bezier(0.12, 0.85, 0.18, 1)", fill: "forwards" }
|
{ duration, easing: "cubic-bezier(0.12, 0.85, 0.18, 1)", fill: "forwards" }
|
||||||
@@ -356,42 +394,10 @@
|
|||||||
Audio.reelStop();
|
Audio.reelStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// rullo con RAGEBAIT: si ferma sulla stanza sbagliata, suspense, poi scatta
|
|
||||||
async function reelLandTease(el, targetIndex, finalY) {
|
|
||||||
const targetRoom = roomAtIndex(el, targetIndex);
|
|
||||||
let dir = Math.random() < 0.5 ? -1 : 1;
|
|
||||||
if (roomAtIndex(el, targetIndex + dir)?.id === targetRoom.id) dir = -dir;
|
|
||||||
if (roomAtIndex(el, targetIndex + dir)?.id === targetRoom.id)
|
|
||||||
return reelLand(el, finalY, 4200); // niente adiacente valida
|
|
||||||
const fakeRoom = roomAtIndex(el, targetIndex + dir);
|
|
||||||
const fakeY = -(targetIndex + dir - 1) * ITEM_H;
|
|
||||||
|
|
||||||
// FASE 1: decelera e "si ferma" sulla stanza SBAGLIATA
|
|
||||||
await el.animate(
|
|
||||||
[{ transform: "translateY(0)" }, { transform: `translateY(${fakeY}px)` }],
|
|
||||||
{ duration: 3600, easing: "cubic-bezier(0.08, 0.85, 0.14, 1)", fill: "forwards" }
|
|
||||||
).finished;
|
|
||||||
Audio.reelStop();
|
|
||||||
|
|
||||||
// FASE 2: gaslighting — sembra fatta
|
|
||||||
Audio.blip(320, 0.18, "sine", 0.16);
|
|
||||||
const win = $(".reel-window");
|
|
||||||
win.classList.add("teasing");
|
|
||||||
mc(`Ferma su ${fakeRoom.nome}...? 🤨`);
|
|
||||||
await sleep(750);
|
|
||||||
|
|
||||||
// FASE 3: SCATTO alla stanza vera
|
|
||||||
Audio.blip(200, 0.14, "sawtooth", 0.22, 90); // whoosh
|
|
||||||
await el.animate(
|
|
||||||
[{ transform: `translateY(${fakeY}px)` }, { transform: `translateY(${finalY}px)` }],
|
|
||||||
{ duration: 520, easing: "cubic-bezier(0.34, 1.56, 0.64, 1)", fill: "forwards" }
|
|
||||||
).finished;
|
|
||||||
win.classList.remove("teasing");
|
|
||||||
Audio.reelStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// GIRO: i 3 rulli partono insieme e si fermano da sinistra a destra,
|
// GIRO: i 3 rulli partono insieme e si fermano da sinistra a destra,
|
||||||
// allineandosi tutti sulla stanza assegnata (l'ultimo può fare il ragebait).
|
// allineandosi sulla stanza assegnata. OGNI colonna può fare il ragebait
|
||||||
|
// (in modo indipendente); le colonne più a destra tengono la suspense più a
|
||||||
|
// lungo, così il dramma "cascata" verso l'ultimo rullo.
|
||||||
async function spinReel(targetId) {
|
async function spinReel(targetId) {
|
||||||
const targetIndex = reelEls.map((el) => buildStripIn(el, targetId))[0]; // uguale per tutti
|
const targetIndex = reelEls.map((el) => buildStripIn(el, targetId))[0]; // uguale per tutti
|
||||||
const finalY = -(targetIndex - 1) * ITEM_H;
|
const finalY = -(targetIndex - 1) * ITEM_H;
|
||||||
@@ -403,7 +409,7 @@
|
|||||||
const mainDur = 4200;
|
const mainDur = 4200;
|
||||||
setTimeout(zoomMachineSteady, mainDur / 2);
|
setTimeout(zoomMachineSteady, mainDur / 2);
|
||||||
|
|
||||||
const stopRiser = Audio.riser(5.0);
|
const stopRiser = Audio.riser(5.6);
|
||||||
|
|
||||||
// tick sincronizzati sul 3° rullo (gira più a lungo)
|
// tick sincronizzati sul 3° rullo (gira più a lungo)
|
||||||
let ticking = true;
|
let ticking = true;
|
||||||
@@ -418,16 +424,14 @@
|
|||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const wantTease = Math.random() < TEASE_CHANCE;
|
const durs = [2600, 3300, 4200];
|
||||||
|
await Promise.all(
|
||||||
// stop staggerato sinistra -> destra
|
reelEls.map((el, i) => {
|
||||||
await Promise.all([
|
// ogni colonna ha una sua chance di bluff; suspense crescente verso destra
|
||||||
reelLand(reelEls[0], finalY, 2600),
|
const teaseMs = Math.random() < TEASE_CHANCE ? 380 + i * 260 : 0;
|
||||||
reelLand(reelEls[1], finalY, 3300),
|
return reelLand(el, targetIndex, finalY, durs[i], teaseMs);
|
||||||
wantTease
|
})
|
||||||
? reelLandTease(reelEls[2], targetIndex, finalY)
|
);
|
||||||
: reelLand(reelEls[2], finalY, 4200),
|
|
||||||
]);
|
|
||||||
|
|
||||||
ticking = false;
|
ticking = false;
|
||||||
stopRiser();
|
stopRiser();
|
||||||
|
|||||||
20
styles.css
20
styles.css
@@ -458,18 +458,22 @@ body {
|
|||||||
box-shadow: 0 0 16px var(--red);
|
box-shadow: 0 0 16px var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* effetto "gaslighting": mentre finge di essersi fermato sulla stanza sbagliata */
|
/* effetto "gaslighting" sulla SINGOLA colonna che finge di fermarsi sbagliata */
|
||||||
.reel-window.teasing {
|
.reel-col.teasing {
|
||||||
animation: teaseShake 0.12s linear infinite;
|
animation: teaseShake 0.1s linear infinite;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.reel-window.teasing .payline {
|
.reel-col.teasing::after {
|
||||||
border-color: var(--red);
|
content: "";
|
||||||
box-shadow: 0 0 26px rgba(255, 59, 92, 0.6);
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
box-shadow: inset 0 0 34px rgba(255, 59, 92, 0.6);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@keyframes teaseShake {
|
@keyframes teaseShake {
|
||||||
0%, 100% { transform: translateX(0); }
|
0%, 100% { transform: translateX(0); }
|
||||||
25% { transform: translateX(-2px); }
|
25% { transform: translateX(-3px); }
|
||||||
75% { transform: translateX(2px); }
|
75% { transform: translateX(3px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.mc-line {
|
.mc-line {
|
||||||
|
|||||||
Reference in New Issue
Block a user