A finger chooser for the club table, in the spirit of Chwazi. Everyone puts a finger on the screen, and once nobody has added or lifted a finger for the chosen wait, the app decides. It works fully offline once it has been opened one time.
Live at: https://chwazi.justplaybo.it/
| Mode | Dock label | What happens |
|---|---|---|
| Pick 1–5 | Scegli | Picks k fingers. With one winner the screen floods in that finger's ink; with several it floods in ink with a paper-coloured hole around each winner. Needs k + 1 fingers. |
| Teams 2–5 | Squadre | Splits the fingers into k balanced teams (sizes differ by at most one). Each finger shows its team's colour and number, so colour-blind players can read it too. Needs k + 1 fingers. |
| Turn order | Ordine | Every finger gets its turn number, revealed 1, 2, 3… Needs 2 fingers. |
- The countdown ring around each finger fills during the wait. Adding or lifting a finger restarts it.
- Fingers that arrive after the decision are ignored.
- The result stays on screen after everyone lifts, so the table can still read the teams or turn order. The next touch clears it and starts a new round. Changing mode or count also clears it.
- Randomness comes from
crypto.getRandomValues, using rejection sampling and a Fisher–Yates shuffle, so every outcome is equally likely.
The settings sheet (top-right button) has: the wait (Breve 1.5 s / Media 2.5 s / Lunga 4 s), the theme (Automatico / Carta light / Panno dark), sound, vibration (hidden where the Vibration API is missing, e.g. iOS), install, and offline status. Settings are saved in localStorage under chwazi.settings.
The UI is in Italian. Browsers whose first language isn't Italian get English.
src/sw.js is an app-shell service worker. On install it precaches every file in dist/, then serves everything cache-first. Any navigation, including deep links, is answered with the cached page. build.mjs stamps the worker with a content hash of all files, so each deploy creates a new cache and deletes the old one. Phones pick up the new version the next time the app is opened. Netlify serves sw.js with Cache-Control: no-cache, so update checks always reach the network.
The fonts are self-hosted (src/fonts/, Latin subset). Caprasimo and Alegreya Sans are both under the SIL Open Font License; the licence texts sit next to them in src/fonts/OFL-*.txt. There are no third-party requests at all; the CSP in netlify.toml is 'self' only.
src/ the site, copied verbatim to dist/
index.html markup, dock, settings sheet
style.css brand tokens (hex values derived from the justplaybo.it OKLCH palette)
boot.js applies a forced theme before first paint
app.js input, state machine, canvas renderer, sound, settings
sw.js service worker (VERSION / PRECACHE stamped at build time)
manifest.webmanifest, icons/, fonts/, favicon.ico
build.mjs src/ → dist/, stamps the service worker (no dependencies)
tools/icon.mjs regenerates icons/icon.svg + favicon.svg
tools/raster.mjs renders the PNG icons from icon.svg
test/smoke.mjs Playwright end-to-end test (multi-touch, both themes, offline reload)
netlify.toml build, headers, CSP, SPA fallback
node build.mjs # → dist/
npx serve dist # any static server; the service worker needs http://localhost or httpsMulti-touch needs a real phone. To test on one, serve over your LAN with HTTPS (the service worker needs a secure context), or deploy a Netlify preview.
End-to-end test (it drives real multi-touch through the Chrome DevTools protocol and writes screenshots to test/shots/):
node build.mjs
docker run --rm -v "$PWD":/app -w /app mcr.microsoft.com/playwright:v1.60.0 \
sh -c "npm i --no-save playwright@1.60.0 >/dev/null && node test/smoke.mjs"After editing tools/icon.mjs, run it, then tools/raster.mjs (same Docker command as the test) to re-render the PNGs: 192, 512 (also used as the maskable icon; the art stays inside the safe zone) and 180 (apple-touch-icon.png).
-
Put this folder in a git repo and push it (GitHub/GitLab). In Netlify, Add new site → Import an existing project, and pick the repo.
netlify.tomlalready sets the build command (node build.mjs) and the publish directory (dist). Without git:npx netlify-cli deploy --build --prodfrom this folder, afternetlify loginandnetlify init/link. -
Site configuration → Domain management → Add a domain:
chwazi.justplaybo.it. -
DNS for
justplaybo.itis on Cloudflare. A wildcard*.justplaybo.itcurrently points at51.15.160.236, so add an explicit record, which takes precedence over the wildcard:Type Name Target Proxy CNAME chwazi<your-site>.netlify.appDNS only (grey cloud) Keep the proxy off so Netlify can issue the Let's Encrypt certificate and serve the site directly.
-
Netlify provisions the certificate and redirects HTTP to HTTPS on its own. Then open the site once on each phone: from then on it works without a connection. Use Installa sul telefono in the settings, or on iOS Share → Add to Home Screen, to launch it full-screen.
- Phones cap the number of simultaneous touches, and some Android skins reserve three-finger gestures (such as a screenshot swipe). A system gesture cancels those touches, and the app treats that as fingers lifting.
- iOS has no Vibration API, so the vibration toggle is hidden there. With the ring/silent switch on, iOS also mutes Web Audio.