This is a submission for Weekend Challenge: Passion Edition
What I Built
The World Cup quarter-finals are on right now. I'm watching matches, yelling at my screen, and I thought: what if I could bottle that feeling into an audio clip?
PassionCast lets you pick your team, choose a passion mode (hype speech, glory moment, rivalry fire, heartbreak, fan anthem, or custom), and generates a 60-second AI commentary clip. Not the generic "your team is amazing" stuff. Actual references to your team's players, history, and rivals.
Pick India, select "Rivalry Fire," hit generate. You get a breathless commentator talking about the India-Pakistan cricket-meets-football tension, with real names and real context. Try Argentina and you get Maradona, La Albiceleste, the Diego legacy. That's what I was going for.
Demo
π Try PassionCast Live
Three pre-generated sample clips are on the page. Hit play, no keys needed. You'll hear:
- Argentina rivalry fire (Match Commentator voice)
- India pre-match hype (Epic Narrator voice)
- England heartbreak (Epic Narrator voice)
To generate your own, you'll need free API keys (30 seconds each):
The flow:
Code
π₯ PassionCast
Your personal AI hype man for the World Cup 2026.
Pick your team. Choose a moment. Get a passionate, AI-generated audio commentary that captures the fire of football fandom.
π Try PassionCast Live
How It Works
- Pick your team from 48 World Cup 2026 nations
- Choose your passion β hype speech, glory moment, rivalry fire, heartbreak, or fan anthem
- Select a voice β epic narrator, match commentator, or poetic storyteller
- Generate β Gemini writes the script, ElevenLabs brings it to life as audio
Tech Stack
| Layer | Technology | Why |
|---|---|---|
| Script generation | Google AI (Gemini 2.0 Flash) | High temperature (0.9) + cultural context = scripts that feel personal, not generic |
| Voice synthesis | ElevenLabs (Multilingual v2) | Low stability (0.4) for emotional range, high style (0.6) for expressiveness |
| Frontend | Vanilla JS + Vite | Fast, no framework overhead, deploys anywhere |
| Hosting | GitHub Pages | Free, automatic deploys via GitHub Actions |
No backend. No databaseβ¦
How I Built It
No backend. No framework. One page, vanilla JS, two API calls. Vite for bundling, GitHub Pages for hosting.
Browser β Gemini 2.0 Flash (writes script) β ElevenLabs (speaks it) β Audio playback
I specifically didn't want a backend here. The whole point is that someone can fork this, drop in their own keys, and have it running in 30 seconds.
The Gemini Part (where I spent most of my time)
Getting Gemini to write passionate content was harder than I expected. At temperature 0.7, everything came out bland. "Your team has a proud history. The fans are excited." Useless.
At 0.9, things got interesting. Combined with a detailed system prompt that includes the team name, their rivals, their football culture, and the specific style I want (narrator vs commentator vs poet), the output started feeling real.
The trick was constraining length. I needed 150-200 words, the sweet spot for 45-60 seconds of audio. Too short and it feels empty. Too long and ElevenLabs starts rushing or the clip drags.
// Note: ${apiKey} is a template literal variable, not a real key.
// Users provide their own free key from aistudio.google.com/apikey
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.9, maxOutputTokens: 500 }
})
}
);
The ElevenLabs Part (where the magic happens)
I tested default voice settings first. Sounded flat. Like a GPS reading football commentary.
The fix: crank down stability to 0.4 (default is higher). This adds emotional variation. The voice wavers, speeds up, gets louder on key phrases. Exactly what you want for sports commentary.
Then style to 0.6 for extra expressiveness, and speaker boost on for clarity.
Three voices, matched to content:
- Brian (dramatic narrator) for glory moments
- Liam (energetic) for rivalry and hype
- George (poetic storyteller) for heartbreak and anthems
body: JSON.stringify({
text: script,
model_id: 'eleven_multilingual_v2',
voice_settings: {
stability: 0.4,
similarity_boost: 0.8,
style: 0.6,
use_speaker_boost: true,
}
})
Other Decisions
I used flag images from flagcdn.com instead of emoji because emoji flags don't render on Windows for England, Wales, and Scotland (they show as black rectangles). Learned that the hard way after building the whole grid with emoji first.
Dark theme with gold/fire accents because... it's a passion app. Light mode would feel wrong.
What I'd Add
If I had another weekend: social sharing (let people post their clips), match-day mode that checks today's fixtures and auto-suggests hype content, and a gallery so you can hear what other fans generated. PRs welcome if you want to tackle any of these.
Prize Categories
Best Use of Google AI: Gemini 2.0 Flash with temperature 0.9 and detailed cultural prompting. Produces scripts with real player names, real rivalry history, and team-specific fan culture for 48 nations. Three distinct writing styles (narrator, commentator, poet) via style instructions in the prompt.
Best Use of ElevenLabs: Multilingual v2 model with deliberately tuned settings. Stability at 0.4 for emotional range (default sounds robotic for sports content). Style at 0.6 for expressiveness. Three voices paired to content types. The output sounds like broadcast commentary, not text-to-speech.








Top comments (16)
Its really impressive ππ»
Thank You ππ»
By any chance can i test this in realtime? i mean its asking for the api keys for generating output are they free?
Yes you can test it both the keys are available for free tier usage.
Hi itβs interesting use case, good work. For my understanding I have couple of questions, did you use structured JSON output from Gemini, or did you parse free-form text? And How do you prevent Gemini from hallucinating player names, scores, or match events?
Good questions. im using free form text not structured json. The script goes straight into elevenLabs as speech so all I need is clean spoken prose. The prompt tells gemini plain text only no markdown no stage directions and it respects that consistently at 150 to 200 words. On hallucination I don't fully prevent it and thats a deliberate choice. This generates hype commentary not factual reporting. If the AI says "remember that night in 2014" and the actual year was 2010 the emotional impact still lands for a hype speech i reduce the risk by including real context per team in the prompt (rivals, culture and history) which anchors gemini to real info and for 48 wellknown World Cup nations its training data is solid on actual football history. Where it breaks is smaller nations with thin football coverage but that's an acceptable tradeoff when the goal is emotional energy over encyclopedic accuracy.
Impressive project! The decision to use a higher Gemini temperature along with carefully engineered prompts clearly paid off. Pairing that with ElevenLabs voice tuning for emotional delivery makes the experience much more immersive. Nicely executed!
Yes true. Thanks for thoughtful reply π€
Itβs good one
Thank You !
Wow this one is epic and interesting
Thank You ππ»
Hey @sarvar_04 The voice experience is especially impressive. But How did you decide on the tone and personality of the AI commentator?
Trial and error i split into 3 personas (cinematic narrator, energetic commentator, poetic storyteller) matched to content types, then tweaked elevenlabs stability down to 0.4 and style up to 0.6 until it stopped sounding like a newsreader and started sounding like a real commentator losing their mind over a goal the personality comes from gemini's prompt the emotion comes from voice settings and both halves need each other.
Hi sir, I wanted to understand, did you stream the audio as it was generated, or wait for the complete audio before playback?
i wait for the complete audio before playback elevenlabs returns the full mp3 as a blob I create an object url from it set it as the audio source and then autoplay. Streaming would feel smoother for longer clips but for 45 to 60 secs of audio the wait is only a few secs and it keeps the code dead simple. one fetch one blob done.