diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index 6a64d39..c33b982 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -2902,9 +2902,11 @@ function stopMeter(uuid){ meterCtl.delete(uuid); }
* autoplay policy is satisfied. Each tone uses a short exponential
* fade-out to avoid click artifacts.
*
- * Tones are skipped if the page is hidden (background tab) — the
- * idea is to notify the LIVE user, not interrupt them when they
- * stepped away.
+ * Tones play even when the tab is in the background — the doorman /
+ * mod use case wants to know a guest just arrived without having to
+ * watch the tab. Browsers throttle some background work but Web
+ * Audio scheduling is preserved as long as the AudioContext was
+ * created after a user gesture (the 'enter' click counts).
* ================================================================== */
let _toneCtx = null;
function _toneCtxGet(){
@@ -2913,8 +2915,12 @@ function _toneCtxGet(){
return _toneCtx;
}
function playToneBlip(freq, durationMs, type){
- if (typeof document !== 'undefined' && document.hidden) return;
const ctx = _toneCtxGet(); if (!ctx) return;
+ /* if the user gesture was throttled (some mobile browsers suspend
+ * the context on tab hide), nudge it back to running before
+ * scheduling — resume() on a suspended context restarts the
+ * timeline so the tone actually fires. */
+ if (ctx.state === 'suspended'){ try { ctx.resume(); } catch(_){} }
try {
const t0 = ctx.currentTime;
const osc = ctx.createOscillator();
@@ -4091,8 +4097,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');