The World Cup is on and you do not have a team.
Every World Cup app ever built is for the people who already care. This one is for everybody else. Name any two nations. It reads their real history, picks the side you were always meant to love, and a stadium announcer swears you in.
Built for the DEV Weekend Challenge: Passion Edition.
The interesting part is that none of the history is invented.
1. Gemini researches, grounded in Google Search. The first call has the Google Search tool switched on. Gemini goes and finds the actual head to head record, the actual famous match, and where both sides actually stand in the tournament being played right now. It comes back with the pages it read and the searches it ran, and the app shows you both. Ask it about Morocco and France and it will tell you about the 2026 quarter final, a result that happened days ago and that no language model could know from training.
2. Gemini writes, forced into a schema.
The second call gets those researched facts and nothing else, and is held to a
responseSchema, so the output is always the same shape: a side, a verdict, the
history, the stakes, one legendary moment, your reason, a line to share, and a
script for the announcer. It is told to pick the side with the deeper wound, not
the more famous name.
These two things cannot be combined into one call. Asking gemini-2.5-flash for
Google Search grounding and a responseSchema at the same time does not throw,
it quietly ignores the schema and hands back prose, which is a fun thing to
discover in a UI that expects JSON. So: research first, then shape.
3. ElevenLabs performs it. Gemini writes the swear-in and ElevenLabs is the voice that delivers it, over a stadium that ElevenLabs also generated: a restless crowd under the scouting screen while it thinks, and an eruption the instant the nation's name lands. The crowd sits below the announcer and never competes with him. Sound is on by default, because a muted reveal is just a poster, and the control to turn it off is on screen the whole time.
npm install
cp .env.example .env.local # add your GEMINI_API_KEY
npm run devGEMINI_API_KEY is free from Google AI Studio.
ELEVENLABS_API_KEY is optional: without it the reveal plays silent and nothing
else changes.
The Gemini free tier allows roughly ten calls a minute, and every reveal costs
two of them. So the marquee matchups are generated ahead of time by
scripts/seed-reveals.mjs and committed to
data/reveals.json. Those answer instantly and cost
nothing, which is what stops a link that travels from handing half its visitors a
rate limit error instead of a team.
The seeded reveals come out of the same grounded pipeline as the live ones, real searches, real sources. They are cached, not faked. Any matchup that is not in the seed is generated live, and gets held in memory for six hours after that (the tournament is still being played, so a stale "what is at stake" is worse than a slow one).
- The nations list, their colours and their nicknames are curated in
lib/teams.ts. The model only ever sees a validated id, never raw user text. - Both routes are rate limited per IP and carry a global daily ceiling, so a free tier key cannot be drained by a link that travels.
- The live announcer runs on
eleven_flash_v2_5because it returns in under a second and the voice has to land on the beat. The pre-rendered fallbacks areeleven_v3, which is slower but expressive and understands audio tags, and latency does not exist when you generate them offline. - Flags are images, not emoji. Windows does not render flag emoji at all, it falls back to letter pairs, and a broken flag is worse than no flag.
- Motion respects
prefers-reduced-motion.
MIT. See LICENSE.
Built by Jonathan Andrei.