Plan overview: goal vs. realistic finish in race-day conditions, course summary and forecast.The same plan on a phone.
Problem
Even splits fail on real courses. Generic pacing tools adjust for elevation at best and ignore race-day weather, wind relative to the course heading, and the late-race fade.
What I built
A free web app that turns a course (34 major races with verified elevation profiles, or any GPX) plus a goal time and a recent result into a km-by-km (or mile) pacing plan.
Coach-style notes for every segment, a printable pace band, a Garmin course export and guides for COROS and Apple Watch.
Solo, end to end: product, UX, code and infrastructure.
Built for my own sub-3 attempt at the Frankfurt Marathon (current PB 3:02). I'm user #1.
Pace for every kilometer over the elevation profile, with uphill sections and the modeled fade zone.Advanced view: grade, pace, weather adjustment and cumulative time for every kilometer.
Architecture
App
Next.js 15 (App Router), TypeScript and Tailwind CSS, deployed on Vercel with a preview deployment per branch.
Data & auth
Supabase: Postgres plus magic-link auth.
Pacing engine
Deterministic: GPX parsing, segmentation, Grade Adjusted Pace and a late-race fade model. Metric internally, unit conversion only in the presentation layer.
Weather
Open-Meteo forecasts, including wind by course heading. One free forecast refresh before race day re-solves splits and notes together.
i18n
Full EN / DE / PL with next-intl.
E-mail & analytics
Transactional e-mail via Resend. Cookieless, self-hosted analytics (Umami).
Payments
Stripe one-time checkout implemented and kept behind a feature flag. The product is currently free.
Strava integration: planned, coming soon.
Where AI is used, and where it isn't
<Engine />
The numbers: deterministic
The numbers come from the deterministic engine, tested against reference fixtures from courses I've raced (Warsaw, Frankfurt).
An LLM should never invent a split.
<Claude />
The narrative: LLM
Claude writes the segment-by-segment race narrative from the engine's structured output, in the user's language (one prompt parameter, not a separate localization pipeline).
Model choice: Claude Sonnet, not Opus. It's production text generation on structured data, and latency matters when a user is waiting for their plan.
Output guardrails: hard rules in the prompt plus deterministic post-processing of the generated text, with tests enforcing copy rules.
Race strategy: pace ranges come from the engine, the segment narrative is written by Claude.
How I built it: AI-native workflow with Claude Code
Project CLAUDE.md
Domain rules, conventions and a definition of done.
Custom slash commands
A feature workflow (explore, plan, approval, branch, code, verify, preview, PR) and a UX audit command.
Code-reviewer subagent
Checks every diff against project rules: units, migrations, i18n, LLM model choice, payment flag.
Post-edit hook
Runs ESLint on changed files.
Preview before production
Every branch gets a Vercel preview, checked on mobile before merge.