import { useMemo } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { SpotCard, SpotCardSkeleton } from '@/components/SpotCard'; import { EventCard, EventCardSkeleton } from '@/components/EventCard'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { LoginArea } from '@/components/auth/LoginArea'; import { useSkateSpots } from '@/hooks/useSkateSpots'; import { useUpcomingSkateEvents } from '@/hooks/useSkateEvents'; import { useLabProfiles, type LabProfile } from '@/hooks/useLabProfiles'; import { useZapLeaderboard } from '@/hooks/useZapLeaderboard'; import { SKATE_SPOT_KIND, type SkateSpot } from '@/lib/types'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useTranslation } from '@/contexts/language'; import { genUserName } from '@/lib/genUserName'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Badge } from '@/components/ui/badge'; import { MapPin, Calendar, Plus, ChevronRight, Sparkles, CheckCircle2, GraduationCap, Video, Crown, Zap, } from 'lucide-react'; import { FounderFollow } from '@/components/FounderFollow'; import { SponsorSlot } from '@/components/SponsorSlot'; function HeroSection() { const { user } = useCurrentUser(); const { t } = useTranslation(); return (
{/* Neon background glows */}
{t('home.tagline')}
{/* Main headline — Bebas Neue split into two lines for visual impact */}

SKATE SPOTS

{/* Subtitle — Barlow Condensed, bold and spaced */}

{t('home.subtitle')}

{t('home.description')}

⚠️ {t('home.betaWarning')}
📱 {t('home.pwaHint')}
{user && ( )}
{/* Login/Signup Area - Prominent for logged out users (desktop only, mobile has it in header) */} {!user && (

Join the community to add spots, create events & more

)}
); } function FeaturedSpots() { const { data: spots, isLoading } = useSkateSpots(8); const { t } = useTranslation(); return (

{t('home.featuredSpots')}

{t('home.featuredSpotsDesc')}

{isLoading ? (
{Array.from({ length: 4 }).map((_, i) => ( ))}
) : spots && spots.length > 0 ? (
{spots.slice(0, 4).map((spot) => ( ))}
) : (

{t('home.noSpotsYet')}

)}
); } function UpcomingEvents() { const { data: events, isLoading } = useUpcomingSkateEvents(4); const { t } = useTranslation(); return (

{t('home.upcomingEvents')}

{t('home.upcomingEventsDesc')}

{isLoading ? (
{Array.from({ length: 4 }).map((_, i) => ( ))}
) : events && events.length > 0 ? (
{events.map((event) => ( ))}
) : (

{t('home.noEventsYet')}

)}
); } function TopZappedSection() { // Last 30 days of zap activity, aggregated once. const since = useMemo(() => Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60, []); const { data } = useZapLeaderboard(since); const { data: spots } = useSkateSpots(200); const spotByCoord = useMemo(() => { const map = new Map(); for (const s of spots ?? []) { map.set(`${SKATE_SPOT_KIND}:${s.pubkey}:${s.identifier}`, s); } return map; }, [spots]); // Only spots we can actually link to, so every row lands somewhere real. const top = useMemo( () => (data?.targets ?? []) .filter((t) => t.key.startsWith(`${SKATE_SPOT_KIND}:`) && spotByCoord.has(t.key)) .slice(0, 4), [data, spotByCoord], ); // Never render an empty section on the home page. if (top.length === 0) return null; return (

TOP ZAPPED

Most supported spots this month

{top.map((t, i) => { const spot = spotByCoord.get(t.key)!; const isShop = spot.types.includes('shop'); return (
{i === 0 ? : `#${i + 1}`}

{spot.title}

{spot.location &&

{spot.location}

}
{t.sats.toLocaleString()}
); })}
); } function TheLabSection() { const { data: profiles, isLoading } = useLabProfiles(100); const { t } = useTranslation(); // Get 2 coaches and 2 filmers const coaches = profiles?.filter(p => p.profileType === 'coach').slice(0, 2) || []; const filmers = profiles?.filter(p => p.profileType === 'filmer').slice(0, 2) || []; const displayProfiles = [...coaches, ...filmers]; return (

THE LAB

{t('home.theLabDesc')}

{isLoading ? (
{Array.from({ length: 4 }).map((_, i) => (
{/* Avatar skeleton */}
{/* Name skeleton */}
{/* Location skeleton */}
{/* Bio skeleton */}
{/* Skills skeleton */}
))}
) : displayProfiles.length > 0 ? (
{displayProfiles.map((profile) => ( ))}
) : (

{t('lab.noCoaches')}

)}
); } function LabProfileCard({ profile }: { profile: LabProfile }) { const author = useAuthor(profile.pubkey); const metadata = author.data?.metadata; const displayName = metadata?.name ?? genUserName(profile.pubkey); const avatar = profile.image || metadata?.picture; const isCoach = profile.profileType === 'coach'; const accentColor = isCoach ? '#ff0040' : '#ff6600'; return (
{displayName.slice(0, 2).toUpperCase()}

{displayName}

{isCoach ? ( ) : (
{profile.location && (

{profile.location}

)} {profile.bio && (

{profile.bio}

)} {profile.skills.length > 0 && (
{profile.skills.slice(0, 3).map((skill: string) => ( {skill} ))} {profile.skills.length > 3 && ( +{profile.skills.length - 3} )}
)}
); } function NewToNostrSection() { const { user } = useCurrentUser(); // Don't show this section if user is already logged in if (user) return null; return (
{/* Neon glow */}

NEW HERE?

Create a free account to add spots, join events, connect with skaters, and become part of the community. No email required.

{/* Founder Follow - prominent placement */}
Follow the Founder

Hold the avatar for 3 seconds to follow and stay updated on new features and events!

Your account works across all apps in the network

); } function CTASection() { const { t } = useTranslation(); return (
{/* Neon glows */}

{t('home.readyToShare')}

{t('home.readyToShareDesc')}

); } function Footer() { const { t } = useTranslation(); return ( ); } export default function Index() { useSeoMeta({ title: 'SkateSpots - Discover Skateboarding Locations Worldwide', description: 'Find the best skateboarding spots, connect with fellow skaters, and discover events near you. Built on Nostr for true decentralization.', }); // Platform sponsors - currently empty (invisible placeholder) // When sponsors are added, pass them here: sponsors={[{name: "Vans", logo: "...", tier: "title"}]} const platformSponsors = undefined; return ( {/* Sponsor slot - invisible until sponsors are added */}