SkateSpots/src/components/Layout.tsx
shakespeare.diy fad3a3323d Replace Impact with Bebas Neue + Barlow Condensed for authentic skate typography
- Install @fontsource/bebas-neue and @fontsource/barlow-condensed
- Add .font-display (Bebas Neue) and .font-condensed (Barlow Condensed) CSS utilities
- Hero headline: split into two lines SKATE / SPOTS with clamp() fluid sizing,
  Bebas Neue replaces Impact throughout
- Subtitle: Barlow Condensed 700 with wide letter-spacing
- Description: Barlow Condensed italic
- All section headings (Featured Spots, Events, The Lab, CTA, New Here?): Bebas Neue
- Section subheadings: Barlow Condensed with tracked uppercase
- Desktop + mobile header logo: Bebas Neue
- Footer logo: Bebas Neue
- Tagline badge: Barlow Condensed 600

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 00:37:32 +07:00

820 lines
33 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from '@/components/ui/dropdown-menu';
import { LoginArea } from '@/components/auth/LoginArea';
import { SasquatchGuide } from '@/components/SasquatchGuide';
import { useSasquatchGuide } from '@/hooks/useSasquatchGuide';
import { BackupReminder } from '@/components/BackupReminder';
import { LanguageToggle } from '@/components/LanguageToggle';
import { useCashu } from '@/hooks/wallet/useCashu';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useTranslation } from '@/contexts/LanguageContext';
import { useLoggedInAccounts } from '@/hooks/useLoggedInAccounts';
import {
MapPin,
Calendar,
Menu,
Home,
Wallet,
Settings,
GraduationCap,
BarChart3,
MoreHorizontal,
Info,
Bookmark,
MessageCircle,
Gift,
LogOut,
User,
LogIn,
Plus,
CheckCircle2,
Database,
Package,
Zap,
Heart,
CreditCard,
} from 'lucide-react';
import { OfflineIndicator } from '@/components/OfflineIndicator';
import { UserStatusIndicator } from '@/components/UserStatusIndicator';
import { SetStatusDialog } from '@/components/SetStatusDialog';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
interface LayoutProps {
children: React.ReactNode;
}
// Nav item keys that map to translation keys
const navItemKeys = [
{ href: '/', labelKey: 'nav.home', icon: Home },
{ href: '/map', labelKey: 'nav.map', icon: MapPin },
{ href: '/lab', labelKey: 'nav.lab', icon: GraduationCap },
{ href: '/events', labelKey: 'nav.events', icon: Calendar },
];
// Format sats for display (e.g., 1234 -> "1.2k", 12345 -> "12k")
function formatSatsCompact(sats: number): string {
if (sats >= 1000000) {
return `${(sats / 1000000).toFixed(1)}M`;
}
if (sats >= 1000) {
return `${(sats / 1000).toFixed(sats >= 10000 ? 0 : 1)}k`;
}
return sats.toString();
}
function NavLink({
href,
label,
icon: Icon,
isActive,
onClick,
}: {
href: string;
label: string;
icon: React.ComponentType<{ className?: string }>;
isActive: boolean;
onClick?: () => void;
}) {
return (
<Link
to={href}
onClick={onClick}
className={cn(
'flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200',
isActive
? 'bg-primary text-primary-foreground shadow-lg shadow-primary/25'
: 'hover:bg-muted text-muted-foreground hover:text-foreground'
)}
>
<Icon className="h-5 w-5" />
<span className="font-medium">{label}</span>
</Link>
);
}
// Mobile header user menu - shows when logged in with dropdown for actions
function MobileHeaderUserMenu({
pubkey,
onLogout,
onSetStatus,
}: {
pubkey: string;
onLogout: () => void;
onSetStatus: () => void;
}) {
const author = useAuthor(pubkey);
const metadata = author.data?.metadata;
const displayName = metadata?.name ?? genUserName(pubkey);
const avatar = metadata?.picture;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="touch-manipulation relative flex items-center gap-2 px-2 py-1.5 rounded-full bg-green-500/10 border border-green-500/30 active:scale-95 transition-transform">
<Avatar className="h-7 w-7 border-2 border-green-500/50">
<AvatarImage src={avatar} alt={displayName} />
<AvatarFallback className="bg-green-500/20 text-green-400 text-[10px] font-bold">
{displayName.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
{/* Status indicator */}
<UserStatusIndicator pubkey={pubkey} size="sm" />
<span className="text-green-400 text-xs font-bold uppercase tracking-wide hidden xs:inline">
<CheckCircle2 className="h-3 w-3 inline mr-1" />
In
</span>
{/* Green online indicator dot */}
<span className="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 bg-green-500 border-2 border-black rounded-full xs:hidden" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56 p-2">
{/* User info header */}
<div className="px-2 py-3 mb-2 rounded-lg bg-green-500/10 border border-green-500/20">
<div className="flex items-center gap-3">
<div className="relative">
<Avatar className="h-10 w-10 border-2 border-green-500/50">
<AvatarImage src={avatar} alt={displayName} />
<AvatarFallback className="bg-green-500/20 text-green-400 text-sm font-bold">
{displayName.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<UserStatusIndicator pubkey={pubkey} size="sm" className="absolute -bottom-1 -right-1" />
</div>
<div className="flex-1 min-w-0">
<p className="font-bold text-sm truncate">{displayName}</p>
<p className="text-xs text-green-500 flex items-center gap-1">
<CheckCircle2 className="h-3 w-3" />
Logged In
</p>
</div>
</div>
</div>
{/* Set Status - highlighted */}
<DropdownMenuItem
onClick={onSetStatus}
className="flex items-center gap-2 cursor-pointer mb-1 bg-green-500/10 border border-green-500/30 rounded-lg"
>
<span className="relative flex h-2.5 w-2.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500" />
</span>
<span className="text-green-400 font-bold">Set Status</span>
<span className="ml-auto text-xs text-green-500">🛹</span>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/create-spot" className="flex items-center gap-2 cursor-pointer">
<Plus className="h-4 w-4 text-[#ccff00]" />
<span className="text-[#ccff00] font-medium">Add Spot</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/settings" className="flex items-center gap-2 cursor-pointer">
<User className="h-4 w-4" />
My Profile
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/settings?tab=mydata" className="flex items-center gap-2 cursor-pointer">
<Database className="h-4 w-4 text-[#ccff00]" />
<span className="text-[#ccff00]">My Data</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/stash" className="flex items-center gap-2 cursor-pointer">
<Package className="h-4 w-4 text-purple-400" />
<span className="text-purple-400">My Stash</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/wallet" className="flex items-center gap-2 cursor-pointer">
<Wallet className="h-4 w-4 text-orange-400" />
<span className="text-orange-400">Wallet</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={onLogout}
className="flex items-center gap-2 cursor-pointer text-red-400"
>
<LogOut className="h-4 w-4" />
Log Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
function MobileNav({
isActive,
cashuBalance,
isLoggedIn,
userPubkey,
userAvatar,
userName,
t,
}: {
isActive: (path: string) => boolean;
cashuBalance: number;
isLoggedIn: boolean;
userPubkey?: string;
userAvatar?: string;
userName?: string;
t: (key: string) => string;
}) {
// Decide which tabs to show — max 5 on small screens
// Logged in: Home, Map, Lab, Events, Profile (wallet in profile dropdown)
// Logged out: Home, Map, Lab, Events, Login
const tabs = [
...navItemKeys,
...(isLoggedIn
? [{ href: '/wallet', labelKey: 'nav.wallet', icon: Wallet }]
: []),
];
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 md:hidden">
{/* Nav bar */}
<div
className="glass border-t border-white/10"
style={{ paddingBottom: 'env(safe-area-inset-bottom, 0px)', paddingLeft: 'env(safe-area-inset-left, 0px)', paddingRight: 'env(safe-area-inset-right, 0px)' }}
>
<div className="flex items-stretch justify-around px-1 pt-1 pb-1">
{tabs.map((item) => {
const active = isActive(item.href);
const isWallet = item.href === '/wallet';
return (
<Link
key={item.href}
to={item.href}
className={cn(
'relative flex flex-col items-center justify-center gap-0.5 py-1.5 px-1 rounded-xl transition-all duration-150 flex-1 touch-manipulation active:scale-90',
active
? isWallet ? 'text-orange-400' : 'text-[#ff0040]'
: 'text-gray-500'
)}
>
{/* Active indicator dot */}
{active && (
<span className={cn(
'absolute top-0 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full',
isWallet ? 'bg-orange-400' : 'bg-[#ff0040]'
)} />
)}
{isWallet ? (
<div className="relative">
<Wallet className={cn('h-[22px] w-[22px] transition-transform', active && 'scale-110')} />
{cashuBalance > 0 && (
<span className="absolute -top-1.5 -right-2.5 min-w-[16px] h-4 px-1 text-[8px] font-bold bg-orange-500 text-white rounded-full flex items-center justify-center leading-none">
{formatSatsCompact(cashuBalance)}
</span>
)}
</div>
) : (
<item.icon className={cn('h-[22px] w-[22px] transition-transform', active && 'scale-110')} />
)}
<span className="text-[9px] xs:text-[10px] font-semibold leading-tight tracking-wide uppercase">
{t(item.labelKey)}
</span>
</Link>
);
})}
{/* Profile / Login tab */}
<Link
to="/settings"
className={cn(
'relative flex flex-col items-center justify-center gap-0.5 py-1.5 px-1 rounded-xl transition-all duration-150 flex-1 touch-manipulation active:scale-90',
isActive('/settings') ? 'text-[#ff0040]' : 'text-gray-500'
)}
>
{isActive('/settings') && (
<span className="absolute top-0 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-[#ff0040]" />
)}
{isLoggedIn && userPubkey ? (
<div className="relative">
<Avatar className={cn(
'h-[22px] w-[22px] border-2 transition-transform',
isActive('/settings') ? 'border-[#ff0040] scale-110' : 'border-gray-600'
)}>
<AvatarImage src={userAvatar} alt={userName} />
<AvatarFallback className="bg-[#ff0040]/20 text-[#ff0040] text-[8px] font-bold">
{(userName || '??').slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 bg-green-500 border border-black rounded-full" />
</div>
) : (
<User className="h-[22px] w-[22px]" />
)}
<span className="text-[9px] xs:text-[10px] font-semibold leading-tight tracking-wide uppercase">
{isLoggedIn ? 'Me' : 'Log In'}
</span>
</Link>
</div>
</div>
</nav>
);
}
export function Layout({ children }: LayoutProps) {
const location = useLocation();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [showStatusDialog, setShowStatusDialog] = useState(false);
const { currentMessage, dismissCurrentMessage } = useSasquatchGuide();
const { user } = useCurrentUser();
const { totalBalance: cashuBalance, isInitialized: hasCashu } = useCashu();
const { t } = useTranslation();
const { currentUser: currentAccount, removeLogin } = useLoggedInAccounts();
const author = useAuthor(user?.pubkey || '');
const displayName = author.data?.metadata?.name ?? genUserName(user?.pubkey);
const userAvatar = author.data?.metadata?.picture;
const isActive = (path: string) => {
if (path === '/') return location.pathname === '/';
return location.pathname.startsWith(path);
};
return (
<div className="min-h-screen bg-black text-white">
{/* Desktop Header */}
<header className="hidden md:flex sticky top-0 z-50 border-b border-red-500/30 bg-black/95 backdrop-blur">
<div className="container flex items-center justify-between h-16 px-4">
<Link to="/" className="flex items-center gap-3 group">
<div className="relative">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-red-500 to-orange-500 flex items-center justify-center shadow-lg">
<MapPin className="h-5 w-5 text-white" />
</div>
</div>
<span className="font-display text-2xl neon-red tracking-wide" style={{
textShadow: '0 0 10px #ff0040, 0 0 20px #ff0040'
}}>SKATESPOTS</span>
</Link>
<nav className="flex items-center gap-1">
{navItemKeys.map((item) => (
<Link
key={item.href}
to={item.href}
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-lg transition-all duration-200 text-sm font-bold uppercase tracking-wide',
isActive(item.href)
? 'text-red-500'
: 'text-gray-400 hover:text-red-400'
)}
>
<item.icon className="h-4 w-4" />
{t(item.labelKey)}
</Link>
))}
{/* More Menu - Desktop */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-lg transition-all duration-200 text-sm font-bold uppercase tracking-wide h-auto',
(isActive('/bounties') || isActive('/stats') || isActive('/about'))
? 'text-red-500'
: 'text-gray-400 hover:text-red-400'
)}
>
<MoreHorizontal className="h-4 w-4" />
{t('nav.more')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
{user && (
<DropdownMenuItem asChild>
<Link to="/collections" className="flex items-center gap-2 cursor-pointer">
<Bookmark className="h-4 w-4" />
My Collections
</Link>
</DropdownMenuItem>
)}
{user && (
<DropdownMenuItem asChild>
<Link to="/stash" className="flex items-center gap-2 cursor-pointer">
<Package className="h-4 w-4 text-purple-400" />
<span className="text-purple-400">My Stash</span>
</Link>
</DropdownMenuItem>
)}
<DropdownMenuItem asChild>
<Link to="/bounties" className="flex items-center gap-2 cursor-pointer">
<Gift className="h-4 w-4 text-orange-400" />
<span className="text-orange-400">Map & Earn</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/stats" className="flex items-center gap-2 cursor-pointer">
<BarChart3 className="h-4 w-4" />
{t('nav.stats')}
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link to="/about" className="flex items-center gap-2 cursor-pointer">
<Info className="h-4 w-4" />
{t('nav.about')}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/card" className="flex items-center gap-2 cursor-pointer">
<CreditCard className="h-4 w-4 text-[#ccff00]" />
<span className="text-[#ccff00]">Rider Card</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/supporters" className="flex items-center gap-2 cursor-pointer">
<Heart className="h-4 w-4 text-[#ff0040]" />
<span className="text-[#ff0040]">Supporters ⚡</span>
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</nav>
<div className="flex items-center gap-3">
{/* Messages Quick Access - Desktop */}
{user && (
<Link to="/messages">
<Button
variant="ghost"
size="icon"
className={cn(
isActive('/messages')
? "text-[#ff0040] bg-[#ff0040]/10"
: "text-gray-400 hover:text-[#ff0040] hover:bg-[#ff0040]/10"
)}
title="Messages"
>
<MessageCircle className="h-5 w-5" />
</Button>
</Link>
)}
{/* Collections Quick Access - Desktop */}
{user && (
<Link to="/collections">
<Button
variant="ghost"
size="icon"
className={cn(
isActive('/collections')
? "text-[#ccff00] bg-[#ccff00]/10"
: "text-gray-400 hover:text-[#ccff00] hover:bg-[#ccff00]/10"
)}
title="My Collections"
>
<Bookmark className="h-5 w-5" />
</Button>
</Link>
)}
{/* Wallet Quick Access - Desktop */}
{user && (
<Link to="/wallet">
<Button
variant="ghost"
className={cn(
"gap-2 px-3",
isActive('/wallet')
? "text-orange-500 bg-orange-500/10"
: "text-gray-400 hover:text-orange-400 hover:bg-orange-500/10"
)}
>
<Wallet className="h-5 w-5" />
{hasCashu && cashuBalance > 0 && (
<span className="text-sm font-bold">
{formatSatsCompact(cashuBalance)}
</span>
)}
</Button>
</Link>
)}
{/* Status Quick Access - Desktop */}
{user && (
<Button
variant="outline"
className="gap-2 px-3 border-green-500/50 text-green-400 hover:text-green-300 hover:bg-green-500/10 hover:border-green-500"
onClick={() => setShowStatusDialog(true)}
title="Set Your Status"
>
<span className="relative flex h-2.5 w-2.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500" />
</span>
<span className="text-sm font-bold">Status</span>
</Button>
)}
{/* Offline Indicator */}
<OfflineIndicator />
{/* Language Toggle */}
<LanguageToggle compact className="text-gray-400 hover:text-white" />
<Link to="/settings">
<Button variant="ghost" size="icon">
<Settings className="h-5 w-5" />
</Button>
</Link>
<LoginArea className="max-w-60" />
</div>
</div>
</header>
{/* Mobile Header */}
<header className="md:hidden sticky top-0 z-50 border-b border-red-500/30 bg-black/95 backdrop-blur safe-area-top">
<div className="flex items-center justify-between h-14 px-3 gap-3">
<Link to="/" className="flex items-center gap-2 touch-manipulation active:opacity-80 flex-1 min-w-0">
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-red-500 to-orange-500 flex items-center justify-center flex-shrink-0 shadow-lg">
<MapPin className="h-4 w-4 text-white" />
</div>
<span className="font-display text-xl neon-red tracking-wide" style={{
textShadow: '0 0 8px #ff0040'
}}>SKATESPOTS</span>
</Link>
<div className="flex items-center gap-1 flex-shrink-0">
{/* User menu when logged in / Login button when not */}
{user ? (
<MobileHeaderUserMenu
pubkey={user.pubkey}
onLogout={() => removeLogin(currentAccount?.id || '')}
onSetStatus={() => setShowStatusDialog(true)}
/>
) : (
<LoginArea compact className="flex flex-row gap-1" />
)}
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
<SheetTrigger asChild>
<Button variant="ghost" size="icon" className="h-10 w-10 touch-manipulation active:scale-95 transition-transform">
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-80">
<div className="flex flex-col gap-6 mt-6">
{/* Wallet Card - Mobile Menu */}
{user && (
<Link
to="/wallet"
onClick={() => setMobileMenuOpen(false)}
className="block"
>
<div className={cn(
"p-4 rounded-xl border-2 transition-all",
isActive('/wallet')
? "bg-orange-500/20 border-orange-500/50"
: "bg-orange-500/10 border-orange-500/30 hover:bg-orange-500/20"
)}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-orange-500/20 flex items-center justify-center">
<Wallet className="h-5 w-5 text-orange-500" />
</div>
<div>
<p className="font-bold text-sm">{t('wallet.cashu.title')}</p>
<p className="text-xs text-muted-foreground">Ecash</p>
</div>
</div>
{hasCashu && (
<div className="text-right">
<p className="font-bold text-orange-500">
{cashuBalance.toLocaleString()}
</p>
<p className="text-xs text-muted-foreground">sats</p>
</div>
)}
</div>
</div>
</Link>
)}
<div className="flex flex-col gap-2">
{navItemKeys.map((item) => (
<NavLink
key={item.href}
href={item.href}
label={t(item.labelKey)}
icon={item.icon}
isActive={isActive(item.href)}
onClick={() => setMobileMenuOpen(false)}
/>
))}
{user && (
<NavLink
href="/messages"
label="Messages"
icon={MessageCircle}
isActive={isActive('/messages')}
onClick={() => setMobileMenuOpen(false)}
/>
)}
{user && (
<NavLink
href="/collections"
label="My Collections"
icon={Bookmark}
isActive={isActive('/collections')}
onClick={() => setMobileMenuOpen(false)}
/>
)}
{user && (
<NavLink
href="/stash"
label="My Stash"
icon={Package}
isActive={isActive('/stash')}
onClick={() => setMobileMenuOpen(false)}
/>
)}
<NavLink
href="/bounties"
label="Map & Earn"
icon={Gift}
isActive={isActive('/bounties')}
onClick={() => setMobileMenuOpen(false)}
/>
<NavLink
href="/stats"
label={t('nav.stats')}
icon={BarChart3}
isActive={isActive('/stats')}
onClick={() => setMobileMenuOpen(false)}
/>
<NavLink
href="/about"
label={t('nav.about')}
icon={Info}
isActive={isActive('/about')}
onClick={() => setMobileMenuOpen(false)}
/>
<NavLink
href="/card"
label="Rider Card"
icon={CreditCard}
isActive={isActive('/card')}
onClick={() => setMobileMenuOpen(false)}
/>
<NavLink
href="/supporters"
label="Supporters ⚡"
icon={Heart}
isActive={isActive('/supporters')}
onClick={() => setMobileMenuOpen(false)}
/>
<NavLink
href="/settings"
label={t('nav.settings')}
icon={Settings}
isActive={isActive('/settings')}
onClick={() => setMobileMenuOpen(false)}
/>
</div>
{/* Language Toggle in Mobile Menu */}
<div className="py-3 border-t">
<LanguageToggle className="w-full justify-start" variant="ghost" />
</div>
{/* Login/Logout Section */}
<div className="pt-4 border-t space-y-3">
{user ? (
<>
{/* User Profile Card */}
<div className="p-4 rounded-xl bg-gradient-to-br from-green-500/10 to-green-500/5 border border-green-500/20">
<div className="flex items-center gap-3">
<Avatar className="h-12 w-12 border-2 border-green-500/50">
<AvatarImage src={author.data?.metadata?.picture} alt={displayName} />
<AvatarFallback className="bg-[#ff0040]/20 text-[#ff0040] text-sm font-bold">
{displayName.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="font-bold text-base truncate">{displayName}</p>
<p className="text-sm text-green-500 flex items-center gap-1.5">
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
Logged In
</p>
</div>
</div>
</div>
{/* Logout Button - Big and Clear */}
<Button
variant="outline"
size="lg"
className="w-full justify-center gap-3 text-red-400 border-red-500/50 hover:bg-red-500/20 hover:text-red-300 hover:border-red-500 h-12 text-base font-bold"
onClick={() => {
removeLogin(currentAccount?.id || '');
setMobileMenuOpen(false);
}}
>
<LogOut className="h-5 w-5" />
Log Out
</Button>
</>
) : (
<>
{/* Not Logged In - Clear CTA */}
<div className="p-4 rounded-xl bg-gradient-to-br from-[#ff0040]/10 to-[#ff0040]/5 border border-[#ff0040]/20 text-center">
<User className="h-10 w-10 mx-auto mb-2 text-[#ff0040]/50" />
<p className="font-bold text-base mb-1">Not Logged In</p>
<p className="text-sm text-muted-foreground mb-4">
Log in to add spots, zap skaters, and save your favorites
</p>
<LoginArea className="w-full justify-center" />
</div>
</>
)}
</div>
</div>
</SheetContent>
</Sheet>
</div>
</div>
</header>
{/* Backup Reminder */}
<BackupReminder />
{/* Main Content — extra bottom padding on mobile so the fixed bottom nav never overlaps */}
<main className="pb-[calc(5rem+env(safe-area-inset-bottom,0px))] md:pb-6">
{children}
</main>
{/* Footer - Desktop */}
<footer className="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>Made with <span className="text-red-500">❤️</span> for the skateboarding community</span>
<div className="flex items-center gap-4 text-xs text-gray-500">
<Link
to="/license"
className="hover:text-[#ccff00] transition-colors font-medium"
>
License
</Link>
<Link
to="/supporters"
className="hover:text-[#ccff00] transition-colors font-medium"
>
Supporters
</Link>
<span className="flex items-center gap-1.5">
<span>Not sponsored by</span>
<span className="inline-flex items-center gap-1.5">
<img
src="https://opensats.org/favicon.ico"
alt="OpenSats"
className="h-4 w-4"
/>
<span className="font-medium text-gray-400">OpenSats</span>
</span>
</span>
</div>
</div>
</div>
</footer>
{/* No mobile footer - bottom nav provides navigation, keeps UI clean */}
{/* Mobile Bottom Navigation */}
<MobileNav
isActive={isActive}
cashuBalance={cashuBalance}
isLoggedIn={!!user}
userPubkey={user?.pubkey}
userAvatar={userAvatar}
userName={displayName}
t={t}
/>
{/* Sasquatch Guide (Desktop only) */}
<div className="hidden md:block">
<SasquatchGuide
message={currentMessage}
onDismiss={dismissCurrentMessage}
/>
</div>
{/* Set Status Dialog */}
<SetStatusDialog
open={showStatusDialog}
onOpenChange={setShowStatusDialog}
/>
</div>
);
}