Inspiration
Range of motion is an arc, and almost nobody at home can measure theirs. A goniometer is the tool physical therapists use to measure joint angles, and it normally lives in a clinic. Most people who stretch, lift, or rebuild strength after an injury have no way to see whether they're actually improving. They just feel it.
We wanted that measurement to be as easy as opening a browser tab. Arc is a personal tool for anyone at any age, not a clinical one. No doctor or therapist sees the data. It's for people who want to know how far they move and whether it's getting better over weeks.
What it does
Arc is a webcam goniometer for home use, live at getarc.health. Press Start recording and the browser tracks your body through the webcam. MediaPipe pose tracking runs on your device, so the video never leaves it. Arc then:
- measures joint range of motion in degrees
- counts reps and sets against your plan, with rests that run by themselves
- saves each session to your account when the last set ends
One account covers a household, with a profile for each person. Dashboards show:
- peak range against a goal
- rep-by-rep detail
- a fatigue proxy
- weekly consistency
A 3D body at adult proportions works through your range, from rest to your best rep, over a band marking the range end to end.
Arc, the coach. After sign-up you chat with Arc (Gemini) about what you want from your body. Arc builds your profile and a training week from that chat, and the dashboard shows the week as a month calendar of planned, done, and not-recorded days. While you record, Arc listens for start, pause, resume, skip, rest, stop, how many, and repeat. After each set and each session it reads your numbers back in plain English with ElevenLabs.
Arc supports 15 exercises in four body areas (upper body, back, legs, core), from bicep curls to the seated knee extension.
How we built it
Arc is a MERN app with TypeScript throughout: MongoDB Atlas, Express 5, React 19, and Node 20. The frontend uses Vite, Tailwind CSS 4, Motion, and Radix. The 3D body is React Three Fiber, and pose tracking in the browser is MediaPipe Tasks Vision. The app is hosted on Render with a domain from Porkbun, and Vitest tests run in CI on every pull request. We split the code into three npm workspaces (dependencies, backend, frontend). The rep counter and fatigue proxy live in the shared dependencies package, so the browser, the API, and the camera app run the same logic.
Our computer-vision teammates built a Python desktop camera app (OpenCV and MediaPipe) that measures the same way and posts sessions to the same API with its own key.
Voice coaching runs through the API server. Gemini writes Arc's words and ElevenLabs speaks them. The keys stay on the server and never reach the browser. Without them, Arc falls back to its own questions, templates, and the browser's voice.
Every change went through a branch and a pull request, about 30 in total. The visual identity is "Calibre": white paper, a navy chassis, cobalt measurement ink, and square corners, like an instrument.
Challenges we ran into
Measuring an angle from a webcam. Each exercise turns MediaPipe landmarks into one metric that always grows as the rep deepens. For the elbow, that's
$$\text{ROM} = 180^\circ - \theta, \qquad \theta = \arccos!\left(\frac{\vec{u}\cdot\vec{v}}{\lVert\vec{u}\rVert\,\lVert\vec{v}\rVert}\right)$$
where $\vec{u}$ and $\vec{v}$ point from the elbow to the shoulder and the wrist. Raw landmarks jitter, so we smooth them with a One Euro filter. The rep counter uses hysteresis: a rep starts above an enter angle and ends below an exit angle, and anything shorter than a minimum rep time is ignored as noise.
Saying something honest about fatigue. We compare the first and last reps of a set: how much peak range shrank (ROM decay) and how much the tempo slowed (tempo drift). These combine into an index from 0 to 1. At 0.12 Arc nudges you, and at 0.25 it ends the set early. It needs at least 4 reps to say anything, and it is labelled a proxy everywhere, never a diagnosis.
Trusting the data. The server recomputes fatigue and the summary from the raw reps on every session it stores. Each account sees only its own profiles, and a profile owned by someone else looks exactly like a missing one. The camera app's key is compared in constant time.
Changing course fast. We trialed Postgres with TimescaleDB for a day and then closed that pull request to stay on MongoDB. We also went through several visual identities before landing on Calibre, and fixed a stutter in the 3D figure by building it once and re-posing it. Keeping two measurement stacks (the browser and the Python app) in agreement on rep thresholds is still an open item.
Accomplishments that we're proud of
- It's live. Anyone can open getarc.health, record with a webcam, and see their numbers with nothing to install.
- Privacy by design: pose tracking runs on the device, and the video never leaves it.
- One shared engine, so the rep counter and fatigue proxy are the same code in the browser, API, and tests.
- A full voice loop: Arc builds your plan, listens for commands, and reads your results back.
- Strong test coverage across the shared engine, API, frontend, and camera app, run in CI on every pull request.
- A cohesive design that works down to 320 px wide, plus one-click demo data that seeds six weeks of sessions.
What we learned
- Trustworthy numbers come from the unglamorous parts: smoothing, hysteresis, and recomputing on the server.
- Labelling a fatigue score as a proxy is as much a design decision as an engineering one.
- Putting the shared engine in one package kept three codebases honest.
- Small pull requests with CI let us move fast without breaking the live site.
- Early user testing showed that feedback that only says whether you hit your goal angle isn't enough. People want specific, constructive advice after an exercise.
What's next for Arc
- Make the post-exercise read as detailed and constructive as Arc's Gemini-written feedback, not just a goal check.
- Let users edit the suggested plan directly while Arc builds their schedule, and move on to the next workout of the day after finishing all sets.
- Save plan changes reliably and send people straight into recording for that workout.
- Agree on one set of rep thresholds between the browser and the Python camera app.
- Offer a choice of Arc's voice and language per household.
Built With
- and
- elevenlabs
- express.js
- gemini
- mediapipe
- mongodb
- node.js
- opencv
- react
- render
- tailwind
- three.js
- typescript
Log in or sign up for Devpost to join the conversation.