diff --git a/assets/custom.css b/assets/custom.css
index 95bb552..8e0eb2a 100644
--- a/assets/custom.css
+++ b/assets/custom.css
@@ -124,3 +124,13 @@ footer a:hover {
area the app UI does not cover (e.g. PWA navigation-bar insets) matches. */
html:not(.dark), html:not(.dark) body { background-color: rgb(234, 243, 234); }
html.dark, html.dark body { background-color: rgb(34, 39, 31); }
+
+/* ---- PWA safe areas: with viewport-fit=cover the page draws under system
+ bars — the bands then show the theme-correct page background instead of
+ OS-default white, and this padding keeps app content clear of the bars. ---- */
+@media (display-mode: standalone) {
+ #root {
+ padding-top: env(safe-area-inset-top, 0px);
+ padding-bottom: env(safe-area-inset-bottom, 0px);
+ }
+}
diff --git a/assets/maskable-icon.png b/assets/maskable-icon.png
index 859759c..d2dfbf6 100644
Binary files a/assets/maskable-icon.png and b/assets/maskable-icon.png differ
diff --git a/patch_index.py b/patch_index.py
index e230846..49fc35a 100644
--- a/patch_index.py
+++ b/patch_index.py
@@ -39,6 +39,22 @@ if "x-coop-build" not in html:
marker = ''.format(int(time.time()))
html = html.replace("", "\n " + marker, 1)
+# 3b. PWA chrome: let the page draw under system bars (Android 15 edge-to-edge,
+# display cutouts, iOS safe areas). The page background is theme-correct
+# (custom.css), so the top/bottom bands match the app theme; safe-area
+# padding in custom.css keeps content clear of the bars. Also drop
+# crossorigin="use-credentials" from the manifest link — Firefox Android
+# fails to resolve manifest icons on credentialed fetches (blank/missing
+# launcher icon).
+html = html.replace(
+ "initial-scale=1, interactive-widget=resizes-content",
+ "initial-scale=1, interactive-widget=resizes-content, viewport-fit=cover",
+)
+html = html.replace(
+ '',
+ '',
+)
+
open(PATH, "w", encoding="utf-8").write(html)
# 4. Web manifest identity: co-op name + a 512 icon installable as "any".