feat: overhaul landing page and add demo with SVG map
Overhaul landing page from login screen to proper hero + features. Add interactive demo page showing CCC Camp 2026 event with SVG camp map, animated friend markers, status system, and friend list panel. Add ecosystem footer to both pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9a8ea19f89
commit
96e46af9dc
|
|
@ -0,0 +1,481 @@
|
||||||
|
import Link from 'next/link'
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'rMaps Demo - CCC Camp 2026',
|
||||||
|
description: 'See how rMaps helps you find your friends at events. A demo showcasing real-time location sharing, event maps, and privacy-first friend finding at CCC Camp 2026.',
|
||||||
|
openGraph: {
|
||||||
|
title: 'rMaps Demo - CCC Camp 2026',
|
||||||
|
description: 'Privacy-first real-time location sharing for events. Find your crew at CCC Camp 2026.',
|
||||||
|
url: 'https://rmaps.online/demo',
|
||||||
|
siteName: 'rMaps',
|
||||||
|
type: 'website',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Mock Data ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
const friends = [
|
||||||
|
{
|
||||||
|
name: 'Mika',
|
||||||
|
emoji: '🦊',
|
||||||
|
color: '#10b981',
|
||||||
|
status: 'At Main Stage',
|
||||||
|
lastSeen: '1 min ago',
|
||||||
|
x: 520,
|
||||||
|
y: 155,
|
||||||
|
online: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Zara',
|
||||||
|
emoji: '🐙',
|
||||||
|
color: '#6366f1',
|
||||||
|
status: 'Heading to Food Court',
|
||||||
|
lastSeen: '3 min ago',
|
||||||
|
x: 340,
|
||||||
|
y: 260,
|
||||||
|
online: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Leo',
|
||||||
|
emoji: '🐻',
|
||||||
|
color: '#f59e0b',
|
||||||
|
status: 'In Tent',
|
||||||
|
lastSeen: '12 min ago',
|
||||||
|
x: 160,
|
||||||
|
y: 310,
|
||||||
|
online: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ren',
|
||||||
|
emoji: '🦉',
|
||||||
|
color: '#ef4444',
|
||||||
|
status: 'At Workshop Hall',
|
||||||
|
lastSeen: '2 min ago',
|
||||||
|
x: 640,
|
||||||
|
y: 290,
|
||||||
|
online: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Juno',
|
||||||
|
emoji: '🐧',
|
||||||
|
color: '#ec4899',
|
||||||
|
status: 'Getting coffee',
|
||||||
|
lastSeen: '5 min ago',
|
||||||
|
x: 400,
|
||||||
|
y: 180,
|
||||||
|
online: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kai',
|
||||||
|
emoji: '🐝',
|
||||||
|
color: '#06b6d4',
|
||||||
|
status: 'At Soldering Village',
|
||||||
|
lastSeen: '8 min ago',
|
||||||
|
x: 280,
|
||||||
|
y: 130,
|
||||||
|
online: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
'At Main Stage',
|
||||||
|
'Heading to Food Court',
|
||||||
|
'In Tent',
|
||||||
|
'At Workshop Hall',
|
||||||
|
'Getting coffee',
|
||||||
|
'At Soldering Village',
|
||||||
|
'Exploring',
|
||||||
|
'At the Lake',
|
||||||
|
'On my way!',
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── SVG Camp Map ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
function CampMap() {
|
||||||
|
return (
|
||||||
|
<div className="relative w-full rounded-xl bg-slate-900/60 overflow-hidden border border-slate-700/30">
|
||||||
|
<svg viewBox="0 0 800 400" className="w-full h-auto" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
{/* Background terrain */}
|
||||||
|
<rect width="800" height="400" fill="#0c1222" />
|
||||||
|
|
||||||
|
{/* Grass / ground patches */}
|
||||||
|
<ellipse cx="400" cy="200" rx="380" ry="180" fill="#0f1a2e" />
|
||||||
|
<ellipse cx="400" cy="200" rx="360" ry="170" fill="#111d33" />
|
||||||
|
|
||||||
|
{/* Paths - main walkways */}
|
||||||
|
<path
|
||||||
|
d="M100 200 L350 200 L500 150 L700 200"
|
||||||
|
fill="none"
|
||||||
|
stroke="rgba(148,163,184,0.15)"
|
||||||
|
strokeWidth="20"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M350 200 L350 350"
|
||||||
|
fill="none"
|
||||||
|
stroke="rgba(148,163,184,0.15)"
|
||||||
|
strokeWidth="16"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M500 150 L600 300"
|
||||||
|
fill="none"
|
||||||
|
stroke="rgba(148,163,184,0.15)"
|
||||||
|
strokeWidth="14"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M350 200 L250 120"
|
||||||
|
fill="none"
|
||||||
|
stroke="rgba(148,163,184,0.12)"
|
||||||
|
strokeWidth="12"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ── Area: Main Stage (top right) ── */}
|
||||||
|
<rect x="460" y="100" width="140" height="90" rx="12" fill="rgba(16,185,129,0.08)" stroke="rgba(16,185,129,0.25)" strokeWidth="1.5" />
|
||||||
|
<rect x="490" y="115" width="80" height="30" rx="4" fill="rgba(16,185,129,0.15)" />
|
||||||
|
<text x="530" y="135" textAnchor="middle" fill="rgba(16,185,129,0.7)" fontSize="11" fontWeight="600">STAGE</text>
|
||||||
|
<text x="530" y="175" textAnchor="middle" fill="#94a3b8" fontSize="11" fontWeight="600">Main Stage</text>
|
||||||
|
|
||||||
|
{/* ── Area: Food Court (center-left) ── */}
|
||||||
|
<rect x="290" y="220" width="130" height="80" rx="12" fill="rgba(249,115,22,0.08)" stroke="rgba(249,115,22,0.25)" strokeWidth="1.5" />
|
||||||
|
{/* Food stall icons */}
|
||||||
|
<rect x="305" y="235" width="20" height="15" rx="3" fill="rgba(249,115,22,0.2)" />
|
||||||
|
<rect x="335" y="235" width="20" height="15" rx="3" fill="rgba(249,115,22,0.2)" />
|
||||||
|
<rect x="365" y="235" width="20" height="15" rx="3" fill="rgba(249,115,22,0.2)" />
|
||||||
|
<rect x="305" y="260" width="20" height="15" rx="3" fill="rgba(249,115,22,0.2)" />
|
||||||
|
<rect x="335" y="260" width="20" height="15" rx="3" fill="rgba(249,115,22,0.2)" />
|
||||||
|
<rect x="365" y="260" width="20" height="15" rx="3" fill="rgba(249,115,22,0.2)" />
|
||||||
|
<text x="355" y="292" textAnchor="middle" fill="#94a3b8" fontSize="11" fontWeight="600">Food Court</text>
|
||||||
|
|
||||||
|
{/* ── Area: Workshops (right) ── */}
|
||||||
|
<rect x="570" y="240" width="150" height="90" rx="12" fill="rgba(99,102,241,0.08)" stroke="rgba(99,102,241,0.25)" strokeWidth="1.5" />
|
||||||
|
<rect x="585" y="255" width="50" height="25" rx="4" fill="rgba(99,102,241,0.12)" />
|
||||||
|
<rect x="645" y="255" width="50" height="25" rx="4" fill="rgba(99,102,241,0.12)" />
|
||||||
|
<rect x="585" y="288" width="50" height="25" rx="4" fill="rgba(99,102,241,0.12)" />
|
||||||
|
<rect x="645" y="288" width="50" height="25" rx="4" fill="rgba(99,102,241,0.12)" />
|
||||||
|
<text x="645" y="325" textAnchor="middle" fill="#94a3b8" fontSize="11" fontWeight="600">Workshops</text>
|
||||||
|
|
||||||
|
{/* ── Area: Camping (bottom-left) ── */}
|
||||||
|
<rect x="90" y="260" width="160" height="100" rx="12" fill="rgba(234,179,8,0.06)" stroke="rgba(234,179,8,0.2)" strokeWidth="1.5" />
|
||||||
|
{/* Tent icons */}
|
||||||
|
<polygon points="120,300 135,280 150,300" fill="rgba(234,179,8,0.15)" stroke="rgba(234,179,8,0.3)" strokeWidth="1" />
|
||||||
|
<polygon points="165,310 180,290 195,310" fill="rgba(234,179,8,0.15)" stroke="rgba(234,179,8,0.3)" strokeWidth="1" />
|
||||||
|
<polygon points="120,335 135,315 150,335" fill="rgba(234,179,8,0.12)" stroke="rgba(234,179,8,0.25)" strokeWidth="1" />
|
||||||
|
<polygon points="175,340 190,320 205,340" fill="rgba(234,179,8,0.12)" stroke="rgba(234,179,8,0.25)" strokeWidth="1" />
|
||||||
|
<text x="170" y="355" textAnchor="middle" fill="#94a3b8" fontSize="11" fontWeight="600">Camping</text>
|
||||||
|
|
||||||
|
{/* ── Area: Soldering Village (top-left) ── */}
|
||||||
|
<rect x="200" y="80" width="120" height="70" rx="12" fill="rgba(6,182,212,0.08)" stroke="rgba(6,182,212,0.25)" strokeWidth="1.5" />
|
||||||
|
<rect x="215" y="95" width="30" height="20" rx="3" fill="rgba(6,182,212,0.15)" />
|
||||||
|
<rect x="255" y="95" width="30" height="20" rx="3" fill="rgba(6,182,212,0.15)" />
|
||||||
|
<text x="260" y="140" textAnchor="middle" fill="#94a3b8" fontSize="10" fontWeight="600">Soldering Village</text>
|
||||||
|
|
||||||
|
{/* ── Area: Info / Coffee (center) ── */}
|
||||||
|
<circle cx="400" cy="180" r="22" fill="rgba(148,163,184,0.06)" stroke="rgba(148,163,184,0.2)" strokeWidth="1" />
|
||||||
|
<text x="400" y="176" textAnchor="middle" fill="#64748b" fontSize="14">☕</text>
|
||||||
|
<text x="400" y="192" textAnchor="middle" fill="#64748b" fontSize="7">INFO</text>
|
||||||
|
|
||||||
|
{/* ── Entrance marker ── */}
|
||||||
|
<rect x="60" y="185" width="50" height="30" rx="6" fill="rgba(148,163,184,0.1)" stroke="rgba(148,163,184,0.2)" strokeWidth="1" />
|
||||||
|
<text x="85" y="204" textAnchor="middle" fill="#64748b" fontSize="9" fontWeight="600">ENTER</text>
|
||||||
|
|
||||||
|
{/* ── Trees / decoration ── */}
|
||||||
|
<circle cx="450" cy="350" r="8" fill="rgba(16,185,129,0.1)" />
|
||||||
|
<circle cx="470" cy="345" r="6" fill="rgba(16,185,129,0.08)" />
|
||||||
|
<circle cx="130" cy="230" r="6" fill="rgba(16,185,129,0.08)" />
|
||||||
|
<circle cx="750" cy="150" r="10" fill="rgba(16,185,129,0.06)" />
|
||||||
|
<circle cx="740" cy="170" r="7" fill="rgba(16,185,129,0.08)" />
|
||||||
|
|
||||||
|
{/* ── Friend markers ── */}
|
||||||
|
{friends.map((f) => (
|
||||||
|
<g key={f.name}>
|
||||||
|
{/* Pulse ring for online friends */}
|
||||||
|
{f.online && (
|
||||||
|
<circle cx={f.x} cy={f.y} r="14" fill="none" stroke={f.color} strokeWidth="2" opacity="0.3">
|
||||||
|
<animate attributeName="r" from="14" to="24" dur="2s" repeatCount="indefinite" />
|
||||||
|
<animate attributeName="opacity" from="0.4" to="0" dur="2s" repeatCount="indefinite" />
|
||||||
|
</circle>
|
||||||
|
)}
|
||||||
|
{/* Dot */}
|
||||||
|
<circle cx={f.x} cy={f.y} r="12" fill={f.color} stroke="#0f172a" strokeWidth="3" />
|
||||||
|
{/* Emoji */}
|
||||||
|
<text x={f.x} y={f.y + 4} textAnchor="middle" fontSize="12">{f.emoji}</text>
|
||||||
|
{/* Name label */}
|
||||||
|
<rect x={f.x - 20} y={f.y + 16} width="40" height="16" rx="4" fill="rgba(15,23,42,0.85)" stroke={f.color} strokeWidth="0.5" />
|
||||||
|
<text x={f.x} y={f.y + 27} textAnchor="middle" fill="#e2e8f0" fontSize="9" fontWeight="500">{f.name}</text>
|
||||||
|
</g>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Map legend */}
|
||||||
|
<div className="absolute bottom-3 left-3 flex flex-wrap gap-3 text-xs text-slate-400">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-emerald-500" /> Stage
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-orange-500" /> Food
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-indigo-500" /> Workshops
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-yellow-500" /> Camping
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Simulated live badge */}
|
||||||
|
<div className="absolute top-3 right-3 flex items-center gap-1.5 bg-slate-800/80 backdrop-blur-sm rounded-full px-3 py-1.5 border border-slate-700/50">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||||
|
<span className="text-xs text-slate-300 font-medium">Live</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Friend List Panel ────────────────────────────────────── */
|
||||||
|
|
||||||
|
function FriendListPanel() {
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-800/50 rounded-2xl border border-slate-700/50 overflow-hidden">
|
||||||
|
<div className="px-5 py-3 border-b border-slate-700/50 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xl">👥</span>
|
||||||
|
<span className="font-semibold text-sm">Friends in Room</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-slate-400">{friends.filter((f) => f.online).length}/{friends.length} online</span>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 space-y-1">
|
||||||
|
{friends.map((f) => (
|
||||||
|
<div
|
||||||
|
key={f.name}
|
||||||
|
className="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-slate-700/30 transition-colors"
|
||||||
|
>
|
||||||
|
{/* Avatar */}
|
||||||
|
<div
|
||||||
|
className="w-9 h-9 rounded-full flex items-center justify-center text-lg flex-shrink-0 ring-2"
|
||||||
|
style={{ backgroundColor: `${f.color}20`, boxShadow: `0 0 0 2px ${f.color}` }}
|
||||||
|
>
|
||||||
|
{f.emoji}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info */}
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium text-slate-200">{f.name}</span>
|
||||||
|
<span
|
||||||
|
className="w-2 h-2 rounded-full flex-shrink-0"
|
||||||
|
style={{ backgroundColor: f.online ? '#10b981' : '#6b7280', boxShadow: f.online ? '0 0 6px #10b981' : 'none' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400 truncate">{f.status}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Last seen */}
|
||||||
|
<span className="text-xs text-slate-500 flex-shrink-0">{f.lastSeen}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status selector preview */}
|
||||||
|
<div className="px-5 py-3 border-t border-slate-700/50">
|
||||||
|
<p className="text-xs text-slate-500 mb-2">Your status:</p>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{statusOptions.slice(0, 5).map((s) => (
|
||||||
|
<span
|
||||||
|
key={s}
|
||||||
|
className={`text-xs px-2.5 py-1 rounded-full border transition-colors cursor-default ${
|
||||||
|
s === 'At Main Stage'
|
||||||
|
? 'bg-emerald-500/15 border-emerald-500/40 text-emerald-400'
|
||||||
|
: 'bg-slate-700/30 border-slate-600/40 text-slate-400 hover:border-slate-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<span className="text-xs px-2.5 py-1 rounded-full bg-slate-700/30 border border-slate-600/40 text-slate-500 cursor-default">
|
||||||
|
+{statusOptions.length - 5} more
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Page ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
export default function DemoPage() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white">
|
||||||
|
{/* Nav */}
|
||||||
|
<nav className="border-b border-slate-700/50 backdrop-blur-sm sticky top-0 z-50 bg-slate-900/80">
|
||||||
|
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Link href="/" className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 bg-gradient-to-br from-emerald-400 to-emerald-600 rounded-lg flex items-center justify-center font-bold text-slate-900 text-sm">
|
||||||
|
rM
|
||||||
|
</div>
|
||||||
|
<span className="font-semibold text-lg">rMaps</span>
|
||||||
|
</Link>
|
||||||
|
<span className="text-slate-600">/</span>
|
||||||
|
<span className="text-sm text-slate-400">Demo</span>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="text-sm px-4 py-2 bg-emerald-600 hover:bg-emerald-500 rounded-lg transition-colors font-medium"
|
||||||
|
>
|
||||||
|
Create Your Map
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Hero / Event Header */}
|
||||||
|
<section className="max-w-7xl mx-auto px-6 pt-12 pb-8">
|
||||||
|
<div className="text-center max-w-3xl mx-auto">
|
||||||
|
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-emerald-500/10 border border-emerald-500/20 rounded-full text-emerald-400 text-sm mb-6">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||||
|
Live Demo
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl sm:text-5xl font-bold mb-4 bg-gradient-to-r from-emerald-300 via-teal-300 to-cyan-300 bg-clip-text text-transparent">
|
||||||
|
CCC Camp 2026
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-slate-300 mb-3">
|
||||||
|
Find your friends across the campground in real time
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-slate-400 mb-6">
|
||||||
|
<span>📍 Ziegeleipark Mildenberg</span>
|
||||||
|
<span>📅 Aug 2026</span>
|
||||||
|
<span>👥 6 friends connected</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Member avatars */}
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
{friends.map((f) => (
|
||||||
|
<div
|
||||||
|
key={f.name}
|
||||||
|
className="w-10 h-10 rounded-full flex items-center justify-center text-lg ring-2 ring-slate-800"
|
||||||
|
style={{ backgroundColor: `${f.color}30` }}
|
||||||
|
title={f.name}
|
||||||
|
>
|
||||||
|
{f.emoji}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<span className="text-sm text-slate-400 ml-2">6 friends</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Intro text */}
|
||||||
|
<section className="max-w-7xl mx-auto px-6 pb-6">
|
||||||
|
<p className="text-center text-sm text-slate-400 max-w-2xl mx-auto">
|
||||||
|
This demo shows how <span className="text-slate-200 font-medium">rMaps</span> works at a
|
||||||
|
real event. See your friends on the camp map, check their status, and find each other without
|
||||||
|
sharing your location with any central server.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Main Demo Content */}
|
||||||
|
<section className="max-w-7xl mx-auto px-6 pb-16">
|
||||||
|
<div className="grid lg:grid-cols-3 gap-6">
|
||||||
|
{/* Map - takes 2 columns */}
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<div className="bg-slate-800/50 rounded-2xl border border-slate-700/50 overflow-hidden">
|
||||||
|
<div className="flex items-center justify-between px-5 py-3 border-b border-slate-700/50">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xl">🗺️</span>
|
||||||
|
<span className="font-semibold text-sm">Camp Map</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs text-slate-400">Last updated 2 min ago</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4">
|
||||||
|
<CampMap />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Friend List - 1 column */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<FriendListPanel />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* How It Works (brief) */}
|
||||||
|
<section className="max-w-7xl mx-auto px-6 pb-16">
|
||||||
|
<h2 className="text-2xl font-bold text-center mb-8">How rMaps Works at Events</h2>
|
||||||
|
<div className="grid sm:grid-cols-3 gap-6">
|
||||||
|
<div className="bg-slate-800/30 rounded-xl border border-slate-700/30 p-6 text-center">
|
||||||
|
<div className="w-12 h-12 mx-auto mb-4 bg-emerald-500/10 rounded-xl flex items-center justify-center text-2xl">
|
||||||
|
📱
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold mb-2 text-slate-200">Open & Share</h3>
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Create a map room and share the link with your crew. No app install needed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800/30 rounded-xl border border-slate-700/30 p-6 text-center">
|
||||||
|
<div className="w-12 h-12 mx-auto mb-4 bg-indigo-500/10 rounded-xl flex items-center justify-center text-2xl">
|
||||||
|
📍
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold mb-2 text-slate-200">See Everyone</h3>
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Friends appear on the map in real time. Set your status so everyone knows where you are.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800/30 rounded-xl border border-slate-700/30 p-6 text-center">
|
||||||
|
<div className="w-12 h-12 mx-auto mb-4 bg-rose-500/10 rounded-xl flex items-center justify-center text-2xl">
|
||||||
|
🔒
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold mb-2 text-slate-200">Stay Private</h3>
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Only people in your room see your location. Go invisible anytime. No tracking.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Bottom CTA */}
|
||||||
|
<section className="max-w-7xl mx-auto px-6 pb-20 text-center">
|
||||||
|
<div className="bg-slate-800/50 rounded-2xl border border-slate-700/50 p-10">
|
||||||
|
<h2 className="text-3xl font-bold mb-3">Ready to Find Your Friends?</h2>
|
||||||
|
<p className="text-slate-400 mb-6 max-w-lg mx-auto">
|
||||||
|
Create a map room for your next event. Share the link, and never lose your crew in the crowd again.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-block px-8 py-4 bg-emerald-600 hover:bg-emerald-500 rounded-xl text-lg font-medium transition-all shadow-lg shadow-emerald-900/30"
|
||||||
|
>
|
||||||
|
Create Your Map
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="border-t border-slate-700/50 py-8">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-slate-500 mb-4">
|
||||||
|
<span className="font-medium text-slate-400">r* Ecosystem</span>
|
||||||
|
<a href="https://rspace.online" className="hover:text-slate-300 transition-colors">🌌 rSpace</a>
|
||||||
|
<a href="https://rmaps.online" className="hover:text-slate-300 transition-colors font-medium text-slate-300">🗺️ rMaps</a>
|
||||||
|
<a href="https://rnotes.online" className="hover:text-slate-300 transition-colors">📝 rNotes</a>
|
||||||
|
<a href="https://rvote.online" className="hover:text-slate-300 transition-colors">🗳️ rVote</a>
|
||||||
|
<a href="https://rfunds.online" className="hover:text-slate-300 transition-colors">💰 rFunds</a>
|
||||||
|
<a href="https://rtrips.online" className="hover:text-slate-300 transition-colors">✈️ rTrips</a>
|
||||||
|
<a href="https://rcart.online" className="hover:text-slate-300 transition-colors">🛒 rCart</a>
|
||||||
|
<a href="https://rwallet.online" className="hover:text-slate-300 transition-colors">💼 rWallet</a>
|
||||||
|
<a href="https://rfiles.online" className="hover:text-slate-300 transition-colors">📁 rFiles</a>
|
||||||
|
<a href="https://rnetwork.online" className="hover:text-slate-300 transition-colors">🌐 rNetwork</a>
|
||||||
|
</div>
|
||||||
|
<p className="text-center text-xs text-slate-600">
|
||||||
|
Part of the r* ecosystem — collaborative tools for communities.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
436
src/app/page.tsx
436
src/app/page.tsx
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import Link from 'next/link';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { AuthButton } from '@/components/AuthButton';
|
import { AuthButton } from '@/components/AuthButton';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
|
@ -99,165 +100,324 @@ export default function HomePage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen flex flex-col items-center justify-center p-4">
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||||
<div className="max-w-md w-full space-y-8">
|
{/* ── Hero Section ─────────────────────────────────────────── */}
|
||||||
{/* Logo/Title */}
|
<section className="relative overflow-hidden">
|
||||||
<div className="text-center">
|
{/* Subtle background grid */}
|
||||||
<h1 className="text-5xl font-bold mb-2">
|
<div className="absolute inset-0 opacity-[0.03]" style={{
|
||||||
<span className="text-rmaps-primary">r</span>Maps
|
backgroundImage: 'radial-gradient(circle, #10b981 1px, transparent 1px)',
|
||||||
</h1>
|
backgroundSize: '40px 40px',
|
||||||
<p className="text-white/60">Find your friends at events</p>
|
}} />
|
||||||
<div className="mt-3">
|
|
||||||
<AuthButton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Quick Rejoin Card - show when user has saved info and last room */}
|
<div className="relative max-w-5xl mx-auto px-6 pt-16 pb-12 text-center">
|
||||||
{isLoaded && name && lastRoom && (
|
{/* Logo */}
|
||||||
<div className="room-panel rounded-2xl p-6">
|
<div className="flex items-center justify-center gap-3 mb-8">
|
||||||
<p className="text-white/60 text-sm text-center mb-4">Welcome back, {name}!</p>
|
<div className="w-12 h-12 bg-gradient-to-br from-emerald-400 to-emerald-600 rounded-xl flex items-center justify-center font-bold text-slate-900 text-lg">
|
||||||
<button
|
rM
|
||||||
onClick={handleRejoinLastRoom}
|
</div>
|
||||||
className="btn-primary w-full text-lg py-3 flex items-center justify-center gap-2"
|
<h1 className="text-4xl sm:text-5xl font-bold">
|
||||||
|
<span className="text-rmaps-primary">r</span>Maps
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Headline */}
|
||||||
|
<h2 className="text-3xl sm:text-5xl font-bold mb-4 bg-gradient-to-r from-emerald-300 via-teal-200 to-cyan-300 bg-clip-text text-transparent leading-tight">
|
||||||
|
Find Your Friends, Anywhere
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg sm:text-xl text-white/60 max-w-2xl mx-auto mb-8">
|
||||||
|
Privacy-first real-time location sharing for events, festivals, and camps.
|
||||||
|
See where your crew is without trusting a central server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* CTA buttons */}
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-6">
|
||||||
|
<Link
|
||||||
|
href="/demo"
|
||||||
|
className="px-8 py-3.5 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-xl transition-all shadow-lg shadow-emerald-900/30 text-lg"
|
||||||
>
|
>
|
||||||
<span className="text-xl">{emoji}</span>
|
Try the Demo
|
||||||
<span>Rejoin /{lastRoom}</span>
|
</Link>
|
||||||
</button>
|
<a
|
||||||
<p className="text-white/40 text-xs text-center mt-3">
|
href="#get-started"
|
||||||
Or create/join a different room below
|
className="px-8 py-3.5 bg-white/5 hover:bg-white/10 text-white font-medium rounded-xl transition-colors border border-white/10 text-lg"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-white/40">
|
||||||
|
No app install. No sign-up required to join a room.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Feature Cards ────────────────────────────────────────── */}
|
||||||
|
<section className="max-w-5xl mx-auto px-6 pb-16">
|
||||||
|
<div className="grid sm:grid-cols-3 gap-6">
|
||||||
|
{/* Real-time GPS */}
|
||||||
|
<div className="bg-slate-800/40 rounded-2xl border border-slate-700/40 p-6 text-center hover:border-emerald-500/30 transition-colors">
|
||||||
|
<div className="w-14 h-14 mx-auto mb-4 bg-emerald-500/10 rounded-xl flex items-center justify-center">
|
||||||
|
<svg className="w-7 h-7 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold mb-2 text-white">Real-time GPS</h3>
|
||||||
|
<p className="text-sm text-white/50">
|
||||||
|
Share your live location with friends. See everyone on the map updating in real time as you move.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Main Card */}
|
{/* Event Maps */}
|
||||||
<div className="room-panel rounded-2xl p-6 space-y-6">
|
<div className="bg-slate-800/40 rounded-2xl border border-slate-700/40 p-6 text-center hover:border-indigo-500/30 transition-colors">
|
||||||
{/* User Setup */}
|
<div className="w-14 h-14 mx-auto mb-4 bg-indigo-500/10 rounded-xl flex items-center justify-center">
|
||||||
<div className="space-y-4">
|
<svg className="w-7 h-7 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
<h2 className="text-lg font-medium">Your Profile</h2>
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 6.75V15m6-6v8.25m.503 3.498l4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 00-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0z" />
|
||||||
|
</svg>
|
||||||
<div>
|
|
||||||
<label className="block text-sm text-white/60 mb-2">Your Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
placeholder="Enter your name"
|
|
||||||
className="input w-full"
|
|
||||||
maxLength={20}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm text-white/60 mb-2">Your Avatar</label>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
{EMOJI_OPTIONS.map((e) => (
|
|
||||||
<button
|
|
||||||
key={e}
|
|
||||||
onClick={() => setEmoji(e)}
|
|
||||||
className={`w-10 h-10 rounded-lg text-xl flex items-center justify-center transition-all ${
|
|
||||||
emoji === e
|
|
||||||
? 'bg-rmaps-primary scale-110'
|
|
||||||
: 'bg-white/10 hover:bg-white/20'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{e}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold mb-2 text-white">Event Maps</h3>
|
||||||
|
<p className="text-sm text-white/50">
|
||||||
|
Navigate festivals, camps, and conferences. Custom maps with labeled stages, food courts, and meeting points.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className="border-white/10" />
|
{/* Privacy First */}
|
||||||
|
<div className="bg-slate-800/40 rounded-2xl border border-slate-700/40 p-6 text-center hover:border-rose-500/30 transition-colors">
|
||||||
{/* Create Room */}
|
<div className="w-14 h-14 mx-auto mb-4 bg-rose-500/10 rounded-xl flex items-center justify-center">
|
||||||
{!isCreating ? (
|
<svg className="w-7 h-7 text-rose-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
<div className="space-y-4">
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" />
|
||||||
<button
|
</svg>
|
||||||
onClick={() => setIsCreating(true)}
|
|
||||||
className="btn-primary w-full text-lg py-3"
|
|
||||||
disabled={!name.trim() || !isAuthenticated}
|
|
||||||
title={!isAuthenticated ? 'Sign in with EncryptID to create rooms' : ''}
|
|
||||||
>
|
|
||||||
{isAuthenticated ? 'Create New Map' : 'Sign in to Create Map'}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="text-center text-white/40 text-sm">or</div>
|
|
||||||
|
|
||||||
{/* Join Room */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={joinSlug}
|
|
||||||
onChange={(e) => setJoinSlug(e.target.value)}
|
|
||||||
placeholder="Enter room name or code"
|
|
||||||
className="input w-full"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleJoinRoom}
|
|
||||||
className="btn-secondary w-full"
|
|
||||||
disabled={!name.trim() || !joinSlug.trim()}
|
|
||||||
>
|
|
||||||
Join Existing Map
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<h3 className="text-lg font-semibold mb-2 text-white">Privacy First</h3>
|
||||||
<div className="space-y-4">
|
<p className="text-sm text-white/50">
|
||||||
<div>
|
Zero-knowledge architecture. You control who sees you. Go invisible anytime. No tracking, no data collection.
|
||||||
<label className="block text-sm text-white/60 mb-2">
|
</p>
|
||||||
Room Name (optional)
|
</div>
|
||||||
</label>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
</section>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={roomName}
|
|
||||||
onChange={(e) => setRoomName(e.target.value)}
|
|
||||||
placeholder="e.g., 39c3-crew"
|
|
||||||
className="input flex-1"
|
|
||||||
maxLength={20}
|
|
||||||
/>
|
|
||||||
<span className="text-white/40">.rmaps.online</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-white/40 mt-1">
|
|
||||||
Leave blank for a random code
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-3">
|
{/* ── How It Works ─────────────────────────────────────────── */}
|
||||||
<button
|
<section className="max-w-5xl mx-auto px-6 pb-20">
|
||||||
onClick={() => setIsCreating(false)}
|
<h2 className="text-2xl sm:text-3xl font-bold text-center mb-10 text-white">How It Works</h2>
|
||||||
className="btn-ghost flex-1"
|
<div className="grid sm:grid-cols-3 gap-8">
|
||||||
>
|
{/* Step 1 */}
|
||||||
Cancel
|
<div className="flex flex-col items-center text-center">
|
||||||
</button>
|
<div className="w-12 h-12 rounded-full bg-emerald-500/15 border border-emerald-500/30 flex items-center justify-center text-emerald-400 font-bold text-lg mb-4">
|
||||||
<button
|
1
|
||||||
onClick={handleCreateRoom}
|
|
||||||
className="btn-primary flex-1"
|
|
||||||
disabled={!name.trim()}
|
|
||||||
>
|
|
||||||
Create Map
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<h3 className="font-semibold text-white mb-2">Create a Map Room</h3>
|
||||||
|
<p className="text-sm text-white/50">
|
||||||
|
Sign in and create a room for your event. Get a shareable link like <span className="text-white/70 font-mono text-xs">rmaps.online/ccc-camp</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 2 */}
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<div className="w-12 h-12 rounded-full bg-indigo-500/15 border border-indigo-500/30 flex items-center justify-center text-indigo-400 font-bold text-lg mb-4">
|
||||||
|
2
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-white mb-2">Share with Friends</h3>
|
||||||
|
<p className="text-sm text-white/50">
|
||||||
|
Send the link to your crew. They join instantly from any device -- no app download, no account needed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 3 */}
|
||||||
|
<div className="flex flex-col items-center text-center">
|
||||||
|
<div className="w-12 h-12 rounded-full bg-cyan-500/15 border border-cyan-500/30 flex items-center justify-center text-cyan-400 font-bold text-lg mb-4">
|
||||||
|
3
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-white mb-2">Find Each Other</h3>
|
||||||
|
<p className="text-sm text-white/50">
|
||||||
|
See everyone on the map in real time. Set your status, share meeting points, and never lose your friends again.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Connector lines (decorative) */}
|
||||||
<div className="text-center text-white/40 text-sm space-y-2">
|
<div className="hidden sm:flex items-center justify-center mt-8">
|
||||||
<p>Privacy-first location sharing</p>
|
<div className="flex items-center gap-2 text-white/20 text-sm">
|
||||||
<p>
|
<span>Built for</span>
|
||||||
Built for{' '}
|
|
||||||
<a
|
<a
|
||||||
href="https://events.ccc.de/"
|
href="https://events.ccc.de/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-rmaps-primary hover:underline"
|
className="text-rmaps-primary/60 hover:text-rmaps-primary transition-colors"
|
||||||
>
|
>
|
||||||
CCC events
|
CCC events
|
||||||
</a>
|
</a>
|
||||||
|
<span>and beyond</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Get Started (existing login/room interface) ──────────── */}
|
||||||
|
<section id="get-started" className="scroll-mt-8 pb-16">
|
||||||
|
<main className="flex flex-col items-center px-4">
|
||||||
|
<div className="max-w-md w-full space-y-8">
|
||||||
|
{/* Section heading */}
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold mb-2 text-white">Get Started</h2>
|
||||||
|
<p className="text-white/50 text-sm mb-4">Create or join a map room</p>
|
||||||
|
<div className="mt-3">
|
||||||
|
<AuthButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Rejoin Card - show when user has saved info and last room */}
|
||||||
|
{isLoaded && name && lastRoom && (
|
||||||
|
<div className="room-panel rounded-2xl p-6">
|
||||||
|
<p className="text-white/60 text-sm text-center mb-4">Welcome back, {name}!</p>
|
||||||
|
<button
|
||||||
|
onClick={handleRejoinLastRoom}
|
||||||
|
className="btn-primary w-full text-lg py-3 flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
|
<span className="text-xl">{emoji}</span>
|
||||||
|
<span>Rejoin /{lastRoom}</span>
|
||||||
|
</button>
|
||||||
|
<p className="text-white/40 text-xs text-center mt-3">
|
||||||
|
Or create/join a different room below
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Main Card */}
|
||||||
|
<div className="room-panel rounded-2xl p-6 space-y-6">
|
||||||
|
{/* User Setup */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-medium">Your Profile</h3>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-white/60 mb-2">Your Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Enter your name"
|
||||||
|
className="input w-full"
|
||||||
|
maxLength={20}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-white/60 mb-2">Your Avatar</label>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{EMOJI_OPTIONS.map((e) => (
|
||||||
|
<button
|
||||||
|
key={e}
|
||||||
|
onClick={() => setEmoji(e)}
|
||||||
|
className={`w-10 h-10 rounded-lg text-xl flex items-center justify-center transition-all ${
|
||||||
|
emoji === e
|
||||||
|
? 'bg-rmaps-primary scale-110'
|
||||||
|
: 'bg-white/10 hover:bg-white/20'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{e}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="border-white/10" />
|
||||||
|
|
||||||
|
{/* Create Room */}
|
||||||
|
{!isCreating ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsCreating(true)}
|
||||||
|
className="btn-primary w-full text-lg py-3"
|
||||||
|
disabled={!name.trim() || !isAuthenticated}
|
||||||
|
title={!isAuthenticated ? 'Sign in with EncryptID to create rooms' : ''}
|
||||||
|
>
|
||||||
|
{isAuthenticated ? 'Create New Map' : 'Sign in to Create Map'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="text-center text-white/40 text-sm">or</div>
|
||||||
|
|
||||||
|
{/* Join Room */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={joinSlug}
|
||||||
|
onChange={(e) => setJoinSlug(e.target.value)}
|
||||||
|
placeholder="Enter room name or code"
|
||||||
|
className="input w-full"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleJoinRoom}
|
||||||
|
className="btn-secondary w-full"
|
||||||
|
disabled={!name.trim() || !joinSlug.trim()}
|
||||||
|
>
|
||||||
|
Join Existing Map
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-white/60 mb-2">
|
||||||
|
Room Name (optional)
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={roomName}
|
||||||
|
onChange={(e) => setRoomName(e.target.value)}
|
||||||
|
placeholder="e.g., 39c3-crew"
|
||||||
|
className="input flex-1"
|
||||||
|
maxLength={20}
|
||||||
|
/>
|
||||||
|
<span className="text-white/40">.rmaps.online</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-white/40 mt-1">
|
||||||
|
Leave blank for a random code
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsCreating(false)}
|
||||||
|
className="btn-ghost flex-1"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleCreateRoom}
|
||||||
|
className="btn-primary flex-1"
|
||||||
|
disabled={!name.trim()}
|
||||||
|
>
|
||||||
|
Create Map
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Ecosystem Footer ─────────────────────────────────────── */}
|
||||||
|
<footer className="border-t border-slate-700/50 py-8">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-slate-500 mb-4">
|
||||||
|
<span className="font-medium text-slate-400">r* Ecosystem</span>
|
||||||
|
<a href="https://rspace.online" className="hover:text-slate-300 transition-colors">🌌 rSpace</a>
|
||||||
|
<a href="https://rmaps.online" className="hover:text-slate-300 transition-colors font-medium text-slate-300">🗺️ rMaps</a>
|
||||||
|
<a href="https://rnotes.online" className="hover:text-slate-300 transition-colors">📝 rNotes</a>
|
||||||
|
<a href="https://rvote.online" className="hover:text-slate-300 transition-colors">🗳️ rVote</a>
|
||||||
|
<a href="https://rfunds.online" className="hover:text-slate-300 transition-colors">💰 rFunds</a>
|
||||||
|
<a href="https://rtrips.online" className="hover:text-slate-300 transition-colors">✈️ rTrips</a>
|
||||||
|
<a href="https://rcart.online" className="hover:text-slate-300 transition-colors">🛒 rCart</a>
|
||||||
|
<a href="https://rwallet.online" className="hover:text-slate-300 transition-colors">💼 rWallet</a>
|
||||||
|
<a href="https://rfiles.online" className="hover:text-slate-300 transition-colors">📁 rFiles</a>
|
||||||
|
<a href="https://rnetwork.online" className="hover:text-slate-300 transition-colors">🌐 rNetwork</a>
|
||||||
|
</div>
|
||||||
|
<p className="text-center text-xs text-slate-600">
|
||||||
|
Part of the r* ecosystem — collaborative tools for communities.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</footer>
|
||||||
</main>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue