Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,10 @@
</head>
<body>
<div id="map"></div>
<div id="offline-banner" class="offline-banner">
<span>You are offline — cached tiles and GPS recording still work</span>
</div>
<!-- #offline-banner is created lazily by main.ts only when the app goes offline.
Keeping it out of the static HTML avoids a position:fixed+transform compositing
layer at first paint, which (with #map) contributed to the iOS-26 WKWebView
whole-page render freeze. -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>
19 changes: 16 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,11 +511,24 @@ document.getElementById('map')?.focus();
document.body.style.zoom = '100%';

// ── Offline detection ─────────────────────────────────────────────────────────
// The banner is created lazily (not in static HTML) so it isn't a position:fixed +
// transform compositing layer at first paint — that, with #map, contributed to the
// iOS-26 WKWebView whole-page render freeze. It only ever exists once the app has
// been offline.
function updateOfflineBanner(): void {
const banner = document.getElementById('offline-banner');
if (!banner) return;
let banner = document.getElementById('offline-banner');
if (navigator.onLine) {
banner.classList.remove('visible');
banner?.classList.remove('visible');
return;
}
if (!banner) {
banner = document.createElement('div');
banner.id = 'offline-banner';
banner.className = 'offline-banner';
banner.innerHTML = '<span>You are offline — cached tiles and GPS recording still work</span>';
document.body.appendChild(banner);
// Mount hidden, then reveal next frame so the slide-in transition plays.
requestAnimationFrame(() => banner!.classList.add('visible'));
} else {
banner.classList.add('visible');
}
Expand Down
9 changes: 7 additions & 2 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

html { height: 100%; width: 100%; padding: 0; margin: 0; }
body { height: 100%; width: 100%; padding: 0; margin: 0; }
/* isolation: isolate keeps mix-blend-mode contained to the map's own stacking context. */
#map { position: absolute; top: 0; bottom: 0; right: 0; left: 0; isolation: isolate; }
/* No `isolation: isolate` here on purpose: an empty, full-screen *composited* #map
layer at first paint was a prime trigger for the iOS-26 WKWebView render freeze
(whole page never composites to screen until a reload). Containment isn't needed —
the hillshade overlay's mix-blend-mode:multiply blends against the opaque base
tiles directly beneath it regardless, and multiply against the white body behind a
full-screen map is a no-op. */
#map { position: absolute; top: 0; bottom: 0; right: 0; left: 0; }

/* Blend at the layer container so multiply composites against the base-layer sibling below. */
.hillshade-blend { mix-blend-mode: multiply; }
Expand Down
Loading