Skip to content
vicxdevPublic

About

A World Cup 2026 prediction game for a group of friends: live board, per-match locking, awards ceremony. Cloudflare Pages + Functions + D1, no dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Prode — World Cup 2026

A prediction game for a group of friends, built for the 2026 World Cup. Everyone calls the score of every match, the board scores itself from live results, and each matchday closes with a Wrapped-style awards ceremony.

Live demo: prode-4ib.pages.dev — log in with demo / demo. The players, their picks and their predictions are fictional. Sign-up is closed on the demo.

Prode is what this game is called in Argentina. The name stayed; everything else is in English.

The board


What it does

  • Pick every match. Both scores, entered up to 30 minutes before kickoff.
  • 3 / 1 / 0. Three points for the exact score, one for the right outcome, none for a miss.
  • Nobody sees your pick until the match locks. The server withholds other people's picks; it does not just hide them in the page.
  • Live results. Scores come from ESPN's public scoreboard and refresh on their own.
  • Two competitions. Each matchday has a winner, and the overall table adds them all up.
  • TOPs. A separate, private prediction of how the tournament ends: champion, podium, top scorer, golden glove, golden ball. It locks when the round of 32 starts.
  • Awards. Over thirty titles computed from the picks, from Sharpshooter to Park the Bus, shown as a full-screen story when a matchday closes. The rules are in AWARDS.md.
  • Installs as an app. A manifest, a service worker and per-browser install instructions.
  • Light and dark, and it works on a phone.
Overall table Points per matchday
Light mode How it works
On a phone Matches on a phone

The board, live

Opening the board

The awards ceremony

The awards ceremony

On a phone

On a phone


How it is built

No framework, no build step, no dependencies. One HTML file, a handful of Cloudflare Pages Functions and a SQLite database.

public/index.html      the whole front end: styles, board, awards engine, ceremony
public/fixtures.js     GENERATED: the 104 matches, grouped by matchday and day
functions/api/*.js     login, sign-up, approval, picks, TOPs, the reminder cron
functions/_shared.js   passwords, session tokens, per-match locking
functions/_kickoffs.js GENERATED: every kickoff, which is what the locking reads
schema.sql             players, picks, outrights, reminders
scripts/               fixture and kickoff generators, the demo seed, a database backup
promo/                 a 1080x1920 animated promo page, rendered to video frame by frame

Stack: Cloudflare Pages (static) + Pages Functions (the API) + D1 (SQLite). The free tier carries it comfortably.

Auth is hand-rolled and deliberately small: PBKDF2-SHA256 at 100,000 iterations for passwords, a stateless HMAC-SHA256 session token in an HttpOnly cookie. New accounts land as pending and an admin approves them, which is what keeps the game among people who know each other.

Anti-cheat is the part worth reading. A prediction game is only fun if nobody can see the others before kickoff, and only fun to run if nobody can enter a pick after it.

  • Each match locks 30 minutes before its own kickoff, not at the start of the matchday. A round of matches on different days opens and closes match by match.
  • The lock is enforced on the server, in functions/api/picks.js, against the generated kickoff table. The front end shows the same state, but rejecting a late pick never depends on it.
  • GET /api/picks omits other people's picks for matches that have not locked yet. There is nothing in the page to inspect.
  • The TOPs are private for the whole tournament: the endpoint returns only your own.
  • Match numbering comes from the generated fixtures, so a pick cannot be moved to another match.

Results come from site.api.espn.com, matched by ESPN's event id rather than by team name, because the knockout brackets say "Group L Winner" until they do not. When ESPN answers an empty or failing date range (it happens around some dates), the request splits in half and retries down to single days.


Running it

npm install -g wrangler          # or use npx, as the scripts below do
npx wrangler d1 create prode     # paste the id it prints into wrangler.toml
npx wrangler d1 execute prode --local --file schema.sql
node scripts/seed-demo.mjs > seed-demo.sql
npx wrangler d1 execute prode --local --file seed-demo.sql
npm run dev                      # http://localhost:8788

.dev.vars holds the local secrets:

AUTH_SECRET="anything-long-and-random"
DEMO_MODE="1"

To deploy, apply the same schema and seed with --remote, set the secrets on the project (npx wrangler pages secret put AUTH_SECRET --project-name prode) and run npm run deploy.

Variable What it does
AUTH_SECRET signs the session cookie. Required.
DEMO_MODE 1 closes sign-up, for a public demo.
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID optional: a message when somebody asks for access.
MAILJET_API_KEY, MAILJET_API_SECRET, MAILJET_FROM optional: the matchday reminder emails.
CRON_SECRET optional: guards /api/cron, which the GitHub Action calls.

Making it yours

It is built for the 2026 World Cup, with that tournament's emblem, flags and calendar. To point it at another tournament, fork it and change these by hand:

  1. scripts/gen-fixtures.mjs — the ESPN league path and the date range of each matchday. Run npm run fixtures, then npm run kickoffs.
  2. functions/_matchdays.js — the matchday labels, deadlines and match counts it prints.
  3. public/index.html — the FLAGS and TEAM_NAMES tables, and the tier sets that decide what counts as an upset.
  4. AWARDS.md and the awards engine — the titles are jokes from an Argentine group chat, kept in English. Write your own.
  5. The artwork in public/ — emblem, icons, the share card, the background pattern.

Regenerating fixtures renumbers the matches, so do it before anyone has entered a pick.


Notes

  • No award photographs ship with this repository. The ceremony looks for public/awards/<slug>.jpg behind each title and falls back to a gradient when the file is not there. The ones used privately were press photographs, which cannot be redistributed. See public/awards/README.md for the file names.
  • promo/promo-prode-2026.mp4 is the original Spanish cut. The page that generates it, promo/promo.html, is in English; re-render it with node promo/render.mjs plus ffmpeg if you want an English video.
  • The FIFA World Cup 2026 emblem, the tournament name and the national flags belong to their owners. They are here because this is a World Cup prediction game, not because the project claims any rights to them.
  • The code is MIT. See LICENSE.

Made by @vicxdev.

About

A World Cup 2026 prediction game for a group of friends: live board, per-match locking, awards ceremony. Cloudflare Pages + Functions + D1, no dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages