diff --git a/app/layout.tsx b/app/layout.tsx index e5930fd..db45356 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata, Viewport } from "next"; import { Geist } from "next/font/google"; import { Header } from "@/components/Header"; import { BottomNav } from "@/components/BottomNav"; +import { ViewportRedrawOnResume } from "@/components/ViewportRedrawOnResume"; import "./globals.css"; const geistSans = Geist({ @@ -48,6 +49,7 @@ export default function RootLayout({ suppressHydrationWarning > +
{children}
diff --git a/components/ViewportRedrawOnResume.tsx b/components/ViewportRedrawOnResume.tsx new file mode 100644 index 0000000..a9bb6ee --- /dev/null +++ b/components/ViewportRedrawOnResume.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useEffect } from "react"; + +// iOS Safari bug: with viewportFit=cover + maximumScale=1, restoring from +// bfcache after lock/unlock can leave the visual viewport in a stale +// "zoomed" state. Nudging scroll + forcing a reflow on pageshow +// (persisted) realigns it without changing the zoom level. +export function ViewportRedrawOnResume() { + useEffect(() => { + const onShow = (e: PageTransitionEvent) => { + if (!e.persisted) return; + window.scrollTo(window.scrollX, window.scrollY); + void document.body.offsetHeight; + }; + window.addEventListener("pageshow", onShow); + return () => window.removeEventListener("pageshow", onShow); + }, []); + return null; +}