v3.1 — Big Banana Studios / Lewis Family
Biofeedback-informed emotional regulation. A Polar H10 chest strap feeds heart rate to the browser over Bluetooth; the app learns a calm baseline and steps in at three escalating thresholds — notice, breathe, leave the room — based on Dr. John Gottman's research on Diffuse Physiological Arousal.
Everything runs on the device. There is no server, no API key, and no request that carries anything typed into it. The model is downloaded once and then runs locally on the GPU.
git init
git add .
git commit -m "Controlled Burn v3.1"
git branch -M main
git remote add origin https://github.com/<you>/controlled-burn.git
git push -u origin main
Then Settings → Pages → Source: Deploy from a branch → main / (root).
It will be live at https://<you>.github.io/controlled-burn/ in a minute or two.
Nothing here needs a build step. Every path in the app is relative (./sw.js,
./assets/…), which is what lets a project site work from /controlled-burn/
rather than the domain root.
.nojekyll is intentional — it stops GitHub running the files through Jekyll,
which would otherwise ignore anything beginning with an underscore.
Opening index.html from the file system will not work. file:// is not a
secure context, so the service worker will not register, the module worker
cannot import the library, and Web Bluetooth is unavailable.
GitHub Pages is HTTPS, so the deployed app is fine. For local work:
npx http-server . -p 8080
# then open http://127.0.0.1:8080/
localhost counts as a secure context, so everything works there too.
The app is installable and works with no signal, but the model has to be downloaded once while online. Open the app on wifi, go to any of the AI tabs and press Wake it. That pulls roughly 800 MB.
After that, two separate caches keep it working offline:
| What | Where | Managed by |
|---|---|---|
| App shell — HTML, worker, icons, manifest | controlled-burn-shell-<version> | sw.js |
| Transformers.js library + ONNX Runtime binaries | controlled-burn-shell-<version> | sw.js |
| Model weights (~800 MB) | transformers-cache | Transformers.js |
sw.js deliberately does not cache the model. Transformers.js already has a
better cache for it, and duplicating 800 MB on a phone would be rude. The
service worker also never deletes a cache it did not create, so a deploy can
never cost you that download.
Heart rate monitoring, the alerts, the notifications and the breathing exercise have no network dependency at all and work offline from the first load.
Chrome or Edge on desktop or Android.
Without WebGPU the heart rate side still works in full; only the five AI tabs go quiet, and the app says so rather than failing silently. Try Demo simulates a strap if you want to see the thresholds fire without wearing one.
index.html the whole app - markup, styles, logic
worker.js LFM2.5 inference, off the main thread
sw.js offline cache
manifest.webmanifest install metadata
assets/ icons
.nojekyll tell GitHub Pages not to run Jekyll
The model worker is a separate file for two reasons: generation holds a thread for tens of seconds and would otherwise stall the heart rate listener, and a same-origin worker inherits the page's service worker, which is what lets its library import come from cache when there is no signal.
Bump VERSION in sw.js. The old shell cache is dropped on activate
and the new files take over; the model cache is untouched.
Anyone with the app open sees a quiet update ready link next to the version
number rather than being reloaded underneath them — this app can be mid-alert,
and nothing should interrupt that.
idEvery Big Banana app is published on the same origin
(big-banana-studios.github.io), which makes the manifest id member
load-bearing.
id resolves against the origin, not against the manifest URL. start_url
and scope resolve the way you would expect; id does not.
manifest at https://big-banana-studios.github.io/Controlled-Burn/manifest.webmanifest
"start_url": "./" -> .../Controlled-Burn/ correct
"id": "./" -> https://big-banana-studios.github.io/ WRONG
"id": "/Controlled-Burn/" -> .../Controlled-Burn/ correct
Two apps on this domain that both say "id": "./" claim the same identity, and
the browser treats installing the second as an update to the first — same window,
same icon, one app where there should be two.
So: either give id an absolute path, or leave it out entirely, in which case it
falls back to the fully-resolved start_url and is unique automatically.
Thresholds live in the TH object near the top of the script block in
index.html:
| Tier | Trigger | Sustain | Cooldown |
|---|---|---|---|
| Yellow | baseline + 8 bpm | 6s | 45s |
| Amber | baseline + 16 bpm | 8s | 60s |
| Red | baseline + 24 bpm, or 100 bpm | 12s | 120s |
A tier only fires after the heart rate has held above its line for the sustain window. A high water mark stops lower alerts re-firing on the way back down; it clears only when the heart rate drops under the yellow line.
Note that a fast climb still rings all three on the way up — 70 to 96 bpm in one step gives yellow at 6s, amber at 8s, red at 12s. That is deliberate and pinned by a test.
Nothing typed into the app leaves the device. The only thing written to disk is
an optional resting heart rate — one number, in localStorage, offered on the
connect screen and never applied without being chosen. No text, no history, no
analytics.
Thinking tool, not a replacement for real conversation.
4 commits
HTML
85.7%
JavaScript
14.3%
v3.1 — Big Banana Studios / Lewis Family
Biofeedback-informed emotional regulation. A Polar H10 chest strap feeds heart rate to the browser over Bluetooth; the app learns a calm baseline and steps in at three escalating thresholds — notice, breathe, leave the room — based on Dr. John Gottman's research on Diffuse Physiological Arousal.
Everything runs on the device. There is no server, no API key, and no request that carries anything typed into it. The model is downloaded once and then runs locally on the GPU.
git init
git add .
git commit -m "Controlled Burn v3.1"
git branch -M main
git remote add origin https://github.com/<you>/controlled-burn.git
git push -u origin main
Then Settings → Pages → Source: Deploy from a branch → main / (root).
It will be live at https://<you>.github.io/controlled-burn/ in a minute or two.
Nothing here needs a build step. Every path in the app is relative (./sw.js,
./assets/…), which is what lets a project site work from /controlled-burn/
rather than the domain root.
.nojekyll is intentional — it stops GitHub running the files through Jekyll,
which would otherwise ignore anything beginning with an underscore.
Opening index.html from the file system will not work. file:// is not a
secure context, so the service worker will not register, the module worker
cannot import the library, and Web Bluetooth is unavailable.
GitHub Pages is HTTPS, so the deployed app is fine. For local work:
npx http-server . -p 8080
# then open http://127.0.0.1:8080/
localhost counts as a secure context, so everything works there too.
The app is installable and works with no signal, but the model has to be downloaded once while online. Open the app on wifi, go to any of the AI tabs and press Wake it. That pulls roughly 800 MB.
After that, two separate caches keep it working offline:
| What | Where | Managed by |
|---|---|---|
| App shell — HTML, worker, icons, manifest | controlled-burn-shell-<version> | sw.js |
| Transformers.js library + ONNX Runtime binaries | controlled-burn-shell-<version> | sw.js |
| Model weights (~800 MB) | transformers-cache | Transformers.js |
sw.js deliberately does not cache the model. Transformers.js already has a
better cache for it, and duplicating 800 MB on a phone would be rude. The
service worker also never deletes a cache it did not create, so a deploy can
never cost you that download.
Heart rate monitoring, the alerts, the notifications and the breathing exercise have no network dependency at all and work offline from the first load.
Chrome or Edge on desktop or Android.
Without WebGPU the heart rate side still works in full; only the five AI tabs go quiet, and the app says so rather than failing silently. Try Demo simulates a strap if you want to see the thresholds fire without wearing one.
index.html the whole app - markup, styles, logic
worker.js LFM2.5 inference, off the main thread
sw.js offline cache
manifest.webmanifest install metadata
assets/ icons
.nojekyll tell GitHub Pages not to run Jekyll
The model worker is a separate file for two reasons: generation holds a thread for tens of seconds and would otherwise stall the heart rate listener, and a same-origin worker inherits the page's service worker, which is what lets its library import come from cache when there is no signal.
Bump VERSION in sw.js. The old shell cache is dropped on activate
and the new files take over; the model cache is untouched.
Anyone with the app open sees a quiet update ready link next to the version
number rather than being reloaded underneath them — this app can be mid-alert,
and nothing should interrupt that.
idEvery Big Banana app is published on the same origin
(big-banana-studios.github.io), which makes the manifest id member
load-bearing.
id resolves against the origin, not against the manifest URL. start_url
and scope resolve the way you would expect; id does not.
manifest at https://big-banana-studios.github.io/Controlled-Burn/manifest.webmanifest
"start_url": "./" -> .../Controlled-Burn/ correct
"id": "./" -> https://big-banana-studios.github.io/ WRONG
"id": "/Controlled-Burn/" -> .../Controlled-Burn/ correct
Two apps on this domain that both say "id": "./" claim the same identity, and
the browser treats installing the second as an update to the first — same window,
same icon, one app where there should be two.
So: either give id an absolute path, or leave it out entirely, in which case it
falls back to the fully-resolved start_url and is unique automatically.
Thresholds live in the TH object near the top of the script block in
index.html:
| Tier | Trigger | Sustain | Cooldown |
|---|---|---|---|
| Yellow | baseline + 8 bpm | 6s | 45s |
| Amber | baseline + 16 bpm | 8s | 60s |
| Red | baseline + 24 bpm, or 100 bpm | 12s | 120s |
A tier only fires after the heart rate has held above its line for the sustain window. A high water mark stops lower alerts re-firing on the way back down; it clears only when the heart rate drops under the yellow line.
Note that a fast climb still rings all three on the way up — 70 to 96 bpm in one step gives yellow at 6s, amber at 8s, red at 12s. That is deliberate and pinned by a test.
Nothing typed into the app leaves the device. The only thing written to disk is
an optional resting heart rate — one number, in localStorage, offered on the
connect screen and never applied without being chosen. No text, no history, no
analytics.
Thinking tool, not a replacement for real conversation.
4 commits
HTML
85.7%
JavaScript
14.3%