redesign: /onboarding full rewrite

Hero:
- "SKATE YOUR WORLD" headline with neon red/lime treatment
- Beta badge pill with live pulse dot
- Feature pills row (spots, lab, events, gear, stash, radio, zaps)
- Primary CTA right in the hero — no scrolling to get started
- "No email. No password. Own your identity forever." copy

Layout:
- Removed mechanical 4-step progress tracker (step 2 auto-completing was confusing)
- Replaced with natural scrolling sections with clear headings
- Logged-in state shows green confirmation bar with FounderFollow

What you can do:
- 6-card grid: Map, Lab, Events, Buy/Sell, Stash, Radio — all linked

What is Nostr:
- Skater-friendly explanation with 4 benefit pills
- Deep dive button for relay education modal

Zaps & Wallet:
- "Optional" badge so new users don't feel overwhelmed
- Side-by-side: Cashu (built-in) + External wallet (NWC)

FAQ (accordion — 8 questions, no duplicates):
- "Is it free?" — combined the two identical free questions
- "What is Nostr?" — proper explanation
- "What's an nsec?" — key backup warning
- "What signers work?" — desktop/Android/iOS with real links
- "Can I use it on my phone?" — PWA + Zapstore mention
- "What is a zap?" — simple skater framing
- "Will my spots be deleted?" — relay permanence
- "Private property spots?" — community responsibility
- "How to report a bug?" — links to cordn.net beta coordinator

Beta section:
- cordn.net/p/npub1e2ayjl... as primary beta contact (clickable card)
- Founder npub with Copy + DM buttons as secondary
- Removed v0.1.0 hardcoded version string

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-24 02:00:12 +07:00
parent 254484a354
commit d8e49e7f65

View file

