feat: Refactor drawer for improved animations and update shadow styles across components

This commit is contained in:
2025-12-05 13:52:15 -05:00
parent bdee155760
commit 17cc85d177
10 changed files with 279 additions and 182 deletions

View File

@@ -31,11 +31,14 @@ export default function RootLayout({
return (
<html lang="en" className={`${ptSerif.variable}`} suppressHydrationWarning>
<head>
<script
defer
src="https://umami-iccw808w4wk088o0w4o8c8kg.coolify.soconnor.dev/script.js"
data-website-id="415c64e5-98c5-4975-bf49-2c900fe6b1b5"
/>
{/* only load analytics on production */}
{process.env.NODE_ENV === "production" && (
<script
defer
src="https://umami-iccw808w4wk088o0w4o8c8kg.coolify.soconnor.dev/script.js"
data-website-id="415c64e5-98c5-4975-bf49-2c900fe6b1b5"
/>
)}
</head>
<body>
<ThemeProvider

View File

@@ -22,23 +22,20 @@ export default function HomePage() {
return (
<main className="relative h-dvh w-screen overflow-hidden bg-black text-white font-serif">
{/* Unified shadow container for navbar + drawer */}
<div className="absolute top-0 left-0 right-0 bottom-0 pointer-events-none z-[1000]" style={{ boxShadow: 'inset 0 0 40px 10px rgb(0 0 0 / 0.3)' }}>
{/* Navbar - always visible */}
<Navbar isDiscoveryOpen={isDiscoveryOpen} onToggleDiscovery={() => setIsDiscoveryOpen(!isDiscoveryOpen)} />
{/* Navbar - always visible */}
<Navbar isDiscoveryOpen={isDiscoveryOpen} onToggleDiscovery={() => setIsDiscoveryOpen(!isDiscoveryOpen)} />
{/* Right Drawer - only render after mount to prevent hydration mismatch */}
{mounted && (
<Drawer
shop={selectedShop}
shops={COFFEE_SHOPS}
onSelect={setSelectedShop}
onClose={() => setSelectedShop(null)}
isOpen={isDiscoveryOpen}
onToggleOpen={() => setIsDiscoveryOpen(false)}
/>
)}
</div>
{/* Right Drawer - only render after mount to prevent hydration mismatch */}
{mounted && (
<Drawer
shop={selectedShop}
shops={COFFEE_SHOPS}
onSelect={setSelectedShop}
onClose={() => setSelectedShop(null)}
isOpen={isDiscoveryOpen}
onToggleOpen={() => setIsDiscoveryOpen(false)}
/>
)}
{/* Map Background */}
<div className="absolute inset-0 z-0">
@@ -48,6 +45,7 @@ export default function HomePage() {
setSelectedShop(shop);
}}
selectedShop={selectedShop}
isDiscoveryOpen={isDiscoveryOpen}
/>
</div>

View File

@@ -1,4 +1,4 @@
import { X, MapPin, Globe, Phone, Coffee, ExternalLink, Search, ChevronRight } from "lucide-react";
import { X, MapPin, Globe, Phone, Coffee, ExternalLink, Search, ChevronRight, ChevronLeft } from "lucide-react";
import { Card } from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { ScrollArea } from "~/components/ui/scroll-area";
@@ -31,10 +31,13 @@ interface DrawerProps {
export default function Drawer({ shop, shops, onSelect, onClose, isOpen, onToggleOpen }: DrawerProps) {
const [searchQuery, setSearchQuery] = useState("");
const [imageLoading, setImageLoading] = useState(true);
const [activeShop, setActiveShop] = useState<CoffeeShop | null>(shop);
// Reset loading state when shop changes
// Update activeShop when shop changes, but only if it's not null
// This allows us to keep displaying the shop details while animating out
useEffect(() => {
if (shop) {
setActiveShop(shop);
setImageLoading(true);
}
}, [shop]);
@@ -46,154 +49,172 @@ export default function Drawer({ shop, shops, onSelect, onClose, isOpen, onToggl
return (
<div
className={`absolute top-20 left-0 h-[calc(100dvh-6rem)] w-full sm:w-[400px] z-30 p-4 pointer-events-none transition-transform duration-300 ease-in-out ${isOpen || shop ? 'translate-x-0' : '-translate-x-full'}`}
className={`absolute top-20 left-0 h-[calc(100dvh-6rem)] w-full sm:w-[400px] z-30 px-4 pt-3 pointer-events-none transition-transform duration-300 ease-in-out ${isOpen || shop ? 'translate-x-0' : '-translate-x-full'}`}
>
<Card className="h-full w-full bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 overflow-hidden flex flex-col gap-0 pointer-events-auto rounded-r-xl p-0 border-0 transition-all duration-300">
{shop ? (
// Details View
// Details View
<div className="h-full flex flex-col relative">
<Card className="h-full w-full bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 overflow-hidden relative shadow-xl rounded-r-xl border-0">
{/* Details View */}
<div
className={`absolute inset-0 z-20 transition-transform duration-300 ease-in-out bg-background/80 backdrop-blur-3xl ${shop ? 'translate-x-0' : 'translate-x-full'}`}
>
{activeShop && (
<div className="h-full flex flex-col relative">
<div className="absolute top-4 right-4 z-50 flex gap-2">
<Button
variant="ghost"
size="icon"
onClick={onClose}
className="bg-background/20 hover:bg-background/40 text-foreground rounded-full h-8 w-8 backdrop-blur-md border border-border/50"
>
<ChevronLeft className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => {
onClose();
if (onToggleOpen) onToggleOpen();
}}
className="bg-background/20 hover:bg-background/40 text-foreground rounded-full h-8 w-8 backdrop-blur-md border border-border/50"
>
<X className="w-4 h-4" />
</Button>
</div>
<ScrollArea className="flex-1 min-h-0">
{/* Header Image - Now part of scroll area */}
<div className="h-64 relative w-full bg-muted/20">
{imageLoading && (
<div
className="absolute inset-0 z-10 flex items-center justify-center"
style={{
maskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)',
WebkitMaskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)'
}}
>
<Skeleton className="h-full w-full absolute inset-0" />
<Coffee className="h-12 w-12 text-muted-foreground/50 animate-pulse relative z-20" />
</div>
)}
<div className="absolute inset-0 z-0">
<img
src={activeShop.image}
alt={activeShop.name}
className={`w-full h-full object-cover transition-opacity duration-500 ${imageLoading ? 'opacity-0' : 'opacity-100'}`}
onLoad={() => setImageLoading(false)}
style={{
maskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)',
WebkitMaskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)'
}}
/>
</div>
</div>
{/* Content - Overlaps image slightly or just follows */}
<div className="p-8 -mt-12 relative z-10">
<h2 className="text-3xl font-bold font-serif mb-4 text-primary leading-tight drop-shadow-md">{activeShop.name}</h2>
<div className="space-y-4 mb-8">
<div className="flex items-start gap-3 text-muted-foreground font-serif text-sm">
<MapPin className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
<span>{activeShop.address}</span>
</div>
{activeShop.phone && (
<div className="flex items-center gap-3 text-muted-foreground font-serif text-sm">
<Phone className="w-5 h-5 text-primary flex-shrink-0" />
<a href={`tel:${activeShop.phone}`} className="hover:text-foreground transition-colors">{activeShop.phone}</a>
</div>
)}
{activeShop.website && (
<div className="flex items-center gap-3 text-muted-foreground font-serif text-sm">
<Globe className="w-5 h-5 text-primary flex-shrink-0" />
<a href={activeShop.website} target="_blank" rel="noopener noreferrer" className="hover:text-foreground transition-colors flex items-center gap-1">
Visit Website <ExternalLink className="w-3 h-3" />
</a>
</div>
)}
</div>
<Separator className="bg-border/50 mb-6" />
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-2 text-foreground font-serif">About</h3>
<p className="text-muted-foreground leading-relaxed font-serif text-lg">
{activeShop.description}
</p>
</div>
<Button
asChild
size="sm"
className="w-auto px-6 bg-primary/20 hover:bg-primary/40 text-foreground font-semibold rounded-lg shadow-lg transition-all hover:scale-[1.02] border border-primary/50 backdrop-blur-md"
>
<a
href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${activeShop.name}, ${activeShop.address}`)}`}
target="_blank"
rel="noopener noreferrer"
>
Get Directions
</a>
</Button>
</div>
</div>
</ScrollArea>
</div>
)}
</div>
{/* List View */}
<div
className={`absolute inset-0 z-10 transition-all duration-300 ease-in-out flex flex-col h-full bg-background/0 ${shop ? '-translate-x-1/4 opacity-0 pointer-events-none' : 'translate-x-0 opacity-100 pointer-events-auto'}`}
>
<div className="p-4 border-b border-border/50 relative">
<Button
variant="ghost"
size="icon"
onClick={onClose}
className="absolute top-4 right-4 z-50 bg-background/20 hover:bg-background/40 text-foreground rounded-full h-8 w-8 backdrop-blur-md border border-border/50"
onClick={onToggleOpen}
className="absolute top-4 right-4 bg-background/20 hover:bg-background/40 text-foreground rounded-full h-8 w-8 backdrop-blur-md border border-border/50"
>
<X className="w-4 h-4" />
</Button>
<ScrollArea className="flex-1 min-h-0">
{/* Header Image - Now part of scroll area */}
<div className="h-64 relative w-full bg-muted/20">
{imageLoading && (
<div
className="absolute inset-0 z-10 flex items-center justify-center"
style={{
maskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)',
WebkitMaskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)'
}}
>
<Skeleton className="h-full w-full absolute inset-0" />
<Coffee className="h-12 w-12 text-muted-foreground/50 animate-pulse relative z-20" />
</div>
)}
<div className="absolute inset-0 z-0">
<img
src={shop.image}
alt={shop.name}
className={`w-full h-full object-cover transition-opacity duration-500 ${imageLoading ? 'opacity-0' : 'opacity-100'}`}
onLoad={() => setImageLoading(false)}
style={{
maskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)',
WebkitMaskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)'
}}
/>
</div>
</div>
{/* Content - Overlaps image slightly or just follows */}
<div className="p-8 -mt-12 relative z-10">
<h2 className="text-3xl font-bold font-serif mb-4 text-primary leading-tight drop-shadow-md">{shop.name}</h2>
<div className="space-y-4 mb-8">
<div className="flex items-start gap-3 text-muted-foreground font-serif text-sm">
<MapPin className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
<span>{shop.address}</span>
</div>
{shop.phone && (
<div className="flex items-center gap-3 text-muted-foreground font-serif text-sm">
<Phone className="w-5 h-5 text-primary flex-shrink-0" />
<a href={`tel:${shop.phone}`} className="hover:text-foreground transition-colors">{shop.phone}</a>
</div>
)}
{shop.website && (
<div className="flex items-center gap-3 text-muted-foreground font-serif text-sm">
<Globe className="w-5 h-5 text-primary flex-shrink-0" />
<a href={shop.website} target="_blank" rel="noopener noreferrer" className="hover:text-foreground transition-colors flex items-center gap-1">
Visit Website <ExternalLink className="w-3 h-3" />
</a>
</div>
)}
</div>
<Separator className="bg-border/50 mb-6" />
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-2 text-foreground font-serif">About</h3>
<p className="text-muted-foreground leading-relaxed font-serif text-lg">
{shop.description}
</p>
</div>
<Button
asChild
size="sm"
className="w-auto px-6 bg-primary/20 hover:bg-primary/40 text-foreground font-semibold rounded-lg shadow-lg transition-all hover:scale-[1.02] border border-primary/50 backdrop-blur-md"
>
<a
href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${shop.name}, ${shop.address}`)}`}
target="_blank"
rel="noopener noreferrer"
>
Get Directions
</a>
</Button>
</div>
</div>
</ScrollArea>
</div>
) : (
// List View
<div className="flex flex-col h-full">
<div className="p-4 border-b border-border/50 bg-background/40 backdrop-blur-md relative">
<Button
variant="ghost"
size="icon"
onClick={onToggleOpen}
className="absolute top-4 right-4 bg-background/20 hover:bg-background/40 text-foreground rounded-full h-8 w-8 backdrop-blur-md border border-border/50"
>
<X className="w-4 h-4" />
</Button>
<h2 className="text-xl font-bold font-serif mb-4 text-primary">Discover Coffee</h2>
<div className="relative">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search shops..."
className="pl-9 bg-background/50 border-border/50 focus:bg-background transition-colors"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<h2 className="text-xl font-bold font-serif mb-4 text-primary">Discover Coffee</h2>
<div className="relative">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search shops..."
className="pl-9 bg-background/20 border-border/50 focus:bg-background/40 transition-colors"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<ScrollArea className="flex-1 min-h-0">
<div className="p-4 space-y-3">
{filteredShops.map((s) => (
<div
key={s.id}
onClick={() => onSelect(s)}
className="group flex items-center gap-4 p-3 rounded-lg hover:bg-muted/50 transition-colors cursor-pointer border border-transparent hover:border-border/50"
>
<div className="h-16 w-16 rounded-md overflow-hidden flex-shrink-0 bg-muted relative">
<img src={s.image} alt={s.name} className="h-full w-full object-cover" />
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold font-serif text-foreground truncate group-hover:text-primary transition-colors">{s.name}</h3>
<p className="text-sm text-muted-foreground truncate">{s.address}</p>
</div>
<ChevronRight className="h-5 w-5 text-muted-foreground group-hover:text-primary transition-colors" />
</div>
))}
{filteredShops.length === 0 && (
<div className="text-center py-8 text-muted-foreground">
<Coffee className="h-12 w-12 mx-auto mb-3 opacity-20" />
<p>No shops found matching "{searchQuery}"</p>
</div>
)}
</div>
</ScrollArea>
</div>
)}
<ScrollArea className="flex-1 min-h-0">
<div className="p-4 space-y-3">
{filteredShops.map((s) => (
<div
key={s.id}
onClick={() => onSelect(s)}
className="group flex items-center gap-4 p-3 rounded-lg hover:bg-muted/50 transition-colors cursor-pointer border border-transparent hover:border-border/50"
>
<div className="h-16 w-16 rounded-md overflow-hidden flex-shrink-0 bg-muted relative">
<img src={s.image} alt={s.name} className="h-full w-full object-cover" />
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold font-serif text-foreground truncate group-hover:text-primary transition-colors">{s.name}</h3>
<p className="text-sm text-muted-foreground truncate">{s.address}</p>
</div>
<ChevronRight className="h-5 w-5 text-muted-foreground group-hover:text-primary transition-colors" />
</div>
))}
{filteredShops.length === 0 && (
<div className="text-center py-8 text-muted-foreground">
<Coffee className="h-12 w-12 mx-auto mb-3 opacity-20" />
<p>No shops found matching "{searchQuery}"</p>
</div>
)}
</div>
</ScrollArea>
</div>
</Card>
</div>
);

