Make the clock behave on iOS
Two real bugs, both worst on iOS: The frame was taller than the screen in Safari. .app declared `height:100dvh;height:100vh`, so the fallback won and 100vh on iOS is the large viewport — the control bar and part of the bottom panel sat behind the toolbar. The declarations are now in the right order. The wake lock never came back. iOS drops it whenever the app backgrounds, but `lock` was never cleared, so the `!lock` guard blocked every later re-request and the screen started sleeping mid-game. It now listens for `release` and clears the handle; against a stubbed API the old code stays at one request after a release-and-return, the new code makes a second. Smaller iOS fixes: claim a playback audio session so the ringer switch doesn't silence the clock (this pauses other audio — the README says how to drop it), resume the audio context when returning from the background, suppress the long-press callout, keep sheet scrolling from rubber-banding the app, back 88dvh with 88vh, and name the home-screen icon. Untested on real hardware — there's no WebKit engine here, so this is a code audit plus a Chromium regression run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+31
-5
@@ -7,6 +7,7 @@
|
||||
<meta name="theme-color" content="#332e2b">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-title" content="BG Clock">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<link rel="icon" href="icon-192.png">
|
||||
@@ -40,13 +41,16 @@
|
||||
font-feature-settings:"tnum" 1;
|
||||
-webkit-tap-highlight-color:transparent;
|
||||
-webkit-user-select:none;user-select:none;
|
||||
-webkit-touch-callout:none; /* no iOS long-press callout on a panel */
|
||||
overscroll-behavior:none;
|
||||
touch-action:manipulation;
|
||||
}
|
||||
|
||||
/* ---------- frame ---------- */
|
||||
.app{
|
||||
height:100dvh;height:100vh;
|
||||
height:100vh;height:100dvh; /* dvh must win — 100vh on iOS Safari is the
|
||||
large viewport, so the bar and the bottom
|
||||
panel end up under the toolbar */
|
||||
display:grid;
|
||||
grid-template-rows:1fr auto 1fr;
|
||||
}
|
||||
@@ -193,7 +197,8 @@
|
||||
padding:8px 22px max(22px,env(safe-area-inset-bottom));
|
||||
transform:translateY(101%);
|
||||
transition:transform .26s cubic-bezier(.32,.72,0,1);
|
||||
max-height:88dvh;overflow-y:auto;
|
||||
max-height:88vh;max-height:88dvh;overflow-y:auto;
|
||||
overscroll-behavior:contain; /* scrolling the sheet can't rubber-band the app */
|
||||
box-shadow:0 -18px 50px rgba(0,0,0,.45);
|
||||
}
|
||||
.sheet.on{transform:translateY(0)}
|
||||
@@ -478,8 +483,18 @@
|
||||
if(ac.state === "suspended") ac.resume();
|
||||
return ac;
|
||||
}
|
||||
// iOS silences Web Audio through the ringer switch unless the page claims a
|
||||
// playback session. Trade-off: it also interrupts whatever else is playing,
|
||||
// so it's only claimed the first time the clock actually makes a sound.
|
||||
var claimedSession = false;
|
||||
function claimAudioSession(){
|
||||
if(claimedSession) return;
|
||||
claimedSession = true;
|
||||
try{ if(navigator.audioSession) navigator.audioSession.type = "playback"; }catch(e){}
|
||||
}
|
||||
function tone(freq, at, dur, peak, type, partial){
|
||||
var c = audio(); if(!c) return;
|
||||
claimAudioSession();
|
||||
var t0 = c.currentTime + at;
|
||||
var o = c.createOscillator(), g = c.createGain();
|
||||
o.type = type || "sine";
|
||||
@@ -773,11 +788,22 @@
|
||||
/* ============ keep the screen awake ============ */
|
||||
var lock = null;
|
||||
function keepAwake(){
|
||||
if(!navigator.wakeLock) return;
|
||||
navigator.wakeLock.request("screen").then(function(l){ lock = l; }).catch(function(){});
|
||||
if(!navigator.wakeLock || lock) return;
|
||||
if(document.visibilityState !== "visible") return; // request() rejects when hidden
|
||||
navigator.wakeLock.request("screen").then(function(l){
|
||||
lock = l;
|
||||
// the OS drops the lock whenever the app goes to the background; without
|
||||
// clearing it here the stale handle blocks every later re-request
|
||||
l.addEventListener("release", function(){ lock = null; });
|
||||
}).catch(function(){});
|
||||
}
|
||||
document.addEventListener("visibilitychange", function(){
|
||||
if(document.visibilityState === "visible" && !lock) keepAwake();
|
||||
if(document.visibilityState !== "visible") return;
|
||||
keepAwake();
|
||||
// iOS interrupts the audio context on backgrounding; nudge it back so the
|
||||
// flag still sounds even if nobody has tapped since returning
|
||||
// (old webkitAudioContext.resume() returns undefined, so don't chain on it)
|
||||
if(ac && ac.state === "suspended"){ try{ ac.resume(); }catch(e){} }
|
||||
});
|
||||
keepAwake();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user