DEV Community

Cover image for Stillpoint: a breathing guide that waits 32 seconds before it tells you what to do
Phạm Cảnh
Phạm Cảnh

Posted on Edited on

Stillpoint: a breathing guide that waits 32 seconds before it tells you what to do

Presage SmartSpectra SDK Challenge Submission

This is a submission for the Presage SmartSpectra SDK Challenge

TL;DR: Most breathing apps hand you a pace and ask you to match it. Stillpoint spends 32 seconds
measuring how you breathe, then starts the guide from that measurement — clamped to 6–12 seconds
— and only ever slows it down (with a 12-second cap for usability). In the recorded run: 2:05,
9 guided breaths, average pulse 72 bpm, average breathing 12.4 /min, HRV 37 ms, signal
Good. Camera only, nothing leaves the machine, and there is a demo you can run in two commands with no API key and no webcam.

Judge it in 3 minutes

If you have Look at
90 seconds The demo video from 0:36 (first guided breaths) to 0:56, then the summary at 2:06
2 minutes npm install && npm run demo → http://127.0.0.1:4317. No key, no camera, the real pacer
1 minute lib/session.mjs for the SDK wiring, public/lib/pacer.js for the rule, and test/pacer.test.mjs for the edge cases
10 seconds The pacer trace below: for both inputs, the guide period never goes below the measured period

What I Built

Stillpoint is a two-minute, camera-only breathing retreat. It reads your breathing rate through a
webcam with the Presage SmartSpectra SDK, waits while you settle, then paces you from where you
already are.

The rule that shapes the whole app: inside the 6–12 second range, the guide may slow you down but
never speed you up.
Above 12 seconds a usability cap holds the guide at 12 seconds, which is the
one deliberate exception.

  • A 32-second settle measures your breathing rate while the interface stays quiet. The breathing estimate warms up during that window; the last moments are the transition into pacing.
  • The pacer starts at your measured period, clamped to 6–12 seconds.
  • If your rhythm is faster than 10 seconds, it eases toward 10 by 750 ms per breath: 6.0 → 6.75 → 7.5 → 8.25 → 9.0 → 9.75 → 10.0.
  • If your rhythm is already 10–12 seconds, the target is your own period, so the guide holds.
  • An orb grows on a 40% inhale and settles on a 60% exhale; the landscape's mist and the lake reflection follow the same breath value.
  • Reliable pulse samples add heartbeat ripples to the lake. HRV is shown when confidence is high enough, never scored.
  • Expression is optional and off by default; when enabled it tints the scene palette.

There is no wearable, no account, no recording, and no upload.

Stillpoint settle screen: a glowing orb, a small camera preview, the text "Settle in — Breathe naturally · 16s", and pulse, breathing, and HRV fields showing dashes while the settle window fills.

The first 32 seconds: no pace is offered. The preview is only for framing, and the metric fields
stay as dashes until the SDK has enough signal. The orb drifts on a slow idle rhythm so the screen is
not frozen while it measures; it is not guiding the breath yet.

What makes this different

Breathing apps are a crowded space, including in this challenge. Stillpoint is built around a
negative constraint rather than a feature list: it never makes a slow breather go faster inside the
6–12 second range.
The
guide is a slow control loop that starts from your measurement, and the visual is there to support
the pace, not to grade you. No score, no streak, no garden to keep alive.

That difference is testable, not just a claim:

Pacer trace for a 7.8-second and a 4.5-second natural breath. Both inputs are inside the 6–12 second range; the guide period rises from the measured value toward 10 seconds and never drops below the measured line.

Each step is one completed breath. Left: a 7.8 s natural breath is held, then eased toward 10 s.
Right: a 4.5 s natural breath starts at the 6 s floor, then eases toward 10 s. The dashed line is
the natural period fed in — the same value the app receives from the SDK's breathing rate — and the
guide never crosses below it. The trace was produced by running public/lib/pacer.js with those two
inputs, not sketched for the post.

Demo

Watch the 2-minute demo on YouTube

The demo is a live session on a Linux x64 machine with a USB camera: the local camera preview, live
SmartSpectra pulse and breathing readings (HRV when the signal is ready), validation hints, the
adaptive pacer, and the summary. It runs the full default session.

Chapters:

  • 0:00 — consent and framing
  • 0:04 — settle: the app measures and says nothing
  • 0:36 — guided breathing from the measured rhythm
  • 2:06 — session ends; the summary with the run's averages

The recorded run, with numbers

Stillpoint session summary: 2:05 total, 9 guided breaths, average pulse 72 bpm, breathing rate 12.4 per minute, HRV 37 ms, signal quality Good.

The summary from the session in the video. The app never shows a score; these are the four metrics
from that run, not a judgement.

This is one participant on one machine, not a study. It is here so you can check that the numbers on
screen come from the SDK rather than a random generator. The simulated mode is labelled
Simulated everywhere, precisely so the two cannot be confused.

