A fast, mobile-first web app for logging your gym workouts and tracking your strength trends over time. Built as an installable Progressive Web App — add it to your phone's home screen and it works offline, right there at the rack. Also looks great on your computer.
No accounts, no servers, no tracking. Your data is stored locally on your device, with one-tap backup/transfer via an export file.
- Set-by-set logging built for the rack — each set is a row you fill and then tick off. The tick is a 38px target, the numeric fields open the number pad, and every new set is pre-filled from the set before it.
- Rest timer that starts the moment you complete a set, with a countdown
ring,
+15s, and skip. Configurable (off / 60s / 90s / 2m / 3m). - Last time, inline — every set row shows what you did for that same set last session, so progressive overload needs no mental arithmetic.
- Live session summary — exercises, sets done vs. planned, and running volume, with a progress bar that turns green when the day is finished.
- Program-aware — load any day from your 86-week training plan with its set/rep/tempo targets instead of retyping exercise names.
- Trends — per-exercise charts for estimated 1‑rep‑max (Epley), top-set weight, and total volume, with hover crosshair and readouts, plus automatic personal records and body-weight tracking.
- History with totals (sessions, exercises, sets, volume).
- Dark and light themes, each with its own hand-picked palette rather than an inverted one, plus lb / kg display units.
- Adapts to the screen — a thumb-reachable bottom tab bar on the phone; a sidebar and two-column boards on the desktop.
- Backup & transfer — export your data to a JSON file and import it on another device (merge or replace).
- Offline-first PWA — installable, works with no signal.
- Tokens over hard-coded values. Color, spacing, radius, elevation, and
motion all live as CSS custom properties at the top of
css/styles.css; the light theme reassigns those tokens instead of overriding components. - Chart colors are validated, not eyeballed. The four series steps pass lightness-band, chroma, colorblind-separation, normal-vision, and contrast checks against both the dark and light chart surfaces.
- Charts draw in real pixels (measured per container, redrawn on resize)
rather than scaling a fixed
viewBox, so axis text stays crisp at any width. Each is a single series, so the card title identifies it and no legend box is needed; a visually hidden table mirrors the data for screen readers. - No emoji in the UI.
js/icons.jsis a 24px, 1.75-stroke line set that inheritscurrentColor, so icons match text weight and theme everywhere.
This repo's Markdown plan (Workout Tracker - Block #N ... .md, 86 weeks) is
parsed into data/program.json so you can pull a prescribed day straight into
the logger:
- On the Log tab, tap Load a day from your program.
- Pick the week and session (e.g. Monday – 5x10 – Day 1).
- The day's exercises are added with their set/rep/tempo targets, and each shows what you lifted last time — just fill in weight and reps.
Exercise-name autocomplete also draws from the program, and the empty tables in the Markdown are templates only (no weights are stored there).
This is automatic: a GitHub Action
(.github/workflows/rebuild-program.yml) regenerates data/program.json and
commits it back whenever you push a change to a Workout Tracker - Block*.md
file (or to the parser) on main — so the deployed app's program picker always
matches your plan. You can also trigger it manually from the repo's Actions
tab ("Run workflow").
To regenerate it locally instead:
python3 tools/import_program.py # rewrites data/program.json- Push this repo to GitHub (already done if you're reading this there).
- In the repo, go to Settings → Pages.
- Under Build and deployment, set Source = Deploy from a branch,
choose the branch (e.g.
main) and folder/ (root), then Save. - Wait a minute, then open the URL it gives you, e.g.
https://philquist.github.io/workouts/.
The included
.nojekyllfile makes sure GitHub Pages serves the app as-is.
It's all static files, so any static server works:
# Python
python3 -m http.server 8000
# then open http://localhost:8000
# or Node
npx serve .Open the site in your phone's browser, then:
- iOS (Safari): Share → Add to Home Screen.
- Android (Chrome): menu ⋮ → Install app (or tap the Install button in the app's top bar).
It then launches full-screen like a native app and works offline.
Everything lives in your browser's localStorage on the device you're using —
nothing leaves your phone or computer. That means:
- Data is per-device. To move it, use Data → Export on one device and Import on the other.
- Back up regularly with Export. Clearing your browser data / site data will erase the log.
Want automatic sync across devices later? The data layer (
js/db.js) is isolated behind a smallDBAPI specifically so a synced backend can be added without rewriting the UI.
index.html app shell: nav, app bar, rest timer, toast
css/styles.css design tokens + every component; dark and light themes
js/icons.js inline SVG icon set (24px grid, currentColor)
js/db.js data layer (localStorage) + analytics
js/charts.js dependency-free SVG line chart with a hover layer
js/sync.js optional private-Gist sync across devices
js/app.js views + hash router (Log / History / Trends / Data)
manifest.webmanifest PWA manifest
sw.js service worker (offline caching)
icons/ app icons (generated)
data/program.json training program parsed from the Markdown logs
tools/import_program.py rebuilds data/program.json from the Markdown plan
tools/make_icons.py regenerates the icon set (pure Python, no deps)
The app itself has no build step and no dependencies — just open
index.html via a static server. The tools/ scripts (standard-library
Python only) are needed only to regenerate the program data or icons.
- Estimated 1RM uses the Epley formula:
weight × (1 + reps / 30)(a single rep reports the weight itself). - Volume is
Σ (weight × reps)across all sets. - The lb/kg setting is a display label only — it doesn't convert your stored numbers, so pick one and stay consistent.