Grade-adjusted pacing plans for trail races. Upload a course GPX, get a per-kilometer plan that knows steep climbs are power-hikes, not runs, and an honest finish range instead of a false-precision single number. Everything runs in your browser. Your GPX never leaves your device.
▸ Open the live app · no signup, no upload, load an example and go
Most pace planners assume you run every hill. On real trails, steep climbs are power-hikes, and pretending otherwise makes every plan wrong from the first big wall. GradePace is built around three ideas the mainstream tools skip:
- Power-hikes are planned, not ignored. Above a transition grade (default 18%), iso-effort running is physically unavailable, so the plan switches to hiking at a fixed vertical speed (VAM). The elevation profile is colored by what you'll actually do — descent, runnable, climb, power-hike.
- Self-calibration instead of guessed knobs. Upload a run you recorded and GradePace inverts its own model against it: it measures your personal terrain factor (with stopped time filtered out) rather than asking you to invent one. Route exports with synthetic timestamps are detected and refused. This is the moat — Strava has the data to self-calibrate and doesn't.
- Honest uncertainty. Pre-race prediction can't beat day-of biology (sleep, heat, and fueling swing a 70 km race by 20–40 min). The finish is shown as a range — −8%/+10% uncalibrated, −5%/+7% once calibrated — with the model's central estimate in the middle. Admitting uncertainty is more state of the art, not less.
The Minetti (2002) energy-cost model is the foundation; the calibration layer is the product.
You can share a plan as an image, a link that carries your settings, a printable race-day PDF, or a watch-ready GPX whose waypoints carry your projected ETAs. The interface speaks English, French, and Spanish.
- Parse —
<trkpt>(or<rtept>fallback) → lat/lon/ele, plus optional timestamps for the calibration path. - Distance — cumulative Haversine.
- Resample — even 10 m stations (kills gradient spikes from near-coincident GPS fixes).
- Smooth — centered moving average over a fixed 30 m physical window.
- Gradient — Δelevation / Δdistance per segment; D+ via a 5 m hysteresis deadband (density-stable, noise-robust).
- Cost → pace — the Minetti energy-cost polynomial (clamped to its validated ±45% range) scales your flat pace; above the transition grade, segments hard-switch to power-hiking at fixed VAM.
- Plan — aggregate into km or mile splits, project the finish, wrap it in the uncertainty range.
Calibration inverts the same forward model: predicted total (at terrain ×1.00) vs. your actual moving time → a measured terrain factor, applied with one click.
npm install
npm run dev # local dev server
npm run build # production build (also what CI runs)
npm run test # engine + app tests (Vitest, 119 tests)
npm run lintUseful scripts (run with npx tsx — the engine uses extensionless TS imports):
npx tsx scripts/gen-og.mjs # regenerate og.png from the live share card
npx tsx scripts/render-card-preview.mjs a.gpx out.png # preview the share card for any course
node scripts/calibrate-scan.ts efforts/*.gpx # fit terrain factors across recorded runs
node scripts/prior-scan.ts efforts/*.gpx # course-signal vs factor analysis (negative result)src/
lib/pacing.ts Pure engine: parse, distance, resample, smoothing,
gradients, Minetti cost, splits, moving time,
calibration fit, finish range. No React. Unit-tested.
lib/format.ts Time/pace formatters shared by UI and share card.
lib/shareCard.ts Shareable plan image as a self-contained SVG.
lib/rasterize.ts SVG → PNG in the browser.
lib/gradeColor.ts Shared grade→color scale (chart + map + share card).
lib/basemaps.ts Basemap catalog: terrain / standard / satellite / hybrid.
lib/pois.ts Overpass POIs (water, toilets, viewpoints): bbox query,
endpoint race, client-side route-corridor filter.
lib/nutrition.ts Nutrition plan: hourly carb/fluid/sodium targets applied
to each projected segment between aid stations.
lib/logistics.ts Race logistics: aid-station dwell time, wall-clock
ETAs from a start time, cutoff-barrier warnings.
lib/persistence.ts Local save/restore of the last uploaded plan.
lib/planSheet.ts Printable race-day plan sheet (stats, profile, aid ETAs,
nutrition, full pacing table) for the PDF export.
lib/planGpx.ts Watch-ready GPX export: course track + aid waypoints
named with projected ETAs.
App.tsx UI: upload, effort inputs, calibration, share, table.
ElevationChart.tsx Grade-colored profile, hand-rolled SVG (lazy chunk).
CourseMap.tsx Map with the grade-colored route, aid stations, basemap
switcher, scale bar, opt-in POI overlay (lazy Leaflet).
ErrorBoundary.tsx Styled fallback instead of a white screen.
Vite + React 19 + TypeScript, Tailwind v4, Leaflet, a hand-rolled SVG
chart, Vitest + Playwright. Installable PWA (the app shell works offline).
Client-side only — no backend, no database, no auth. Deployed on Vercel,
auto-deploy from main. The pure engine in src/lib/pacing.ts is the asset;
its Minetti anchors, clamp, and split invariants are locked by tests.
Active development. Current technical state, decisions, and roadmap live in STATUS.md.
Issues and PRs welcome — see CONTRIBUTING.md. The short version: keep the engine pure and tested, verify against reality (known race lengths, published D+, the Minetti paper), and prefer honest uncertainty over false precision.
MIT © Alvaro Serero
Built by Alvaro Serero for his own race in the Fontainebleau forest. If GradePace helps you plan yours, a ⭐ is appreciated.



