Skip to content

Alvaro5/grade-pace

Repository files navigation

GradePace

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.

CI License: MIT Live demo Client-side only

▸ Open the live app  ·  no signup, no upload, load an example and go

Elevation profile of the Imperial Trail colored by what you will do on each section: blue descents, green runnable, amber climbs, rose power-hikes, with R1/R2/R3 aid stations and projected arrival times.


Why GradePace

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.

See it

Stat cards: distance 68.75 km, elevation gain 1,193 m, power-hike 0.98 km, projected finish 7:35:30, expect 6:59 to 8:21. Course map on a topographic basemap with the route colored by grade, amber aid-station markers, and a checkered finish flag.
An honest finish range, not a single fake-precise number, plus distance, D+, and how much of the course you'll walk. A grade-colored course map (terrain / satellite / hybrid basemaps) with aid stations and opt-in POIs: water, toilets, viewpoints, cafés, springs, shelters, parking, picnic spots.
Nutrition plan table with carbs, fluids, sodium, and calories for each segment between aid stations, plus totals. Per-kilometer splits table showing grade, D+, hike fraction, target pace, and elapsed time.
A nutrition plan that puts carbs, fluids, and sodium on each segment between aid stations, sized by projected time, not distance. Per-km (or per-mile) splits: target pace, climb, hike share, and elapsed time, all consistent with the projected finish.

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.

How it works

  1. Parse<trkpt> (or <rtept> fallback) → lat/lon/ele, plus optional timestamps for the calibration path.
  2. Distance — cumulative Haversine.
  3. Resample — even 10 m stations (kills gradient spikes from near-coincident GPS fixes).
  4. Smooth — centered moving average over a fixed 30 m physical window.
  5. Gradient — Δelevation / Δdistance per segment; D+ via a 5 m hysteresis deadband (density-stable, noise-robust).
  6. 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.
  7. 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.

Getting started

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 lint

Useful 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)

Project structure

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.

Tech

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.

Status & roadmap

Active development. Current technical state, decisions, and roadmap live in STATUS.md.

Contributing

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.

License

MIT © Alvaro Serero


Built by Alvaro Serero for his own race in the Fontainebleau forest. If GradePace helps you plan yours, a ⭐ is appreciated.

About

Grade-adjusted trail-race pacing from a GPX: power-hike-aware splits, self-calibration from your recorded runs, and an honest finish range. Runs entirely in your browser.

Topics

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors