feat: redesign landing page with full feature showcase
Replace minimal 3-card landing page with comprehensive feature presentation matching the rApp ecosystem style: - Hero with ecosystem badge and gradient accents - 4 core feature cards (GPS, navigation, meeting points, privacy) - 6 differentiator cards (c3nav, pinging, Google import, PWA, QR, CRDT) - How It Works steps - Use case cards (festivals, city exploration, group coordination) - Technical highlights grid (MapLibre, OSRM, c3nav, Automerge, etc.) - Preserved existing Get Started form and room join flow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
56901d8e45
commit
460dc5045f
399
src/app/page.tsx
399
src/app/page.tsx
|
|
@ -7,10 +7,8 @@ import { AuthButton } from '@/components/AuthButton';
|
||||||
import { EcosystemFooter } from '@/components/EcosystemFooter';
|
import { EcosystemFooter } from '@/components/EcosystemFooter';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
|
||||||
// Emoji options for avatars
|
|
||||||
const EMOJI_OPTIONS = ['🐙', '🦊', '🐻', '🐱', '🦝', '🐸', '🦉', '🐧', '🦋', '🐝'];
|
const EMOJI_OPTIONS = ['🐙', '🦊', '🐻', '🐱', '🦝', '🐸', '🦉', '🐧', '🦋', '🐝'];
|
||||||
|
|
||||||
// Generate a URL-safe room slug
|
|
||||||
function generateSlug(): string {
|
function generateSlug(): string {
|
||||||
return nanoid(8).toLowerCase();
|
return nanoid(8).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
@ -26,8 +24,6 @@ export default function HomePage() {
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
const [lastRoom, setLastRoom] = useState<string | null>(null);
|
const [lastRoom, setLastRoom] = useState<string | null>(null);
|
||||||
|
|
||||||
// Load saved user info from localStorage on mount
|
|
||||||
// If opened as installed PWA (standalone mode), auto-redirect to last room
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let loadedEmoji = '';
|
let loadedEmoji = '';
|
||||||
try {
|
try {
|
||||||
|
|
@ -40,7 +36,6 @@ export default function HomePage() {
|
||||||
loadedEmoji = user.emoji;
|
loadedEmoji = user.emoji;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Load last visited room (show "Rejoin" button, but don't auto-redirect)
|
|
||||||
const lastVisited = localStorage.getItem('rmaps_last_room');
|
const lastVisited = localStorage.getItem('rmaps_last_room');
|
||||||
if (lastVisited) {
|
if (lastVisited) {
|
||||||
setLastRoom(lastVisited);
|
setLastRoom(lastVisited);
|
||||||
|
|
@ -48,14 +43,12 @@ export default function HomePage() {
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore parse errors
|
// Ignore parse errors
|
||||||
}
|
}
|
||||||
// Set random emoji if none loaded
|
|
||||||
if (!loadedEmoji) {
|
if (!loadedEmoji) {
|
||||||
setEmoji(EMOJI_OPTIONS[Math.floor(Math.random() * EMOJI_OPTIONS.length)]);
|
setEmoji(EMOJI_OPTIONS[Math.floor(Math.random() * EMOJI_OPTIONS.length)]);
|
||||||
}
|
}
|
||||||
setIsLoaded(true);
|
setIsLoaded(true);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
// Auto-fill name from EncryptID when authenticated
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthenticated && authUsername && !name) {
|
if (isAuthenticated && authUsername && !name) {
|
||||||
setName(authUsername);
|
setName(authUsername);
|
||||||
|
|
@ -64,31 +57,21 @@ export default function HomePage() {
|
||||||
|
|
||||||
const handleCreateRoom = async () => {
|
const handleCreateRoom = async () => {
|
||||||
if (!name.trim()) return;
|
if (!name.trim()) return;
|
||||||
|
|
||||||
// Require EncryptID auth to create rooms
|
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
alert('Please sign in with EncryptID to create a room.');
|
alert('Please sign in with EncryptID to create a room.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const slug = roomName.trim()
|
const slug = roomName.trim()
|
||||||
? roomName.toLowerCase().replace(/[^a-z0-9]/g, '-').slice(0, 20)
|
? roomName.toLowerCase().replace(/[^a-z0-9]/g, '-').slice(0, 20)
|
||||||
: generateSlug();
|
: generateSlug();
|
||||||
|
|
||||||
// Store user info in localStorage for the session
|
|
||||||
localStorage.setItem('rmaps_user', JSON.stringify({ name, emoji }));
|
localStorage.setItem('rmaps_user', JSON.stringify({ name, emoji }));
|
||||||
localStorage.setItem('rmaps_last_room', slug);
|
localStorage.setItem('rmaps_last_room', slug);
|
||||||
|
|
||||||
// Navigate to the room (will create it if it doesn't exist)
|
|
||||||
router.push(`/${slug}`);
|
router.push(`/${slug}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleJoinRoom = () => {
|
const handleJoinRoom = () => {
|
||||||
if (!name.trim() || !joinSlug.trim()) return;
|
if (!name.trim() || !joinSlug.trim()) return;
|
||||||
|
|
||||||
localStorage.setItem('rmaps_user', JSON.stringify({ name, emoji }));
|
localStorage.setItem('rmaps_user', JSON.stringify({ name, emoji }));
|
||||||
|
|
||||||
// Clean the slug
|
|
||||||
const cleanSlug = joinSlug.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
const cleanSlug = joinSlug.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
||||||
localStorage.setItem('rmaps_last_room', cleanSlug);
|
localStorage.setItem('rmaps_last_room', cleanSlug);
|
||||||
router.push(`/${cleanSlug}`);
|
router.push(`/${cleanSlug}`);
|
||||||
|
|
@ -100,173 +83,395 @@ export default function HomePage() {
|
||||||
router.push(`/${lastRoom}`);
|
router.push(`/${lastRoom}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||||
{/* ── Hero Section ─────────────────────────────────────────── */}
|
|
||||||
<section className="relative overflow-hidden">
|
|
||||||
{/* Subtle background grid */}
|
|
||||||
<div className="absolute inset-0 opacity-[0.03]" style={{
|
|
||||||
backgroundImage: 'radial-gradient(circle, #10b981 1px, transparent 1px)',
|
|
||||||
backgroundSize: '40px 40px',
|
|
||||||
}} />
|
|
||||||
|
|
||||||
<div className="relative max-w-5xl mx-auto px-6 pt-16 pb-12 text-center">
|
{/* ── Hero ──────────────────────────────────────────────────── */}
|
||||||
{/* Logo */}
|
<section className="relative overflow-hidden">
|
||||||
<div className="flex items-center justify-center gap-3 mb-8">
|
{/* Background glow */}
|
||||||
<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">
|
<div className="absolute top-[-40%] left-[20%] w-[600px] h-[600px] bg-emerald-500/8 rounded-full blur-[120px] pointer-events-none" />
|
||||||
|
<div className="absolute top-[-20%] right-[10%] w-[400px] h-[400px] bg-cyan-500/6 rounded-full blur-[100px] pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="relative max-w-4xl mx-auto px-6 pt-20 pb-16 text-center">
|
||||||
|
{/* Ecosystem badge */}
|
||||||
|
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-sm mb-8">
|
||||||
|
Part of the rSpace Ecosystem
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logo + Name */}
|
||||||
|
<div className="flex items-center justify-center gap-3 mb-6">
|
||||||
|
<div className="w-14 h-14 bg-gradient-to-br from-emerald-400 to-teal-600 rounded-xl flex items-center justify-center font-bold text-slate-900 text-xl shadow-lg shadow-emerald-500/20">
|
||||||
rM
|
rM
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-4xl sm:text-5xl font-bold">
|
<h1 className="text-5xl sm:text-6xl font-bold">
|
||||||
<span className="text-rmaps-primary">r</span>Maps
|
<span className="text-rmaps-primary">r</span>Maps
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Headline */}
|
{/* 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">
|
<h2 className="text-3xl sm:text-5xl font-bold mb-5 bg-gradient-to-r from-emerald-300 via-teal-200 to-cyan-300 bg-clip-text text-transparent leading-tight">
|
||||||
Collaborative Maps for Everyone
|
Real-Time Collaborative Maps
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg sm:text-xl text-white/60 max-w-2xl mx-auto mb-8">
|
<p className="text-lg sm:text-xl text-slate-400 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||||
A flexible, privacy-first map tool for real-time location sharing, event navigation, and group coordination.
|
Share live locations, navigate indoor and outdoor spaces, coordinate meetups
|
||||||
Create a room, share a link, and see your crew on the map.
|
— all from the browser. No app install. No tracking. No data collection.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* CTA buttons */}
|
{/* CTA buttons */}
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-6">
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-6">
|
||||||
<a
|
<a
|
||||||
href="https://demo.rmaps.online"
|
href="https://demo.rmaps.online"
|
||||||
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"
|
className="px-8 py-3.5 bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 text-white font-medium rounded-xl transition-all shadow-lg shadow-emerald-900/30 text-lg"
|
||||||
>
|
>
|
||||||
Try the Demo
|
Try the Demo
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#get-started"
|
href="#get-started"
|
||||||
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"
|
className="px-8 py-3.5 bg-white/5 hover:bg-white/10 text-white font-medium rounded-xl transition-colors border border-slate-700 text-lg"
|
||||||
>
|
>
|
||||||
Get Started
|
Create a Room
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-slate-500">
|
||||||
<p className="text-sm text-white/40">
|
No sign-up required to join. Works on any device.
|
||||||
No app install. No sign-up required to join a room.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Feature Cards ────────────────────────────────────────── */}
|
{/* ── Core Features ─────────────────────────────────────────── */}
|
||||||
<section className="max-w-5xl mx-auto px-6 pb-16">
|
<section className="max-w-6xl mx-auto px-6 py-16">
|
||||||
<div className="grid sm:grid-cols-3 gap-6">
|
<div className="text-center mb-12">
|
||||||
{/* Real-time GPS */}
|
<span className="inline-block px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs font-medium uppercase tracking-wider mb-4">
|
||||||
<div className="bg-slate-800/40 rounded-2xl border border-slate-700/40 p-6 text-center hover:border-emerald-500/30 transition-colors">
|
Core Features
|
||||||
<div className="w-14 h-14 mx-auto mb-4 bg-emerald-500/10 rounded-xl flex items-center justify-center">
|
</span>
|
||||||
<svg className="w-7 h-7 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
<h2 className="text-2xl sm:text-3xl font-bold text-white">
|
||||||
|
Everything you need to find your friends
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||||
|
{/* Live Location */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6 hover:border-emerald-500/30 transition-colors">
|
||||||
|
<div className="w-11 h-11 mb-4 bg-emerald-500/10 border border-emerald-500/20 rounded-xl flex items-center justify-center">
|
||||||
|
<svg className="w-6 h-6 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="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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold mb-2 text-white">Real-time GPS</h3>
|
<h3 className="font-semibold mb-2 text-white">Live GPS Sharing</h3>
|
||||||
<p className="text-sm text-white/50">
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
Share your live location with friends. See everyone on the map updating in real time as you move.
|
Real-time location updates via WebSocket. See everyone on the map
|
||||||
|
as they move, with stale detection and high-accuracy fallback.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Event Maps */}
|
{/* Indoor + Outdoor Navigation */}
|
||||||
<div className="bg-slate-800/40 rounded-2xl border border-slate-700/40 p-6 text-center hover:border-indigo-500/30 transition-colors">
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6 hover:border-cyan-500/30 transition-colors">
|
||||||
<div className="w-14 h-14 mx-auto mb-4 bg-indigo-500/10 rounded-xl flex items-center justify-center">
|
<div className="w-11 h-11 mb-4 bg-cyan-500/10 border border-cyan-500/20 rounded-xl flex items-center justify-center">
|
||||||
<svg className="w-7 h-7 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
<svg className="w-6 h-6 text-cyan-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold mb-2 text-white">Event Maps</h3>
|
<h3 className="font-semibold mb-2 text-white">Indoor + Outdoor Nav</h3>
|
||||||
<p className="text-sm text-white/50">
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
Navigate festivals, camps, and conferences. Custom maps with labeled stages, food courts, and meeting points.
|
Turn-by-turn routing via OSRM outdoors, seamless switch to c3nav
|
||||||
|
for indoor venues. Multi-floor, level-aware navigation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Privacy First */}
|
{/* Meeting Points */}
|
||||||
<div className="bg-slate-800/40 rounded-2xl border border-slate-700/40 p-6 text-center hover:border-rose-500/30 transition-colors">
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6 hover:border-indigo-500/30 transition-colors">
|
||||||
<div className="w-14 h-14 mx-auto mb-4 bg-rose-500/10 rounded-xl flex items-center justify-center">
|
<div className="w-11 h-11 mb-4 bg-indigo-500/10 border border-indigo-500/20 rounded-xl flex items-center justify-center">
|
||||||
<svg className="w-7 h-7 text-rose-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
<svg className="w-6 h-6 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3 3v1.5M3 21v-6m0 0l2.77-.693a9 9 0 016.208.682l.108.054a9 9 0 006.086.71l3.114-.732a48.524 48.524 0 01-.005-10.499l-3.11.732a9 9 0 01-6.085-.711l-.108-.054a9 9 0 00-6.208-.682L3 4.5M3 15V4.5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold mb-2 text-white">Meeting Points</h3>
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
Drop waypoints for meetups, events, and points of interest.
|
||||||
|
Search by address, share coordinates, or pin from your location.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Privacy */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6 hover:border-rose-500/30 transition-colors">
|
||||||
|
<div className="w-11 h-11 mb-4 bg-rose-500/10 border border-rose-500/20 rounded-xl flex items-center justify-center">
|
||||||
|
<svg className="w-6 h-6 text-rose-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
<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" />
|
<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" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold mb-2 text-white">Privacy First</h3>
|
<h3 className="font-semibold mb-2 text-white">Privacy First</h3>
|
||||||
<p className="text-sm text-white/50">
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
Zero-knowledge architecture. You control who sees you. Go invisible anytime. No tracking, no data collection.
|
Ghost mode, precision levels, one-toggle location sharing.
|
||||||
|
Zero tracking, zero data collection. You control who sees you.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── How It Works ─────────────────────────────────────────── */}
|
{/* ── What Makes rMaps Different ────────────────────────────── */}
|
||||||
<section className="max-w-5xl mx-auto px-6 pb-20">
|
<section className="max-w-6xl mx-auto px-6 py-16">
|
||||||
<h2 className="text-2xl sm:text-3xl font-bold text-center mb-10 text-white">How It Works</h2>
|
<div className="text-center mb-12">
|
||||||
|
<span className="inline-block px-3 py-1 rounded-full bg-teal-500/10 border border-teal-500/20 text-teal-400 text-xs font-medium uppercase tracking-wider mb-4">
|
||||||
|
Beyond Google Maps
|
||||||
|
</span>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-white">
|
||||||
|
What makes rMaps different
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid sm:grid-cols-2 gap-6">
|
||||||
|
{/* CCC Event Integration */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 shrink-0 bg-emerald-500/10 border border-emerald-500/20 rounded-lg flex items-center justify-center text-lg">
|
||||||
|
🏕️
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1.5 text-white">CCC Event Integration</h3>
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
Native c3nav integration for 39C3, Camp, and other CCC events. Indoor maps with
|
||||||
|
multi-floor routing, venue bounds detection, and automatic map switching
|
||||||
|
when you walk inside.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Push Notifications & Pinging */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 shrink-0 bg-cyan-500/10 border border-cyan-500/20 rounded-lg flex items-center justify-center text-lg">
|
||||||
|
📡
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1.5 text-white">Location Pinging</h3>
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
Request a friend's location with one tap. Push notifications via
|
||||||
|
Web Push API with vibration alerts. Works even when the app is
|
||||||
|
backgrounded via service worker.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Google Maps Import */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 shrink-0 bg-indigo-500/10 border border-indigo-500/20 rounded-lg flex items-center justify-center text-lg">
|
||||||
|
📦
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1.5 text-white">Google Maps Import</h3>
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
Import your saved places from Google Takeout ZIP exports. GeoJSON
|
||||||
|
parsing, auto-emoji mapping by place type, and preview before
|
||||||
|
importing.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* PWA & Offline */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 shrink-0 bg-amber-500/10 border border-amber-500/20 rounded-lg flex items-center justify-center text-lg">
|
||||||
|
📱
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1.5 text-white">PWA & Offline Mode</h3>
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
Install as a native app. Three-tier service worker caching keeps
|
||||||
|
maps accessible offline with up to 500 cached tiles. Background
|
||||||
|
sync handles location updates.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* QR Sharing */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 shrink-0 bg-violet-500/10 border border-violet-500/20 rounded-lg flex items-center justify-center text-lg">
|
||||||
|
🔗
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1.5 text-white">Instant Room Sharing</h3>
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
Generate a QR code or shareable link for any room. Friends scan or tap
|
||||||
|
to join instantly — no account needed, no app download.
|
||||||
|
Native share dialog on mobile.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CRDT Sync */}
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-10 h-10 shrink-0 bg-rose-500/10 border border-rose-500/20 rounded-lg flex items-center justify-center text-lg">
|
||||||
|
🔄
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-1.5 text-white">Conflict-Free Sync</h3>
|
||||||
|
<p className="text-sm text-slate-400 leading-relaxed">
|
||||||
|
Automerge CRDT architecture ensures everyone sees the same map state,
|
||||||
|
even through disconnections. WebSocket real-time sync with automatic
|
||||||
|
reconnection and state recovery.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── How It Works ──────────────────────────────────────────── */}
|
||||||
|
<section className="max-w-5xl mx-auto px-6 py-16">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<span className="inline-block px-3 py-1 rounded-full bg-cyan-500/10 border border-cyan-500/20 text-cyan-400 text-xs font-medium uppercase tracking-wider mb-4">
|
||||||
|
How It Works
|
||||||
|
</span>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-white">
|
||||||
|
Three steps to find your crew
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid sm:grid-cols-3 gap-8">
|
<div className="grid sm:grid-cols-3 gap-8">
|
||||||
{/* Step 1 */}
|
|
||||||
<div className="flex flex-col items-center text-center">
|
<div className="flex flex-col items-center text-center">
|
||||||
<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">
|
<div className="w-14 h-14 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">
|
||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-white mb-2">Create a Map Room</h3>
|
<h3 className="font-semibold text-white mb-2">Create a Map Room</h3>
|
||||||
<p className="text-sm text-white/50">
|
<p className="text-sm text-slate-400">
|
||||||
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>
|
Sign in and name your room. Get a link like{' '}
|
||||||
|
<span className="text-white/70 font-mono text-xs">camp.rmaps.online</span>{' '}
|
||||||
|
or a custom slug.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Step 2 */}
|
|
||||||
<div className="flex flex-col items-center text-center">
|
<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">
|
<div className="w-14 h-14 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">
|
||||||
2
|
2
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-white mb-2">Share with Friends</h3>
|
<h3 className="font-semibold text-white mb-2">Share with Friends</h3>
|
||||||
<p className="text-sm text-white/50">
|
<p className="text-sm text-slate-400">
|
||||||
Send the link to your crew. They join instantly from any device -- no app download, no account needed.
|
Send the link or scan the QR code. Friends join from any browser
|
||||||
|
— no app download, no account creation needed.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Step 3 */}
|
|
||||||
<div className="flex flex-col items-center text-center">
|
<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">
|
<div className="w-14 h-14 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">
|
||||||
3
|
3
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-white mb-2">Find Each Other</h3>
|
<h3 className="font-semibold text-white mb-2">Navigate Together</h3>
|
||||||
<p className="text-sm text-white/50">
|
<p className="text-sm text-slate-400">
|
||||||
See everyone on the map in real time. Set your status, share meeting points, and never lose your friends again.
|
See everyone in real time. Drop meeting points, get turn-by-turn
|
||||||
|
directions, and ping friends when you need to regroup.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Use Cases ─────────────────────────────────────────────── */}
|
||||||
|
<section className="max-w-5xl mx-auto px-6 py-16">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<span className="inline-block px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 text-xs font-medium uppercase tracking-wider mb-4">
|
||||||
|
Built For
|
||||||
|
</span>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-white">
|
||||||
|
Maps for every gathering
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid sm:grid-cols-3 gap-6">
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6 text-center hover:border-emerald-500/30 transition-colors">
|
||||||
|
<div className="text-3xl mb-3">🏕️</div>
|
||||||
|
<h3 className="font-semibold mb-2 text-white">Festivals & Camps</h3>
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Navigate massive venues with indoor maps. Find stages, food courts,
|
||||||
|
and your crew across multi-day events like CCC Camp.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6 text-center hover:border-cyan-500/30 transition-colors">
|
||||||
|
<div className="text-3xl mb-3">🏙️</div>
|
||||||
|
<h3 className="font-semibold mb-2 text-white">City Exploration</h3>
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Exploring a new city with friends? Share locations, drop pins at
|
||||||
|
restaurants and landmarks, import your Google Maps saved places.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-800/30 rounded-2xl border border-slate-700/50 p-6 text-center hover:border-indigo-500/30 transition-colors">
|
||||||
|
<div className="text-3xl mb-3">🤝</div>
|
||||||
|
<h3 className="font-semibold mb-2 text-white">Group Coordination</h3>
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Conferences, retreats, team offsites. Set meeting points, ping
|
||||||
|
stragglers, and get walking directions to the next session.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Connector lines (decorative) */}
|
<div className="flex items-center justify-center mt-8">
|
||||||
<div className="hidden sm:flex items-center justify-center mt-8">
|
<div className="flex items-center gap-2 text-slate-500 text-sm">
|
||||||
<div className="flex items-center gap-2 text-white/20 text-sm">
|
<span>First built for</span>
|
||||||
<span>Built for</span>
|
|
||||||
<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/60 hover:text-rmaps-primary transition-colors"
|
className="text-emerald-500/70 hover:text-emerald-400 transition-colors"
|
||||||
>
|
>
|
||||||
CCC events
|
CCC events
|
||||||
</a>
|
</a>
|
||||||
<span>and beyond</span>
|
<span>— now for any gathering</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Get Started (existing login/room interface) ──────────── */}
|
{/* ── Technical Highlights ──────────────────────────────────── */}
|
||||||
<section id="get-started" className="scroll-mt-8 pb-16">
|
<section className="max-w-5xl mx-auto px-6 py-16">
|
||||||
|
<div className="text-center mb-10">
|
||||||
|
<span className="inline-block px-3 py-1 rounded-full bg-slate-700/50 border border-slate-600/50 text-slate-400 text-xs font-medium uppercase tracking-wider mb-4">
|
||||||
|
Under the Hood
|
||||||
|
</span>
|
||||||
|
<h2 className="text-2xl sm:text-3xl font-bold text-white">
|
||||||
|
Built on open standards
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-center">
|
||||||
|
{[
|
||||||
|
{ label: 'MapLibre GL', desc: 'Open-source maps' },
|
||||||
|
{ label: 'OSRM', desc: 'Outdoor routing' },
|
||||||
|
{ label: 'c3nav', desc: 'Indoor navigation' },
|
||||||
|
{ label: 'Automerge', desc: 'CRDT sync' },
|
||||||
|
{ label: 'Web Push', desc: 'Notifications' },
|
||||||
|
{ label: 'Service Worker', desc: 'Offline & PWA' },
|
||||||
|
{ label: 'WebSocket', desc: 'Real-time sync' },
|
||||||
|
{ label: 'EncryptID', desc: 'Identity & auth' },
|
||||||
|
].map((tech) => (
|
||||||
|
<div key={tech.label} className="bg-slate-800/20 rounded-xl border border-slate-700/30 p-4">
|
||||||
|
<p className="text-sm font-medium text-white">{tech.label}</p>
|
||||||
|
<p className="text-xs text-slate-500 mt-0.5">{tech.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Get Started ───────────────────────────────────────────── */}
|
||||||
|
<section id="get-started" className="scroll-mt-8 py-16">
|
||||||
<main className="flex flex-col items-center px-4">
|
<main className="flex flex-col items-center px-4">
|
||||||
<div className="max-w-md w-full space-y-8">
|
<div className="max-w-md w-full space-y-8">
|
||||||
{/* Section heading */}
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-2xl sm:text-3xl font-bold mb-2 text-white">Get Started</h2>
|
<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>
|
<p className="text-slate-500 text-sm mb-4">Create or join a map room</p>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<AuthButton />
|
<AuthButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Rejoin Card - show when user has saved info and last room */}
|
{/* Quick Rejoin */}
|
||||||
{isLoaded && name && lastRoom && (
|
{isLoaded && name && lastRoom && (
|
||||||
<div className="room-panel rounded-2xl p-6">
|
<div className="room-panel rounded-2xl p-6">
|
||||||
<p className="text-white/60 text-sm text-center mb-4">Welcome back, {name}!</p>
|
<p className="text-white/60 text-sm text-center mb-4">Welcome back, {name}!</p>
|
||||||
|
|
@ -285,10 +490,8 @@ export default function HomePage() {
|
||||||
|
|
||||||
{/* Main Card */}
|
{/* Main Card */}
|
||||||
<div className="room-panel rounded-2xl p-6 space-y-6">
|
<div className="room-panel rounded-2xl p-6 space-y-6">
|
||||||
{/* User Setup */}
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-medium">Your Profile</h3>
|
<h3 className="text-lg font-medium">Your Profile</h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-white/60 mb-2">Your Name</label>
|
<label className="block text-sm text-white/60 mb-2">Your Name</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -300,7 +503,6 @@ export default function HomePage() {
|
||||||
maxLength={20}
|
maxLength={20}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-white/60 mb-2">Your Avatar</label>
|
<label className="block text-sm text-white/60 mb-2">Your Avatar</label>
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
|
@ -323,7 +525,6 @@ export default function HomePage() {
|
||||||
|
|
||||||
<hr className="border-white/10" />
|
<hr className="border-white/10" />
|
||||||
|
|
||||||
{/* Create Room */}
|
|
||||||
{!isCreating ? (
|
{!isCreating ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<button
|
<button
|
||||||
|
|
@ -334,10 +535,7 @@ export default function HomePage() {
|
||||||
>
|
>
|
||||||
{isAuthenticated ? 'Create New Map' : 'Sign in to Create Map'}
|
{isAuthenticated ? 'Create New Map' : 'Sign in to Create Map'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="text-center text-white/40 text-sm">or</div>
|
<div className="text-center text-white/40 text-sm">or</div>
|
||||||
|
|
||||||
{/* Join Room */}
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -376,7 +574,6 @@ export default function HomePage() {
|
||||||
Leave blank for a random code
|
Leave blank for a random code
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsCreating(false)}
|
onClick={() => setIsCreating(false)}
|
||||||
|
|
@ -399,7 +596,7 @@ export default function HomePage() {
|
||||||
</main>
|
</main>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Ecosystem Footer ─────────────────────────────────────── */}
|
{/* ── Ecosystem Footer ──────────────────────────────────────── */}
|
||||||
<EcosystemFooter current="rMaps" />
|
<EcosystemFooter current="rMaps" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue