Phone-first scorekeeping for the Geebag crew: a landing page plus one app per game.
- Pétanque — pick who's here today once, then build 1v1 or 2v2 teams from drop-downs (or 🎲 randomise). Tap the points after each end; first to the target (default 13) wins. Rematch in one tap, log past games, all-time 👑 champion, 💩 geebag and 🍑 Fanny tracking.
- Gin Rummy — the regulars are pre-picked, target score is selectable (default 100). Score hand by hand, log trips with the card (3×Q, 3×8…), fix any hand afterwards. Stats for 👑 champion, 💩 geebag (most last places) and 🃏 Trips King — A Total Trips Geebag.
server.py stdlib-only Python server: serves public/ and the JSON API
public/
index.html Total Geebag Sports landing page
shared/core.js data layer: local cache, offline outbox, sync, players
shared/stats.js stat crunching shared by the hub and both apps
shared/base.css shared look
petanque/ index.html, app.js, style.css
gin/ index.html, app.js, style.css
data/ db.json + backups/ (created on first run, not in git)
deploy/ nginx snippet and systemd unit
No build step, no dependencies: vanilla app.js per game, Python 3.9+ on the server.
python3 server.py # http://127.0.0.1:8787Everything is one JSON document at data/db.json: players (shared between games), each
game's settings, pétanque sessions ("who's here today") and every game. Each phone keeps a
copy and queues its changes, so scoring keeps working if the park signal drops and syncs
when it comes back (the dot in the top-right corner: green = saved, yellow = saving,
orange = offline, red = passcode needed). Other phones poll every few seconds, so a second
phone can follow a live game.
Items carry a version number and the server never lets an older version overwrite a newer
one. Deletes are soft ("deleted": true), and a copy of the file is kept per day in
data/backups/.
See DEPLOY.md for putting it on the webserver.