diff --git a/nextjs-app/components/Navigation.module.css b/nextjs-app/components/Navigation.module.css index d4e4b15..306091f 100644 --- a/nextjs-app/components/Navigation.module.css +++ b/nextjs-app/components/Navigation.module.css @@ -233,5 +233,10 @@ box-shadow: 0 -2px 12px rgba(26, 22, 18, 0.06); /* Respect iPhone home-indicator inset */ padding-bottom: env(safe-area-inset-bottom, 0); + /* Compensate for iOS Chrome's auto-hiding URL bar — Navigation.tsx + writes the offset based on the Visual Viewport API. translate3d + (instead of translateY) forces hardware compositing so the bar + doesn't lag/flicker during the toolbar animation. */ + transform: translate3d(0, var(--mobile-bar-offset, 0px), 0); } } diff --git a/nextjs-app/components/Navigation.tsx b/nextjs-app/components/Navigation.tsx index 73c38a8..24bbdc8 100644 --- a/nextjs-app/components/Navigation.tsx +++ b/nextjs-app/components/Navigation.tsx @@ -5,6 +5,7 @@ 'use client'; +import { useEffect } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useComparison } from '@/hooks/useComparison'; @@ -50,6 +51,33 @@ export function Navigation() { return pathname.startsWith(path); }; + /** + * iOS Chrome (and some Android browsers) auto-hide their URL bar on scroll, + * which grows the visual viewport without changing the layout viewport. + * `position: fixed; bottom: 0` sticks to the layout viewport, so our tab + * bar appears to float mid-screen with a gap beneath it. Track the delta + * via VisualViewport and apply it as a translate so the bar always sits + * flush against the visible bottom edge. + */ + useEffect(() => { + const vv = window.visualViewport; + if (!vv) return; + const root = document.documentElement; + const update = () => { + const offset = window.innerHeight - (vv.height + vv.offsetTop); + // Only positive offsets are meaningful (bar hidden → push down). + root.style.setProperty('--mobile-bar-offset', `${Math.max(0, offset)}px`); + }; + update(); + vv.addEventListener('resize', update); + vv.addEventListener('scroll', update); + return () => { + vv.removeEventListener('resize', update); + vv.removeEventListener('scroll', update); + root.style.removeProperty('--mobile-bar-offset'); + }; + }, []); + const items = [ { href: '/', label: 'Search', Icon: SearchIcon }, { href: '/compare', label: 'Compare', Icon: CompareIcon },