From 51f211d72a4c4ad99c4ea30043eefb22e59042f6 Mon Sep 17 00:00:00 2001 From: Ulas Date: Thu, 16 Apr 2026 09:59:16 +0200 Subject: [PATCH] fix: resolve iOS PWA bottom nav positioning via flex layout Switch .nav-bottom from position: fixed to a flex child of .app-shell. With position: fixed and will-change: transform (used for the hide/show animation), iOS compositor layers can misplace the element. As a flex child (position: relative; flex-shrink: 0) at the end of a height: 100dvh container the nav is guaranteed to sit flush at the physical screen bottom. Remove padding-bottom clearance from .app-content, .tasks-page and .dashboard - no longer needed since the nav no longer overlaps the content area. --- CHANGELOG.md | 5 +++++ package.json | 2 +- public/styles/dashboard.css | 2 +- public/styles/layout.css | 12 +++++++----- public/styles/tasks.css | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db2bfa..19a924e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.20.4] - 2026-04-16 + +### Fixed +- iOS PWA: bottom navigation bar appeared visually higher than on Android. Changed `.nav-bottom` from `position: fixed` to a flex child of `.app-shell` (`position: relative; flex-shrink: 0`). With `position: fixed` and `will-change: transform` (used for the hide/show animation), iOS's compositor could misplace the nav bar. As a flex child at the end of a `height: 100dvh` container, the nav is guaranteed to sit flush at the physical screen bottom on all platforms. Removed the redundant `padding-bottom` clearance from `.app-content`, `.tasks-page`, and `.dashboard` (no longer needed since the nav no longer overlaps the content area). + ## [0.20.3] - 2026-04-16 ### Fixed diff --git a/package.json b/package.json index 6680ef7..01b12e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.20.3", + "version": "0.20.4", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/styles/dashboard.css b/public/styles/dashboard.css index 4a2a408..ed487bf 100644 --- a/public/styles/dashboard.css +++ b/public/styles/dashboard.css @@ -16,7 +16,7 @@ * -------------------------------------------------------- */ .dashboard { padding: var(--space-4); - padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-16)); + padding-bottom: var(--space-16); max-width: var(--content-max-width); margin: 0 auto; } diff --git a/public/styles/layout.css b/public/styles/layout.css index 0fcf55f..7dffffa 100644 --- a/public/styles/layout.css +++ b/public/styles/layout.css @@ -96,7 +96,6 @@ .app-content { flex: 1; min-height: 0; - padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom)); overflow-y: auto; overscroll-behavior-y: contain; -webkit-overflow-scrolling: touch; @@ -114,10 +113,13 @@ * Leichter Blur-Effekt für Glassmorphismus. * -------------------------------------------------------- */ .nav-bottom { - position: fixed; - bottom: 0; - left: 0; - right: 0; + /* Flex-Kind von .app-shell statt position: fixed - zuverlässiger auf iOS PWA. + * position: fixed kann durch will-change: transform in iOS-Compositor-Layern + * falsch referenziert werden. Als Flex-Kind am Ende der 100dvh-App-Shell ist + * die Position garantiert am Viewport-Bottom. */ + position: relative; + flex-shrink: 0; + width: 100%; background-color: color-mix(in srgb, var(--color-surface) 85%, transparent); border-top: 1px solid var(--color-border-subtle); display: flex; diff --git a/public/styles/tasks.css b/public/styles/tasks.css index fb26d0c..db5eb5e 100644 --- a/public/styles/tasks.css +++ b/public/styles/tasks.css @@ -14,7 +14,7 @@ * -------------------------------------------------------- */ .tasks-page { padding: var(--space-4); - padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-16)); + padding-bottom: var(--space-16); max-width: var(--content-max-width); margin: 0 auto; }