The Murph Challenge isn’t a workout.
It’s a systems failure conducted at heart-rate redline.
If you’ve ever tried to remember whether you’re on rep 183 or 193 of squats while your lungs are filing a formal complaint, you already know: human memory is not a reliable datastore under load.
So I built a Murph tracker that does exactly one job well—count reps—while I focus on the important things, like not dying.
🎖️ What is Murph (and why people keep doing it)
The Murph Challenge is performed on Memorial Day to honor Lt. Michael P. Murphy, a Navy SEAL killed in Afghanistan in 2005.
It was his favorite workout. Originally named “Body Armor”, which feels accurate in the same way “production incident” feels accurate.
The canonical version:
- 1 mile run
- 100 pull-ups
- 200 push-ups
- 300 squats
- 1 mile run
Optional difficulty modifier: wear a 20 lb vest and rethink your life choices.
An illustration of the canonical version
🛠️ Design constraints
This app is used mid-workout.
That means:
- One hand
- Shaky fingers
- Spotty reception
- Zero patience
So I optimized for latency, clarity, and failure tolerance, not framework points on Twitter.
Stack choices
- Vanilla JavaScript
Because hydration is for apps that have time. This one does not. - Tailwind CSS
Big buttons. High contrast. No custom CSS archaeology six months later. - PWA
Installable. Offline. Works in gyms that double as Faraday cages. - LocalStorage
Because losing progress at 299 squats is how phones get thrown.
No backend. No accounts. No syncing.
Your suffering is local-first.
💡 A couple implementation details I actually like
1. One workout, multiple intensities
Not everyone jumps straight into full Murph. Instead of duplicating logic, I used a simple multiplier.
getDefaultState(isHalfMurph = false) { const multiplier = isHalfMurph ? 0.5 : 1; return { sections: [ { id: 'pullups', name: 'Pull-ups', total: Math.round(100 * multiplier), }, // ... ] };}
One code path.
No forks.
Dry code & wet t-shirt –> is a good combination.
2. Confetti, because completion matters
Finishing Murph is a moment.
A sad toast message does not cut it.
So I added a lightweight confetti engine that fires multiple bursts when you cross the line.
celebrate() { this.fire({ particleCount: 100, origin: { x: 0.5, y: 0.4 } }); setTimeout(() => { this.fire({ particleCount: 75, origin: { x: 0.2, y: 0.5 } }); }, 200);}
Is this necessary?
No.
Is it correct?
Absolutely.
📱 Why a PWA instead of “just an app”
Zero friction beats store polish.
- No App Store approval
- No updates at workout time
- No network dependency
The service worker caches everything. Once it loads, it stays loaded—whether you’re on a track, in a garage gym, or questioning your pull-up strategy halfway through.
🚀 Try it
If you want:
- A Murph tracker that doesn’t lag
- A clean vanilla JS codebase
- Proof you don’t need 400 dependencies to ship something useful
Check out the repo and take it for a spin.
Build simple things.
Make them resilient.
Celebrate when they work –> preferably with confetti.
Discover more from Ido Green
Subscribe to get the latest posts sent to your email.

