This is a submission for Weekend Challenge: Passion Edition
What I Built
Why do I like running?
It goes back to when I was neck-deep in endurance and HIIT workouts while building strength. I could deadlift a reasonable weight and hold my own through an intense HIIT session for close to an hour. One day I thought, let me try going for a run.
That was 2023. I signed up for my first 5K on a road course. It was a bit of a love-hate relationship with that route, but I finished in around 42 minutes. Crossing the finish line alongside hundreds of other runners was unlike anything I'd experienced before.
(Quick side note: I've always been terrible at waking up early, so most of my training happens in the evening or at night.)
A year later, I somehow decided my first half marathon with barely any preparation was a good idea. I hadn't run at all in the two weeks leading up to race day, slept only two hours the night before, and even reached the start line 15 minutes late. By kilometer 16 I wanted to quit. Every step hurt, and I seriously considered turning around.
But I've always had one rule: if I start something, I don't give up easily.
Nearly three hours later I crossed the finish line inside the stadium, cramping in both legs and barely able to stand. A stranger asked if it was my first half marathon. I said yes. He smiled and said, "Congratulations on leveling up."
That moment stayed with me. It wasn't about the medal—it was about discovering what my mind and body were capable of.
That's when I realized running and strength training weren't temporary hobbies anymore. They became part of my life, like brushing my teeth. Soon after, I found a running coach because I knew I wanted to do this for the long haul.
(Side note, unrelated to this challenge: I also built a runner's portfolio if you're curious.)
Every run since gets logged on Strava—pace, heart rate, cadence, the works. But the data just sits there as numbers on a screen. It never feels like anything.
So I built Race Recap Radio.
Upload a .gpx or .tcx file exported from Strava, Garmin, Coros, Nike Run Club, or similar platforms, and the app turns your run into a broadcast-style race recap. Gemini writes the commentary from your actual run data, while ElevenLabs brings it to life with energetic sports commentary.
Real splits become real narrative beats: the grind, the redline heart-rate moment, the closing kick, and the payoff.
The demo on the homepage is my actual June 28 evening 10K: 10.11 km in 1:14:04, with my heart rate climbing from 158 to 197 bpm and a late surge that set two PRs.
Here's part of what Gemini and ElevenLabs produced:
"Alright sports fans, buckle up! We're diving deep into the Evening Run, a monumental 10.11 kilometer effort... Then came the ultimate test! The heart rate climbed, reaching a blistering 197 bpm, absolutely redlining! That's precisely where the notable surge ignited, between kilometer 8.6 and 9.1! And what a closing kick!"
Nothing in that commentary is invented. Every moment comes directly from the run itself—the surge, the heart-rate spike, the finishing kick—all detected from the uploaded activity.
The part I care about most is that it's not just my run.
Upload your own .gpx or .tcx file and you'll get a recap built entirely from your activity. Parsing happens entirely in your browser, so the raw GPS trace never leaves your device—only a compact summary is sent to generate the commentary.
Demo
🔗 Live app: https://race-recap-radio.vercel.app/
- Click "▶ Hear the demo run" to listen to the real 10K above. The audio is pre-generated, so judges can replay it endlessly without consuming API credits.
- Or click "Upload your run" and drop in your own
.gpxor.tcx. Below is a completely different 5K I uploaded to demonstrate that every upload generates a fresh recap rather than replaying canned audio.
Code
🔗 https://github.com/karthik-zoro-96/race-recap-radio
karthik-zoro-96
/
race-recap-radio
Turn any run into a hype broadcast-style audio recap
Race Recap Radio 🎙️
Turn a run into a hype, broadcast-style audio recap. Feed a run's real splits, heart rate, and PRs to Gemini, which writes a sports-commentator script, then to ElevenLabs, which voices it. Built for the dev.to Weekend Challenge: Passion Edition.
Two ways to try it:
- ▶ Hear the demo run — a real 10K, PR pace, redline finish. Pre-generated, costs nothing to click.
-
Upload your run — drop in a
.gpxor.tcxexported from Strava, Garmin, Nike, or Coros. Parsed entirely in your browser (raw GPS never leaves your device), then voiced live.
How it works
Run source ──► normalize to RunSummary ──► POST /api/recap ──► { script, audio }
demo (baked) (distance/pace/HR/splits/ Gemini writes the script,
or GPX/TCX best-efforts/PRs) ElevenLabs voices it
upload (client-side parse)
-
lib/gpx.ts— parses.gpx/.tcxin-browser. Prefers each format's native cumulative distance (needed for GPS-less treadmill…
How I Built It
Everything flows through a single RunSummary model. Whether the source is the baked-in demo or an uploaded activity, every run is normalized into the same structure—distance, pace, heart rate, cadence, per-kilometer splits, best efforts, PR count, and detected race events—before anything touches an API.
Parsing real running files
Supporting both .gpx and .tcx turned out to be the trickiest engineering challenge.
TCX files provide cumulative distance at every point, which is essential because treadmill workouts often contain no GPS coordinates at all. GPX files, on the other hand, only contain latitude and longitude, so distance has to be reconstructed using the haversine formula between consecutive points.
Multi-lap TCX files introduce another wrinkle by resetting their cumulative distance every lap, so those segments have to be stitched back together manually. Regardless of format, the raw activity never leaves the browser.
Turning data into commentary
Gemini receives the summarized run data—splits, heart-rate progression, detected surges, PRs, and pacing trends—and writes a structured 45–60 second sports broadcast.
The prompt guides the narration through a consistent flow:
- hype introduction
- the grind
- the redline moment
- the closing kick
- the payoff
If a run has no heart-rate data or no PRs, Gemini naturally adapts instead of mentioning missing information.
Giving it a voice
I originally chose a voice from ElevenLabs' shared Voice Library, only to discover that library voices require a paid subscription for API access—even on free accounts.
The fix was switching to one of the built-in premade voices ("Charlie — Deep, Confident, Energetic"), which works perfectly on the free tier. It was a small lesson, but exactly the kind of issue that can quietly break a live demo.
Protecting the free tier
ElevenLabs' free tier has a very limited generation quota, so I designed the app around it.
- The homepage demo plays a pre-generated MP3 committed as a static asset, meaning unlimited playback without API usage.
- Live generations are protected by Upstash Redis with both a per-IP hourly limit and a deployment-wide lifetime budget.
- If a generation fails midway, its reserved quota is refunded so transient failures don't permanently consume the available budget.
Stack: Next.js 16, React 19, TypeScript, Tailwind CSS v4, Framer Motion, @google/genai, ElevenLabs REST API, Upstash Redis.
Running is already emotional.
I wanted the recap to sound the way it felt.
Prize Categories
Submitting for:
- 🧠 Best Use of Google AI — Gemini transforms real running telemetry into an original sports commentary instead of a templated summary.
- 🔊 Best Use of ElevenLabs — The voice isn't just an output; it's the experience that turns data into something worth listening to.


Top comments (0)