diff --git a/.gitignore b/.gitignore
index 7fcea94e..801d66c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -266,3 +266,4 @@ v2/crates/rvcsi-node/npm/
v2/data/recordings/
v2/data/adaptive_model.json
v2/data/baseline.json
+.claude/launch.json
diff --git a/ui/raw.html b/ui/raw.html
index 22892b21..9bfc5bc7 100644
--- a/ui/raw.html
+++ b/ui/raw.html
@@ -48,6 +48,17 @@
last: --
absent
CV 0%
+
+
+ 🫁 — BPM ·
+
+
+ 💓 — BPM ·
+
0) {
+ if (brBpm) brBpm.textContent = vs.breathing_rate_bpm.toFixed(1) + ' BPM';
+ if (brConf) brConf.textContent = '· ' + ((vs.breathing_confidence || 0) * 100).toFixed(0) + '%';
+ if (brPill) brPill.style.opacity = (vs.breathing_confidence || 0) < 0.2 ? '0.5' : '1.0';
+ } else {
+ if (brBpm) brBpm.textContent = '— BPM';
+ if (brConf) brConf.textContent = '·';
+ if (brPill) brPill.style.opacity = '0.5';
+ }
+ if (vs && typeof vs.heart_rate_bpm === 'number' && Number.isFinite(vs.heart_rate_bpm) && vs.heart_rate_bpm > 0) {
+ if (hrBpm) hrBpm.textContent = vs.heart_rate_bpm.toFixed(0) + ' BPM';
+ if (hrConf) hrConf.textContent = '· ' + ((vs.heartbeat_confidence || 0) * 100).toFixed(0) + '%';
+ if (hrPill) hrPill.style.opacity = (vs.heartbeat_confidence || 0) < 0.2 ? '0.5' : '1.0';
+ } else {
+ if (hrBpm) hrBpm.textContent = '— BPM';
+ if (hrConf) hrConf.textContent = '·';
+ if (hrPill) hrPill.style.opacity = '0.5';
+ }
+
// Per-node level badge from node_features[i].classification (ADR-101).
const nfNow = performance.now() / 1000;
const nf = d.node_features || [];
diff --git a/v2/crates/wifi-densepose-sensing-server/static/raw.html b/v2/crates/wifi-densepose-sensing-server/static/raw.html
index 22892b21..9bfc5bc7 100644
--- a/v2/crates/wifi-densepose-sensing-server/static/raw.html
+++ b/v2/crates/wifi-densepose-sensing-server/static/raw.html
@@ -48,6 +48,17 @@
last: --
absent
CV 0%
+
+
+ 🫁 — BPM ·
+
+
+ 💓 — BPM ·
+
0) {
+ if (brBpm) brBpm.textContent = vs.breathing_rate_bpm.toFixed(1) + ' BPM';
+ if (brConf) brConf.textContent = '· ' + ((vs.breathing_confidence || 0) * 100).toFixed(0) + '%';
+ if (brPill) brPill.style.opacity = (vs.breathing_confidence || 0) < 0.2 ? '0.5' : '1.0';
+ } else {
+ if (brBpm) brBpm.textContent = '— BPM';
+ if (brConf) brConf.textContent = '·';
+ if (brPill) brPill.style.opacity = '0.5';
+ }
+ if (vs && typeof vs.heart_rate_bpm === 'number' && Number.isFinite(vs.heart_rate_bpm) && vs.heart_rate_bpm > 0) {
+ if (hrBpm) hrBpm.textContent = vs.heart_rate_bpm.toFixed(0) + ' BPM';
+ if (hrConf) hrConf.textContent = '· ' + ((vs.heartbeat_confidence || 0) * 100).toFixed(0) + '%';
+ if (hrPill) hrPill.style.opacity = (vs.heartbeat_confidence || 0) < 0.2 ? '0.5' : '1.0';
+ } else {
+ if (hrBpm) hrBpm.textContent = '— BPM';
+ if (hrConf) hrConf.textContent = '·';
+ if (hrPill) hrPill.style.opacity = '0.5';
+ }
+
// Per-node level badge from node_features[i].classification (ADR-101).
const nfNow = performance.now() / 1000;
const nf = d.node_features || [];