Try it in two commands

The pacer is the part worth feeling, so it also runs with a generated signal — no camera, no key:

npm install
npm run demo
# open http://127.0.0.1:4317
Enter fullscreen mode Exit fullscreen mode

The session is labelled Simulated in the interface, and the pacer runs exactly as it does with
the SDK. Nothing is measured. This is a demo of the pacing logic, not of the sensor.

Code

https://github.com/pham-canh/stillpoint

Full setup:

git clone https://github.com/pham-canh/stillpoint.git
cd stillpoint
npm install
cp .env.example .env   # add SMARTSPECTRA_API_KEY
npm start
# open http://127.0.0.1:4317
Enter fullscreen mode Exit fullscreen mode

Requirements and platform notes are in the README. The SmartSpectra Node SDK ships native binaries,
so a live session runs on macOS Apple Silicon, Linux x64/arm64, and Windows x64; Intel Macs are not
supported by the SDK. The UI still loads on unsupported platforms, and npm run demo works
everywhere. A local video can drive the same pipeline through DEMO_VIDEO for testing.

How It Responds to Human Signals

Stillpoint uses three live SmartSpectra signals by default — pulse rate, breathing rate, and
HRV — plus expression when the optional scene setting is enabled. It requests only those
four metrics, not traces, landmarks, blinking, or talking.

The integration path:

  1. The local Node process opens the camera with the SDK's native useCamera(CameraSelection.byId(id), { width: 1280, height: 720, fps: 30 }) path. Frames stay inside that process; there is no browser-to-server frame hop and no browser camera prompt in the default mode.
  2. The SDK emits metric buffers. decodeMetrics reads metrics.breathing.rate.at(-1), metrics.cardio.pulseRate.at(-1), metrics.cardio.hrv.at(-1), and, when enabled, metrics.face.expression.at(-1).
  3. The SDK's videoOutput event provides a processed frame. The server downsamples it to a 320×240 RGBA preview and sends it over the loopback control WebSocket, so you can check framing without a second camera consumer.
  4. The server pushes deduplicated samples to the browser over Server-Sent Events. The optional CAPTURE_MODE=frames fallback keeps the browser getUserMedia → 640×480 RGBA → useCustomInput() / sendFrame() path for machines where the server cannot access a camera.

What the pacer actually does

The rule is small enough to check against the tests
(test/pacer.test.mjs):

Measured breath Start of guide After a few breaths
4.5 s (13.3 bpm) 6.0 s (clamped) 6.75 → 7.5 → 8.25 → 9.0 → 9.75 → 10.0
7.8 s (7.7 bpm) 7.8 s 8.55 → 9.3 → 10.0
10.5 s (5.7 bpm) 10.5 s stays 10.5 s
13.0 s (4.6 bpm) 12.0 s (clamped) stays 12.0 s

That is the whole "adaptive" claim: inside the 6–12 second range a slow breather is never sped up,
and the 6-second floor means a fast breather is slowed gradually rather than yanked to 10 seconds on
the first breath.

There is one deliberate exception: above 12 seconds the cap wins. A 13-second natural breath is
guided at 12 seconds, which is faster than natural. The alternative was a guide that stretches past
what feels usable on screen, so the cap is a product decision rather than hidden behaviour.

What the SDK taught me

Four things I did not get right the first time, all found on real hardware:

  1. Breathing needs the chest, and that fights the laptop posture. Expressions and pulse come through from the face alone; breathing rate needs the shoulders and upper chest. The validation hints ("Place more of the chest in view") are not cosmetic — without them the settle window produces nothing useful.
  2. The streams start at different times. In my logs, pulse arrived first and the breathing rate needed its 30-second window on top. The settle phase exists because of that, not because a countdown looks nice. Anything that reacts before the window fills is reacting to noise.
  3. Confidence is the useful part of a reading. A value with low confidence moves around. The UI hides the number below 10% confidence, the pacer ignores breathing below 25%, and the HRV summary only uses samples above the 10% gate. Showing a shaky number would make the whole experience feel fake.
  4. Frame rate matters more than resolution. At 1280×720 the SDK is happy on a decent USB camera; in poor light, the frame rate drops and the validation status says so. The 320×240 loopback preview exists so the person can fix framing without opening a second camera consumer.

Limits, and what I did not test

Being explicit about the edges:

  • One person has used it in a full live session. The pacer logic is unit-tested, but the "does it feel calm" question has not been tested with a group.
  • I have not measured the delay between a real breath and the orb's response. The breathing rate is a 30-second average, so the guide is intentionally slow rather than twitchy; the exact lag is unmeasured.
  • The thresholds (confidence gates, 32-second settle, 750 ms ease, the 6–12 s clamps) are first choices, not tuned against a dataset.
  • Live sessions need a supported platform and a key. Intel Macs cannot run the SDK's native binaries; the simulated mode runs everywhere.
  • Breathing needs the upper chest in frame. On a laptop at normal distance, expressions and pulse are easier to get than breathing.

