diff --git a/apps/mobile/src/App.css b/apps/mobile/src/App.css index e32ad5c8..d495ff86 100644 --- a/apps/mobile/src/App.css +++ b/apps/mobile/src/App.css @@ -124,9 +124,11 @@ body.dark .auto-sync-indicator { header { background: #ffffff; padding: 0.25rem 0.75rem; - /* Safe area: --safe-area-top is set by JS on mount (env() for iOS, Kotlin bridge for Android). - Fallback 40px ensures header clears notch/status bar even before JS runs. */ - padding-top: calc(var(--safe-area-top, 40px) + 0.4rem); + /* Safe area: --safe-area-top is set by JS on mount (env() probe for iOS, Kotlin bridge for + Android). Fallback to env(safe-area-inset-top) so iOS gets the correct notch inset even + before JS runs; Android WebView returns 0 for env() and the Kotlin bridge sets the real + value, so no hard-coded iOS offset leaks onto Android. */ + padding-top: calc(var(--safe-area-top, env(safe-area-inset-top, 0px)) + 0.4rem); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); position: sticky; top: 0; @@ -2527,7 +2529,7 @@ body.dark .captured-image-preview { flex-direction: column; align-items: center; padding: 0.5rem; - padding-top: var(--safe-area-top, 40px); + padding-top: var(--safe-area-top, env(safe-area-inset-top, 0px)); /* Bottom padding handled by .editor-segment-buttons safe-area padding */ padding-bottom: 0; /* Prevent iOS from scrolling the viewport when keyboard appears */ diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 5250c224..a665f203 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -220,13 +220,24 @@ const EditorOverlay: React.FC = ({ children, onDismiss, keyb }, []); // Set --safe-area-top CSS variable for status bar / notch clearance. - // On iOS: read from env(safe-area-inset-top) via a CSS trick. - // On Android: Kotlin bridge overwrites with real WindowInsets value. - // Default 40px ensures header clears notch even if bridge is slow. + // On iOS: probe env(safe-area-inset-top) via a hidden div — this is the correct + // notch/dynamic-island inset; set it immediately so CSS picks it up. + // On Android: env(safe-area-inset-top) is not supported in WebView, so the probe + // returns 0. The Kotlin bridge overwrites --safe-area-top with the real + // WindowInsets value when the page loads. We must NOT default to a hard-coded + // iOS-specific value (e.g. 40px) here — that leaks iOS-notch spacing onto + // Android devices that have no notch. useEffect(() => { const root = document.documentElement; if (!root.style.getPropertyValue('--safe-area-top')) { - root.style.setProperty('--safe-area-top', '40px'); + // Probe env(safe-area-inset-top): non-zero on iOS, 0 on Android WebView. + const probe = document.createElement('div'); + probe.style.cssText = 'position:fixed;top:0;height:env(safe-area-inset-top,0px);visibility:hidden;pointer-events:none;'; + document.body.appendChild(probe); + const insetPx = probe.offsetHeight; + document.body.removeChild(probe); + // On iOS this is the notch height; on Android it's 0 (bridge sets real value). + root.style.setProperty('--safe-area-top', `${insetPx}px`); } }, []);