This is a submission for the Weekend Challenge: Passion Edition
What I Built
Every Sunday my futevôlei crew meets on the sand in Niterói, Brazil. The rivalries are real: there are rematches people talk about all week, comebacks nobody is allowed to forget, and at least one duel per month that ends with someone buying açaí for everybody.
I am so deep into this passion that I have been building a whole platform for it: DuelUp, a competitive system for amateur beach sports, with ELO ratings, rank tiers from Sand to Legend, and virtual-coin stakes on real matches. It is not launched yet, but the crew already lives by its rules.
This weekend I built the piece every real rivalry deserves and none of us had: an announcer.
DuelUp Live is a fight bill for beach-sport duels. You pick the sport (footvolley, beach tennis, tennis or beach volleyball), write in the two sides with their nicknames and ranks, set the moment ("17-16, match point, blazing sun") and the bad blood ("rematch after last Sunday's comeback"). Then you ring the bell, and an electrifying AI sports announcer calls the match live, in English or in Portuguese with a Brazilian announcer voice, the kind we grew up hearing on the radio.
Gemini writes the play-by-play. ElevenLabs gives it the voice. The rank tiers come straight from DuelUp's real ELO system, and the announcer uses them as drama: when Silver faces Gold, you get an underdog story.
Demo
No signup, nothing to install. Click ⚡ Example to load a ready duel (each sport has three, they cycle), then 🔔 RING THE BELL.
Two things worth knowing while you try it:
- Every ring is a fresh call, written and voiced on the spot. Ring the bell twice on the same duel and you will never hear the same broadcast.
- It takes about 20 seconds, because it genuinely is an LLM writing a script and a TTS model performing it, live.
Code
github.com/vinimabreu/duelup-live
Next.js 14, App Router, zero extra runtime dependencies. Two API routes, one for the writing, one for the voice, both plain REST calls with no SDKs.
How I Built It
The writing (Gemini). The announcer is gemini-flash-latest with a prompt that treats narration as radio, not prose: present tense, short explosive sentences, 90 to 120 words, written for the ear because a TTS voice will perform it. Two decisions mattered more than the prompt itself:
-
Thinking disabled (
thinkingBudget: 0). An announcer should not overthink. Without this, the model's reasoning tokens were eating the output budget and truncating calls mid-sentence. With it, calls come back complete and fast. -
A model cascade. If
gemini-flash-latestreturns a 503 under load, the route falls through togemini-flash-lite-latest, thengemini-2.0-flash. The demo has already survived a real Google traffic spike this way. A demo that only works when the weather is nice is not a demo.
Temperature is 1.0, which is why no two calls are alike. The prompt is also sport-aware: tennis gets called on the court, everything else on the sand, and the rank tiers get woven in as favorite-versus-underdog tension.
The voice (ElevenLabs). eleven_multilingual_v2 with stability set low, because announcers are not stable people. Portuguese calls go to Eduardo, a Brazilian voice that sounds like he has narrated a thousand matches. English calls go to Charlie, who brings the energy. The waveform player on the bill is wired to the real audio element: real progress, real duration, and the bars dance while he shouts.
The bill. The design is a vintage fight poster: paper texture, ink borders, Anton for the headline type, a black promoter plate with the real DuelUp logo, "TALE OF THE TAPE" for the context fields, and the rank badges from the actual game next to each fighter. The whole UI switches between English and Portuguese, announcer included.
Prize Categories
Best use of Google AI and Best use of ElevenLabs. They are not decorations here, they are the two halves of the product: Gemini is the announcer's brain, ElevenLabs is his lungs.
One personal note. Futevôlei is not a theme I picked for a challenge. It is the sport I play every single day, and the crew in this post is my real crew. Getting to spend a weekend building for it, and then writing about it here, was the rare kind of work that does not feel like work.
The platform came first, but the announcer went live before the platform did. That feels right. Passion projects do not follow roadmaps.
Built by Vinicius Pereira · vinimabreu.dev · github.com/vinimabreu


Top comments (0)