feat(frontend): safe-area insets + hardened reduced-motion
- Add viewport export with viewportFit: "cover" so iOS exposes env(safe-area-inset-*) (required for the insets to take effect). - NavRail / TopBar / main / Toaster now respect safe-area insets so content clears the iPhone notch and home indicator in both portrait and landscape. - prefers-reduced-motion: the media query already disabled declared animation classes; harden it with a global transition/animation duration override and kill the scan-line shimmer so motion-sensitive users get a fully static UI. Verified tsc --noEmit + next build clean.
This commit is contained in:
@@ -262,6 +262,7 @@
|
|||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.scan-line::after,
|
.scan-line::after,
|
||||||
|
.scan-line,
|
||||||
.animate-eq,
|
.animate-eq,
|
||||||
.animate-spin-disc,
|
.animate-spin-disc,
|
||||||
.animate-pulse-ring,
|
.animate-pulse-ring,
|
||||||
@@ -269,5 +270,16 @@
|
|||||||
.animate-breathe {
|
.animate-breathe {
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
}
|
}
|
||||||
|
.scan-line {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.001ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.001ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
html { scroll-behavior: auto; }
|
html { scroll-behavior: auto; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import { Bricolage_Grotesque, Inter, JetBrains_Mono } from "next/font/google";
|
import { Bricolage_Grotesque, Inter, JetBrains_Mono } from "next/font/google";
|
||||||
import { ThemeProvider } from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
import { Toaster } from "@/components/primitives/toast";
|
import { Toaster } from "@/components/primitives/toast";
|
||||||
@@ -28,6 +28,13 @@ export const metadata: Metadata = {
|
|||||||
"AI-powered Discord moderation, voice monitoring, and media control console",
|
"AI-powered Discord moderation, voice monitoring, and media control console",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: "#0a0a0c",
|
||||||
|
width: "device-width",
|
||||||
|
initialScale: 1,
|
||||||
|
viewportFit: "cover",
|
||||||
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function Toaster({ position = "bottom-right" }: { position?: string }) {
|
|||||||
|
|
||||||
const pos =
|
const pos =
|
||||||
position === "bottom-right"
|
position === "bottom-right"
|
||||||
? "bottom-4 right-4"
|
? "bottom-[calc(1rem+env(safe-area-inset-bottom))] right-[calc(1rem+env(safe-area-inset-right))]"
|
||||||
: position === "top-right"
|
: position === "top-right"
|
||||||
? "top-4 right-4"
|
? "top-4 right-4"
|
||||||
: "bottom-4 left-1/2 -translate-x-1/2";
|
: "bottom-4 left-1/2 -translate-x-1/2";
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function AppFrame({ children }: { children: React.ReactNode }) {
|
|||||||
<NavRail />
|
<NavRail />
|
||||||
<div className="flex min-w-0 flex-1 flex-col">
|
<div className="flex min-w-0 flex-1 flex-col">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<main className="min-h-0 flex-1 overflow-y-auto px-4 pb-8 sm:px-5">
|
<main className="min-h-0 flex-1 overflow-y-auto px-4 pb-[calc(2rem+env(safe-area-inset-bottom))] sm:px-5">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function NavRail() {
|
|||||||
const path = pathname ?? "/";
|
const path = pathname ?? "/";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="glass m-3 mr-0 flex w-[68px] flex-col items-center gap-1 rounded-[18px] py-4">
|
<nav className="glass mb-[calc(0.75rem+env(safe-area-inset-bottom))] ml-[calc(0.75rem+env(safe-area-inset-left))] mt-[calc(0.75rem+env(safe-area-inset-top))] flex w-[68px] flex-col items-center gap-1 rounded-[18px] py-4">
|
||||||
<div className="flex flex-1 flex-col gap-1">
|
<div className="flex flex-1 flex-col gap-1">
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
<NavItem
|
<NavItem
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function TopBar() {
|
|||||||
: "text-signal";
|
: "text-signal";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-40 flex items-center gap-3 px-4 py-3 sm:gap-4 sm:px-5 sm:py-3.5">
|
<header className="sticky top-0 z-40 flex items-center gap-3 px-4 py-3 pt-[calc(0.75rem+env(safe-area-inset-top))] sm:gap-4 sm:px-5 sm:py-3.5">
|
||||||
<div className="flex min-w-0 items-baseline gap-2 sm:gap-3">
|
<div className="flex min-w-0 items-baseline gap-2 sm:gap-3">
|
||||||
<span className="eyebrow hidden sm:inline">GMW</span>
|
<span className="eyebrow hidden sm:inline">GMW</span>
|
||||||
<h1 className="display truncate text-[1.25rem] text-ink sm:text-[1.5rem]">{label}</h1>
|
<h1 className="display truncate text-[1.25rem] text-ink sm:text-[1.5rem]">{label}</h1>
|
||||||
|
|||||||
Reference in New Issue
Block a user