life, Sport

Optimize Your Murph Challenge Experience with This Tracker

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

How to do it - But as a dog

🛠️ 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.

Standard

Leave a comment