OpenTrailPaper
Open-source bike computer firmware for the LilyGO T5S3 4.7" E-Paper PRO, with offline maps, GPX routes, FIT recording and Bluetooth sensors.
A living calendar of trail ultra registrations. Track opening dates, closing deadlines, and lottery draws for UTMB, Western States, Hardrock, and more — sorted by what needs action next.
Sports & fitnessGitHub @CorresitaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Know the day registration opens. Every lottery draw, every deadline that matters — for the trail ultras you're chasing.
Taken from the repository README
Loading the full README.
Read the full README ↗Never miss a trail ultra registration.
Live Site: racereminder.run
Race Reminder tracks 81 ultra-trail races — the full UTMB World Series calendar (65 events), the World Trail Majors, and independent classics like Western States, Hardrock, and the US 200-milers (Cocodona, Tahoe, Bigfoot, Moab) — with race dates, registration windows, entry methods (lottery vs. first come, first served), and index requirements in one clean calendar.
Popular ultra-trail races sell out within days (sometimes hours) of registration opening, and every race has its own rules: UTMB Mont-Blanc runs a Running Stones lottery, Hong Kong 100 uses a ballot, Eiger Ultra-Trail is first come, first served. Keeping track of all these windows by hand means missed races. Race Reminder puts every window in one place — and emails you when the ones you care about open.
.ics) so you can subscribe from Apple/Google Calendar/api/notify (endpoint ready; schedule not yet created)scripts/scrape.ts syncs official UTMB sites every 2 hours)Want to use the app? Just visit racereminder.run — no setup needed. The steps below are for developing or contributing.
npm install
npm run dev
Open http://localhost:3000.
Only needed to actually send reminder emails — everything else works without configuration:
RESEND_API_KEY=re_... # from https://resend.com; without it, notify runs dry
EMAIL_FROM="Race Reminder <[email protected]>" # a sender verified in Resend (optional)
race-reminder/
├── app/
│ ├── page.tsx # Server component: loads races, renders the page
│ ├── components/
│ │ └── race-browser.tsx # Client component: tabs, filters, subscribe buttons
│ └── api/
│ └── subscribe/route.ts # POST/DELETE subscription endpoint
├── lib/
│ ├── deriveStatus.ts # Facts → status derivation (the core logic)
│ └── subscriptions.ts # Subscriber storage shared by API + notifier
├── scripts/
│ ├── scrape.ts # Checks races against official UTMB sites
│ └── notify.ts # Emails subscribers when registration opens
└── data/
├── races.json # The race database (facts only)
└── subscriptions.json # Subscribers (gitignored, created on first use)
npm run dev # Start the dev server
npm run build # Production build
npm run lint # ESLint
npm run scrape # Diff every UTMB race against its official site (dry run)
npm run scrape -- --write # Apply the changes to data/races.json
npm run notify # Email subscribers whose races just opened (dry run without RESEND_API_KEY)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/subscribe | Subscribe an email to a race ({email, raceId}) |
| DELETE | /api/subscribe | Remove a subscription ({email, raceId}) |
Subscriptions are stored in data/subscriptions.json (gitignored — it contains email addresses). scripts/notify.ts reads them, derives each race's status, and emails subscribers whose race has an open window — at most once per race edition.
Race data lives in data/races.json. The file stores facts only — status is derived at runtime by lib/deriveStatus.ts from the facts plus the current time. Each race record looks like:
{
"id": "utmb-mont-blanc",
"name": "UTMB Mont-Blanc",
"series": "utmb-world-series",
"country": "France",
"raceDate": "2026-08-28T06:00:00+02:00",
"registrationOpens": null,
"registrationCloses": "2026-01-15T23:59:59+01:00",
"registrationType": "lottery",
"entryRequirement": "UTMB Index 100M + Running Stones",
"distancesKm": [15, 40, 60, 101, 145, 174, 300],
"officialUrl": "https://montblanc.utmb.world/"
}
registrationOpens / registrationCloses are null when the organizer has not announced them yet (TBA in the UI). registrationType is fcfs, lottery, or qualification; lottery races may carry a lotteryDrawDate. The only manually stored status is soldOut: true — code cannot know a race sold out. Everything else (open, closing soon, closed, completed, next edition) is a conclusion, not a field.
| Source | Coverage | Data provided |
|---|---|---|
| utmb.world event calendar | 65 UTMB World Series races | Names, dates, countries, distances, official sites |
Official race sites (via npm run scrape) | UTMB subdomains | Event dates, per-distance registration status |
| Manual curation | World Trail Majors + WSER/Hardrock + registration windows | Registration windows, entry requirements, lottery details |
Note: Dates are manually curated and may lag official announcements. Always confirm on the official race website before booking flights.
__NEXT_DATA__ JSON embedded in UTMB race sitesThe research, design decisions, and lessons learned behind this project are documented in PROJECT_JOURNEY.md. The page's information architecture — every UI block, its data, and its display rules — lives in DESIGN.md.
Data corrections are the most valuable contribution right now — if you spot a wrong date or a missing race, open a PR against data/races.json.
git checkout -b fix/eiger-2027-dates)