A new Prediction game built for the 2026 World Cup Browse a match, tap MORE or LESS on app-set lines, lock your best 5 picks a day (or go all-in with a Power Play parlay / a 2× Captain), climb the all-Imperial leaderboard or a private group.
Kickoff is 11 June 2026. The app is deployed live on Firebase
▶ Live app: https://golazo-wc.web.app · Mock demo: https://mustafa-os.github.io/golazo-wc26/
npm install
npm run devOpen the local URL. With no .env, the app runs in MOCK_MODE on sample
fixtures (England v Croatia, Argentina v Algeria) so you can see and feel the whole
game — sign-up, the match rail, props, the More/Less mechanic, the slip, leaderboard,
groups — before any backend exists. There's a one-tap "Try the demo" button too.
Lines (src/lib/lineEngine.js) — every player gets over/under lines on the
metrics that suit their position (strikers: goals/shots; keepers: saves; etc.).
Lines are half-numbers (0.5, 1.5, 2.5…) so every prop resolves cleanly. No odds feed
needed — lines come from position baselines, upgraded to a player's own form when the
stats feed provides it.
Scoring (src/lib/scoringEngine.js) — risk-weighted. A pick's value ≈ 1 / P(it lands),
where the probability is modelled with a Poisson tail on the metric's baseline. A safe
shot-on-target pays ~5–8 pts; a striker brace or a keeper scoring pays up to 100. Wrong
picks score 0 (never negative). A daily streak adds up to +50%.
Both engines are plain dependency-free JS, imported by both the frontend (to
preview points) and the Cloud Functions (to award them) — so they can never disagree.
npm test runs 21 assertions over them.
Users never call the football API. Scheduled Cloud Functions hit API-Football a few times a day and cache everything in Firestore; every student reads from Firestore (huge free tier). 10 players or 1,000 — your API usage is identical.
(scheduled, ~hourly)
API-Football ───────────────────────▶ Cloud Functions ───▶ Firestore
fixtures generate props (matches,
lineups resolve & score props,
player stats roll leaderboard users, slips,
leaderboards, groups)
│
all users read ◀─────┘
generateDailyProps(08:00): fixtures + lineups → write today's propsresolveFinished(hourly): finished matches → player stats → settle every slip, award points, roll streaksrecomputeLeaderboard(hourly): roll user totals into the boardsjoinGroup(callable): add caller to a group by code
| Mode | Trigger | Backend |
|---|---|---|
| MOCK | no .env |
runs entirely on mockData.js (default npm run dev) |
| EMULATOR | VITE_FB_EMULATOR=1 |
real Firebase SDK → local Emulator Suite |
| LIVE | VITE_FB_API_KEY set |
your real Firebase project |
Every page is backend-agnostic via AuthContext / DataContext and the
slipStore / groupStore modules — the same UI runs on mock, emulator, or live.
- Create a project at https://console.firebase.google.com.
- Enable Authentication (Email/Password) and Firestore.
- Copy your web config into
.env(see.env.example). The app leaves MOCK_MODE automatically onceVITE_FB_API_KEYis set. firebase use --add(writes.firebaserc), then deploy rules + indexes:firebase deploy --only firestore:rules,firestore:indexes
- Sign up at https://www.api-football.com. Free tier (100 req/day, no live data) is fine for wiring everything up now.
- Before 11 June, upgrade to Pro ($19/mo) for live data + player stats. That single upgrade is what makes resolution work during the tournament.
- Store the key as a Functions secret — never in client code:
firebase functions:secrets:set API_FOOTBALL_KEY
The engines are ES modules; the functions runtime uses require. An esbuild step
converts them to .cjs next to index.js (wired into predeploy):
cd functions && npm install && npm run deploy # builds engines + deploysnpm run build
firebase deploy --only hosting # Firebase Hosting (firebase.json -> dist, SPA rewrite)A MOCK_MODE demo also auto-deploys to GitHub Pages on every push to main
(.github/workflows/deploy.yml). To make that demo live, add your VITE_FB_*
values as repo secrets and pass them to the build step.
Test the full live code path — real Auth, Firestore reads/writes, security rules,
and the joinGroup callable — entirely offline (needs Java for the Firestore emulator):
# terminal 1 — emulators (auth, firestore, functions)
cd functions && npm install && npm run build:engines && cd ..
firebase emulators:start --only auth,firestore,functions --project demo-over-wc26
# terminal 2 — seed sample matches/props/leaderboard/groups, then run the app
npm run seed
npm run dev:emulator # http://localhost:5174 (VITE_FB_EMULATOR=1)npm test # engine unit tests (lineEngine + scoringEngine), 21 assertionsThe build was also verified headlessly during development: full mock UI flow, the live emulator UI flow, a security-rules suite, and the groups/callable suite.
src/
lib/lineEngine.js line generation (positions, baselines, half-lines)
lib/scoringEngine.js risk-weighted points + slip settlement (Poisson)
lib/mockData.js sample fixtures/squads for MOCK_MODE
lib/mockAuth.js localStorage auth stand-in for MOCK_MODE
lib/slipStore.js slip persistence (mock + Firestore)
lib/groupStore.js groups (mock + Firestore + joinGroup callable)
firebase.js guarded init (MOCK / EMULATOR / LIVE)
context/AuthContext.jsx auth state machine over both backends
context/DataContext.jsx matches + props + leaderboard subscriptions
App.jsx shell: auth gate, slip state, lock-at-kickoff, nav
components/ PropCard (More/Less), PickSlip (slip + results + share)
pages/ AuthScreen, Onboarding, Today, Leaderboard, Groups, Profile
functions/
index.js scheduled jobs + joinGroup callable
apiFootball.js API-Football client (server-side only)
firestore.rules server-only matches/props, protected points, owner-only
slips (locked = one-way latch), member-read groups
firebase.json rules + indexes + functions + hosting + emulator config
scripts/ seed.mjs (emulator seed), test-engines.mjs (npm test)