This commit is contained in:
2026-07-02 12:55:16 +02:00
commit c62e79e4c6
5 changed files with 2009 additions and 0 deletions

View File

@@ -0,0 +1,393 @@
# Linguaggio visivo del gambling / casino / betting online
Guida pratica per replicare l'estetica "hyper-gambling" (slot machine, casino online, betting) su web e app. Riferimenti reali e attuali (2023-2026). Ogni sezione include indicazioni concrete direttamente utilizzabili da uno sviluppatore.
L'obiettivo estetico e' quello di trasmettere **eccitazione, urgenza, lusso e ricompensa immediata**: sfondi scuri, colori neon saturi, oro e metallo, luci lampeggianti, cifre giganti che salgono, effetti di vincita continui.
---
## 1. Palette colori
Il pattern dominante e' **sfondo scuro + accenti saturi ad alto contrasto**. Il buio fa risaltare i neon, l'oro e i bagliori (il glow neon e' visibile SOLO su fondo scuro).
### 1.1 Le combinazioni ricorrenti
| Palette | Colori | Uso / significato |
|---|---|---|
| **Classic Luxury** | Nero, Oro, Argento | Casino high-end, esclusivita', ricchezza |
| **Vibrant / Energetic** | Rosso acceso, Giallo, Verde neon | Aree slot, eccitazione, pubblico giovane |
| **Royal Elegance** | Viola profondo, Oro, Bianco | Lusso, regalita', tavoli VIP |
| **Neon Nightlife** | Neon blue, Hot pink, Electric yellow | Energia notturna, vibe "arcade" |
| **Casino Table** | Verde feltro, Rosso, Oro | Richiamo diretto ai tavoli (roulette, blackjack) |
| **Crypto Dark** | Blu notte / Viola, accenti neon green | Stile Stake / Roobet (vedi sez. 6) |
### 1.2 Palette pronte all'uso (hex)
Gli articoli di branding elencano combinazioni ma raramente danno hex. Ecco valori concreti coerenti con lo stile, pronti da incollare come CSS custom properties:
```css
:root {
/* --- Base scura (sfondi) --- */
--bg-900: #0a0a12; /* quasi nero, blu-viola */
--bg-800: #12121f;
--bg-700: #1b1b2e; /* pannelli / card */
--bg-purple: #1a0b2e; /* variante viola profondo (Roobet-like) */
/* --- Oro / metallo --- */
--gold-500: #ffd700; /* oro puro */
--gold-600: #f5c518;
--gold-700: #d4af37; /* oro antico */
--gold-glow: #ffdf6b;
/* --- Neon --- */
--neon-green: #39ff14; /* verde neon "vincita" */
--neon-cyan: #00fff0;
--neon-pink: #ff2e97; /* hot pink */
--neon-magenta: #ff00e5;
--neon-blue: #2d6bff;
--neon-purple: #b026ff;
/* --- Rosso / verde casino --- */
--casino-red: #e21b3c; /* roulette red, urgenza */
--casino-green: #0f7b3f; /* feltro tavolo */
--casino-green-felt: #1c7a4d;
/* --- Testo --- */
--text: #f5f5fa;
--text-dim: #9aa0b5;
}
```
### 1.3 Gradienti caratteristici
I gradienti sono ovunque: bottoni, jackpot, badge, sfondi. Tre famiglie:
```css
/* Oro metallico (bottoni "PLAY", jackpot) */
--grad-gold: linear-gradient(180deg, #fff3b0 0%, #ffd700 40%, #d4af37 60%, #a67c00 100%);
/* Neon bicolore (CTA arcade, banner) */
--grad-neon: linear-gradient(135deg, #ff2e97 0%, #b026ff 50%, #2d6bff 100%);
/* Viola profondo (sfondo hero) */
--grad-purple-bg: radial-gradient(circle at 50% 0%, #3a1a5e 0%, #1a0b2e 60%, #0a0a12 100%);
/* Verde vincita */
--grad-win: linear-gradient(180deg, #7dff5a, #39ff14, #0f9b2e);
```
Il gradiente radiale posizionato in alto (`radial-gradient(circle at 50% 0%, ...)`) crea il tipico effetto "riflettore/palcoscenico" dietro l'hero o le slot.
### 1.4 Psicologia del colore (perche' funziona)
- **Rosso** = urgenza, eccitazione, fortuna. Usato per CTA, timer, "HOT".
- **Verde** = fortuna, denaro, vincita, feltro del tavolo. Usato per importi vinti, "WIN", saldo positivo.
- **Oro** = ricchezza, premio, esclusivita'. Jackpot, VIP, monete.
- **Viola/Blu profondo** = lusso, mistero, fiducia. Sfondi premium (Roobet, Stake).
- **Neon** = energia, notte, dopamina. Bordi, glow, accenti.
---
## 2. Tipografia
Lo stile richiede caratteri **grassi, condensati, ad alto impatto** per i titoli, e font **display / LED** per i numeri (jackpot, saldo, timer). Tutti i font sotto sono gratuiti.
### 2.1 Display / Headline (grassi e condensati) — Google Fonts
| Font | Caratteristiche | Uso |
|---|---|---|
| **Anton** | Sans grottesco, condensato, ultra-bold, peso unico | Titoli dominanti, "JACKPOT", banner |
| **Bebas Neue** | Condensato alto, elegante, molto diffuso | Headline, etichette, tab |
| **Oswald** | Stretto, 6 pesi (ExtraLight→Bold) | Testi UI densi, header editoriali |
| **Archivo Black** | Sans nero, largo e pesante | CTA, badge |
| **Teko** | Condensato tecnico, moderno | Numeri, contatori, punteggi |
| **Fjalla One** | Display medio-bold | Titoli secondari |
| **Titan One / Luckiest Guy / Bungee** | Chunky, arcade, cartoon | Vibe "juicy"/arcade, premi |
`Bungee` (di David Jonathan Ross, Google Fonts) e' particolarmente adatta all'estetica insegna/marquee urbana.
### 2.2 Numeri LED / 7-segment (jackpot, saldo, timer)
Per i contatori digitali serve un font 7/14-segmenti che imiti i display LED reali:
- **DSEG** (keshikan.net) — famiglia free open source, imita display a 7 e 14 segmenti, con varianti Classic/Modern, weight e italic. E' lo standard de facto. Include `DSEG7` (7 segmenti) e `DSEG14`.
- **Segment7** (Font Library, open source) — monospace, solo 7 segmenti, look calcolatrice/LED autentico.
- **Seven Segment** (FontGet / CDNFonts) — alternativa, free per uso personale.
In alternativa, per un look "digitale" ma leggibile, va bene una monospace tech: **Share Tech Mono**, **Orbitron** (futuristica, ottima per sci-fi/arcade) o **DotGothic16** (dot-matrix), tutte su Google Fonts.
### 2.3 Abbinamenti consigliati
- **Anton** (titoli) + **Inter/Roboto** (corpo) + **DSEG7** (numeri jackpot).
- **Bebas Neue** (headline) + **Oswald** (UI) + **Orbitron** (contatori).
---
## 3. Motivi UI ricorrenti
Elementi grafici che definiscono immediatamente il genere:
- **Reel della slot** — 3-5 colonne verticali che scorrono con simboli (7, campanelle, frutti, diamanti, monete). Effetto blur in movimento + decelerazione elastica allo stop.
- **Ruota della fortuna (wheel of fortune)** — cerchio a spicchi colorati alternati, indicatore/lancetta in alto, decelerazione con `cubic-bezier` per il "quasi-vinci".
- **Monete e gettoni (chips)** — monete dorate 3D (spesso animate/rotanti), fiches da poker colorate.
- **Jackpot counter / ticker** — cifre grandi LED che salgono di continuo (count-up animato), spesso con ticker scorrevole ("$1,204,556" incrementale).
- **Luci da marquee (chase lights)** — cornici di lampadine che si accendono in sequenza attorno a banner/insegne.
- **Confetti, scintille, particelle** — esplosione di coriandoli e monete al momento della vincita.
- **Badge / medaglie / corone** — livelli VIP, loyalty, "BONUS", "FREE SPINS".
- **Banner promozionali + jackpot ticker scorrevole** — tile dei giochi live con jackpot in tempo reale, barra loyalty visibile.
- **Simboli portafortuna** — 777, ferri di cavallo, quadrifogli, diamanti, stelle.
---
## 4. Illuminazione ed effetti
Il "peso visivo" arriva dagli effetti luce/materiale:
- **Neon glow** — bagliore colorato attorno a testo e bordi (layer multipli di shadow). Vedi sez. 5.1.
- **Drop shadow profonde** — le card "galleggiano" su fondo scuro.
- **Chrome / metallico** — gradienti oro/argento con highlight speculari e bordo scuro per dare volume.
- **Shine sweep (riflesso che passa)** — striscia di luce diagonale che attraversa bottoni/carte periodicamente.
- **Inner glow / tubo neon** — inset shadow chiaro per l'effetto "tubo al neon acceso".
- **Bevel / emboss** — bordi smussati su monete e bottoni per il 3D.
- **Pulsazione / flicker** — glow che pulsa o lampeggia come insegna al neon.
> Nota performance/accessibilita': blur grandi e animazioni infinite di `box-shadow` sono costosi da ridipingere. Animare con parsimonia e avvolgere gli effetti pulsanti in `@media (prefers-reduced-motion: reduce)`.
---
## 5. Tecniche CSS concrete
### 5.1 Testo neon (text-shadow a layer)
Layer multipli dello stesso colore con blur crescente = alone luminoso (tecnica CSS-Tricks):
```css
.neon-text {
color: #fff;
text-shadow:
0 0 7px #fff,
0 0 10px #fff,
0 0 21px #fff,
0 0 42px #39ff14,
0 0 82px #39ff14,
0 0 92px #39ff14,
0 0 102px #39ff14,
0 0 151px #39ff14;
}
/* Flicker da insegna al neon */
@keyframes flicker {
0%, 18%, 22%, 25%, 53%, 57%, 100% {
text-shadow:
0 0 4px #fff, 0 0 11px #fff, 0 0 19px #fff,
0 0 40px #39ff14, 0 0 80px #39ff14, 0 0 90px #39ff14,
0 0 100px #39ff14, 0 0 150px #39ff14;
}
20%, 24%, 55% { text-shadow: none; }
}
.neon-text--flicker { animation: flicker 1.5s infinite alternate; }
@media (prefers-reduced-motion: reduce) {
.neon-text--flicker { animation: none; }
}
```
### 5.2 Bordo/box neon glow (bottone, card)
```css
.neon-box {
border: 2px solid var(--neon-pink);
border-radius: 12px;
box-shadow:
0 0 5px var(--neon-pink),
0 0 15px var(--neon-pink),
0 0 30px var(--neon-pink),
inset 0 0 8px rgba(255, 46, 151, .6); /* inner glow "tubo" */
}
```
### 5.3 Bottone oro metallico (CTA "PLAY / SPIN")
```css
.btn-gold {
background: linear-gradient(180deg, #fff3b0 0%, #ffd700 40%, #d4af37 60%, #a67c00 100%);
color: #3a2a00;
font-family: "Anton", sans-serif;
letter-spacing: .05em;
border: 2px solid #a67c00;
border-radius: 14px;
box-shadow:
inset 0 2px 2px rgba(255,255,255,.7), /* highlight superiore */
inset 0 -3px 4px rgba(0,0,0,.35), /* ombra inferiore = volume */
0 6px 0 #7a5c00, /* "spessore" 3D */
0 10px 20px rgba(0,0,0,.5), /* ombra a terra */
0 0 24px rgba(255,215,0,.6); /* glow oro */
text-shadow: 0 1px 0 rgba(255,255,255,.5);
}
.btn-gold:active { transform: translateY(4px); box-shadow: /* riduci il 0 6px 0 ... */ ; }
```
### 5.4 Bordo a gradiente (gradient border)
Due tecniche affidabili:
```css
/* A) border-image */
.grad-border {
border: 3px solid transparent;
border-image: linear-gradient(135deg, #ff2e97, #b026ff, #2d6bff) 1;
}
/* B) doppio background (permette border-radius, la A no) */
.grad-border-radius {
border: 3px solid transparent;
border-radius: 16px;
background:
linear-gradient(var(--bg-800), var(--bg-800)) padding-box,
linear-gradient(135deg, #ff2e97, #b026ff, #2d6bff) border-box;
}
```
### 5.5 Shine sweep (riflesso animato su bottone/card)
```css
.shine { position: relative; overflow: hidden; }
.shine::after {
content: "";
position: absolute; top: 0; left: -150%;
width: 60%; height: 100%;
background: linear-gradient(120deg,
transparent 0%, rgba(255,255,255,.5) 50%, transparent 100%);
transform: skewX(-20deg);
animation: sweep 3s infinite;
}
@keyframes sweep { 0% { left: -150%; } 60%,100% { left: 150%; } }
```
### 5.6 Testo con riempimento a gradiente (oro/neon)
```css
.grad-text {
background: linear-gradient(180deg, #fff3b0, #ffd700, #d4af37);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
```
### 5.7 Luci da marquee (cornice di lampadine)
Con `box-shadow` multipli su uno pseudo-elemento, o piu' semplicemente con un `repeating-radial-gradient` + animazione di opacita' per l'effetto "chase". Per il chase preciso conviene JS o `background-position` animato di un pattern di puntini.
```css
.marquee-lights {
padding: 12px;
border-radius: 16px;
background:
radial-gradient(circle, #ffdf6b 2px, transparent 3px) 0 0 / 20px 20px;
animation: chase .6s steps(2) infinite;
}
@keyframes chase { to { background-position: 20px 0; } }
```
### 5.8 Slot reel (scorrimento verticale)
Il pattern base: una colonna piu' alta del viewport, si trasla verso l'alto con `transform: translateY()` e si ferma con easing elastico. Approcci reali su CodePen (sez. 6): pure CSS con `@keyframes`, JS con `requestAnimationFrame` + decelerazione, o 3D con `rotateX`/`translateZ` per il reel cilindrico.
```css
.reel { height: 120px; overflow: hidden; }
.reel__strip { display: flex; flex-direction: column;
transition: transform 3s cubic-bezier(.15,.9,.25,1); } /* decelerazione */
.reel--spinning .reel__strip { filter: blur(2px); } /* motion blur */
```
### 5.9 Count-up del jackpot
Il conteggio incrementale si fa in JS (aggiornando `textContent`), abbinato al font DSEG7 e a un container con glow verde/oro. Per performance usare `requestAnimationFrame` e `Intl.NumberFormat` per i separatori delle migliaia.
---
## 6. Riferimenti da studiare (URL reali)
### Prodotti / casino live
- **Stake** — https://stake.com/ e https://stake.com/casino/home — tema scuro blu notte, "Stake Originals" con dark theme + accenti colorati, equilibrio professionale/immersivo.
- **Roobet** — https://roobet.com/ — estetica viola + verde neon, "neon-lit realm", playground digitale pulsante.
### Collezioni ispirazione (UI/branding)
- Dribbble — Casino UI: https://dribbble.com/tags/casino-ui
- Dribbble — Gambling UI: https://dribbble.com/tags/gambling-ui
- Dribbble — Casino App: https://dribbble.com/tags/casino_app
- Dribbble — esempio piattaforma: https://dribbble.com/shots/23736814-UX-UI-Design-of-Casino-Gambling-Platform
- Behance — Casino Game UI: https://www.behance.net/search/projects/casino%20game%20ui
- Behance — Casino Web Design: https://www.behance.net/search/projects/casino%20web%20design
- Colorlib — 17 Casino Website Design 2026: https://colorlib.com/wp/casino-website-design/
- Subframe — 25 Casino Website Design Examples: https://www.subframe.com/tips/casino-website-design-examples
### CodePen (slot / reel / animazioni)
- CSS-only Slot Machine: https://codepen.io/tangxuguo/pen/xqrNmx
- Simple CSS Slot Machine Animation: https://codepen.io/cnbkts/pen/QMXPZM
- CSS/JS Slot machine: https://codepen.io/MrFirthy/pen/oGVWqK
- Slot Machine Spinner: https://codepen.io/Thomasbetten/pen/nPeMPo
- Real Slots Reels (3D): https://codepen.io/josfabre/pen/abReBvP
- Reel Slot Machine (no JS): https://codepen.io/stevencamilleri-mrg/pen/abzGOGd
### Font (download)
- Bebas Neue — https://fonts.google.com/specimen/Bebas+Neue
- Anton — https://fonts.google.com/specimen/Anton
- Oswald — https://fonts.google.com/specimen/Oswald
- DSEG (7/14-segment) — https://www.keshikan.net/fonts-e.html
- Segment7 — https://fontlibrary.org/en/font/segment7
### CSS glow / neon (tecniche)
- CSS-Tricks — Neon Text with CSS: https://css-tricks.com/how-to-create-neon-text-with-css/
- Coder's Block — Creating Glow Effects with CSS: https://codersblock.com/blog/creating-glow-effects-with-css/
- FreeFrontend — 40+ CSS Glow Effects: https://freefrontend.com/css-glow-effects/
---
## 7. Checklist rapida per replicare l'estetica
- [ ] Sfondo scuro (near-black blu/viola), MAI bianco puro.
- [ ] Palette: base scura + 1-2 neon + oro. Alto contrasto.
- [ ] Font titoli condensato ultra-bold (Anton/Bebas Neue).
- [ ] Numeri jackpot/saldo con font LED (DSEG7) + glow verde/oro.
- [ ] CTA in oro metallico con bevel 3D + glow.
- [ ] Bordi neon (box-shadow multipli + inset glow) su card/CTA.
- [ ] Gradienti ovunque (oro, neon bicolore, radiale hero).
- [ ] Micro-animazioni: shine sweep, pulse, flicker, count-up.
- [ ] Feedback vincita: confetti + monete + verde neon + glow.
- [ ] Motivi: reel, ruota, monete/chips, marquee lights, ticker jackpot.
- [ ] Rispettare `prefers-reduced-motion`.
---
## Fonti
- The Best Color Schemes for Gambling Websites — https://mightymegaphone.com/2025/02/12/the-best-color-schemes-for-gambling-websites-designers-guide/
- Top 10 Color Palettes for Casino Branding (Lounge Lizard, 2025) — https://www.loungelizard.com/blog/top-10-color-palettes-for-casino-branding/
- The Psychology of Color in Casino Design (BetBoyz) — https://betboyz.com/igaming-branding-and-design/igaming-branding-the-psychology-of-color-in-casino-design/
- The Color of Luck: Palette Choices in Casino Game Design — https://www.seegreatart.art/the-color-of-luck-a-visual-study-of-palette-choices-in-casino-game-design/
- 17 Best Casino Website Design Inspiration 2026 (Colorlib) — https://colorlib.com/wp/casino-website-design/
- 25 Casino Website Design Examples (Subframe) — https://www.subframe.com/tips/casino-website-design-examples
- Inside the Digital Lobby: Casino Interface Design — https://www.muggsybogues.com/inside-the-digital-lobby-a-feature-spotlight-on-casino-interface-design/
- Stake — https://stake.com/ | https://stake.com/casino/home
- Stake Review (aesthetic details) — https://www.sportsgambler.com/review/stake/
- Roobet — https://roobet.com/
- Roobet Review (aesthetic details) — https://www.thespike.gg/reviews/roobet
- Dribbble Casino UI — https://dribbble.com/tags/casino-ui
- Dribbble Gambling UI — https://dribbble.com/tags/gambling-ui
- Behance Casino Game UI — https://www.behance.net/search/projects/casino%20game%20ui
- Bebas Neue (Google Fonts) — https://fonts.google.com/specimen/Bebas+Neue
- Anton (Google Fonts) — https://fonts.google.com/specimen/Anton
- Oswald (Google Fonts) — https://fonts.google.com/specimen/Oswald
- DSEG 7/14-segment fonts — https://www.keshikan.net/fonts-e.html
- Segment7 (Font Library) — https://fontlibrary.org/en/font/segment7
- 17 Royal Casino Fonts (HipFonts) — https://hipfonts.com/casino-fonts/
- Best Casino Fonts Free/Premium (Hyperpix) — https://hyperpix.net/fonts/casino-fonts/
- How to Create Neon Text With CSS (CSS-Tricks) — https://css-tricks.com/how-to-create-neon-text-with-css/
- Creating Glow Effects with CSS (Coder's Block) — https://codersblock.com/blog/creating-glow-effects-with-css/
- 47 Best CSS Glow Effects (TestMu AI) — https://www.testmuai.com/blog/glowing-effects-in-css/
- 40+ CSS Glow Effects (FreeFrontend) — https://freefrontend.com/css-glow-effects/
- CodePen — CSS-only Slot Machine — https://codepen.io/tangxuguo/pen/xqrNmx
- CodePen — Real Slots Reels — https://codepen.io/josfabre/pen/abReBvP
- CodePen — Reel Slot Machine — https://codepen.io/stevencamilleri-mrg/pen/abzGOGd

View File

@@ -0,0 +1,334 @@
# Meccaniche di Suspense e Reveal: slot machine, ruote della fortuna e loot box
> Ricerca applicata alla nostra app: assegniamo le persone alle stanze tramite un algoritmo (risultato **predeterminato**), ma lo riveliamo con un'animazione che costruisce **attesa ed eccitazione**, esattamente come lo "spin" di una slot machine. Questo documento raccoglie la psicologia, le meccaniche di animazione e le tecniche JS/CSS concrete per farlo bene.
---
## 1. La psicologia dello "spin & reveal"
Il punto centrale, ripetuto in quasi tutta la letteratura di game design, è controintuitivo ma fondamentale per noi:
> **Il momento della rivelazione (lo spin, il rallentamento, il drumroll) genera più coinvolgimento del premio stesso.** Un'animazione di 3-5 secondi fa "più lavoro motivazionale" del contenuto effettivo che viene rivelato.
Questo significa che, anche se la stanza è già decisa dall'algoritmo, è la **coreografia della rivelazione** a creare l'emozione. Ecco i meccanismi psicologici da sfruttare.
### 1.1 Anticipazione e dopamina
Il cervello rilascia **più dopamina nell'anticipazione di una ricompensa incerta** che di fronte a una garantita ("dopamine release is significantly higher when a reward is uncertain than when it's guaranteed"). L'incertezza percepita — non il risultato — è il motore emotivo. Da qui la regola: **allungare e drammatizzare la fase di attesa**, non accorciarla.
### 1.2 Variable reward (rinforzo a rapporto variabile)
Le slot machine funzionano su uno **schedule di rinforzo a rapporto variabile** (variable ratio reinforcement), lo stesso principio comportamentale (Skinner) che rende difficile smettere: la ricompensa arriva in modo imprevedibile, e il cervello resta in stato di anticipazione costante. Nel nostro caso l'assegnazione avviene una volta sola, ma possiamo comunque sfruttare l'**imprevedibilità percepita** dell'esito (quale stanza?) durante lo spin.
### 1.3 Effetto near-miss (quasi-vincita)
Il **near-miss** è la situazione in cui i simboli si fermano *quasi* allineati (due jackpot allineati, il terzo a una posizione di distanza). Pur essendo tecnicamente una perdita, stimola il cervello **quasi come una vittoria reale**, rilasciando dopamina e alimentando la sensazione che il colpo grosso sia "a un passo".
Applicazione al nostro reveal: durante lo scorrimento, far **passare vicino** ad altre stanze/opzioni prima di fermarsi su quella finale amplifica la tensione ("stava per uscire l'altra stanza..."). È la stessa logica della roulette CS:GO che rallenta sfiorando altri item.
### 1.4 Escalation ed escalation sensoriale
I designer aumentano deliberatamente l'**arousal** prima di mostrare il contenuto:
- In *Overwatch*, la loot box **trema e vibra** prima di aprirsi.
- Fasci di luce dorati e particellari **segnalano un drop raro PRIMA** che l'item appaia.
- Suoni, animazioni temporizzate e persino lo **screen shake** enfatizzano l'impatto emotivo.
L'escalation crea una **curva di tensione crescente**: più segnali "sta per succedere qualcosa di importante" accumuli, più forte è il picco al reveal.
### 1.5 Il rallentamento finale (slow-down / easing)
Elemento chiave e più imitabile: verso la fine, lo spin **decelera progressivamente**. Questo rallentamento è ciò che il cervello legge come "sta per fermarsi, dove cadrà?" — è il momento di massima tensione. In termini tecnici è una curva di **easing-out** (veloce all'inizio, lento alla fine), spesso con un piccolo **overshoot/rimbalzo** finale (`back-out`) per un "settle" soddisfacente (vedi §2.2).
### 1.6 Principi di design etico da mystery box (da Yu-kai Chou)
Riassunto delle leve applicabili in modo sano:
1. **Basso costo di partecipazione** — l'utente accetta l'incertezza se lo sforzo è minimo.
2. **Investire nel reveal** — il momento teatrale conta più del premio.
3. **Pacing dei reveal** — reveal troppo frequenti diluiscono l'impatto e la sensibilità dopaminergica; spaziarli mantiene la novità.
4. **Floor garantito / "pity"** — nessuno esce a mani vuote; anche l'esito "comune" deve sembrare significativo (importante per noi: qualunque stanza deve essere presentata come una "bella" assegnazione).
---
## 2. Meccaniche di animazione dei rulli (slot reel)
### 2.1 Come gira un rullo
Il rullo è tipicamente una **striscia verticale di simboli** (un contenitore lungo dentro una finestra con `overflow: hidden`). Lo spin si ottiene traslando la striscia in verticale (`transform: translateY(...)`), non spostando i singoli simboli. Approcci comuni:
- **CSS transition/animation** su `translateY` con easing (semplice, performante).
- **Web Animations API** (`element.animate`) per controllo dinamico del valore finale (consigliato per risultato predeterminato — vedi §7).
- **requestAnimationFrame** per controllo frame-by-frame (fisica custom, blur di movimento).
Per la **velocità percepita**, durante la fase veloce si può aggiungere un leggero **motion blur** (es. `filter: blur()` verticale) o semplicemente ripetere i simboli in modo che scorrano rapidamente.
### 2.2 Curve di easing: le più importanti
Il carattere dell'animazione dipende quasi tutto dalla **timing function**. Riferimento pratico (`easings.net`):
| Effetto | Keyword / cubic-bezier | Uso |
|---|---|---|
| `ease-out` | `cubic-bezier(0, 0, 0.58, 1)` | Decelerazione base a fine spin |
| `easeOutBack` (overshoot) | `cubic-bezier(0.34, 1.56, 0.64, 1)` | Il rullo supera il target e torna indietro → "settle" con rimbalzo |
| Wheel of fortune (con molla) | `cubic-bezier(0.440, -0.205, 0.000, 1.130)` | Decelerazione + micro-bounce elastico |
| `ease-in` | `cubic-bezier(0.42, 0, 1, 1)` | Accelerazione iniziale (partenza dello spin) |
**Nota tecnica cruciale sui cubic-bezier**: gli assi `x` (tempo) devono stare tra 0 e 1, ma **`y` (progresso) può uscire da 01**. Un `y > 1` produce **overshoot** (l'elemento supera la destinazione e rientra → effetto "back"/molla); un `y < 0` produce undershoot. È così che si ottiene la sensazione elastica del rimbalzo finale con **due sole maniglie**.
Per un vero **elastic/bounce multi-oscillazione** (più rimbalzi smorzati), un singolo cubic-bezier non basta: serve un `@keyframes` complesso o una libreria (GSAP, Motion, Svelte easing). `easeOutElastic` dà "l'illusione di un rullo che ruota e poi si assesta".
### 2.3 Struttura tipica dello spin di un rullo
1. **Accelerazione** breve (`ease-in`) o partenza istantanea.
2. **Fase veloce** a velocità costante (loop di simboli, eventuale blur).
3. **Decelerazione** lunga (`ease-out`) — è qui che nasce la suspense.
4. **Settle / bounce** finale (`easeOutBack`) — il rullo si allinea al simbolo con un micro-scatto.
### 2.4 Stagger tra i rulli (fermata sequenziale)
Elemento firma delle slot: i rulli **non si fermano tutti insieme**, ma **uno dopo l'altro da sinistra a destra**. Questo:
- prolunga la tensione (l'esito totale si scopre solo all'ultimo rullo);
- abilita l'effetto **near-miss** (i primi rulli mostrano il simbolo giusto, l'ultimo tiene sul filo).
Implementazione: si applica un **delay incrementale** per indice di rullo (es. `delay = index * 400ms`, oppure lo step di aggiornamento è `50ms + index*stagger`). Con Web Animations API basta impostare `delay` diverso per ogni rullo; con CSS, `transition-delay` o `animation-delay` scalati.
### 2.5 Il "bounce/settle" finale
I videogiochi di slot inseriscono "un piccolo rimbalzo nei rulli verso la fine dello spin, subito prima che l'esito finale venga mostrato". Tecnicamente: si fa fermare il rullo **poco oltre** la posizione target e lo si riporta indietro con un `easeOutBack` o un keyframe che overshoota di ~1 riga e rientra. È il dettaglio che rende la fermata "soddisfacente" invece che secca.
**Riferimenti implementativi slot**:
- Victor Toschi, *How to create a Slot Machine animation with CSS and JavaScript* (metodi `init` / `spin` / `shuffle`) + CodePen `toschivictor/pen/JjNZjEj`.
- `ezslots` (jQuery toolkit) e `HTML-slot-machine-example` (vanilla) su GitHub.
- CodePen `MrFirthy/pen/oGVWqK` (CSS/JS slot machine).
---
## 3. Ruota della fortuna / spinning wheel (fisica e landing)
### 3.1 Struttura
La ruota è un cerchio suddiviso in spicchi; ogni spicchio è ruotato di `360° / n * indice` con `transform-origin` sul bordo/centro. Si anima la **rotazione dell'intera ruota** (`transform: rotate(...)`) e un puntatore fisso indica il vincitore in cima.
### 3.2 Decelerazione (l'anima dell'effetto)
Come per i rulli, la magia è nella curva: la ruota parte veloce e **decelera** fino a fermarsi. Esempio reale dall'articolo DEV Community *Wheel of Fortune with CSS* (Web Animations API):
```js
animation = wheel.animate(
[
{ transform: `rotate(${previousEndDegree}deg)` },
{ transform: `rotate(${newEndDegree}deg)` }
],
{
duration: 4000,
easing: 'cubic-bezier(0.440, -0.205, 0.000, 1.130)', // decelerazione + micro-bounce
fill: 'forwards',
iterations: 1
}
);
```
### 3.3 Atterrare su un target (landing predeterminato)
Formula generale:
```
angoloSpicchio = 360 / numeroSpicchi
offsetTarget = angoloSpicchio * indiceVincitore
rotazioneTotale = (giriInteri * 360) + offsetTarget // ± correzione per il puntatore
```
- Diversi **giri interi** (es. `Math.random() * 360 + 1800`, cioè ≥5 giri) servono a rendere l'effetto credibile: "spinnare direttamente sul premio non sembra giusto, servono dei giri completi".
- Si aggiunge una **randomizzazione entro lo spicchio** così che il puntatore non cada sempre esattamente al centro (più realistico).
- Importante: **cancellare l'animazione precedente** (`animation.cancel()`) prima di rilanciarne una nuova, per evitare conflitti.
### 3.4 Riferimenti wheel
- `dev.to/madsstoumann/wheel-of-fortune-with-css` (Web Animations API, cubic-bezier a molla).
- `jamesrwilliams.ca/posts/css-wheel-of-fortune` (approccio CSS puro).
- **Winwheel.js** (`dougtesting.net/winwheel`) — libreria dedicata con `animation.stopAngle`, spin verso segmento predeterminato.
- **Konva** (`konvajs.org/docs/sandbox/Wheel_of_Fortune.html`) — canvas 2D.
- Emanuele Feronato — *JavaScript + CSS 3D Wheel of Fortune*.
---
## 4. Card flip, scratch card, box/envelope e loot box
Alternative allo spin che possiamo usare come **variazione del reveal** (o come "atto finale" dopo lo spin).
### 4.1 Card flip (rivelazione a carta girata)
Tecnica CSS 3D standard (David DeSandro, *Intro to CSS 3D transforms — Card Flip*):
- `perspective` sul contenitore genitore → contesto 3D.
- `transform-style: preserve-3d` sulla carta.
- Due facce sovrapposte; il retro con `backface-visibility: hidden` e pre-ruotato `rotateY(180deg)`.
- Al click/hover si applica `transform: rotateY(180deg)` per rivelare.
Ottimo come **reveal secco e teatrale**: la carta si gira e mostra la stanza. Si combina bene con un piccolo delay/drumroll prima del flip.
### 4.2 Scratch card (gratta e vinci)
Con `<canvas>` e composite operation:
- Si disegna la copertura opaca sul canvas.
- `ctx.globalCompositeOperation = "destination-out"` fa sì che il movimento del cursore **cancelli** la copertura, rivelando il contenuto sotto.
- Si tracciano archi (`ctx.arc`) alla posizione del puntatore e si può calcolare la **percentuale di area grattata** per auto-rivelare al superamento di una soglia.
Vantaggio: introduce **interazione fisica** (l'utente "scopre" la propria stanza), aumentando ownership ed engagement. Riferimenti: Envato Tuts+ *scratch card effect in vanilla JavaScript*; Medium (Mr Zouraiz) *Digital Scratch Card*.
### 4.3 Box / envelope / loot box open
Pattern loot box (Overwatch-style):
1. Oggetto (box/busta) che **vibra e trema** (keyframe di shake) con intensità crescente.
2. Build-up di **particellari e luce** che anticipa la rarità/importanza.
3. **Apertura** (scale + fade + burst) e uscita del contenuto (la stanza).
Lo shake si fa con un `@keyframes` di piccole traslazioni/rotazioni alternate; l'escalation con `animation-duration` decrescente o ampiezza crescente. Il picco luminoso PRIMA del contenuto è il trucco che "vende" il reveal.
---
## 5. Countdown, drumroll ed escalation della tensione
Prima dello spin/reveal, un **build-up** temporizzato aumenta l'attesa.
- **Countdown ring animato**: si anima `stroke-dasharray` di un cerchio SVG per "svuotare" un anello nel tempo, o si sovrappone un anello colorato che si consuma come progress bar (CSS-Tricks, *How to Create an Animated Countdown Timer*).
- **Drumroll audio + visivo**: rullo di tamburi con vibrazione/pulsazione crescente dell'elemento centrale sincronizzata; il picco coincide con l'inizio dello spin.
- **Escalation**: aumentare progressivamente frequenza dei flash, ampiezza dello shake, pitch/volume del suono. La curva di tensione deve **salire** fino al reveal e poi **scaricarsi** al settle.
- **Attenzione UX**: i countdown troppo aggressivi generano stress/panico (Medium, *The Stress of Countdown Clocks*). Per noi il countdown deve essere **eccitante, non ansiogeno** → durate brevi (2-3s), tono celebrativo, nessuna penalità reale.
---
## 6. Come far sembrare RANDOM un risultato PREDETERMINATO
Questo è il cuore del nostro caso d'uso: l'algoritmo ha già scelto la stanza, ma l'animazione deve costruire suspense come se fosse un sorteggio dal vivo.
**Principio guida**: in *tutti* questi sistemi (slot, CS:GO case, loot box) **l'esito è già deciso prima dell'animazione** — "the weapon contained in a case is already predetermined... there is no roulette". L'animazione è pura scenografia. Quindi possiamo replicarlo in modo legittimo.
### Ricetta pratica
1. **Decidi il risultato server-side** (la stanza) e passalo al client.
2. **Costruisci una lista visiva** che *contiene* il risultato in una posizione nota, circondato da distrattori (altre stanze). Es. per una roulette orizzontale stile CS:GO: array lungo di item con il vincitore posizionato vicino alla fine, poi altri item dopo per poter "sfiorare" e superare.
3. **Anima verso quella posizione** con easing-out lungo, così l'utente vede scorrere molte opzioni e non può prevedere dove si fermerà.
4. **Sfrutta il near-miss**: fai passare/rallentare vicino ad altre stanze prima di fermarti su quella giusta (l'effetto "stava per uscire l'altra").
5. **Settle con bounce** (`easeOutBack`) sull'elemento vincitore + highlight/particellari.
6. **Non rivelare mai la posizione target in anticipo** nel DOM visibile: l'ordine può essere reale ma l'occhio non deve poter contare gli spicchi.
### Roulette orizzontale (CS:GO-style) — la più adatta a "assegnazione stanza"
È il pattern che mappa meglio sul nostro problema (una lista di stanze che scorre e si ferma su una).
- Striscia orizzontale di card (le stanze, ripetute/mischiate), dentro una finestra con `overflow: hidden` e un **marker centrale** fisso.
- Si anima `translateX` negativo fino a portare la card vincitrice sotto il marker, con `ease-out` lungo (3-6s).
- La distanza finale include un piccolo **offset random dentro la card** così che non si fermi sempre perfettamente al centro → sembra fisico.
- Riferimenti CodePen: `killerek/pen/gLBgWd` (case opening animation), `marctannous/pen/xGRGBz` (layout CS:GO + winner selection), `memir0/pen/QPEPxm` (random name picker side-scrolling). Discussione GSAP: *Creating CS:GO Roulette Horizontal Spinner*.
---
## 7. Tecniche JS/CSS pratiche
### 7.1 Web Animations API (`element.animate`) — consigliata per risultato dinamico
Ideale quando il valore finale (posizione/rotazione) è calcolato a runtime dall'esito dell'algoritmo.
```js
const spin = strip.animate(
[
{ transform: 'translateX(0)' },
{ transform: `translateX(${-targetX}px)` }
],
{
duration: 5000,
easing: 'cubic-bezier(0.12, 0.8, 0.2, 1)', // decelerazione forte
fill: 'forwards'
}
);
// Sapere quando è finita → trigger del settle/celebrazione
spin.finished.then(() => {
highlightWinner();
});
// oppure: spin.onfinish = () => { ... }
```
Opzioni chiave: `duration` (ms), `easing`, `iterations`, `delay` (per lo **stagger** dei rulli), `fill: 'forwards'` (mantiene lo stato finale). Controllo: `play/pause/reverse/finish/cancel`; `cancel()` prima di un nuovo spin per evitare conflitti.
### 7.2 CSS transition/animation
Semplice e performante per spin a target fisso o loop:
```css
.reel {
transition: transform 4s cubic-bezier(0.12, 0.8, 0.2, 1);
}
.reel.spinning { transform: translateY(-3000px); }
/* settle con overshoot */
.reel.settle {
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
```
Per lo stagger: `transition-delay` / `animation-delay` scalati per indice. Nota: un cubic-bezier a 2 maniglie **non** fa bounce multipli → usare `@keyframes` o libreria.
### 7.3 requestAnimationFrame (fisica custom)
Quando serve controllo totale: decelerazione basata su velocità/attrito, snap al simbolo più vicino, motion blur dipendente dalla velocità.
```js
function frame() {
velocity *= friction; // attrito → decelerazione
offset += velocity;
strip.style.transform = `translateY(${-offset}px)`;
if (velocity > threshold) requestAnimationFrame(frame);
else snapToNearest(); // allineamento finale + bounce
}
```
`requestAnimationFrame` sincronizza il callback con il ciclo di paint del browser → animazioni fluide ed efficienti.
### 7.4 Performance
- Animare **solo `transform` e `opacity`** (compositor-only, no reflow/repaint).
- Evitare di animare `top/left/width/height`.
- `will-change: transform` sugli elementi che spinnano; rimuoverlo a fine animazione.
- Preferire un **singolo loop rAF** condiviso a molti loop indipendenti.
### 7.5 Librerie utili
- **GSAP** — timeline, easing avanzati (`back`, `elastic`, `bounce`), usato spesso per roulette CS:GO.
- **Motion / Web Animations API improvements** (`motion.dev`) — spring nativi, DX migliorata.
- **Winwheel.js** — ruote della fortuna con landing su segmento.
- **Konva** — animazioni canvas 2D (ruota, particellari).
---
## 8. Applicazione consigliata al nostro reveal "assegnazione stanza"
Sequenza proposta, orchestrando i pezzi sopra:
1. **Build-up** (2-3s): countdown ring + drumroll, elemento centrale che pulsa/vibra con escalation. Tono celebrativo, non ansiogeno (§5).
2. **Spin**: roulette orizzontale CS:GO-style (§6) o rulli slot (§2) che scorrono le stanze, con blur/velocità alta.
3. **Decelerazione** lunga con `ease-out` (§2.2, §3.2): momento di massima tensione.
4. **Near-miss**: rallenta sfiorando 1-2 stanze alternative prima del target (§1.3).
5. **Settle** con `easeOutBack` sul target + burst di luce/particellari (§1.4, §2.5).
6. **Reveal finale**: opzionale card flip / apertura box sulla stanza assegnata (§4) con celebrazione.
7. **Floor positivo**: presentare qualsiasi stanza come esito desiderabile (§1.6, punto pity).
Il risultato è deciso dall'algoritmo al passo 0, ma l'utente vive un sorteggio dal vivo credibile ed eccitante.
---
## Fonti
**Psicologia (near-miss, variable reward, anticipazione, loot box)**
- The Psychology Behind Near Misses in Slot Games — https://news.eternalslots.com/the-psychology-behind-near-misses-in-slot-games/
- Slot Machine Psychology: How the Near Miss Effect Drives Player Behavior — https://www.casinocenter.com/slot-machine-psychology-how-the-near-miss-effect-drives-player-behavior-in-online-gaming/
- The Slot Machine Psyche: Variable Ratio Reinforcement (PSU) — https://www.psu.com/news/the-slot-machine-psyche-how-variable-ratio-reinforcement-drives-modern-gaming-engagement/
- The Psychology Behind Slot Machines (problemgambling.com) — https://www.problemgambling.com/post/the-psychology-behind-slot-machines-how-they-keep-you-gambling
- Yu-kai Chou — Decoding the Mystery Box: Reward Design — https://yukaichou.com/advanced-gamification/decoding-the-mystery-box-a-dive-into-the-intricacies-of-reward-design/
- Rare Loot Box Rewards Trigger Larger Arousal and Reward Responses (PMC) — https://pmc.ncbi.nlm.nih.gov/articles/PMC7882574/
- Loot Boxes Hook Players — How We Used the Same Psychology Ethically (Medium) — https://medium.com/avianu/loot-boxes-hook-players-heres-how-we-used-the-same-psychology-ethically-296f44261917
- Zelda's Treasure Chests and the Psychology of Random Rewards (GameTyrant) — https://gametyrant.com/news/zelda-random-rewards-psychology
**Animazione rulli slot**
- Victor Toschi — Slot Machine animation with CSS and JavaScript (Medium) — https://medium.com/@victortoschi/how-to-create-a-slot-machine-animation-with-css-and-javascript-9073ab9db9ea
- CodePen — CSS/JS Slot machine (MrFirthy) — https://codepen.io/MrFirthy/pen/oGVWqK
- ezslots (jQuery slots toolkit, GitHub) — https://github.com/kirkjerk/ezslots
- HTML slot machine example (vanilla JS, GitHub) — https://github.com/flixrp/HTML-slot-machine-example
- Create a slot machine game in vanilla JavaScript (Envato Tuts+) — https://webdesign.tutsplus.com/how-to-create-a-slot-machine-game-in-vanilla-javascript--cms-108974t
**Ruota della fortuna**
- Wheel of Fortune with CSS (DEV / madsstoumann) — https://dev.to/madsstoumann/wheel-of-fortune-with-css-p-pi-1ne9
- A CSS wheel of fortune (James R. Williams) — https://jamesrwilliams.ca/posts/css-wheel-of-fortune
- Winwheel.js — Wheel of Fortune example — https://dougtesting.net/winwheel/examples/wheel_of_fortune
- Wheel of Fortune (Konva canvas) — https://konvajs.org/docs/sandbox/Wheel_of_Fortune.html
- JavaScript + CSS 3D Wheel of Fortune (Emanuele Feronato) — https://emanueleferonato.com/2025/10/31/javascript-css-3d-wheel-of-fortune/
**Roulette orizzontale / case opening (risultato predeterminato)**
- CSGO Roulette Wheel Explained — https://csgoskins.games/csgo-roulette-wheel/
- Case opening animation (CodePen, killerek) — https://codepen.io/killerek/pen/gLBgWd
- CS:GO layout + winner selection (CodePen, marctannous) — https://codepen.io/marctannous/pen/xGRGBz
- CS:GO-like side-scrolling random name picker (CodePen, memir0) — https://codepen.io/memir0/pen/QPEPxm
- GSAP forum — Creating CS:GO Roulette Horizontal Spinner — https://gsap.com/community/forums/topic/42002-help-needed-creating-csgo-roulette-horizontal-spinner/
**Card flip, scratch card**
- Card Flip — Intro to CSS 3D transforms (David DeSandro) — https://3dtransforms.desandro.com/card-flip
- How To Create a Flip Card with CSS (W3Schools) — https://www.w3schools.com/howto/howto_css_flip_card.asp
- Scratch card effect in vanilla JavaScript (Envato Tuts+) — https://webdesign.tutsplus.com/how-to-create-a-scratch-card-effect-in-vanilla-javascript--cms-108922t
- Digital Scratch Card HTML/CSS/JS (Medium, Mr Zouraiz) — https://medium.com/@mr.zouraiz1580/digital-scratch-card-html-8eaf1dad6095
**Countdown / drumroll / tensione**
- How to Create an Animated Countdown Timer (CSS-Tricks) — https://css-tricks.com/how-to-create-an-animated-countdown-timer-with-html-css-and-javascript/
- The Stress of Countdown Clocks in UX Psychology (Medium) — https://medium.com/design-bootcamp/the-stress-of-countdown-clocks-understanding-panic-inducing-timers-in-ux-psychology-b8d1a6333691
**Easing e tecniche JS/CSS**
- Easing Functions Cheat Sheet — https://easings.net/
- cubic-bezier() (CSS-Tricks Almanac) — https://css-tricks.com/almanac/functions/c/cubic-bezier/
- Understanding easing and cubic-bezier curves (Josh Collinsworth) — https://joshcollinsworth.com/blog/easing-curves
- Incorporating elastic ease in CSS animations (LogRocket) — https://blog.logrocket.com/how-to-incorporate-elastic-ease-in-css-animations/
- Using the Web Animations API (MDN) — https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API
- Improvements to the Web Animations API (Motion) — https://motion.dev/docs/improvements-to-the-web-animations-api-dx
- JavaScript Animations with requestAnimationFrame and WAAPI — https://tutorialreference.com/javascript/advanced-topics/animation/javascript-animations-with-requestanimationframe-and-web-animations-api

View File

@@ -0,0 +1,530 @@
# 03 — Interattività e Coinvolgimento
> Come trasformare l'assegnazione di ~20 persone a un set di stanze in uno **spettacolo condiviso**
> in stile scommesse / game-show / slot machine, guardato tutti insieme su un unico schermo.
Questo documento raccoglie idee di game design, meccaniche di gamification, sound design,
momenti di celebrazione e soluzioni tecniche concrete (con nomi di librerie e URL CDN reali)
per rendere la web app **massimamente divertente e coinvolgente**.
---
## 1. Il principio di base: l'attesa vale più del premio
La ricerca sul gambling e sui game-show è concorde: **l'anticipazione conta più del risultato**.
Il rilascio di dopamina è massimo *nei momenti che precedono* la rivelazione, non nella rivelazione
stessa. I "reward" imprevedibili (variable ratio reward schedule) generano una risposta emotiva
molto più forte di quelli prevedibili.
Implicazioni di design per la nostra app:
- **Non rivelare tutto insieme.** Diluire l'assegnazione nel tempo (una persona / una stanza alla
volta) moltiplica i picchi di tensione.
- **Rallentare proprio prima del risultato.** Come la ruota della fortuna che decelera: gli ultimi
secondi prima dello "stop" sono quelli che valgono.
- **Sfruttare il "quasi-vinto" (near-miss).** Far vedere che qualcuno stava per finire nella "stanza
VIP" ma poi... no. Il near-miss tiene il cervello in stato di attesa e alza l'engagement di tutta
la sala.
- **Incertezza come feature, non come bug.** Non mostrare mai l'esito prima del tempo; la sorpresa è
il prodotto.
Questi principi guidano tutte le sezioni successive.
---
## 2. Meccaniche di engagement e gamification
### 2.1 Suspense e reveal sociale
- **Reveal una persona alla volta**: il nome della persona appare, parte lo "spin", e solo dopo si
scopre la stanza. Ogni singola assegnazione diventa un micro-evento.
- **Spinning wheel / slot reel come metafora principale**: la ruota/rullo che gira e decelera è una
delle meccaniche di suspense più efficaci perché il pubblico *vede tutti gli esiti possibili*
scorrere davanti e può "tifare" per uno di essi.
- **Ordine di rivelazione studiato per il dramma** (vedi §6): non alfabetico, ma costruito a
crescendo.
- **Countdown visibile** (3… 2… 1…) prima di ogni spin, con drumroll audio e vibrazione.
### 2.2 Scommesse / pronostici prima del reveal (il cuore "gambling")
Prima che l'algoritmo riveli le assegnazioni, si apre una **finestra di pronostici**:
- Ogni partecipante (o la sala a mano alzata, in modalità single-screen) prova a indovinare
**in quale stanza finirà una data persona**.
- Si possono mostrare **quote/odds** fittizie accanto a ogni stanza ("Stanza VIP paga 5x", "Stanza
Sgabuzzino paga 1.2x") per dare sapore da sala scommesse e orientare l'ironia del gruppo.
- **Punti / gettoni virtuali** da puntare: chi indovina di più sale in classifica.
- Il momento della rivelazione diventa doppiamente teso: non scopri solo dove va la persona, ma
anche *se hai vinto la scommessa*.
Questa meccanica sfrutta esattamente la psicologia dell'incertezza del premio: la posta in gioco
personale amplifica l'attenzione di tutti.
### 2.3 Leaderboard e classifiche
- **Classifica pronosticatori**: chi ha indovinato più assegnazioni.
- Aggiornamento **live dopo ogni reveal**, con animazione di scalata (righe che si riordinano).
- Ranking individuale e/o a squadre; le squadre aumentano il tifo e le esultanze collettive.
- La leaderboard è essa stessa un momento di spettacolo: mostrarla a schermo intero tra un blocco di
reveal e l'altro.
### 2.4 Reazioni, emoji e tifo
- **Emoji reactions in tempo reale** che volano sullo schermo condiviso (🎉 😱 🔥 👑 💀) quando esce
un'assegnazione clamorosa.
- Se i telefoni sono connessi (vedi §7), i partecipanti "lanciano" emoji dal telefono e appaiono
sul proiettore.
- In modalità puramente single-screen: reazioni scatenate da tasti/hotkey dell'MC, o semplicemente
dal tifo dal vivo amplificato dagli effetti audio/visivi.
### 2.5 Streak e momenti speciali
- **Streak del pronosticatore**: 3 assegnazioni indovinate di fila → effetto "on fire" 🔥 con audio
e confetti dedicati.
- **Streak di stanza**: se la stessa stanza riceve N persone di fila → "HOT ROOM" con luci
lampeggianti.
- **Combo/moltiplicatori**: più la streak è lunga, più i punti valgono (moltiplicatore visibile che
sale), come nei giochi arcade.
### 2.6 Feedback game-show generale
- **MC / annunciatore** (testo + audio): battute pre-scritte a tema per ogni tipo di reveal (vedi §6.4).
- **Barra di "hype"** che si riempie con le reazioni della sala.
- **Achievement/badge** sbloccabili ("Primo Jackpot", "Cappotto di pronostici", "Sfortunato del
giorno").
---
## 3. Sound design
Il suono è metà dello spettacolo. Serve un piccolo **kit di effetti** riutilizzabili.
### 3.1 Gli effetti chiave da avere
| Momento | Suono | Quando parte |
|---|---|---|
| Avvio spin | *whoosh* / rullo che accelera | inizio dell'animazione della slot/ruota |
| Rotazione | tick-tick-tick dei rulli | durante lo spin (loop) |
| Decelerazione | ticchettio che rallenta | ultimi secondi prima dello stop |
| Assegnazione normale | *ding* / campanello | quando si ferma su una stanza |
| Stanza VIP / jackpot | *jackpot* + monete che cadono (coin drop) | reveal di una stanza speciale |
| Pre-reveal drammatico | *drumroll* (rullo di tamburi) | durante il countdown 3-2-1 |
| Vittoria / grande momento | *crowd cheer* (folla che esulta) | jackpot, fine evento, streak |
| Errore / near-miss | *buzz* / *sad trombone* | quando un pronostico fallisce |
| Cambio schermata | *swoosh* UI | transizioni tra i blocchi |
### 3.2 Dove trovare effetti sonori GRATIS (royalty-free)
Tutte fonti utilizzabili in progetti personali e commerciali (verificare la licenza del singolo file).
**Mixkit** — nessuna registrazione, uso libero personale e commerciale:
- Slot machine: <https://mixkit.co/free-sound-effects/slot-machine/>
- Casino: <https://mixkit.co/free-sound-effects/casino/>
- Utili qui: reel spin, coin drop, big win jingle.
**Pixabay** — royalty-free, nessuna attribuzione richiesta, download MP3:
- Slot machine: <https://pixabay.com/sound-effects/search/slot-machine/>
- Slot spin: <https://pixabay.com/sound-effects/search/slot%20spin/>
- Jackpot: <https://pixabay.com/sound-effects/search/jackpot/>
- Casino: <https://pixabay.com/sound-effects/search/casino/>
- Per gli altri effetti cercare: `drumroll`, `applause` / `crowd cheer`, `coin`, `ding`, `buzzer`,
`whoosh`.
**Freesound** — database collaborativo Creative Commons (attenzione: alcuni file richiedono
attribuzione, controllare la licenza CC0 vs CC-BY):
- Home / ricerca: <https://freesound.org/>
- Esempio pack slot machine: <https://freesound.org/people/lukaso/packs/4497/>
Suggerimento pratico: scaricare gli MP3, **normalizzare i volumi** (un jackpot troppo forte rispetto
al ding rovina l'esperienza) e tenerli in una cartella `assets/audio/` con nomi parlanti
(`spin.mp3`, `ding.mp3`, `jackpot.mp3`, `drumroll.mp3`, `coin.mp3`, `cheer.mp3`, `buzz.mp3`).
### 3.3 Come far partire i suoni nel browser
Due strade: `<audio>` semplice, oppure la **Web Audio API** / **Howler.js** per controllo fine e
niente latenza.
**Attenzione all'autoplay policy.** I browser (da Chrome 71 in poi) **bloccano l'audio finché non
c'è un'interazione utente**. Un `AudioContext` creato prima di un gesto utente nasce in stato
`suspended`. Va **sbloccato al primo click/tap** (es. il bottone "Inizia lo show") chiamando
`resume()`. Best practice: un unico "gesto di sblocco" all'avvio dell'evento.
Sblocco base con Web Audio API:
```javascript
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// da chiamare dentro un handler di click/tap (es. bottone "Avvia lo show")
function unlockAudio() {
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
document.querySelector('#start-btn').addEventListener('click', unlockAudio, { once: true });
```
**Howler.js** (consigliato: gestisce autoplay, sprite audio, volumi, fallback HTML5 in automatico).
CDN:
```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.4/howler.min.js"></script>
```
Uso:
```javascript
const sfx = {
spin: new Howl({ src: ['assets/audio/spin.mp3'], volume: 0.6 }),
ding: new Howl({ src: ['assets/audio/ding.mp3'] }),
jackpot: new Howl({ src: ['assets/audio/jackpot.mp3'], volume: 0.9 }),
drumroll: new Howl({ src: ['assets/audio/drumroll.mp3'] }),
coin: new Howl({ src: ['assets/audio/coin.mp3'] }),
cheer: new Howl({ src: ['assets/audio/cheer.mp3'] }),
buzz: new Howl({ src: ['assets/audio/buzz.mp3'] }),
};
// esempio: sequenza di un reveal
sfx.drumroll.play();
// ...al momento dello stop
sfx.ding.play();
```
Perché Howler: parte da ~7KB gzipped, usa Web Audio con **fallback su HTML5 Audio**, supporta
**audio sprite** (utile per impacchettare tutti gli effetti in un solo file e ridurre le richieste)
e semplifica volumi/fade. Docs: <https://howlerjs.com/>.
---
## 4. Momenti di celebrazione (celebration UX)
Gli effetti visivi trasformano un reveal in un evento memorabile. La regola d'oro: **scalare
l'intensità dell'effetto in base all'importanza del momento** (assegnazione normale = piccolo puff;
jackpot VIP = tutto lo schermo esplode).
### 4.1 Confetti — `canvas-confetti`
Libreria leggera (< 10KB gzip), performante, esposta come funzione globale `confetti()`.
CDN:
```html
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js"></script>
<!-- alternativa cdnjs -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/canvas-confetti/1.9.3/confetti.min.js"></script> -->
```
**Burst semplice** (assegnazione normale):
```javascript
confetti({ particleCount: 120, spread: 70, origin: { y: 0.6 } });
```
**Fireworks / fuochi d'artificio** (jackpot, fine evento) — due getti laterali ripetuti:
```javascript
const duration = 4 * 1000;
const end = Date.now() + duration;
(function frame() {
confetti({ particleCount: 7, angle: 60, spread: 55, origin: { x: 0 } });
confetti({ particleCount: 7, angle: 120, spread: 55, origin: { x: 1 } });
if (Date.now() < end) requestAnimationFrame(frame);
}());
```
**Confetti a emoji/testo** (es. 👑 per la stanza VIP):
```javascript
const crown = confetti.shapeFromText({ text: '👑', scalar: 2 });
confetti({ shapes: [crown], scalar: 2, particleCount: 40, spread: 100 });
```
Opzioni utili: `particleCount`, `spread`, `angle`, `startVelocity`, `gravity`, `colors` (array HEX),
`shapes` (`square`/`circle`/`star`/custom), `origin.x/.y`, `scalar`, `ticks`.
**Accessibilità**: usare `disableForReducedMotion: true` per rispettare chi preferisce meno
animazioni.
Demo/preset ufficiali (fireworks, snow, school pride, ecc.): <https://www.kirilv.com/canvas-confetti/>
Alternativa con supporto emoji nativo: **js-confetti** — <https://github.com/loonywizard/js-confetti>.
### 4.2 Screen shake, flash e luci lampeggianti
Effetti CSS/JS a basso costo, molto d'impatto sullo schermo grande:
```css
@keyframes shake {
0%,100% { transform: translate(0,0); }
20% { transform: translate(-8px, 4px); }
40% { transform: translate(8px, -4px); }
60% { transform: translate(-6px, -6px); }
80% { transform: translate(6px, 6px); }
}
.shake { animation: shake 0.4s ease-in-out; }
@keyframes flash {
0%,100% { background: transparent; }
50% { background: rgba(255,255,255,0.85); }
}
.flash { animation: flash 0.15s ease-in-out 3; } /* triplo flash */
```
- **Screen shake** sul reveal del jackpot (aggiungere/rimuovere la classe `.shake` sul container).
- **Flash bianco** al momento dello "stop" della slot.
- **Luci lampeggianti** in stile casinò: bordo dello schermo con luci che corrono
(box-shadow animato o una fila di "bulbi" attorno al frame).
### 4.3 Winner spotlight (riflettore sul vincitore)
Al reveal di una persona in stanza speciale, o del "vincitore" dei pronostici:
- **Oscurare lo sfondo** (overlay scuro) e lasciare un **cono di luce/riflettore** (radial-gradient)
sul nome/foto della persona.
- **Zoom-in + pop** dell'elemento (scala da 0.8 a 1.1 e ritorno) con anime.js o CSS.
- Nome grande, corona/badge, confetti a corona (§4.1) e crowd cheer (§3).
### 4.4 Anime.js per orchestrare le animazioni
Per sequenze complesse (countdown → spin → stop → celebrazione) conviene una **timeline**.
CDN:
```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
```
Esempio di reveal a più elementi con **stagger** (le lettere/righe che appaiono a cascata):
```javascript
anime({
targets: '.reveal-letter',
translateY: [40, 0],
opacity: [0, 1],
delay: anime.stagger(60), // 60ms tra un elemento e l'altro
easing: 'easeOutBack'
});
```
Anime.js gestisce timeline, keyframes, easing e staggering: ideale per sincronizzare animazione
visiva e cue audio. Docs: <https://animejs.com/documentation/>.
---
## 5. Haptics / feedback su mobile (`navigator.vibrate`)
Se i partecipanti tengono il telefono in mano (modalità connessa, §7), la **vibrazione** aggiunge un
canale sensoriale al reveal.
API base:
```javascript
// vibrazione singola (ms)
navigator.vibrate(200);
// pattern: [vibra, pausa, vibra, ...]
navigator.vibrate([100, 50, 100]); // doppio buzz "successo"
navigator.vibrate([300, 100, 300, 100, 300]); // jackpot
```
Idee d'uso:
- **Countdown**: micro-vibrazione a ogni tick (3-2-1).
- **Spin**: vibrazione continua/pulsata durante la rotazione.
- **Stop/assegnazione**: singolo buzz secco.
- **Pronostico indovinato**: doppio buzz "successo"; **fallito**: triplo buzz "errore".
- **Jackpot**: pattern lungo e ritmico.
Best practice (da MDN):
- Durate brevi: **50200ms** sono ideali; una nuova `vibrate()` annulla la precedente, quindi
evitare chiamate a raffica.
- Va invocata dentro un **gesto utente** (sticky user activation): legarla a click/tap.
- **Additiva, mai unico canale di feedback**: offrire un toggle per disattivarla (c'è chi la odia o
non la percepisce).
Supporto: Chrome/Edge/Opera/Samsung Internet e Android. **Safari (iOS/macOS) non la supporta** e
**Firefox l'ha rimossa da v129**: fare feature-detection e degradare con grazia.
```javascript
if ('vibrate' in navigator) { navigator.vibrate(200); }
```
Riferimento: <https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate>
---
## 6. Idee specifiche per il reveal "persone → stanze"
Qui sta la vera anima dello spettacolo. Suggerimenti per rendere l'assegnazione un evento condiviso.
### 6.1 Struttura del reveal, una persona alla volta
1. Appare il **nome (e foto)** della persona di turno al centro.
2. Parte lo **spin** della slot/ruota con le stanze come simboli.
3. **Countdown 3-2-1** con drumroll + vibrazione.
4. La ruota **decelera** (near-miss: rallenta vicino a una stanza "clamorosa" e poi supera).
5. **Stop** su una stanza → *ding* / o *jackpot* se speciale → confetti scalati all'importanza.
6. Aggiornamento **leaderboard pronostici** e reazioni della sala.
### 6.2 Scommesse su chi va dove
- Prima del blocco, la sala pronostica: *"Dove finirà Marco?"*.
- **Quote/odds** mostrate accanto alle stanze per ironia e strategia.
- La rivelazione risolve due tensioni insieme (dove va Marco + chi ha indovinato).
### 6.3 Stanze speciali e "VIP room jackpot"
- Designare una o più stanze come **speciali**: "Stanza VIP" 👑, "Suite Presidenziale", oppure la
temutissima "Sgabuzzino" 💀.
- Quando qualcuno ci finisce → **trattamento jackpot**: full-screen fireworks, crowd cheer, coin
drop, screen shake, spotlight.
- Le stanze speciali possono avere **posti limitati** (scarsità = più tensione a ogni assegnazione:
"resta un solo posto VIP!").
- **Progressive jackpot**: una stanza "misteriosa" il cui contenuto/premio cresce a ogni reveal
finché non viene assegnata.
### 6.4 Ordine di rivelazione per il massimo dramma
- **Non alfabetico.** Costruire un crescendo: prima assegnazioni "normali", poi i colpi di scena.
- Tenere per **ultimi** i reveal più attesi (le stanze speciali, le persone-chiave, il pareggio di
una scommessa).
- Alternare ritmo: qualche reveal veloce di fila, poi uno **rallentato al massimo** con countdown
lungo.
- **Blocchi tematici** con schermate-ponte (leaderboard, "coming up next") per far respirare la
tensione e ripartire.
### 6.5 Testi da MC / annunciatore
Frasi pre-scritte (testo a schermo + eventuale TTS/audio) a seconda dell'esito. Esempi:
- Apertura: *"Signore e signori, benvenuti alla GRANDE ASSEGNAZIONE! 20 persone, X stanze, un solo
destino. Che la sorte decida!"*
- Pre-spin: *"Il prossimo concorrente è… MARCO. Dove lo manderà la ruota? Fate le vostre puntate!"*
- Near-miss: *"Ohhh! Era a un passo dalla Suite VIP… ma no!"*
- Assegnazione normale: *"E Marco va nella Stanza 3! Un applauso."*
- Jackpot: *"JACKPOT!!! Marco conquista la STANZA VIP! 👑 Che fortuna sfacciata!"*
- Sfortuna: *"Ahia… Sgabuzzino. Condoglianze, Marco. 💀"*
- Chiusura: *"E con questo le stanze sono assegnate! Applauso finale per tutti i concorrenti!"*
### 6.6 Reazioni ed emoji legate al reveal
- Set di emoji rapide (👑 😱 🔥 💀 🎉 👏) scatenabili dalla sala/telefoni al momento giusto.
- Contatore reazioni per rendere "virale" il momento in sala.
---
## 7. Multiplayer / schermo condiviso (anche senza backend)
Obiettivo: farlo funzionare bene **anche senza server**, con opzioni di upgrade se serve interazione
dai telefoni.
### 7.1 Modalità "single-screen / projector" (zero backend) — consigliata per partire
- **Un solo schermo** (proiettore o TV) mostra tutto lo show; l'MC guida con tastiera/telecomando.
- Tutta la logica gira **client-side** nel browser: l'assegnazione può essere pre-calcolata o
generata in loco, poi il reveal è pura animazione.
- I pronostici si fanno **a voce / a mano alzata**, l'MC li registra o semplicemente la sala tifa.
- Pro: nessuna infrastruttura, nessun rischio di rete, massima affidabilità in evento dal vivo.
- Il modello dei party game (Jackbox, Kahoot) conferma che *un solo schermo condiviso* è al centro
dell'esperienza collettiva; i telefoni sono solo controller opzionali.
### 7.2 Aggiungere i telefoni con un QR code
Come Jackbox.tv / Kahoot: a schermo appare un **QR code + codice stanza**; i partecipanti scansionano
e aprono una pagina "controller" nel browser (nessuna app da installare).
Generare il QR lato client:
```html
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<div id="qr"></div>
<script>
new QRCode(document.getElementById('qr'), 'https://tuo-dominio/join?room=AB12');
</script>
```
(libreria: **qrcodejs** — <https://github.com/davidshimjs/qrcodejs>)
### 7.3 Sincronizzazione telefoni ↔ schermo con backend minimale
Se vuoi che i pronostici/emoji dai telefoni appaiano in tempo reale sul proiettore, serve un piccolo
canale realtime. Opzioni leggere:
- **BroadcastChannel API** — *gratis, zero backend*, ma solo tra schede **dello stesso dispositivo/
browser** (utile per prototipare o per setup a più finestre sullo stesso PC). Non attraversa
dispositivi diversi.
- **WebSocket** su un piccolo server (Node) o servizio serverless.
- **Cloudflare Durable Objects** — ottimi per "stanze" realtime multiplayer con stato condiviso
(coordinamento di una room, WebSocket, stato per-stanza) senza gestire server tradizionali.
- Servizi realtime gestiti (es. Firebase Realtime Database / Firestore, Supabase Realtime) se si
preferisce non scrivere backend.
Raccomandazione: **partire single-screen (§7.1)**, aggiungere il **QR + controller** (§7.2) solo se
si vuole l'interazione dai telefoni, e introdurre un backend realtime **solo se** i pronostici live
diventano centrali.
---
## 8. Stack tecnico consigliato (riepilogo librerie e CDN)
| Scopo | Libreria | CDN |
|---|---|---|
| Confetti / fuochi | **canvas-confetti** | `https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js` |
| Confetti con emoji (alt.) | **js-confetti** | <https://github.com/loonywizard/js-confetti> |
| Audio / SFX | **Howler.js** | `https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.4/howler.min.js` |
| Animazioni / timeline | **anime.js** | `https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js` |
| QR code join | **qrcodejs** | `https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js` |
| Haptics | **Vibration API** (nativa) | `navigator.vibrate()` |
| Sblocco/gestione audio | **Web Audio API** (nativa) | `AudioContext` + `resume()` |
| Realtime opzionale | **Cloudflare Durable Objects** / WebSocket / Firebase | — |
**Checklist di implementazione**
- [ ] Sbloccare l'audio al primo tap (bottone "Avvia lo show").
- [ ] Precaricare tutti gli SFX (Howler) e le immagini prima dell'evento.
- [ ] Scalare gli effetti (confetti/shake/audio) sull'importanza del reveal.
- [ ] Rispettare `prefers-reduced-motion` (`disableForReducedMotion`) e offrire toggle audio/haptics.
- [ ] Feature-detection per `navigator.vibrate` (assente su iOS/Safari e Firefox recenti).
- [ ] Testare su schermo grande + audio potente (l'evento è dal vivo, il volume conta).
- [ ] Curare l'ordine di reveal per il crescendo drammatico.
---
## Fonti
**Confetti / animazioni**
- canvas-confetti (GitHub): <https://github.com/catdad/canvas-confetti>
- canvas-confetti (npm): <https://www.npmjs.com/package/canvas-confetti>
- canvas-confetti (cdnjs): <https://cdnjs.com/libraries/canvas-confetti>
- canvas-confetti (jsDelivr): <https://www.jsdelivr.com/package/npm/canvas-confetti>
- Demo/preset canvas-confetti (Kiril Vatev): <https://www.kirilv.com/canvas-confetti/>
- js-confetti (emoji): <https://github.com/loonywizard/js-confetti>
- anime.js (sito): <https://animejs.com/>
- anime.js (documentazione): <https://animejs.com/documentation/>
- anime.js — stagger: <https://animejs.com/documentation/utilities/stagger/>
**Audio / Web Audio**
- Howler.js (sito): <https://howlerjs.com/>
- Howler.js (GitHub): <https://github.com/goldfire/howler.js/>
- Howler.js (cdnjs): <https://cdnjs.com/libraries/howler>
- MDN — Using the Web Audio API: <https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API>
- MDN — Autoplay guide: <https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Autoplay>
- MDN — Web Audio API best practices: <https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Best_practices>
- Chrome for Developers — Web Audio, Autoplay Policy and Games: <https://developer.chrome.com/blog/web-audio-autoplay>
- Chrome for Developers — Autoplay policy in Chrome: <https://developer.chrome.com/blog/autoplay>
**Effetti sonori gratuiti**
- Mixkit — Slot Machine: <https://mixkit.co/free-sound-effects/slot-machine/>
- Mixkit — Casino: <https://mixkit.co/free-sound-effects/casino/>
- Pixabay — Slot Machine: <https://pixabay.com/sound-effects/search/slot-machine/>
- Pixabay — Slot Spin: <https://pixabay.com/sound-effects/search/slot%20spin/>
- Pixabay — Jackpot: <https://pixabay.com/sound-effects/search/jackpot/>
- Pixabay — Casino: <https://pixabay.com/sound-effects/search/casino/>
- Freesound (home): <https://freesound.org/>
- Freesound — pack Slot Machine (lukaso): <https://freesound.org/people/lukaso/packs/4497/>
**Haptics**
- MDN — Vibration API: <https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API>
- MDN — Navigator.vibrate(): <https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate>
- WICG — Web Haptics API: <https://github.com/WICG/web-haptics>
**Gamification / game-show / psicologia**
- Yu-kai Chou — The Spinning Wheel (gamification): <https://yukaichou.com/gamification-analysis/the-spinning-wheel-a-comprehensive-guide-to-boosting-user-engagement/>
- Guidebook — Event App Gamification: <https://www.guidebook.com/post/master-event-gamification-with-these-engaging-app-features>
- Contest Factory — Gamification & Interactive Mechanics: <https://contestfactory.com/gamification-interactive-mechanics-that-drive-participation/>
- The Conversation — How gambling distorts reality and hooks your brain: <https://theconversation.com/designed-to-deceive-how-gambling-distorts-reality-and-hooks-your-brain-91052>
- NCBI/PMC — Reward anticipation and outcome evaluation in gambling: <https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3971161/>
- Near-miss effect: <https://accenteyes.com/how-near-misses-deceive-your-brain-into-persistent-betting/>
**Schermo condiviso / multiplayer**
- Jackbox — How to play remotely (QR / room code): <https://www.jackboxgames.com/blog/how-to-play-jackbox-games-with-friends-and-family-remotely>
- Kahoot — How to host a live kahoot: <https://support.kahoot.com/hc/en-us/articles/360039422694-How-to-host-a-live-kahoot>
- Kahoot — How to join (PIN / link / QR): <https://support.kahoot.com/hc/en-us/articles/360039890713-Kahoot-join-How-to-join-a-Kahoot-game>
- qrcodejs: <https://github.com/davidshimjs/qrcodejs>
- MDN — BroadcastChannel API: <https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API>
- Cloudflare Durable Objects: <https://developers.cloudflare.com/durable-objects/>

View File

@@ -0,0 +1,708 @@
# Implementazione in Vanilla JS — Tecniche pratiche per una web app "slot machine / ruota / betting"
Guida operativa per costruire un'app in stile slot-machine / spinning-reel / betting con **HTML + CSS + JavaScript puri**, senza framework e senza build step. Ogni sezione contiene snippet pronti da incollare. L'obiettivo è un progetto che parte semplicemente aprendo `index.html` (o con un piccolo live server).
---
## 1. La reel della slot: strip verticale con `transform: translateY`
L'idea di base di una reel (rullo) è banale e robusta: una **striscia verticale di elementi** (i simboli) dentro un contenitore con `overflow: hidden`. Si sposta la striscia con `transform: translateY(...)` e si fa in modo che si fermi (snapping) su un **indice target predeterminato**, con un easing `cubic-bezier` in uscita (ease-out) così il rullo decelera e "atterra" sul risultato deciso in anticipo.
Concetti chiave:
- Ogni simbolo ha un'altezza fissa (`--item-h`). Fermarsi sull'indice `i` significa `translateY(-i * item-h)`.
- Per dare l'illusione di velocità, si **ripete la lista di simboli N volte**: si anima da un offset alto verso l'indice finale, così la reel scorre parecchio prima di fermarsi.
- L'atterraggio deterministico si ottiene calcolando l'offset target **prima** di lanciare l'animazione: il risultato è già deciso, l'animazione è solo estetica.
- La curva `cubic-bezier(0.23, 1, 0.32, 1)` (nota come *easeOutQuint*) dà quella decelerazione "morbida" tipica delle slot. `ease-out` puro equivale a `cubic-bezier(0, 0, 0.58, 1)`.
### HTML
```html
<div class="reel" id="reel1">
<div class="reel__strip">
<!-- i simboli vengono generati/ripetuti via JS -->
<div class="reel__item">🍒</div>
<div class="reel__item">🍋</div>
<div class="reel__item">🔔</div>
<div class="reel__item"></div>
<div class="reel__item">7</div>
</div>
</div>
```
### CSS
```css
:root {
--item-h: 100px; /* altezza di un simbolo */
--visible: 3; /* simboli visibili nella finestra */
}
.reel {
height: calc(var(--item-h) * var(--visible));
width: 120px;
overflow: hidden; /* finestra: taglia tutto il resto */
border-radius: 12px;
background: #111;
box-shadow: inset 0 0 24px rgba(0,0,0,.8);
}
.reel__strip {
display: flex;
flex-direction: column;
will-change: transform; /* hint per il compositor / GPU */
}
.reel__item {
height: var(--item-h);
display: grid;
place-items: center;
font-size: 56px;
user-select: none;
}
/* classe applicata via JS per la fase di spin */
.reel__strip--spinning {
transition: transform 3.2s cubic-bezier(0.23, 1, 0.32, 1);
}
```
### JavaScript — atterraggio su indice predeterminato
```js
const SYMBOLS = ['🍒', '🍋', '🔔', '⭐', '7⃣'];
const ITEM_H = 100;
const LOOPS = 8; // quante volte ripeto la lista per "allungare" lo scorrimento
function buildStrip(stripEl) {
// Ripeto i simboli LOOPS volte così la reel ha molto materiale da scorrere.
const repeated = Array.from({ length: LOOPS }, () => SYMBOLS).flat();
stripEl.innerHTML = repeated
.map((s) => `<div class="reel__item">${s}</div>`)
.join('');
return repeated.length;
}
/**
* Fa girare la reel e la ferma ESATTAMENTE su `targetIndex` (0..SYMBOLS.length-1).
* Il risultato è deciso a priori: passo l'indice che voglio far uscire.
*/
function spinTo(stripEl, targetIndex) {
const totalItems = buildStrip(stripEl);
// Posizione finale: penultimo "giro" + l'indice target, così scorre a lungo.
const finalItem = (LOOPS - 2) * SYMBOLS.length + targetIndex;
const finalY = -finalItem * ITEM_H;
// Reset istantaneo in cima, senza transizione.
stripEl.classList.remove('reel__strip--spinning');
stripEl.style.transform = 'translateY(0)';
// Forzo un reflow così il browser "vede" lo stato iniziale prima di animare.
void stripEl.offsetHeight;
// Attivo la transizione ease-out e imposto il target: la reel decelera e atterra.
stripEl.classList.add('reel__strip--spinning');
stripEl.style.transform = `translateY(${finalY}px)`;
return new Promise((resolve) => {
stripEl.addEventListener('transitionend', () => resolve(targetIndex), {
once: true,
});
});
}
// Uso:
const strip = document.querySelector('#reel1 .reel__strip');
const wanted = Math.floor(Math.random() * SYMBOLS.length); // o un valore deciso dal server
spinTo(strip, wanted).then((idx) => {
console.log('Atterrato su', SYMBOLS[idx]);
});
```
Per una slot a più rulli, si lancia `spinTo` su ogni reel con un piccolo **stagger** (ritardo crescente) e/o durate leggermente diverse, così i rulli si fermano uno dopo l'altro:
```js
const reels = document.querySelectorAll('.reel__strip');
const result = [2, 4, 2]; // simboli target per rullo (deciso a priori)
Promise.all(
[...reels].map((strip, i) =>
new Promise((res) =>
setTimeout(() => spinTo(strip, result[i]).then(res), i * 250)
)
)
).then(() => console.log('Tutti i rulli fermi:', result));
```
> Nota: `transitionend` può scattare più volte se animi più proprietà. Qui animiamo solo `transform`, e usiamo `{ once: true }` per sicurezza. Se serve robustezza extra, si può fare fallback con un `setTimeout` pari alla durata.
---
## 2. Web Animations API vs CSS transitions vs `requestAnimationFrame`
Tre strumenti, tre casi d'uso. Regola generale (web.dev / MDN): **usa CSS dove possibile**, sali di livello solo quando serve controllo imperativo o logica per-frame.
| Tecnica | Quando usarla | Punti di forza | Limiti |
|---|---|---|---|
| **CSS transitions** | Passaggio tra due stati (hover, `.is-open`, classe applicata via JS). È quello che usiamo nello snippet reel sopra. | Semplice, dichiarativo, girato sul compositor (GPU) per `transform`/`opacity`. | Solo A→B; poco controllo dinamico; sincronizzare più elementi è scomodo. |
| **CSS animations (`@keyframes`)** | Animazioni multi-fase/loop puramente estetiche (pulsare di una luce, glow). | Dichiarativo, riavviabile via classe, performante. | I keyframe sono statici; parametrizzarli a runtime richiede custom properties o `@property`. |
| **Web Animations API (`element.animate()`)** | Quando il target è **calcolato a runtime** (es. angolo finale della ruota) e vuoi Promise, `reverse()`, `pause()`, `playbackRate`. Ideale per ruota e slot orchestrate. | Controllo imperativo + performance CSS-like; `animation.finished` è una Promise; keyframe dinamici. | API un filo più verbosa; niente per canvas/WebGL. |
| **`requestAnimationFrame`** | Serve logica **per ogni frame**: canvas 2D/WebGL, fisica, particelle, valori che dipendono dall'input in tempo reale (es. coriandoli, counter animati). | Massimo controllo; unico modo di disegnare su canvas frame-by-frame; sincronizzato al refresh del monitor. | Tutto a mano (easing, timing, cleanup); gira sul main thread. |
### `element.animate()` (WAAPI) — esempio con Promise
```js
const el = document.querySelector('.animate-me');
const anim = el.animate(
[
{ transform: 'translateY(-300px)', opacity: 0 },
{ transform: 'translateY(0)', opacity: 1 },
],
{ duration: 700, easing: 'ease-out', fill: 'forwards' }
);
anim.finished.then(() => console.log('Fatto!')); // niente listener manuali
// anim.reverse(); anim.pause(); anim.playbackRate = 2;
```
`fill: 'forwards'` mantiene lo stato finale invece di tornare allo stato iniziale.
### `requestAnimationFrame` — scheletro per canvas/particelle
```js
let rafId;
function loop(now) {
// update(now); draw();
rafId = requestAnimationFrame(loop);
}
rafId = requestAnimationFrame(loop);
// stop: cancelAnimationFrame(rafId);
```
Per la nostra app: **transitions** per la reel semplice, **WAAPI** per la ruota (angolo target calcolato), **rAF** solo se disegniamo particelle/coriandoli custom su canvas (o deleghiamo a `canvas-confetti`, che lo fa per noi).
---
## 3. Ruota della fortuna: `conic-gradient` + `rotate` verso un angolo target
Due parti indipendenti:
1. **Disegno degli spicchi** con `conic-gradient` (una sola dichiarazione CSS, zero immagini).
2. **Spin** con `transform: rotate(...)` che atterra su un angolo target calcolato in JS (WAAPI o transition).
### CSS — sfondo a spicchi con `conic-gradient`
Per N spicchi uguali, ogni spicchio occupa `360 / N` gradi. Con `conic-gradient` si alternano i colori con degli hard-stop:
```css
.wheel {
--n: 8; /* numero di spicchi */
width: 320px;
aspect-ratio: 1;
border-radius: 50%;
/* 8 spicchi da 45deg alternando due colori */
background: conic-gradient(
#e63946 0deg 45deg,
#f1a208 45deg 90deg,
#e63946 90deg 135deg,
#f1a208 135deg 180deg,
#e63946 180deg 225deg,
#f1a208 225deg 270deg,
#e63946 270deg 315deg,
#f1a208 315deg 360deg
);
transform: rotate(0deg);
will-change: transform;
}
/* indicatore/freccia fisso in alto (ore 12) */
.wheel-wrap { position: relative; display: inline-block; }
.wheel-wrap::after {
content: '';
position: absolute;
top: -6px; left: 50%;
transform: translateX(-50%);
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top: 22px solid #222; /* triangolo che punta verso il centro */
}
```
Si può anche generare il gradient da JS per N arbitrario:
```js
function conicFor(colors) {
const step = 360 / colors.length;
const stops = colors
.map((c, i) => `${c} ${i * step}deg ${(i + 1) * step}deg`)
.join(', ');
return `conic-gradient(${stops})`;
}
wheel.style.background = conicFor(['#e63946','#f1a208','#2a9d8f','#264653',
'#e63946','#f1a208','#2a9d8f','#264653']);
```
### JavaScript — spin che atterra su uno spicchio target
Il trucco per l'atterraggio deterministico: parti dall'angolo corrente, aggiungi **diversi giri interi** (es. `5 * 360`) più l'offset necessario per portare lo spicchio target sotto l'indicatore.
```js
const SEGMENTS = 8;
const SEG = 360 / SEGMENTS; // ampiezza di uno spicchio
let currentRotation = 0; // stato: rotazione accumulata
/**
* Ferma la ruota sullo spicchio `targetIndex` (0 = quello a ore 12 all'inizio).
* L'indicatore è fisso in alto: dobbiamo ruotare la ruota così che il centro
* dello spicchio target finisca a ore 12.
*/
function spinWheel(wheel, targetIndex) {
const fullSpins = 5; // giri "scenografici"
// centro dello spicchio target, misurato in senso orario dal top:
const targetCenter = targetIndex * SEG + SEG / 2;
// per portarlo a ore 12 devo ruotare all'indietro di targetCenter (mod 360)
const landing = (360 - targetCenter) % 360;
// parto dall'attuale angolo normalizzato e aggiungo i giri interi
const base = currentRotation - (currentRotation % 360);
const newRotation = base + fullSpins * 360 + landing;
currentRotation = newRotation;
const anim = wheel.animate(
[{ transform: `rotate(${wheel.dataset.rot || 0}deg)` },
{ transform: `rotate(${newRotation}deg)` }],
{ duration: 4200,
easing: 'cubic-bezier(0.23, 1, 0.32, 1)', // decelerazione morbida
fill: 'forwards' }
);
wheel.dataset.rot = newRotation;
return anim.finished.then(() => targetIndex);
}
// Uso:
const wheel = document.querySelector('.wheel');
spinWheel(wheel, 3).then((i) => console.log('Vinto lo spicchio', i));
```
Versione alternativa con **CSS transition** invece di WAAPI (stesso principio, si legge l'angolo finale con `transitionend`):
```js
wheel.style.transition = 'transform 4.2s cubic-bezier(0.23,1,0.32,1)';
wheel.style.transform = `rotate(${newRotation}deg)`;
wheel.addEventListener('transitionend', () => { /* ... */ }, { once: true });
```
> Attenzione all'accumulo: non resettare mai a `rotate(0)` senza `transition: none`, o la ruota "srotolerà" all'indietro. Meglio tenere la rotazione **sempre crescente** come nello snippet (`currentRotation` accumula).
---
## 4. Coriandoli con `canvas-confetti`
[`canvas-confetti`](https://github.com/catdad/canvas-confetti) è la libreria de-facto per l'effetto coriandoli: performante, zero-config, usa canvas + `requestAnimationFrame` internamente (opzionalmente un Web Worker).
### Inclusione via CDN
```html
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js"></script>
```
Esposta come funzione globale `confetti()` su `window`. Ritorna una Promise che si risolve al termine.
### Uso base e opzioni
```js
// burst semplice al centro
confetti();
// burst personalizzato dal punto del click
button.addEventListener('click', (e) => {
confetti({
particleCount: 120,
spread: 70,
startVelocity: 45,
origin: {
x: e.clientX / window.innerWidth,
y: e.clientY / window.innerHeight,
},
colors: ['#e63946', '#f1a208', '#2a9d8f', '#ffd166'],
});
});
```
Opzioni principali: `particleCount` (default 50), `angle` (90 = verso l'alto), `spread` (apertura in gradi), `startVelocity`, `origin.x/.y` (0..1), `colors` (array HEX).
### Celebrazione "a raffica" (per il momento del risultato)
```js
function celebrate(durationMs = 2500) {
const end = Date.now() + durationMs;
(function frame() {
confetti({ particleCount: 7, angle: 60, spread: 55, origin: { x: 0 } });
confetti({ particleCount: 7, angle: 120, spread: 55, origin: { x: 1 } });
if (Date.now() < end) requestAnimationFrame(frame);
})();
}
```
### Canvas dedicato (per non toccare il DOM globale)
```js
const myCanvas = document.querySelector('#fx');
const myConfetti = confetti.create(myCanvas, { resize: true, useWorker: true });
myConfetti({ particleCount: 100, spread: 160 });
```
---
## 5. Suoni: pool di `<audio>` preload vs Web Audio API / Howler.js
### Opzione A — pool di `<audio>` preload (zero dipendenze)
Un singolo `<audio>` non si può sovrapporre a sé stesso (il secondo play interrompe il primo). Per effetti rapidi ripetuti (tick della reel, click) conviene un **pool** di elementi clonati.
```html
<audio id="snd-spin" src="sfx/spin.mp3" preload="auto"></audio>
<audio id="snd-win" src="sfx/win.mp3" preload="auto"></audio>
<audio id="snd-tick" src="sfx/tick.mp3" preload="auto"></audio>
```
```js
function makePool(src, size = 6) {
const pool = Array.from({ length: size }, () => {
const a = new Audio(src);
a.preload = 'auto';
return a;
});
let i = 0;
return () => {
const a = pool[i = (i + 1) % pool.length];
a.currentTime = 0;
a.play().catch(() => {}); // ignora blocco autoplay finché non c'è interazione
};
}
const playTick = makePool('sfx/tick.mp3', 8);
// durante lo spin: playTick() a ogni simbolo che passa
```
> **Autoplay policy**: i browser bloccano l'audio finché non c'è un gesto utente (click/tap). "Sblocca" l'audio al primo click (es. sul pulsante Spin) facendo partire e mettendo subito in pausa un suono muto.
### Opzione B — Web Audio API (latenza minima, controllo fine)
Per timing preciso e nessuna latenza (importante per i tick sincronizzati con l'animazione) conviene decodificare una volta il buffer e riprodurlo con `AudioBufferSourceNode` (usa-e-getta).
```js
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const buffers = {};
async function load(name, url) {
const res = await fetch(url);
const arr = await res.arrayBuffer();
buffers[name] = await ctx.decodeAudioData(arr);
}
function play(name, { volume = 1 } = {}) {
const src = ctx.createBufferSource();
src.buffer = buffers[name];
const gain = ctx.createGain();
gain.gain.value = volume;
src.connect(gain).connect(ctx.destination);
src.start();
}
// sblocco su primo gesto utente:
document.addEventListener('pointerdown', () => ctx.resume(), { once: true });
await Promise.all([load('tick', 'sfx/tick.mp3'), load('win', 'sfx/win.mp3')]);
```
### Opzione C — Howler.js (comodo, audio sprite, cross-browser)
[Howler.js](https://howlerjs.com/) astrae Web Audio con fallback HTML5, gestisce autoplay, volume, loop e **audio sprite** (un solo file con più suoni). Ottimo se vuoi meno codice a mano.
```html
<script src="https://cdn.jsdelivr.net/npm/howler@2.2.4/dist/howler.min.js"></script>
```
```js
// suono singolo
const win = new Howl({ src: ['sfx/win.mp3'], volume: 0.8 });
win.play();
// audio sprite: un file, tanti effetti (offset e durata in ms)
const sfx = new Howl({
src: ['sfx/sprite.webm', 'sfx/sprite.mp3'],
sprite: {
tick: [0, 120],
spin: [200, 1800],
win: [2100, 2500],
},
});
sfx.play('tick');
sfx.play('win');
```
Per la nostra app: **pool `<audio>`** o **Howler sprite** per gli effetti brevi, va benissimo. Web Audio se vuoi sincronizzazione al millisecondo tra tick e animazione.
---
## 6. Struttura di stato: partecipanti, stanze, assegnazioni + reveal sequenziale
Per un'app "assegna partecipanti a stanze" (o premi), tieni uno stato semplice, serializzabile e centralizzato. Nessuna libreria: un oggetto + funzioni pure.
```js
const state = {
participants: [
{ id: 'p1', name: 'Alice' },
{ id: 'p2', name: 'Bob' },
{ id: 'p3', name: 'Carla' },
{ id: 'p4', name: 'Dario' },
],
rooms: [
{ id: 'r1', name: 'Stanza Rossa', capacity: 2 },
{ id: 'r2', name: 'Stanza Blu', capacity: 2 },
],
// risultato: mappa participantId -> roomId (riempita dall'estrazione)
assignments: {}, // { p1: 'r1', p2: 'r2', ... }
revealedCount: 0, // quanti risultati sono già stati mostrati
};
```
### Calcolo delle assegnazioni (deciso a priori, poi rivelato)
```js
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function assign(state) {
const pool = shuffle(state.participants);
const slots = state.rooms.flatMap((r) =>
Array.from({ length: r.capacity }, () => r.id)
);
const result = {};
pool.forEach((p, i) => { result[p.id] = slots[i]; });
state.assignments = result;
state.revealedCount = 0;
return result;
}
```
### Reveal sequenziale (un risultato alla volta, con animazione)
L'idea: l'estrazione è già calcolata; il **reveal** la mostra uno per uno, ognuno accompagnato da spin della reel/ruota + suono + eventuale confetti. `async/await` rende la sequenza leggibile.
```js
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
async function revealAll(state, { onReveal }) {
const entries = Object.entries(state.assignments); // [ [pId, rId], ... ]
for (const [participantId, roomId] of entries) {
const participant = state.participants.find((p) => p.id === participantId);
const room = state.rooms.find((r) => r.id === roomId);
// 1) anima (reel/ruota) fino all'indice della stanza target
const roomIndex = state.rooms.findIndex((r) => r.id === roomId);
await spinWheel(document.querySelector('.wheel'), roomIndex);
// 2) effetti + aggiorna UI
playTick(); // o sfx.play('win')
onReveal(participant, room);
state.revealedCount++;
// 3) pausa prima del prossimo
await wait(700);
}
celebrate(); // confetti finale
}
// Uso:
assign(state);
revealAll(state, {
onReveal: (p, r) => {
const li = document.createElement('li');
li.textContent = `${p.name}${r.name}`;
document.querySelector('#results').appendChild(li);
},
});
```
Vantaggi di questa struttura: lo stato è un **singolo oggetto** facile da loggare/persistere (`localStorage.setItem('state', JSON.stringify(state))`), la logica di estrazione è separata dal reveal (testabile), e il reveal sequenziale è una semplice iterazione `for...of` con `await`.
---
## 7. Font gratuiti consigliati (Google Fonts) + `<link>` reali
Mix consigliato: un **display bold** per titoli/brand, un **mono/LED** per numeri e counter stile slot, e un **pixel/arcade** opzionale per il tocco retro-casino.
```html
<!-- preconnect: velocizza il fetch dei font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Orbitron: display geometrico "sci-fi / HUD", ottimo per titoli e numeri -->
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap" rel="stylesheet">
<!-- Share Tech Mono: monospace stile terminale/LED, perfetto per i contatori -->
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap" rel="stylesheet">
<!-- Press Start 2P: bitmap arcade anni '80 (usare a 8/16/24px) -->
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<!-- Bungee: display bold "insegna/luna park", ottimo per bottoni e brand -->
<link href="https://fonts.googleapis.com/css2?family=Bungee&display=swap" rel="stylesheet">
```
```css
:root {
--font-display: 'Orbitron', system-ui, sans-serif;
--font-led: 'Share Tech Mono', ui-monospace, monospace;
--font-arcade: 'Press Start 2P', monospace;
}
h1, .brand { font-family: var(--font-display); font-weight: 800; }
.counter { font-family: var(--font-led); letter-spacing: 2px; }
.badge { font-family: var(--font-arcade); font-size: 12px; }
```
### Font 7-segment "vero" (LED): DSEG (self-hosted)
Google Fonts non ha un font 7-segmenti. Per un display LED autentico usa **DSEG** (SIL Open Font License), da self-hostare con `@font-face`:
```css
/* scarica i .woff2 da https://www.keshikan.net/fonts-e.html e mettili in /fonts */
@font-face {
font-family: 'DSEG7 Classic';
src: url('fonts/DSEG7Classic-Bold.woff2') format('woff2');
font-weight: 700;
font-display: swap;
}
.led-display { font-family: 'DSEG7 Classic', monospace; }
```
> In alternativa a DSEG, se vuoi restare solo su Google Fonts, **Share Tech Mono** o **DotGothic16** danno un look "digitale" più che sufficiente senza self-hosting.
---
## 8. Setup vanilla "apri e vai" (uno o pochi file)
Obiettivo: doppio click su `index.html` e funziona; al massimo un mini live server.
Struttura minima consigliata:
```
elsa-gambling/
├─ index.html # markup + <link> font + <script> CDN + <script src="app.js">
├─ style.css # tutti gli stili
├─ app.js # stato + logica (usa import ES module se ti serve)
├─ sfx/ # suoni (mp3/webm)
└─ fonts/ # eventuale DSEG self-hosted
```
Scheletro `index.html`:
```html
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Elsa Gambling</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main id="app"><!-- UI --></main>
<!-- helper via CDN -->
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/howler@2.2.4/dist/howler.min.js"></script>
<!-- il tuo codice -->
<script src="app.js"></script>
</body>
</html>
```
Consigli pratici:
- **Un solo file va benissimo per prototipare** (CSS in `<style>`, JS in `<script>`): parte davvero solo aprendo il file. Separa in più file quando cresce.
- **ES Modules** (`<script type="module" src="app.js">` + `import`) funzionano solo via `http://`, **non** con `file://` (blocco CORS). Se usi `type="module"`, serve un live server; se vuoi il puro doppio-click su `file://`, resta su script classici (niente `import`).
- **Fetch di file locali** (es. `decodeAudioData` via `fetch('sfx/..')`, o caricare un JSON) **non** funziona su `file://`: serve un server. Aggira precaricando i suoni con `new Audio()`/Howler invece di `fetch`, oppure usa un live server.
- **Live server istantaneo**, zero install: `npx serve` oppure `python3 -m http.server 8000` nella cartella del progetto. Anche l'estensione "Live Server" di VS Code.
- **`will-change: transform`** su reel/ruota e usa solo `transform`/`opacity` per le animazioni: restano sul compositor e non causano reflow.
- **Rispetta `prefers-reduced-motion`**: riduci/annulla spin e coriandoli per chi lo richiede.
```css
@media (prefers-reduced-motion: reduce) {
.reel__strip--spinning { transition-duration: 0.2s; }
.wheel { transition: none; }
}
```
---
## 9. Librerie helper via CDN (riepilogo `<script>`)
```html
<!-- Coriandoli: performante, canvas + rAF, opzionale Web Worker -->
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/dist/confetti.browser.min.js"></script>
<!-- Audio: Web Audio con fallback HTML5, audio sprite, volume/loop -->
<script src="https://cdn.jsdelivr.net/npm/howler@2.2.4/dist/howler.min.js"></script>
```
Opzionali, se servono:
```html
<!-- bezier-easing: calcola easing cubic-bezier in JS (per animazioni rAF custom) -->
<script src="https://cdn.jsdelivr.net/npm/bezier-easing@2.1.0/dist/bezier-easing.min.js"></script>
<!-- js-confetti: alternativa a canvas-confetti, supporta emoji come coriandoli -->
<script src="https://cdn.jsdelivr.net/npm/js-confetti@latest/dist/js-confetti.browser.js"></script>
```
> Buona pratica: **fissa la versione** (es. `@1.9.4`) invece di `@latest`, così l'app non si rompe quando esce un major.
---
## Fonti
- Slot machine con HTML/CSS/JS (translateY + cubic-bezier): <https://javascript.plainenglish.io/step-by-step-guide-to-building-a-retro-slot-machine-with-html-css-and-javascript-for-beginners-3f866041c7c0>
- Simple CSS Slot Machine Animation (CodePen): <https://codepen.io/cnbkts/pen/QMXPZM>
- `cubic-bezier()` — MDN: <https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/cubic-bezier>
- `cubic-bezier()` — CSS-Tricks: <https://css-tricks.com/almanac/functions/c/cubic-bezier/>
- Interactive Guide to CSS Transitions — Josh W. Comeau: <https://www.joshwcomeau.com/animation/css-transitions/>
- CSS vs JavaScript animations — web.dev: <https://web.dev/articles/css-vs-javascript>
- CSS & JavaScript animation performance — MDN: <https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/CSS_JavaScript_animation_performance>
- Using the Web Animations API — MDN: <https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API>
- CSS Animations vs Web Animations API — CSS-Tricks: <https://css-tricks.com/css-animations-vs-web-animations-api/>
- `requestAnimationFrame()` — MDN: <https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame>
- Wheel of Fortune with CSS — Mads Stoumann (DEV): <https://dev.to/madsstoumann/wheel-of-fortune-with-css-p-pi-1ne9>
- JavaScript + CSS 3D Wheel of Fortune — Emanuele Feronato: <https://emanueleferonato.com/2025/10/31/javascript-css-3d-wheel-of-fortune/>
- A CSS wheel of fortune — James R. Williams: <https://jamesrwilliams.ca/posts/css-wheel-of-fortune>
- `conic-gradient()` — CSS-Tricks: <https://css-tricks.com/almanac/functions/c/conic-gradient/>
- canvas-confetti (GitHub): <https://github.com/catdad/canvas-confetti>
- canvas-confetti su jsDelivr: <https://www.jsdelivr.com/package/npm/canvas-confetti>
- Howler.js (sito): <https://howlerjs.com/>
- Howler.js (GitHub + README): <https://github.com/goldfire/howler.js/>
- Web Audio API — MDN: <https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API>
- DSEG — font 7/14 segmenti: <https://www.keshikan.net/fonts-e.html>
- DSEG (GitHub): <https://github.com/keshikan/DSEG>
- Orbitron — Google Fonts: <https://fonts.google.com/specimen/Orbitron>
- Share Tech Mono — Google Fonts: <https://fonts.google.com/specimen/Share+Tech+Mono>
- Press Start 2P — Google Fonts: <https://fonts.google.com/specimen/Press+Start+2P>
- Bungee — Google Fonts: <https://fonts.google.com/specimen/Bungee>
- DotGothic16 — Google Fonts: <https://fonts.google.com/specimen/DotGothic16>
</content>
</invoke>

44
docs/README.md Normal file
View File

@@ -0,0 +1,44 @@
# 🎰 Elsa Gambling — Docs
Materiale di ricerca per costruire una web app (HTML + JS vanilla) che assegna ~20 persone a un
insieme di stanze, e **rivela l'assegnazione come uno show di scommesse/slot machine**: il risultato
lo decide un algoritmo, ma l'esperienza è pura suspense da casinò.
Tutti i documenti sono ricerche fatte sul web (fonti reali con URL in fondo a ogni file).
## Indice
| # | Documento | A cosa serve |
|---|-----------|--------------|
| 01 | [Stile visivo gambling](./01-stile-visivo-gambling.md) | Palette neon/oro, font LED/7-segment, motivi slot/chip/jackpot, tecniche CSS glow copiabili |
| 02 | [Meccaniche suspense & reveal](./02-meccaniche-suspense-reveal.md) | Psicologia dello spin&reveal, easing dei rulli, **come far sembrare "random" un risultato già deciso** |
| 03 | [Interattività & coinvolgimento](./03-interattivita-coinvolgimento.md) | Gamification, scommesse/quote, sound design (SFX free), coriandoli, modalità schermo condiviso |
| 04 | [Implementazione vanilla JS](./04-implementazione-vanilla-js.md) | Codice concreto: rulli, ruota, canvas-confetti, audio, font Google, setup single-file |
## Idea in una riga
> L'algoritmo calcola l'assegnazione **persona → stanza**. La UI non la mostra: la **fa girare**.
> Rulli che rallentano, near-miss, drumroll, poi il nome della stanza si ferma con un *ding* e
> partono i coriandoli. Persona dopo persona, in crescendo, con la VIP room come jackpot finale.
## Punti chiave emersi dalla ricerca
- **L'attesa vale più del premio**: la dopamina è nell'anticipazione, non nel risultato → investire
sull'animazione di build-up più che sul reveal secco (doc 02, 03).
- **Predeterminato ≠ noioso**: si accumulano giri interi casuali e si atterra sull'indice/angolo target
con un `cubic-bezier` easeOut/easeOutBack. Sembra random, ma finisce dove vuoi tu (doc 02, 04).
- **Solo `transform` e `opacity`** per animare fluido a 60fps; rispettare `prefers-reduced-motion` (doc 01, 04).
- **Zero backend per partire**: modalità single-screen/proiettore. Upgrade opzionale con QR code stile
Jackbox/Kahoot o realtime (doc 03).
- **Stack leggero via CDN**: `canvas-confetti` per le celebrazioni, `Howler.js` per l'audio; tutto il
resto CSS/JS vanilla (doc 03, 04).
## Prossimo passo
Definire l'**algoritmo di matching** (vincoli: capienza stanze, preferenze, coppie/gruppi, eventuali
"quote" per il gioco delle scommesse). Poi lo attacchiamo al livello di reveal descritto nel doc 02/04.
## Note
- Le palette hex nel doc 01 sono coerenti e pronte all'uso, ma non sono brand ufficiali (dichiarato nel file).
- `DSEG` (font 7-segment) va self-hostato: non è su Google Fonts. Fallback solo-Google: Share Tech Mono / DotGothic16 (doc 04).