Wellness only. SmartSpectra metrics are not FDA-cleared and are not a diagnosis or treatment.
Stillpoint does not provide medical advice, make medical decisions, or monitor anyone covertly.

Consent and Data Handling

Consent. The camera is not opened until the person ticks an explicit consent checkbox and
presses Begin. In the default native mode the local Stillpoint process opens the camera at that
point; the browser does not request camera access, so there is no extra browser prompt. Expression
analysis is optional and off by default.

Frames. Video only, never audio. In the default mode the local Node process opens the camera at
up to 1280×720 and the SDK processes frames in memory. A downsampled 320×240 RGBA preview is sent to
the page over a loopback WebSocket. Frames are not recorded, written to disk, cached, logged, or
uploaded. The live preview can be hidden. The CAPTURE_MODE=frames fallback uses browser capture and
a loopback frame WebSocket instead.

Measurements. Pulse, breathing, HRV, and optional expression are computed locally by the
SmartSpectra SDK running natively in the Node process. Derived values are held in memory for the
session, shown in the interface, and discarded when the session ends. Stillpoint does not persist
them.

Simulated mode. npm run demo / SIGNAL_SOURCE=mock generates pulse, breathing, and HRV values
in the local Node process. It opens no camera, needs no API key, measures nobody, and is labelled
Simulated on screen and in the summary.

Network and third parties. Stillpoint does not send frames or measurements to Presage. The
SmartSpectra SDK authenticates the API key, verifies the subscription, and may obtain its model or
update files from Presage; those requests do not include Stillpoint's frames or measurements.
Stillpoint has no accounts, analytics, ads, or tracking, and uses no optional AI service. The SDK
telemetry option is explicitly disabled
(enableTelemetry: false).
The page loads no external scripts, fonts, or images.

Retention and deletion. There is no stored data to retain or delete. Stopping the session,
pressing Escape, hiding the tab, disconnecting, or finishing normally releases the camera, closes the
sockets, and releases the in-memory session state. Closing the tab clears the rest.

Security. The server binds to 127.0.0.1, origin-checks WebSocket and control requests, and sets
a strict Content Security Policy. The intended URL is http://127.0.0.1:4317.

Demo video. The recording was made separately, with the participant's consent, outside the app's
data path. Stillpoint itself does not record the session. If the optional DEMO_VIDEO setting is
used, it reads a local clip chosen by the operator; that clip is not uploaded.

Full details: https://github.com/pham-canh/stillpoint/blob/main/PRIVACY.md and the in-app /privacy
page.

FAQ

Do I need an API key to try it? No. npm run demo needs no key and no camera. The
simulated mode is labelled on screen, and the pacer is the same code path as a live session.

Is the video uploaded anywhere? No. In the default mode the local Node process holds the camera
and the SDK runs in that process. Only a 320×240 preview travels over loopback to the page.

Why not use a wearable? The point is a two-minute break with nothing to charge, pair, or wear.
The webcam is already there; the constraint is making it useful without turning the session into a
vitals dashboard.

What happens with a very slow natural breath? Between 6 and 12 seconds the pacer holds or
lengthens your period; it never shortens it. Above 12 seconds it stays at 12 so the guide remains
usable, which is the one case where the guide is faster than the natural breath.

Prize Categories

Entering both:

  • Most Creative or Delightful Use — a webcam becomes a quiet, responsive landscape rather than a vitals dashboard. The adaptive pacer starts from the person instead of a preset, and the optional expression palette makes the same two minutes look different for different people.
  • Best Thoughtful Real-World Use — a low-friction two-minute microbreak for desk workers, students, and caregivers. No wearable, no account, no streak, no score. The session is short enough to repeat, and the privacy design removes the main reason people avoid camera-based tools.

Top comments (2)

Collapse
 
onelifevibe profile image
OneLifeVibe •

The decision to start from the person's actual breathing rate, rather than a fixed pace, is what makes this stand out. Most breathing apps ask the body to meet the app; here the app meets the body first, and that 32-second settle feels like the honest way to do it. Showing HRV without turning it into a score is a thoughtful touch too — it keeps the two minutes calm instead of competitive. The local-only camera handling is reassuring as well. Curious how the adaptive pacer behaves for someone whose natural rate is already very slow — does it still try to ease toward ten seconds, or does it mostly hold steady?

Collapse
 
phamcanh profile image
Phạm Cảnh •

Thanks — it holds steady. The pacer starts at the measured period (clamped to 6–12s) and only eases toward 10s when the measured period is shorter than 10s. If someone's natural period is 10–12s, the target is their own period, so the guide doesn't shorten it; it just stays with them. The one edge case is the 12s cap: a natural breath longer than 12s stays at 12s rather than going slower, so the guide remains usable. The pacer tests cover both cases.