fix(pwa): fix UI overlap, scroll bleed and wrong nav height on iOS

Three root causes fixed:

1. Double safe-area padding: pwa.css set padding-top/bottom on body
   globally, but page containers already account for safe-area-inset
   in their height calculations. Removed body vertical padding (kept
   only in standalone media query for padding-top).

2. Wrong nav token: all page containers used --nav-height-mobile (56px)
   instead of --nav-bottom-height (68px = 56px scroll + 12px dots),
   causing 12px of content to render behind the bottom nav.

3. Scroll bleed: fixed-height page containers lacked overflow:hidden,
   allowing scroll events to propagate to the body. Added
   overscroll-behavior-y:contain on app-content globally.

Fixes #16
This commit is contained in:
Ulas
2026-04-04 22:02:19 +02:00
parent 7eb06ed905
commit 2508473265
13 changed files with 40 additions and 18 deletions
+2 -1
View File
@@ -15,9 +15,10 @@
.budget-page {
display: flex;
flex-direction: column;
height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom));
height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom));
max-width: var(--content-max-width);
margin: 0 auto;
overflow: hidden;
}
@media (min-width: 1024px) {
+1 -1
View File
@@ -15,7 +15,7 @@
.calendar-page {
display: flex;
flex-direction: column;
height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom));
height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom));
max-width: var(--content-max-width);
margin: 0 auto;
overflow: hidden;
+2 -1
View File
@@ -15,9 +15,10 @@
.contacts-page {
display: flex;
flex-direction: column;
height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom));
height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom));
max-width: var(--content-max-width);
margin: 0 auto;
overflow: hidden;
}
@media (min-width: 1024px) {
+2 -2
View File
@@ -16,7 +16,7 @@
* -------------------------------------------------------- */
.dashboard {
padding: var(--space-4);
padding-bottom: calc(var(--nav-height-mobile) + var(--safe-area-inset-bottom) + var(--space-16));
padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-16));
max-width: var(--content-max-width);
margin: 0 auto;
}
@@ -883,7 +883,7 @@
* -------------------------------------------------------- */
.fab-container {
position: fixed;
bottom: calc(var(--nav-height-mobile) + 24px + var(--safe-area-inset-bottom));
bottom: calc(var(--nav-bottom-height) + 24px + var(--safe-area-inset-bottom));
right: var(--space-4);
z-index: calc(var(--z-nav) - 1);
display: flex;
+3 -2
View File
@@ -97,6 +97,7 @@
flex: 1;
padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom));
overflow-y: auto;
overscroll-behavior-y: contain;
}
/* Sidebar auf Mobile verstecken */
@@ -217,7 +218,7 @@
* -------------------------------------------------------- */
.page-fab {
position: fixed;
bottom: calc(var(--nav-height-mobile) + 24px + var(--safe-area-inset-bottom));
bottom: calc(var(--nav-bottom-height) + 24px + var(--safe-area-inset-bottom));
right: var(--space-4);
width: 52px;
height: 52px;
@@ -865,7 +866,7 @@
/* FAB (Floating Action Button) */
.fab {
position: fixed;
bottom: calc(var(--nav-height-mobile) + var(--safe-area-inset-bottom) + var(--space-4));
bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-4));
right: var(--space-4);
width: 52px;
height: 52px;
+2 -1
View File
@@ -15,9 +15,10 @@
.meals-page {
display: flex;
flex-direction: column;
height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom));
height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom));
max-width: var(--content-max-width);
margin: 0 auto;
overflow: hidden;
}
@media (min-width: 1024px) {
+2 -1
View File
@@ -15,9 +15,10 @@
.notes-page {
display: flex;
flex-direction: column;
height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom));
height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom));
max-width: var(--content-max-width);
margin: 0 auto;
overflow: hidden;
}
@media (min-width: 1024px) {
+9 -3
View File
@@ -15,10 +15,11 @@ html, body {
-webkit-tap-highlight-color: transparent;
}
/* ── Safe Area Insets (Notch, Dynamic Island, Gesture Bar) ── */
/* ── Safe Area Insets (Notch, Dynamic Island, Gesture Bar) ──
* Nur horizontale Safe Areas auf body - vertikale werden von
* Standalone-Modus (padding-top) und Nav/Seiten (padding-bottom) gehandhabt.
* Kein body padding-top/bottom hier, sonst doppelt mit Seiten-Berechnungen. */
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
@@ -61,4 +62,9 @@ nav,
body {
padding-top: env(safe-area-inset-top);
}
/* Kein Scroll-Bleed - Content bleibt in seinem Container */
.app-content {
overscroll-behavior-y: contain;
}
}
+2 -1
View File
@@ -15,9 +15,10 @@
.shopping-page {
display: flex;
flex-direction: column;
height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom));
height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom));
max-width: var(--content-max-width);
margin: 0 auto;
overflow: hidden;
}
@media (min-width: 1024px) {
+1 -1
View File
@@ -14,7 +14,7 @@
* -------------------------------------------------------- */
.tasks-page {
padding: var(--space-4);
padding-bottom: calc(var(--nav-height-mobile) + var(--safe-area-inset-bottom) + var(--space-16));
padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-16));
max-width: var(--content-max-width);
margin: 0 auto;
}
+3 -3
View File
@@ -12,9 +12,9 @@
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
*/
const SHELL_CACHE = 'oikos-shell-v22';
const PAGES_CACHE = 'oikos-pages-v22';
const ASSETS_CACHE = 'oikos-assets-v22';
const SHELL_CACHE = 'oikos-shell-v23';
const PAGES_CACHE = 'oikos-pages-v23';
const ASSETS_CACHE = 'oikos-assets-v23';
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
// App-Shell: sofort benötigt für ersten Render