Pick Your Side : j'ai construit une machine qui vous fabrique une équipe de Coupe du monde, ancrée dans la vraie histoire
Toutes les apps Coupe du monde sont pour les gens qui suivent déjà. Celle-ci est pour tous les autres : on donne deux nations, elle lit leur vraie histoire, choisit le camp qu'on était censé aimer, et un annonceur de stade nous fait prêter serment. Toute l'architecture plie sur une règle : rien de l'histoire n'est inventé. Gemini fait la recherche avec le grounding Google Search actif, puis réécrit dans un responseSchema strict, puis ElevenLabs joue le tout sur une foule de stade qu'ElevenLabs a aussi générée.
Le thème du challenge était la passion, alors j'ai pris le chemin le plus littéral. Toutes les apps Coupe du monde jamais construites sont pour ceux qui suivent déjà : calendrier, tableau, fantasy. Toutes supposent qu'on est arrivé avec un camp. Quelques milliards de personnes vont regarder ce tournoi, et l'écrasante majorité n'a pas d'équipe. Pick Your Side est pour elles.
On donne deux nations. Elle lit leur histoire réelle, choisit celle qu'on était censé aimer, explique pourquoi, et un annonceur de stade nous fait prêter serment. Deux nations en entrée. Un camp en sortie, et une raison de le porter.

La contrainte de design : la passion fabriquée ne fonctionne que si la source est vraie
Le vrai problème, c'est qu'on ne peut pas simuler ça avec un joli adjectif. Si l'app raconte que Ghana et Uruguay ont une histoire amère et que l'histoire est inventée, la passion qu'elle vous tend est contrefaite, et dès qu'on vérifie on se sent bête et on ne rouvre jamais. Alors toute l'architecture plie sur une règle : rien de l'histoire n'est inventé.
Deux appels Gemini (parce qu'un seul ne marche pas)
- L'appel 1 a le grounding Google Search actif. Gemini va chercher le vrai bilan face-à-face, la vraie rencontre légendaire, et où en sont les deux sélections dans le tournoi en cours. Il revient avec les pages lues et les recherches effectuées, et l'app affiche les deux. Sur Maroc-France, il vous parle du quart de finale 2026, un résultat qui date d'il y a quelques jours et qu'aucun LLM ne pouvait savoir depuis son entraînement.
- L'appel 2 reçoit ces faits recherchés et rien d'autre, tenu à un responseSchema pour que la sortie ait toujours la même forme : camp, verdict, histoire, enjeux, un moment légendaire, votre raison, une phrase à partager, un script d'annonceur. Consigne : choisir le camp avec la blessure la plus profonde, pas le nom le plus célèbre.
Les deux ne peuvent pas être combinés en un seul appel. Demander à gemini-2.5-flash le grounding Search et un responseSchema en même temps ne lève pas d'erreur : ça ignore silencieusement le schéma et renvoie de la prose, ce qui est amusant à découvrir dans une UI qui attend du JSON. Donc : recherche d'abord, forme ensuite.


ElevenLabs joue le tout (et génère aussi le stade)
Gemini écrit le serment et ElevenLabs est la voix qui le prononce, par-dessus un stade qu'ElevenLabs a aussi généré : une foule agitée sous l'écran de scouting pendant qu'il réfléchit, et une éruption à l'instant où le nom du pays tombe. La foule est mixée sous l'annonceur et ne rivalise jamais avec lui. Le son est actif par défaut, parce qu'une révélation muette n'est qu'une affiche, et la commande pour le couper est visible en permanence.
- Annonceur en direct : eleven_flash_v2_5 (retour sous la seconde pour que la voix tombe sur le temps).
- Révélations vedettes pré-rendues : eleven_v3 (plus lent mais expressif, comprend les balises audio, et la latence n'existe pas quand on génère hors ligne).
- Ambiance de stade : générée aussi par ElevenLabs. Agitée pendant le scouting, en éruption à la révélation.

À propos du cache
Le free-tier de Gemini autorise environ dix appels par minute, et chaque révélation en coûte deux. Alors les matchs vedettes sont pré-générés par scripts/seed-reveals.mjs et commités dans data/reveals.json. Ils répondent instantanément et ne coûtent rien, ce qui empêche un lien qui voyage de servir une erreur de rate-limit à la moitié des visiteurs au lieu d'une équipe. Les révélations pré-générées sortent du même pipeline ancré que les live (vraies recherches, vraies sources) : elles sont mises en cache, pas inventées. Tout match non pré-généré est traité en live puis gardé six heures en mémoire. Le tournoi est encore en cours, donc un « ce qui est en jeu » périmé est pire qu'un lent.
Les détails qui comptent plus qu'ils ne le devraient
- Les nations, leurs couleurs et leurs surnoms sont curés dans lib/teams.ts. Le modèle ne voit jamais qu'un identifiant validé, jamais de texte utilisateur brut.
- Les deux routes sont limitées par IP avec un plafond quotidien global au-dessus, pour qu'une clé free-tier ne puisse pas être vidée par un lien qui voyage.
- Les drapeaux sont des images, pas des emoji. Windows ne rend pas les emoji drapeau (il retombe sur des paires de lettres), et un drapeau cassé est pire que pas de drapeau.
- Les animations respectent prefers-reduced-motion. L'éruption du stade devient un fondu pour les visiteurs qui le demandent.

Pick Your Side: I Built a Machine That Manufactures a World Cup Team for You, Grounded in Real Football History
Voir le projet