@ -4,7 +4,6 @@ import { Link } from 'react-router-dom';
import { Layout } from '@/components/Layout'; import { Layout } from '@/components/Layout';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Dialog, DialogContent } from '@/components/ui/dialog'; import { Dialog, DialogContent } from '@/components/ui/dialog';
import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useCashu } from '@/hooks/wallet/useCashu'; import { useCashu } from '@/hooks/wallet/useCashu';
@ -17,7 +16,6 @@ import { cn } from '@/lib/utils';
import { import {
CheckCircle2, CheckCircle2,
Globe, Globe,
DollarSign,
Shield, Shield,
Video, Video,
Zap, Zap,
@ -25,89 +23,81 @@ import {
Calendar, Calendar,
GraduationCap, GraduationCap,
AlertCircle, AlertCircle,
ShoppingBag,
ExternalLink, ExternalLink,
Wallet, Wallet,
ChevronRight, ChevronDown,
Nut, Nut,
BookOpen, BookOpen,
Crown, ShoppingBag,
Package,
Key,
Smartphone,
MessageCircle,
Radio,
Lock,
Users,
} from 'lucide-react'; } from 'lucide-react';
import { FounderFollow } from '@/components/FounderFollow'; import { FounderFollow } from '@/components/FounderFollow';
// Progress tracker component // ─── Accordion FAQ ────────────────────────────────────────────────────────────
function OnboardingProgress({ function FAQ({ q, a }: { q: string; a: React.ReactNode }) {
currentStep, const [open, setOpen] = useState(false);
totalSteps,
completedSteps
}: {
currentStep: number;
totalSteps: number;
completedSteps: number[];
}) {
const progressPercent = (completedSteps.length / totalSteps) * 100;
return ( return (
<div className="mb-8"> <button
<div className="flex items-center justify-between mb-2"> onClick={() => setOpen(o => !o)}
<span className="text-sm font-bold text-gray-400 uppercase tracking-wide"> className="w-full text-left group"
Your Progress >
</span> <div className={cn(
<span className="text-sm font-bold text-[#ccff00]"> 'rounded-2xl border transition-all duration-200',
{completedSteps.length} of {totalSteps} complete open
</span> ? 'bg-white/6 border-white/15'
</div> : 'bg-white/3 border-white/8 hover:bg-white/5 hover:border-white/12'
<Progress value={progressPercent} className="h-2 bg-gray-800" /> )}>
<div className="flex justify-between mt-3"> <div className="flex items-center justify-between p-4 gap-3">
{[1, 2, 3, 4].map((step) => ( <span className="font-bold text-sm text-white leading-snug">{q}</span>
<div <ChevronDown className={cn(
key={step} 'h-4 w-4 text-gray-500 flex-shrink-0 transition-transform duration-200',
className={cn( open && 'rotate-180 text-[#ff0040]'
"flex items-center gap-2", )} />
completedSteps.includes(step) ? "text-[#ccff00]" : "text-gray-500" </div>
)} {open && (
> <div className="px-4 pb-4 text-sm text-gray-400 leading-relaxed border-t border-white/6 pt-3">
<div className={cn( {a}
"w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-all",
completedSteps.includes(step)
? "bg-[#ccff00] text-black border-[#ccff00]"
: currentStep === step
? "border-[#ff0040] text-[#ff0040] bg-[#ff0040]/10"
: "border-gray-600"
)}>
{completedSteps.includes(step) ? <CheckCircle2 className="h-5 w-5" /> : step}
</div>
<span className="hidden sm:inline text-xs font-medium">
{step === 1 && "Login"}
{step === 2 && "Explore"}
{step === 3 && "Wallet"}
{step === 4 && "Go Skate"}
</span>
</div> </div>
))} )}
</div> </div>
</button>
);
}
// ─── Feature pill ─────────────────────────────────────────────────────────────
function FeaturePill({ emoji, label }: { emoji: string; label: string }) {
return (
<div className="flex items-center gap-2 px-3 py-2 rounded-full bg-white/5 border border-white/8 text-sm text-gray-300">
<span>{emoji}</span>
<span className="font-medium">{label}</span>
</div> </div>
); );
} }
export default function OnboardingPage() { export default function OnboardingPage() {
useSeoMeta({ useSeoMeta({
title: 'Get Started - SkateSpots', title: 'Get Started — SkateSpots',
description: 'Learn how to use SkateSpots and join the skateboarding community on Nostr.', description: 'Join the decentralized skateboarding community. Find spots, connect with coaches, send zaps. Free forever.',
}); });
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const { isInitialized: hasCashu, totalBalance: cashuBalance } = useCashu(); const { isInitialized: hasCashu, totalBalance: cashuBalance } = useCashu();
const { toast } = useToast(); const { toast } = useToast();
const [showRelayEducation, setShowRelayEducation] = useState(false); const [showRelayEducation, setShowRelayEducation] = useState(false);
const [hasSeenRelayEducation, setHasSeenRelayEducation] = useLocalStorage('seen-relay-education', false); const [, setHasSeenRelayEducation] = useLocalStorage('seen-relay-education', false);
const handleRelayEducationComplete = () => { const handleRelayEducationComplete = () => {
setShowRelayEducation(false); setShowRelayEducation(false);
setHasSeenRelayEducation(true); setHasSeenRelayEducation(true);
toast({ toast({
title: '🎉 Nice!', title: '🎉 Nice!',
description: 'You now understand how Nostr works. Let\'s go skate!', description: "You now understand how Nostr works. Let's go skate!",
}); });
}; };
@ -115,499 +105,360 @@ export default function OnboardingPage() {
navigator.clipboard.writeText('npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx'); navigator.clipboard.writeText('npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx');
toast({ toast({
title: 'Copied!', title: 'Copied!',
description: 'npub copied to clipboard. Paste it in your favorite Nostr client to send a DM.', description: 'npub copied to clipboard.',
}); });
}; };
// Bug report template for DM const bugReportUrl = `/messages/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx`;
const bugReportTemplate = `🐛 Bug Report
What happened:
What I expected:
Steps to reproduce:
Device/Browser: ${navigator.userAgent.includes('Mobile') ? 'Mobile' : 'Desktop'} - ${navigator.userAgent.split(' ').slice(-2).join(' ')}
Version: v0.1.0`;
const bugReportUrl = `/messages/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx?message=${encodeURIComponent(bugReportTemplate)}`;
// Calculate completed steps
const completedSteps: number[] = [];
if (user) completedSteps.push(1);
if (user) completedSteps.push(2); // Exploration is available once logged in
if (hasCashu || (user && cashuBalance > 0)) completedSteps.push(3); // Wallet setup
// Step 4 is always the final action step
const currentStep = user ? (hasCashu ? 4 : 3) : 1;
return ( return (
<Layout> <Layout>
<div className="container px-4 py-12 max-w-4xl"> <div className="container px-4 py-10 max-w-3xl">
{/* Hero Section with Progress */}
<div className="text-center mb-8"> {/* ── Hero ── */}
<h1 className="text-4xl md:text-6xl font-black uppercase mb-4 neon-header"> <div className="text-center mb-12 relative">
<span className="neon-red">GET</span> <span className="neon-lime">ROLLING</span> <div className="absolute inset-0 -z-10 blur-[120px] opacity-20 pointer-events-none"
style={{ background: 'radial-gradient(ellipse at 50% 0%, #ff0040 0%, transparent 70%)' }} />
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#ff0040]/10 border border-[#ff0040]/30 text-[#ff0040] text-xs font-black uppercase tracking-widest mb-5">
<span className="w-1.5 h-1.5 rounded-full bg-[#ff0040] animate-pulse" />
Beta — Open to skaters worldwide
</div>
<h1 className="text-5xl md:text-7xl font-black uppercase leading-none mb-4">
<span style={{ color: '#ff0040', textShadow: '0 0 40px rgba(255,0,64,0.4)' }}>SKATE</span>
<br />
<span style={{ color: '#ccff00', textShadow: '0 0 40px rgba(204,255,0,0.3)' }}>YOUR WORLD</span>
</h1> </h1>
<p className="text-lg md:text-xl text-gray-400 max-w-2xl mx-auto mb-6"> <p className="text-gray-400 text-lg max-w-xl mx-auto leading-relaxed mb-8">
Connect with your local scene. Share spots. Find sessions. It's that simple. A <strong className="text-white">decentralized</strong> skate community. No company owns it.
No algorithm buries your spots. No ban hammer. Just skaters.
</p> </p>
</div>
{/* Progress Tracker */} {/* Feature pills */}
<OnboardingProgress <div className="flex flex-wrap gap-2 justify-center mb-8">
currentStep={currentStep} <FeaturePill emoji="🗺️" label="Spot map" />
totalSteps={4} <FeaturePill emoji="🎓" label="Coaches & filmers" />
completedSteps={completedSteps} <FeaturePill emoji="📅" label="Events" />
/> <FeaturePill emoji="🛒" label="Buy/sell gear" />
<FeaturePill emoji="⚡" label="Bitcoin zaps" />
{/* Login/Signup Section - Step 1 */} <FeaturePill emoji="🎒" label="Gear stash" />
{!user ? ( <FeaturePill emoji="🎵" label="NoGood Radio" />
<Card className="bg-gradient-to-br from-red-500/20 to-lime-500/20 border-2 border-red-500/50 mb-8 relative overflow-hidden" id="login-section">
{/* Step indicator */}
<div className="absolute top-4 left-4">
<div className="w-10 h-10 rounded-full bg-[#ff0040] flex items-center justify-center font-black text-xl shadow-[0_0_20px_rgba(255,0,64,0.5)]">
1
</div>
</div>
<CardContent className="p-6 pt-16 text-center">
<h2 className="text-2xl font-black uppercase mb-3">CREATE YOUR ACCOUNT</h2>
<p className="text-gray-300 mb-6 text-sm max-w-md mx-auto">
Log in with your Nostr account or create a new one. Your identity works across all Nostr apps - no company controls it.
</p>
<LoginArea className="flex justify-center" />
</CardContent>
</Card>
) : (
<Card className="bg-green-950/30 border-2 border-green-500/50 mb-8">
<CardContent className="p-4 flex items-center gap-4">
<div className="w-10 h-10 rounded-full bg-green-500 flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="h-6 w-6 text-white" />
</div>
<div className="flex-1">
<p className="font-bold text-green-400">Step 1 Complete!</p>
<p className="text-sm text-gray-400">You're logged in and ready to explore.</p>
</div>
{/* Founder Follow - hold for 3s */}
<div className="flex-shrink-0 flex flex-col items-center">
<FounderFollow size="sm" showBadge={true} />
</div>
</CardContent>
</Card>
)}
<div className="space-y-8">
{/* Quick Info Banner with Learn More */}
<Card className="bg-gray-900/50 border-gray-800">
<CardContent className="p-4">
<div className="flex items-center gap-4">
<div className="text-3xl">⚡</div>
<div className="flex-1">
<p className="text-gray-300 text-sm">
<strong className="text-white">SkateSpots</strong> uses <strong className="text-[#ccff00]">Nostr</strong> -
a decentralized protocol where <strong className="text-red-500">YOU</strong> own your data.
No company can delete your spots or ban your account.
</p>
</div>
</div>
<div className="mt-3 pt-3 border-t border-gray-800">
<Button
variant="outline"
size="sm"
onClick={() => setShowRelayEducation(true)}
className="gap-2 border-purple-500/50 text-purple-400 hover:bg-purple-500/10"
>
<BookOpen className="h-4 w-4" />
Learn How Nostr Works
</Button>
</div>
</CardContent>
</Card>
{/* Step 2: Explore - What You Can Do */}
<Card className={cn(
"border-2 relative overflow-hidden",
user ? "bg-blue-950/20 border-blue-500/50" : "bg-gray-900/50 border-gray-700 opacity-60"
)}>
<div className="absolute top-4 left-4">
<div className={cn(
"w-10 h-10 rounded-full flex items-center justify-center font-black text-xl",
user ? "bg-blue-500 shadow-[0_0_20px_rgba(59,130,246,0.5)]" : "bg-gray-700"
)}>
2
</div>
</div>
<CardContent className="p-6 pt-16">
<h2 className="text-2xl font-black uppercase mb-4">EXPLORE THE COMMUNITY</h2>
<p className="text-gray-300 mb-6">
Everything is free! Browse spots, find coaches, join events - no Bitcoin required.
</p>
<div className="grid sm:grid-cols-3 gap-4 mb-6">
{[
{ icon: MapPin, label: "Spots & Shops", desc: "Find skate spots worldwide", href: "/map", color: "red" },
{ icon: GraduationCap, label: "The Lab", desc: "Connect with coaches & filmers", href: "/lab", color: "orange" },
{ icon: Calendar, label: "Events", desc: "Join sessions & competitions", href: "/events", color: "lime" },
].map((item) => (
<Link key={item.href} to={item.href}>
<Card className={cn(
"h-full transition-all hover:scale-105",
`bg-${item.color}-950/30 border-${item.color}-500/30 hover:border-${item.color}-500/60`
)}>
<CardContent className="p-4 text-center">
<item.icon className={cn("h-8 w-8 mx-auto mb-2", `text-${item.color}-500`)} />
<h3 className="font-bold text-sm mb-1">{item.label}</h3>
<p className="text-xs text-gray-400">{item.desc}</p>
</CardContent>
</Card>
</Link>
))}
</div>
{user && (
<div className="flex flex-wrap gap-2 justify-center">
<Link to="/map">
<Button className="bg-[#ff0040] hover:bg-[#ff0040]/90 gap-2 font-bold uppercase">
<MapPin className="h-4 w-4" />
Explore Map
</Button>
</Link>
<Link to="/create-spot">
<Button variant="outline" className="border-[#ccff00] text-[#ccff00] hover:bg-[#ccff00]/10 gap-2 font-bold uppercase">
Add Your First Spot
<ChevronRight className="h-4 w-4" />
</Button>
</Link>
</div>
)}
</CardContent>
</Card>
{/* Step 3: Wallet Setup */}
<Card className={cn(
"border-2 relative overflow-hidden",
user ? "bg-orange-950/20 border-orange-500/50" : "bg-gray-900/50 border-gray-700 opacity-60"
)}>
<div className="absolute top-4 left-4">
<div className={cn(
"w-10 h-10 rounded-full flex items-center justify-center font-black text-xl",
user ? "bg-orange-500 shadow-[0_0_20px_rgba(249,115,22,0.5)]" : "bg-gray-700"
)}>
3
</div>
</div>
<CardContent className="p-6 pt-16">
<div className="flex items-center gap-2 mb-2">
<h2 className="text-2xl font-black uppercase">OPTIONAL: SET UP WALLET</h2>
<span className="text-xs bg-gray-700 text-gray-300 px-2 py-0.5 rounded uppercase font-bold">Optional</span>
</div>
<p className="text-gray-300 mb-6">
Want to tip skaters who share awesome spots? Set up a wallet to send zaps (Bitcoin tips).
<strong className="text-green-400"> Totally optional!</strong>
</p>
<div className="grid sm:grid-cols-2 gap-4">
{/* Cashu Wallet - Built-in */}
<Card className="bg-orange-950/30 border-orange-500/30">
<CardContent className="p-4">
<div className="flex items-center gap-3 mb-3">
<div className="w-12 h-12 rounded-lg bg-orange-500/20 flex items-center justify-center">
<Nut className="h-6 w-6 text-orange-500" />
</div>
<div>
<h3 className="font-bold">Cashu Wallet</h3>
<p className="text-xs text-gray-400">Built into SkateSpots</p>
</div>
</div>
<p className="text-sm text-gray-300 mb-3">
No setup needed! Receive Lightning payments directly in the app.
</p>
{hasCashu && cashuBalance > 0 ? (
<div className="flex items-center justify-between p-2 bg-green-950/30 rounded border border-green-500/30">
<span className="text-sm text-green-400 font-bold">Balance:</span>
<span className="text-sm font-bold text-green-400">{cashuBalance.toLocaleString()} sats</span>
</div>
) : (
<Link to="/wallet">
<Button className="w-full bg-orange-500 hover:bg-orange-600 gap-2">
<Wallet className="h-4 w-4" />
Open Wallet
</Button>
</Link>
)}
</CardContent>
</Card>
{/* External Wallet - NWC */}
<Card className="bg-purple-950/30 border-purple-500/30">
<CardContent className="p-4">
<div className="flex items-center gap-3 mb-3">
<div className="w-12 h-12 rounded-lg bg-purple-500/20 flex items-center justify-center">
<Zap className="h-6 w-6 text-purple-500" />
</div>
<div>
<h3 className="font-bold">External Wallet</h3>
<p className="text-xs text-gray-400">Connect via NWC</p>
</div>
</div>
<p className="text-sm text-gray-300 mb-3">
Already have a Lightning wallet? Connect it with Nostr Wallet Connect.
</p>
{user ? (
<WalletModal>
<Button variant="outline" className="w-full border-purple-500/50 text-purple-400 hover:bg-purple-500/10 gap-2">
<Wallet className="h-4 w-4" />
Connect Wallet
</Button>
</WalletModal>
) : (
<Button variant="outline" className="w-full" disabled>
Login first to connect
</Button>
)}
</CardContent>
</Card>
</div>
<p className="text-xs text-gray-500 text-center mt-4">
💡 Tip: $5 in Bitcoin = hundreds of zaps to share with the community
</p>
</CardContent>
</Card>
{/* Step 4: Go Skate! */}
<Card className={cn(
"border-2 relative overflow-hidden",
user ? "bg-[#ccff00]/10 border-[#ccff00]/50" : "bg-gray-900/50 border-gray-700 opacity-60"
)}>
<div className="absolute top-4 left-4">
<div className={cn(
"w-10 h-10 rounded-full flex items-center justify-center font-black text-xl",
user ? "bg-[#ccff00] text-black shadow-[0_0_20px_rgba(204,255,0,0.5)]" : "bg-gray-700"
)}>
4
</div>
</div>
<CardContent className="p-6 pt-16 text-center">
<h2 className="text-3xl font-black uppercase mb-4">GO SKATE! 🛹</h2>
<p className="text-gray-300 mb-6 max-w-md mx-auto">
Real skateboarding happens offline. Use SkateSpots to discover spots, then get out there and shred!
</p>
{user ? (
<div className="flex flex-wrap gap-3 justify-center">
<Link to="/map">
<Button size="lg" className="bg-[#ff0040] hover:bg-[#ff0040]/90 gap-2 font-black uppercase shadow-[0_0_20px_rgba(255,0,64,0.5)]">
<MapPin className="h-5 w-5" />
Find Spots Near You
</Button>
</Link>
<Link to="/events">
<Button size="lg" variant="outline" className="border-[#ccff00] text-[#ccff00] hover:bg-[#ccff00]/10 gap-2 font-black uppercase">
<Calendar className="h-5 w-5" />
Join an Event
</Button>
</Link>
</div>
) : (
<p className="text-gray-500 italic">Complete the steps above to get started</p>
)}
</CardContent>
</Card>
{/* 100% Free Banner */}
<Card className="bg-green-950/30 border-green-500/30">
<CardContent className="p-6">
<div className="flex items-start gap-4">
<CheckCircle2 className="h-8 w-8 text-green-500 flex-shrink-0" />
<div>
<h3 className="text-xl font-black uppercase mb-2">100% FREE TO USE</h3>
<p className="text-gray-300">
You don't need any Bitcoin to use SkateSpots. Browse spots, add locations, connect with pros, join events - all free.
Zaps are completely optional for those who want to support creators.
</p>
</div>
</div>
</CardContent>
</Card>
{/* Why Nostr - Compact Version */}
<Card className="bg-purple-950/30 border-purple-500/30">
<CardContent className="p-6">
<div className="flex items-center gap-3 mb-4">
<Globe className="h-8 w-8 text-purple-500" />
<h3 className="text-xl font-black uppercase">WHY NOSTR FOR SKATERS?</h3>
</div>
<div className="grid sm:grid-cols-2 gap-4">
<div className="p-4 bg-black/50 rounded-lg border border-red-500/20">
<div className="flex items-center gap-2 mb-2">
<Shield className="h-5 w-5 text-red-500" />
<h4 className="font-bold uppercase text-sm">YOUR SPOTS ARE PERMANENT</h4>
</div>
<p className="text-sm text-gray-400">
No company can delete the spots you share. Your contributions live on multiple servers.
</p>
</div>
<div className="p-4 bg-black/50 rounded-lg border border-green-500/20">
<div className="flex items-center gap-2 mb-2">
<DollarSign className="h-5 w-5 text-green-500" />
<h4 className="font-bold uppercase text-sm">DIRECT TIPS TO CREATORS</h4>
</div>
<p className="text-sm text-gray-400">
Zap skaters directly - no platform taking 30%. Money goes straight to them.
</p>
</div>
<div className="p-4 bg-black/50 rounded-lg border border-orange-500/20">
<div className="flex items-center gap-2 mb-2">
<Shield className="h-5 w-5 text-orange-500" />
<h4 className="font-bold uppercase text-sm">OWN YOUR IDENTITY</h4>
</div>
<p className="text-sm text-gray-400">
Your Nostr profile works everywhere. Build your rep once, take it to any app.
</p>
</div>
<div className="p-4 bg-black/50 rounded-lg border border-blue-500/20">
<div className="flex items-center gap-2 mb-2">
<Video className="h-5 w-5 text-blue-500" />
<h4 className="font-bold uppercase text-sm">SUPPORT LOCAL FILMERS</h4>
</div>
<p className="text-sm text-gray-400">
Show real appreciation with real value - not just likes that mean nothing.
</p>
</div>
</div>
</CardContent>
</Card>
{/* Common Questions */}
<div>
<h2 className="text-3xl font-black uppercase mb-6 text-center">COMMON QUESTIONS</h2>
<div className="space-y-4">
{[
{
q: 'DO I NEED BITCOIN TO USE THIS APP?',
a: 'No! SkateSpots is 100% free. You can browse spots, add locations, find pros at The Lab, create events, and connect with the community without any Bitcoin. Zaps are optional for those who want to tip creators.'
},
{
q: 'WHAT\'S AN NSEC AND WHY IS IT IMPORTANT?',
a: 'Your nsec (secret key) is like a password that proves you own your account. Unlike regular passwords, there\'s no "forgot password" option. If you lose it, you lose your account forever. That\'s why you download a backup when you sign up.'
},
{
q: 'IS THIS FREE?',
a: 'Yes! Creating a Nostr account and using SkateSpots is 100% free. You only need Bitcoin if you want to send zaps (tips), and even then you can receive zaps without having any Bitcoin yourself.'
},
{
q: 'WHY CAN\'T I RESET MY PASSWORD?',
a: 'Nostr doesn\'t have passwords - it has cryptographic keys. This means no company (including us) can access your account, but it also means no one can reset it for you. Your nsec IS your account. Keep it safe!'
},
{
q: 'CAN I USE THIS ON MY PHONE?',
a: 'Yes! SkateSpots works on any device with a web browser. On mobile, you can add it to your home screen for an app-like experience. Just visit the site and use the "Add to Home Screen" option in your browser menu.'
}
].map((faq, i) => (
<Card key={i} className="bg-gray-900 border-gray-700">
<CardContent className="p-6">
<h3 className="font-bold mb-2">{faq.q}</h3>
<p className="text-sm text-gray-400">{faq.a}</p>
</CardContent>
</Card>
))}
</div>
</div> </div>
{/* CTA */} {/* Primary CTA */}
<Card className="bg-gradient-to-br from-red-500/20 to-lime-500/20 border-red-500/30"> {!user ? (
<CardContent className="p-8 text-center"> <div className="flex flex-col items-center gap-3">
<h2 className="text-3xl font-black uppercase mb-4">READY TO SHRED?</h2> <p className="text-sm text-gray-500 uppercase tracking-wider font-bold mb-1">Create your free account</p>
<p className="text-gray-300 mb-6">Start exploring spots, connecting with skaters, and building the community.</p> <LoginArea className="flex justify-center" />
<p className="text-xs text-gray-600 mt-2">No email. No password. Own your identity forever.</p>
</div>
) : (
<div className="flex flex-wrap gap-3 justify-center">
<Link to="/map">
<Button size="lg" className="bg-[#ff0040] hover:bg-[#ff0040]/90 gap-2 font-black uppercase shadow-[0_0_30px_rgba(255,0,64,0.3)]">
<MapPin className="h-5 w-5" />
Find Spots Near You
</Button>
</Link>
<Link to="/create-spot">
<Button size="lg" variant="outline" className="border-[#ccff00]/50 text-[#ccff00] hover:bg-[#ccff00]/10 gap-2 font-black uppercase">
Add Your First Spot
</Button>
</Link>
</div>
)}
</div>
{/* ── Logged in: step 1 complete ── */}
{user && (
<div className="flex items-center gap-4 p-4 rounded-2xl bg-green-500/8 border border-green-500/25 mb-8">
<div className="w-9 h-9 rounded-full bg-green-500 flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="h-5 w-5 text-white" />
</div>
<div className="flex-1">
<p className="font-bold text-green-400 text-sm">You're in! Account created ✓</p>
<p className="text-xs text-gray-500">Your identity is yours — stored in your key, not on our servers.</p>
</div>
<div className="flex-shrink-0">
<FounderFollow size="sm" showBadge={true} />
</div>
</div>
)}
<div className="space-y-6">
{/* ── What you can do ── */}
<section>
<h2 className="text-xs font-black uppercase tracking-widest text-gray-600 mb-4">Everything you can do — free</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{[
{ icon: MapPin, label: 'Discover Spots', desc: 'Interactive map with thousands of spots worldwide', href: '/map', color: '#ff0040' },
{ icon: GraduationCap,label: 'The Lab', desc: 'Find coaches, filmers & video editors near you', href: '/lab', color: '#ff6600' },
{ icon: Calendar, label: 'Events', desc: 'Competitions, sessions, meetups', href: '/events', color: '#ccff00' },
{ icon: ShoppingBag, label: 'Buy / Sell Gear', desc: 'Peer-to-peer marketplace for used skate gear', href: '/marketplace',color: '#00cc66' },
{ icon: Package, label: 'My Stash', desc: 'Private encrypted inventory of everything you own', href: '/stash', color: '#a855f7' },
{ icon: Radio, label: 'NoGood Radio', desc: '24/7 lofi/synthwave/electronica while you skate', href: '/radio', color: '#06b6d4' },
].map(({ icon: Icon, label, desc, href, color }) => (
<Link key={href} to={href}>
<div className="h-full p-3.5 rounded-2xl bg-white/3 border border-white/8 hover:bg-white/6 hover:border-white/15 transition-all group">
<Icon className="h-5 w-5 mb-2 transition-colors" style={{ color }} />
<p className="font-bold text-sm text-white leading-tight mb-1">{label}</p>
<p className="text-[11px] text-gray-600 leading-snug">{desc}</p>
</div>
</Link>
))}
</div>
</section>
{/* ── What is Nostr ── */}
<section className="rounded-2xl border border-purple-500/20 bg-gradient-to-br from-purple-500/8 to-black/60 p-5">
<div className="flex items-center gap-3 mb-4">
<div className="w-9 h-9 rounded-xl bg-purple-500/20 flex items-center justify-center">
<Globe className="h-5 w-5 text-purple-400" />
</div>
<div>
<h3 className="font-black text-white uppercase tracking-wide">What is Nostr?</h3>
<p className="text-xs text-gray-500">The protocol that powers SkateSpots</p>
</div>
</div>
<p className="text-sm text-gray-400 leading-relaxed mb-4">
Nostr is an open protocol — like email, but for social apps. Your account is a <strong className="text-white">cryptographic key pair</strong> that you own. No company can delete your spots, ban your account, or sell your data. Your identity works across every Nostr app.
</p>
<div className="grid grid-cols-2 gap-2 mb-4">
{[
{ icon: Shield, label: 'Your spots are permanent', color: 'text-red-400' },
{ icon: Lock, label: 'No one can ban you', color: 'text-orange-400' },
{ icon: Users, label: 'Identity works everywhere', color: 'text-purple-400' },
{ icon: Zap, label: 'Direct Bitcoin tips', color: 'text-yellow-400' },
].map(({ icon: Icon, label, color }) => (
<div key={label} className="flex items-center gap-2 p-2.5 rounded-xl bg-white/4 border border-white/6">
<Icon className={cn('h-4 w-4 flex-shrink-0', color)} />
<span className="text-xs font-bold text-gray-300">{label}</span>
</div>
))}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setShowRelayEducation(true)}
className="gap-2 border-purple-500/40 text-purple-400 hover:bg-purple-500/10 text-xs"
>
<BookOpen className="h-3.5 w-3.5" />
Deep dive: How Nostr works
</Button>
</section>
{/* ── Wallet (optional) ── */}
<section className="rounded-2xl border border-orange-500/20 bg-gradient-to-br from-orange-500/6 to-black/60 p-5">
<div className="flex items-center justify-between mb-1">
<h3 className="font-black text-white uppercase tracking-wide">⚡ Zaps & Wallet</h3>
<span className="text-[10px] font-black uppercase tracking-wider bg-green-500/15 text-green-400 border border-green-500/30 px-2 py-0.5 rounded-full">Optional</span>
</div>
<p className="text-sm text-gray-400 leading-relaxed mb-4">
Zaps are Bitcoin tips you send directly to a skater who dropped a sick spot, filmed a banger clip, or ran a great session. No platform fee. No middleman. 100% goes to them.
</p>
<div className="grid sm:grid-cols-2 gap-3">
<div className="p-4 rounded-xl bg-orange-500/8 border border-orange-500/20">
<div className="flex items-center gap-2 mb-2">
<Nut className="h-4 w-4 text-orange-400" />
<span className="font-bold text-sm text-white">Built-in Cashu wallet</span>
</div>
<p className="text-xs text-gray-500 mb-3">Receive Lightning payments right in the app. No setup.</p>
{hasCashu && cashuBalance > 0 ? (
<div className="flex items-center justify-between p-2 bg-green-950/30 rounded-lg border border-green-500/30">
<span className="text-xs text-green-400 font-bold">Balance</span>
<span className="text-sm font-black text-green-400">{cashuBalance.toLocaleString()} sats</span>
</div>
) : (
<Link to="/wallet">
<Button size="sm" className="w-full bg-orange-500 hover:bg-orange-600 gap-2 text-xs font-bold">
<Wallet className="h-3.5 w-3.5" />
Open Wallet
</Button>
</Link>
)}
</div>
<div className="p-4 rounded-xl bg-purple-500/8 border border-purple-500/20">
<div className="flex items-center gap-2 mb-2">
<Zap className="h-4 w-4 text-purple-400" />
<span className="font-bold text-sm text-white">Connect external wallet</span>
</div>
<p className="text-xs text-gray-500 mb-3">Have Alby, Mutiny, or another wallet? Connect it via NWC.</p>
{user ? (
<WalletModal>
<Button size="sm" variant="outline" className="w-full border-purple-500/40 text-purple-400 hover:bg-purple-500/10 gap-2 text-xs font-bold">
<Wallet className="h-3.5 w-3.5" />
Connect via NWC
</Button>
</WalletModal>
) : (
<Button size="sm" variant="outline" className="w-full text-xs" disabled>
Log in first
</Button>
)}
</div>
</div>
<p className="text-[11px] text-gray-600 text-center mt-3">💡 $5 of Bitcoin = hundreds of zaps to spread across the community</p>
</section>
{/* ── FAQ ── */}
<section>
<h2 className="text-xs font-black uppercase tracking-widest text-gray-600 mb-4">Common questions</h2>
<div className="space-y-2">
<FAQ
q="Is SkateSpots free?"
a="100% free. Browse spots, add locations, find coaches, create events, join sessions — all free, forever. Bitcoin (zaps) is optional if you want to tip creators."
/>
<FAQ
q="What is Nostr and why does SkateSpots use it?"
a={<>Nostr is an open protocol for decentralized apps — think of it like the internet itself, not owned by any company. Your SkateSpots account is a cryptographic key pair that <strong className="text-white">you own</strong>. No one can delete your spots, ban your account, or hold your data hostage. Your identity also works on other Nostr apps like Primal, Damus, or Amethyst.</>}
/>
<FAQ
q="What's an nsec and why is it important?"
a={<>Your <code className="text-[#ccff00] text-xs bg-white/5 px-1 py-0.5 rounded">nsec</code> is your private key — it's the only proof you own your account. Unlike passwords, there is <strong className="text-white">no "forgot my password"</strong>. If you lose your nsec, your account is gone forever. When you create an account, download your backup immediately and store it somewhere safe (password manager, written down offline).</>}
/>
<FAQ
q="What signers / apps work with SkateSpots?"
a={
<div className="space-y-2">
<p>On <strong className="text-white">desktop</strong>: <a href="https://getalby.com" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Alby</a> or <a href="https://github.com/fiatjaf/nos2x" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">nos2x</a> browser extensions.</p>
<p>On <strong className="text-white">Android</strong>: <a href="https://github.com/greenart7c3/Amber" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Amber</a> (best experience with the native app).</p>
<p>On <strong className="text-white">iOS</strong>: <a href="https://apps.apple.com/app/nostore/id1666553677" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Nostore</a> or paste your nsec directly (less secure).</p>
<p>You can also log in by pasting your nsec directly — but using a signer extension is safer.</p>
</div>
}
/>
<FAQ
q="Can I use SkateSpots on my phone?"
a={<>Yes! SkateSpots is a PWA (Progressive Web App). Visit <strong className="text-white">skate-spots.org</strong> in your phone's browser and tap <em>"Add to Home Screen"</em> for a full app experience. Or download it from <a href="https://zapstore.dev/apps/org.skatespots.app" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Zapstore</a> — a decentralized app store.</>}
/>
<FAQ
q="What is a zap?"
a="A zap is an instant Bitcoin tip sent over the Lightning Network. When you zap a skater, the sats land in their wallet within seconds — no PayPal, no Venmo, no cut taken. The person who filmed that 4-stair gets paid directly by fans."
/>
<FAQ
q="Will my spots be deleted if SkateSpots shuts down?"
a="No. Your spots are published to Nostr relays — distributed servers that anyone can run. Even if SkateSpots as a product disappeared tomorrow, your data would still exist on relays and any Nostr client could display it."
/>
<FAQ
q="Can I add a spot that's on private property?"
a="You can, but use common sense. Spots on private property should be marked accordingly so skaters know the deal. SkateSpots is not responsible for trespassing — always be respectful of owners and locals."
/>
<FAQ
q="How do I report a bug or give feedback?"
a={
<div className="space-y-2">
<p>DM the SkateSpots team directly on Nostr, or reach out to our beta coordinator:</p>
<a
href="https://cordn.net/p/npub1e2ayjlmmaksx2rtt0z4jzyskunfwh2lwe89xav4qtnhgu4wxsudsp98a4l"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-3 py-2 rounded-xl bg-yellow-500/10 border border-yellow-500/30 text-yellow-400 text-xs font-bold hover:bg-yellow-500/20 transition-colors"
>
<ExternalLink className="h-3.5 w-3.5" />
Beta feedback → cordn.net
</a>
</div>
}
/>
</div>
</section>
{/* ── Final CTA ── */}
<section className="rounded-2xl overflow-hidden relative">
<div className="absolute inset-0 pointer-events-none"
style={{ background: 'linear-gradient(135deg, rgba(255,0,64,0.15) 0%, rgba(204,255,0,0.08) 100%)' }} />
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-[#ff0040]/60 via-[#ff6600]/40 to-[#ccff00]/40" />
<div className="relative p-8 text-center">
<h2 className="text-4xl font-black uppercase mb-3" style={{ textShadow: '0 0 30px rgba(255,0,64,0.3)' }}>READY TO SHRED?</h2>
<p className="text-gray-400 mb-6 max-w-sm mx-auto">Stop scrolling. Get the app. Find a spot. Skate it.</p>
<div className="flex flex-wrap gap-3 justify-center"> <div className="flex flex-wrap gap-3 justify-center">
<Link to="/map"> <Link to="/map">
<Button size="lg" className="bg-red-500 hover:bg-red-600 gap-2 uppercase font-black text-lg"> <Button size="lg" className="bg-[#ff0040] hover:bg-[#ff0040]/90 gap-2 font-black uppercase shadow-[0_0_30px_rgba(255,0,64,0.25)]">
<MapPin className="h-5 w-5" /> <MapPin className="h-5 w-5" />
EXPLORE SPOTS Explore Spots
</Button> </Button>
</Link> </Link>
<Link to="/lab"> <Link to="/lab">
<Button size="lg" variant="outline" className="border-lime-500 hover:bg-lime-500/10 gap-2 uppercase font-black text-lg"> <Button size="lg" variant="outline" className="border-[#ccff00]/50 text-[#ccff00] hover:bg-[#ccff00]/8 gap-2 font-black uppercase">
<GraduationCap className="h-5 w-5" /> <GraduationCap className="h-5 w-5" />
VISIT THE LAB The Lab
</Button> </Button>
</Link> </Link>
</div> </div>
</CardContent> </div>
</Card> </section>
{/* Beta Testing Notice */} {/* ── Beta Testing Card ── */}
<Card className="bg-yellow-950/20 border-yellow-500/30"> <section className="rounded-2xl border border-yellow-500/25 bg-yellow-500/5 p-5">
<CardContent className="p-6"> <div className="flex items-start gap-3 mb-4">
<div className="flex items-start gap-4"> <div className="w-8 h-8 rounded-lg bg-yellow-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<AlertCircle className="h-6 w-6 text-yellow-500 flex-shrink-0 mt-0.5" /> <AlertCircle className="h-4 w-4 text-yellow-400" />
<div className="flex-1"> </div>
<h3 className="font-bold text-yellow-500 uppercase mb-2">Beta Testing</h3> <div>
<p className="text-sm text-gray-300 mb-4"> <h3 className="font-black text-yellow-400 uppercase text-sm tracking-wide">Beta — We want your feedback</h3>
Thanks for testing SkateSpots! This app is under active development – <p className="text-sm text-gray-400 mt-1 leading-relaxed">
you may encounter bugs or security issues. Your feedback helps us improve. SkateSpots is under active development. You may hit rough edges — that's on us, not you. Report bugs, suggest features, tell us what sucks.
</p> </p>
<div className="flex flex-col sm:flex-row gap-3 items-start sm:items-center"> </div>
<div className="flex-1 bg-black/30 rounded-lg p-3 border border-yellow-500/20 min-w-0"> </div>
<p className="text-xs text-gray-400 mb-1">Report bugs or share feedback:</p>
<code className="text-xs text-yellow-400 break-all"> <div className="grid sm:grid-cols-2 gap-3">
npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx {/* Beta coordinator */}
</code> <a
</div> href="https://cordn.net/p/npub1e2ayjlmmaksx2rtt0z4jzyskunfwh2lwe89xav4qtnhgu4wxsudsp98a4l"
<div className="flex gap-2 flex-shrink-0"> target="_blank"
<Button rel="noopener noreferrer"
variant="outline" className="flex items-center gap-3 p-3.5 rounded-xl bg-yellow-500/8 border border-yellow-500/20 hover:bg-yellow-500/15 hover:border-yellow-500/35 transition-all group"
size="sm" >
className="border-yellow-500/50 text-yellow-500 hover:bg-yellow-500/10" <div className="w-8 h-8 rounded-full bg-yellow-500/20 flex items-center justify-center flex-shrink-0">
onClick={copyNpub} <MessageCircle className="h-4 w-4 text-yellow-400" />
> </div>
Copy npub <div className="flex-1 min-w-0">
</Button> <p className="text-xs font-black text-yellow-400 uppercase tracking-wide">Beta Coordinator</p>
{user ? ( <p className="text-xs text-gray-500 truncate">cordn.net → message us</p>
<Link to={bugReportUrl}> </div>
<Button <ExternalLink className="h-3.5 w-3.5 text-yellow-500/50 group-hover:text-yellow-400 transition-colors flex-shrink-0" />
size="sm" </a>
className="bg-yellow-500 text-black hover:bg-yellow-400"
> {/* DM founder */}
Report Bug <div className="flex items-center gap-3 p-3.5 rounded-xl bg-white/3 border border-white/8">
</Button> <div className="w-8 h-8 rounded-full bg-white/8 flex items-center justify-center flex-shrink-0">
</Link> <Key className="h-4 w-4 text-gray-400" />
) : ( </div>
<a <div className="flex-1 min-w-0">
href="https://njump.me/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx" <p className="text-xs font-black text-gray-300 uppercase tracking-wide">DM on Nostr</p>
target="_blank" <p className="text-[10px] text-gray-600 font-mono truncate">npub1yrnuj…</p>
rel="noopener noreferrer" </div>
> <div className="flex gap-1.5 flex-shrink-0">
<Button <button
size="sm" onClick={copyNpub}
className="bg-yellow-500 text-black hover:bg-yellow-400" className="px-2 py-1 rounded-lg bg-white/6 border border-white/10 text-[10px] font-bold text-gray-400 hover:text-white hover:bg-white/10 transition-all"
> >
View Profile Copy
</Button> </button>
</a> {user && (
)} <Link to={bugReportUrl}>
</div> <button className="px-2 py-1 rounded-lg bg-yellow-500/15 border border-yellow-500/30 text-[10px] font-bold text-yellow-400 hover:bg-yellow-500/25 transition-all">
</div> DM
</button>
</Link>
)}
</div> </div>
</div> </div>
</CardContent> </div>
</Card>
<p className="text-[11px] text-gray-600 mt-3 text-center">
Found a bug? We fix it same day. Your reports make the app better for every skater.
</p>
</section>
</div> </div>
</div> </div>
{/* Relay Education Dialog */} {/* Relay Education Dialog */}
<Dialog open={showRelayEducation} onOpenChange={setShowRelayEducation}> <Dialog open={showRelayEducation} onOpenChange={setShowRelayEducation}>
<DialogContent className="w-[calc(100%-1rem)] sm:max-w-lg p-0 bg-transparent border-none"> <DialogContent className="w-[calc(100%-1rem)] sm:max-w-lg p-0 bg-transparent border-none">
<RelayEducation <RelayEducation
onComplete={handleRelayEducationComplete} onComplete={handleRelayEducationComplete}
onSkip={() => setShowRelayEducation(false)} onSkip={() => setShowRelayEducation(false)}
/> />