View File

@@ -72,7 +72,7 @@ export function LocateControl() {
size="icon"
onClick={handleLocate}
disabled={loading}
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-2xl text-foreground"
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-xl text-foreground"
>
<Locate className={`h-5 w-5 ${loading ? 'animate-pulse' : ''}`} />
<span className="sr-only">Locate me</span>

View File

@@ -3,7 +3,7 @@
import { MapContainer, TileLayer, Marker, Tooltip, useMap } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useTheme } from "next-themes";
import { MapStyleControl } from "./MapStyleControl";
import { LocateControl } from './LocateControl';
@@ -25,24 +25,91 @@ interface MapProps {
shops: CoffeeShop[];
onShopSelect: (shop: CoffeeShop) => void;
selectedShop: CoffeeShop | null;
isDiscoveryOpen: boolean;
}
const MapController = ({ selectedShop }: { selectedShop: CoffeeShop | null }) => {
const MapController = ({ selectedShop, isDiscoveryOpen }: { selectedShop: CoffeeShop | null, isDiscoveryOpen: boolean }) => {
const map = useMap();
const prevOpenRef = useRef(isDiscoveryOpen);
useEffect(() => {
// Handle general panning when drawer toggles (and no shop is selected)
const isDesktop = window.innerWidth >= 640;
if (prevOpenRef.current !== isDiscoveryOpen) {
if (!selectedShop && isDesktop) {
// If opening, we want to shift the view to the right (so panning map left? wait)
// If drawer opens on LEFT, the center of the remaining view is to the RIGHT.
// We want the content to move into that new center.
// So we pan the map in the POSITIVE X direction?
// Test: map.panBy([200, 0]) moves the map image 200px to the RIGHT relative to the viewport.
// This means a point at x=0 moves to x=200.
// This puts it into the open area. Correct.
// Inverting based on user feedback that it shifted wrong way.
// Opening -> Shift Map Image Left (View Right?) -> offset [-200, 0]
// Closing -> Shift Map Image Right (View Left?) -> offset [200, 0]
const offset = isDiscoveryOpen ? [-200, 0] : [200, 0];
map.panBy(offset as [number, number], {
animate: true,
duration: 0.5
});
}
prevOpenRef.current = isDiscoveryOpen;
}
}, [isDiscoveryOpen, selectedShop, map]);
useEffect(() => {
if (selectedShop) {
map.flyTo([selectedShop.lat, selectedShop.lng], 16, {
const targetLat = selectedShop.lat;
const targetLng = selectedShop.lng;
// Calculate offset if discovery panel is open and we're on desktop
let flyToOption = {
duration: 1.5,
easeLinearity: 0.25,
});
};
const isDesktop = window.innerWidth >= 640;
if (isDiscoveryOpen && isDesktop) {
// We need to offset the center so the point appears to the right of the drawer
// The drawer is 400px wide. We want to shift the center left by 200px (half drawer width)
// so that the target point appears 200px to the right of current center
// Get current zoom
const zoom = 16;
// Project the lat/lng to point
const point = map.project([targetLat, targetLng], zoom);
// Subtract offset (shift 'center' to the left, which moves 'view' to the right? Wait.)
// If we want the point to be at x + 200 (screen coords relative to center),
// we need the map center to be at x - 200 relative to point.
// Actually simpler: We want the point (targetLat, targetLng) to be at screen coordinates (width/2 + 200, height/2).
// Or simply: shift the target point by -200px in x before passing to flyTo? No, flyTo takes strict LatLng.
// Correct approach:
// Find the LatLng that, when centered, puts our target LatLng at the desired pixels.
// Center + Offset = Target -> Center = Target - Offset
const targetPoint = map.project([targetLat, targetLng], zoom);
// We want the target to appear 200px (half drawer width) to the right of the map center.
// So the new center should be 200px LEFT of the target.
const newCenterPoint = targetPoint.subtract([200, 0]);
const newCenter = map.unproject(newCenterPoint, zoom);
map.flyTo(newCenter, zoom, flyToOption);
} else {
map.flyTo([targetLat, targetLng], 16, flyToOption);
}
}
}, [selectedShop, map]);
}, [selectedShop, map, isDiscoveryOpen]);
return null;
};
const Map = ({ shops, onShopSelect, selectedShop }: MapProps) => {
const Map = ({ shops, onShopSelect, selectedShop, isDiscoveryOpen }: MapProps) => {
useEffect(() => {
// Fix for Leaflet default icon not found
// @ts-expect-error Fix for Leaflet default icon not found
@@ -122,7 +189,7 @@ const Map = ({ shops, onShopSelect, selectedShop }: MapProps) => {
zoomControl={false}
attributionControl={false}
>
<MapController selectedShop={selectedShop} />
<MapController selectedShop={selectedShop} isDiscoveryOpen={isDiscoveryOpen} />
<div className="absolute bottom-8 right-4 z-[1000] flex flex-col gap-2 items-end">
<LocateControl />
<ZoomControls />
@@ -141,7 +208,14 @@ const Map = ({ shops, onShopSelect, selectedShop }: MapProps) => {
click: () => onShopSelect(shop),
}}
>
<Tooltip direction="top" offset={[0, -20]} opacity={0.9}>
<Tooltip
key={`${shop.id}-${selectedShop?.id === shop.id}`}
direction="top"
offset={[0, -20]}
opacity={0.9}
permanent={selectedShop?.id === shop.id}
className={selectedShop?.id === shop.id ? 'force-show' : ''}
>
<div className="font-serif font-semibold text-sm">
{shop.name}
</div>

View File

@@ -20,6 +20,7 @@ interface MapLoaderProps {
shops: CoffeeShop[];
onShopSelect: (shop: CoffeeShop) => void;
selectedShop: CoffeeShop | null;
isDiscoveryOpen: boolean;
}
// Move dynamic import outside component to prevent re-imports
@@ -36,6 +37,6 @@ const Map = dynamic(() => import("./Map"), {
),
});
export default function MapLoader({ shops, onShopSelect, selectedShop }: MapLoaderProps) {
return <Map shops={shops} onShopSelect={onShopSelect} selectedShop={selectedShop} />;
export default function MapLoader({ shops, onShopSelect, selectedShop, isDiscoveryOpen }: MapLoaderProps) {
return <Map shops={shops} onShopSelect={onShopSelect} selectedShop={selectedShop} isDiscoveryOpen={isDiscoveryOpen} />;
}

View File

@@ -19,7 +19,7 @@ export function MapStyleControl({ currentStyle, onStyleChange }: MapStyleControl
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-2xl text-foreground">
<Button variant="outline" size="icon" className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-xl text-foreground">
<Layers className="h-5 w-5" />
<span className="sr-only">Change map style</span>
</Button>

View File

@@ -1,4 +1,4 @@
import { Coffee, PanelLeft, X } from "lucide-react";
import { Coffee, Search, X } from "lucide-react";
import { Button } from "~/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip";
import { useState, useEffect } from "react";
@@ -41,7 +41,7 @@ export default function Navbar({ isDiscoveryOpen, onToggleDiscovery }: NavbarPro
return (
<>
<div className="absolute top-4 left-4 right-4 z-[1000] flex justify-center pointer-events-none">
<div className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border border-border/50 rounded-xl p-2 flex items-center justify-between w-full pointer-events-auto">
<div className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border border-border/50 rounded-xl p-2 flex items-center justify-between w-full pointer-events-auto shadow-xl">
<div className="flex items-center gap-2 relative">
{/* Pulsing indicator ring - only during onboarding */}
{isOnboarding && showTooltip && (
@@ -61,11 +61,11 @@ export default function Navbar({ isDiscoveryOpen, onToggleDiscovery }: NavbarPro
}}
className={`h-10 w-10 rounded-lg hover:bg-background/40 transition-colors ${isDiscoveryOpen ? 'bg-background/40 text-primary' : 'text-muted-foreground'}`}
>
<PanelLeft className="h-5 w-5" />
<Search className="h-5 w-5" />
<span className="sr-only">Toggle Panel</span>
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" className="bg-background/80 backdrop-blur-xl border-border/50 text-foreground font-semibold shadow-2xl">
<TooltipContent side="right" className="bg-background/80 backdrop-blur-xl border-border/50 text-foreground font-semibold font-serif shadow-2xl">
<p>Discover Coffee Shops</p>
</TooltipContent>
</Tooltip>

View File

@@ -46,7 +46,7 @@ export function WelcomeModal() {
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-md bg-background/80 backdrop-blur-xl border-border/50">
<DialogContent className="sm:max-w-md bg-background/80 backdrop-blur-xl border-border/50 font-serif">
<DialogHeader>
<div className="mx-auto bg-primary/10 p-3 rounded-full mb-4 w-fit">
<Coffee className="h-8 w-8 text-primary" />

View File

@@ -13,7 +13,7 @@ export function ZoomControls() {
variant="outline"
size="icon"
onClick={() => map.zoomIn()}
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-2xl text-foreground hover:bg-background/70 dark:hover:bg-background/75"
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-xl text-foreground hover:bg-background/70 dark:hover:bg-background/75"
>
<Plus className="h-5 w-5" />
<span className="sr-only">Zoom in</span>
@@ -22,7 +22,7 @@ export function ZoomControls() {
variant="outline"
size="icon"
onClick={() => map.zoomOut()}
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-2xl text-foreground hover:bg-background/70 dark:hover:bg-background/75"
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-xl text-foreground hover:bg-background/70 dark:hover:bg-background/75"
>
<Minus className="h-5 w-5" />
<span className="sr-only">Zoom out</span>
@@ -31,7 +31,7 @@ export function ZoomControls() {
variant="outline"
size="icon"
onClick={() => map.setView([40.9645, -76.8845], 15)}
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-2xl text-foreground hover:bg-background/70 dark:hover:bg-background/75"
className="bg-background/60 dark:bg-background/65 backdrop-blur-2xl border-border/50 dark:border-border/50 h-10 w-10 rounded-lg shadow-xl text-foreground hover:bg-background/70 dark:hover:bg-background/75"
>
<Home className="h-5 w-5" />
<span className="sr-only">Reset view</span>