A browser-based tool for tracking a half-marathon training block from plan to race day.
I built this to keep tabs on my own training: how much distance I've planned, what I've actually completed, whether I'm on track, and what's coming up next. It runs entirely in the browser with no backend, no login, and no accounts. Your data stays in localStorage.
- CSV training plan import. Drop in a CSV file with your workout schedule. The app parses it and replaces the active plan. You can also download a sample CSV or reset to the built-in default plan.
- Per-workout completion tracking. Tap any workout to see its details (planned km, target pace, notes). Mark it complete or undo it. Completion state persists across sessions.
- Overview tab. Shows a race countdown, today's workouts with quick-complete buttons, the next upcoming workout, overall progress (workouts and km), and a this-week dot summary.
- Analysis tab. Displays total km logged, average pace, average speed, an on-track/off-track indicator (based on whether 90%+ of past workouts are done), a weekly volume bar chart (planned vs actual), and a breakdown by workout type.
- Calendar tab. A monthly grid with dots color-coded by workout type, showing completed, scheduled, and missed days. Includes a scrollable upcoming-workouts list. Click any day with a workout to open its detail sheet.
- localStorage persistence. All workout data and completion state is saved to the browser automatically. No server, no syncing.
npm install
npm startThe app opens at http://localhost:3000.
Click the Import button in the header. You can drag and drop a .csv file or browse to select one. The expected CSV columns are:
| Column | Required | Format |
|---|---|---|
date |
yes | YYYY-MM-DD |
type |
yes | Easy Run, Tempo Run, Interval Run, Long Run, Half Marathon Race (and common aliases like recovery, threshold, lsd, speed work) |
distance_km |
yes | number (km) |
pace_target |
no | free text, e.g. 5:20 |
notes |
no | free text |
The column names are flexible: distance or km or dist work in place of distance_km; pace or target_pace work for pace_target; note or description work for notes.
Example:
date,type,distance_km,pace_target,notes
2026-03-27,Tempo Run,8,5:20,Focus on cadence
2026-03-29,Long Run,14,6:00,Easy effort
2026-04-01,Easy Run,6,6:30,RecoveryReact 19, Lucide React for icons, plain CSS. No routing library, no state management library beyond React context.