fix: respect ios safe areas
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s

This commit is contained in:
zv
2026-06-13 22:31:55 +02:00
parent 1ae1be68ec
commit f434126c96
3 changed files with 50 additions and 6 deletions

View File

@@ -46,7 +46,7 @@ html {
}
body {
min-height: 100vh;
min-height: 100dvh;
background: hsl(var(--background));
color: hsl(var(--foreground));
-webkit-font-smoothing: antialiased;
@@ -89,12 +89,56 @@ select {
scrollbar-gutter: stable;
}
.app-header-safe {
padding-top: env(safe-area-inset-top);
}
.app-main-safe {
padding-bottom: calc(7rem + env(safe-area-inset-bottom));
}
.mobile-nav-safe {
bottom: calc(0.75rem + env(safe-area-inset-bottom));
left: calc(0.75rem + env(safe-area-inset-left));
right: calc(0.75rem + env(safe-area-inset-right));
}
.modal-overlay {
position: fixed;
inset: -1px 0 0;
box-sizing: border-box;
height: calc(100dvh + 1px);
min-height: calc(100dvh + 1px);
background: hsl(var(--foreground) / 0.55);
}
.modal-safe-area {
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
}
@media (min-width: 640px) {
@layer utilities {
.modal-safe-area {
padding:
calc(1rem + env(safe-area-inset-top))
calc(1rem + env(safe-area-inset-right))
calc(1rem + env(safe-area-inset-bottom))
calc(1rem + env(safe-area-inset-left));
}
}
}
@media (min-width: 1024px) {
@layer utilities {
.modal-safe-area {
padding:
calc(2rem + env(safe-area-inset-top))
calc(2rem + env(safe-area-inset-right))
calc(2rem + env(safe-area-inset-bottom))
calc(2rem + env(safe-area-inset-left));
}
}
}
@supports (-moz-appearance: none) {

View File

@@ -88,7 +88,7 @@ export function DayForecastModal({
<AnimatePresence>
{day ? (
<motion.div
className="modal-overlay z-[90] flex items-center justify-center p-0 sm:p-4 lg:p-8"
className="modal-overlay modal-safe-area z-[90] flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
@@ -98,7 +98,7 @@ export function DayForecastModal({
role="dialog"
aria-modal="true"
aria-labelledby="day-forecast-title"
className="h-full w-full overflow-hidden bg-background shadow-card sm:max-w-6xl sm:rounded-panel sm:border sm:border-border/70"
className="h-full max-h-full w-full overflow-hidden bg-background shadow-card sm:max-w-6xl sm:rounded-panel sm:border sm:border-border/70"
initial={{ opacity: 0, y: 28, scale: 0.985 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.99 }}

View File

@@ -15,7 +15,7 @@ export function AppShell({ children }: { children: React.ReactNode }) {
const { t } = useI18n();
return (
<div className="min-h-screen overflow-x-hidden bg-background">
<header className="sticky top-0 z-40 border-b border-border/70 bg-surface">
<header className="app-header-safe sticky top-0 z-40 border-b border-border/70 bg-surface">
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3 sm:px-6 lg:px-8">
<Link href="/" className="text-2xl font-semibold tracking-[-0.09em] text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent">
wtr<span className="text-accent">.</span>
@@ -35,8 +35,8 @@ export function AppShell({ children }: { children: React.ReactNode }) {
</div>
</div>
</header>
<main className="mx-auto max-w-7xl px-4 pb-28 pt-5 sm:px-6 sm:pt-8 lg:px-8">{children}</main>
<nav aria-label={t("nav.mobile")} className="fixed inset-x-3 bottom-3 z-50 flex justify-around rounded-panel border border-border/70 bg-surface p-1.5 shadow-card md:hidden">
<main className="app-main-safe mx-auto max-w-7xl px-4 pt-5 sm:px-6 sm:pt-8 lg:px-8">{children}</main>
<nav aria-label={t("nav.mobile")} className="mobile-nav-safe fixed z-50 flex justify-around rounded-panel border border-border/70 bg-surface p-1.5 shadow-card md:hidden">
{NAV_ITEMS.map((item, index) => {
const Icon = icons[index];
const active = item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);