ui(map): float the mobile toolbar so the map fills the screen

Mobile map was paying for a stacked toolbar under the header, leaving the map
~78% of the viewport.

- MapPage: the toolbar is absolute over the map on mobile (gradient fade,
  pointer-events pass-through) and static on desktop (md:static); controls get
  translucent/blurred backgrounds for legibility over the map.
- Container height now subtracts header + bottom nav only (desktop footer
  subtraction removed); Layout hides the desktop footer on full-bleed routes.
- Loading pill / offline banner nudged below the floating bar on mobile.

Measured at 390x844 (headless): map starts under the header and fills 730px =
86.5% of the viewport (was ~78%).
This commit is contained in:
SkateSpots 2026-09-29 17:05:20 +07:00
parent 322ec58ada
commit 5bf58499ea
2 changed files with 14 additions and 11 deletions

View file

@ -1051,7 +1051,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
</main>
{/* Footer - Desktop */}
<footer className="hidden md:block border-t border-gray-800 bg-black/95 py-6">
<footer className={cn(isFullBleed ? 'hidden' : 'hidden md:block', 'border-t border-gray-800 bg-black/95 py-6')}>
<div className="container px-4">
<div className="flex flex-col md:flex-row items-center justify-between gap-4 text-sm text-gray-400">
<span className="flex items-center gap-2">

View file

@ -1038,10 +1038,13 @@ export default function MapPage() {
<>
{/* Full-height map container: subtract header + bottom nav on mobile, header only on desktop */}
{/* 100dvh handles iOS Safari URL bar shrink correctly */}
<div className="h-[calc(100dvh-var(--header-mobile)-var(--bottom-nav))] md:h-[calc(100vh-4rem-var(--footer-desktop))] flex flex-col">
{/* ── Toolbar ── */}
<div className="border-b border-white/8 bg-black/95 backdrop-blur">
<div className="px-3 py-2">
<div className="relative h-[calc(100dvh-var(--header-mobile)-var(--bottom-nav))] md:h-[calc(100vh-4rem)] flex flex-col">
{/* ── Toolbar ──
Mobile: floats OVER the map (absolute) so the map fills the whole
screen; the gradient fades into the map and the transparent wrapper
passes touches through to it. Desktop keeps the in-flow bar. */}
<div className="pointer-events-none absolute inset-x-0 top-0 z-[1000] bg-gradient-to-b from-black/85 via-black/55 to-transparent pb-5 md:static md:z-auto md:border-b md:border-white/8 md:bg-black/95 md:pb-0 md:backdrop-blur">
<div className="pointer-events-auto px-3 py-2">
{/* Single row: search pill + view toggle + filter button */}
<div className="flex items-center gap-2">
{/* Search pill */}
@ -1051,7 +1054,7 @@ export default function MapPage() {
placeholder={t('mapPage.searchPlaceholder')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-9 bg-white/5 border-white/10 text-white placeholder:text-gray-600 rounded-full text-sm focus:border-[#ff0040]/50 focus-visible:ring-0 focus-visible:ring-offset-0 transition-all"
className="pl-9 h-9 bg-black/50 backdrop-blur-md border-white/15 md:bg-white/5 md:border-white/10 text-white placeholder:text-gray-500 rounded-full text-sm focus:border-[#ff0040]/50 focus-visible:ring-0 focus-visible:ring-offset-0 transition-all"
/>
{searchQuery && (
<button onClick={() => setSearchQuery('')} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white">
@ -1061,7 +1064,7 @@ export default function MapPage() {
</div>
{/* View toggle — compact pill */}
<div className="flex items-center rounded-full border border-white/10 bg-white/4 p-0.5 flex-shrink-0">
<div className="flex items-center rounded-full border border-white/15 bg-black/50 backdrop-blur-md md:bg-white/4 md:border-white/10 p-0.5 flex-shrink-0">
<button
className={cn('h-8 w-8 rounded-full flex items-center justify-center transition-all touch-manipulation active:scale-90',
viewMode === 'map' ? 'bg-[#ff0040] shadow-lg shadow-[#ff0040]/30' : 'text-gray-500 hover:text-gray-300')}
@ -1087,8 +1090,8 @@ export default function MapPage() {
className={cn(
'relative h-9 w-9 rounded-full flex items-center justify-center flex-shrink-0 border transition-all touch-manipulation active:scale-90 md:hidden',
totalActiveFilters > 0
? 'bg-[#ff0040]/20 border-[#ff0040]/60 text-[#ff0040]'
: 'bg-white/5 border-white/10 text-gray-400 hover:text-gray-200 hover:border-white/20'
? 'bg-[#ff0040]/25 border-[#ff0040]/60 text-[#ff0040]'
: 'bg-black/50 backdrop-blur-md border-white/15 text-gray-300 md:bg-white/5 md:border-white/10 md:text-gray-400 hover:text-gray-200 hover:border-white/20'
)}
aria-label="Filters"
>
@ -1658,7 +1661,7 @@ export default function MapPage() {
<div className="ss-loadbar h-full w-1/3 bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00]" />
</div>
{mapSpots.length === 0 && (
<div className="absolute top-3 left-1/2 -translate-x-1/2 z-[1001]">
<div className="absolute top-[4.5rem] left-1/2 -translate-x-1/2 z-[1001] md:top-3">
<div className="flex items-center gap-2 rounded-full bg-black/60 px-3.5 py-1.5 backdrop-blur-md ring-1 ring-white/10 shadow-lg">
<span className="h-3.5 w-3.5 rounded-full border-2 border-[#ccff00]/30 border-t-[#ccff00] animate-spin" />
<span className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/70">
@ -1677,7 +1680,7 @@ export default function MapPage() {
honestly, and offers a retry. Shown only once we know the relay
is unreachable, so a slow cold start never flashes it. */}
{!isLoading && relayUnreachable && allLocations.length > 0 && (
<div className="absolute top-3 left-1/2 z-[1002] -translate-x-1/2">
<div className="absolute top-[4.5rem] left-1/2 z-[1002] -translate-x-1/2 md:top-3">
<div className="pointer-events-auto flex items-center gap-2 rounded-full border border-amber-400/30 bg-black/75 px-3 py-1.5 shadow-lg backdrop-blur-md">
<WifiOff className="h-3.5 w-3.5 text-amber-400" />
<span className="font-mono text-[10px] uppercase tracking-[0.16em] text-amber-200/90">