Add rStack demo page with Alpine Explorer 2026 showcase
Interactive demo at /demo featuring 6 rStack tool previews: rMaps (SVG route map), rNotes (packing checklist), rCal (calendar grid), rVote (group polls), rFunds (expense tracking), rCart (shared gear). Each card links to the corresponding r*.online service. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5f33bcbc7d
commit
da52b6ad19
|
|
@ -0,0 +1,632 @@
|
|||
import Link from 'next/link'
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'rTrips Demo - Alpine Explorer 2026',
|
||||
description: 'See how rTrips and the rStack ecosystem power collaborative trip planning. A demo showcasing rMaps, rNotes, rCal, rVote, rFunds, and rCart working together.',
|
||||
}
|
||||
|
||||
/* ─── Mock Data ─────────────────────────────────────────────── */
|
||||
|
||||
const members = [
|
||||
{ name: 'Alex', color: 'bg-teal-500' },
|
||||
{ name: 'Sam', color: 'bg-cyan-500' },
|
||||
{ name: 'Jordan', color: 'bg-blue-500' },
|
||||
{ name: 'Riley', color: 'bg-violet-500' },
|
||||
{ name: 'Casey', color: 'bg-amber-500' },
|
||||
{ name: 'Morgan', color: 'bg-rose-500' },
|
||||
]
|
||||
|
||||
const packingList = [
|
||||
{ item: 'Hiking boots (broken in!)', checked: true },
|
||||
{ item: 'Rain jacket & layers', checked: true },
|
||||
{ item: 'Headlamp + spare batteries', checked: true },
|
||||
{ item: 'First aid kit', checked: false },
|
||||
{ item: 'Sunscreen SPF 50', checked: true },
|
||||
{ item: 'Trekking poles', checked: false },
|
||||
{ item: 'Refillable water bottle', checked: true },
|
||||
{ item: 'Via ferrata gloves', checked: false },
|
||||
]
|
||||
|
||||
const tripRules = [
|
||||
'Majority vote on daily activities',
|
||||
'Shared expenses split equally',
|
||||
'Quiet hours after 10pm in huts',
|
||||
'Everyone carries their own pack',
|
||||
]
|
||||
|
||||
const calendarEvents: Record<number, { label: string; color: string }[]> = {
|
||||
6: [{ label: 'Fly to Geneva', color: 'bg-teal-500' }],
|
||||
7: [{ label: 'Chamonix check-in', color: 'bg-teal-500' }],
|
||||
8: [{ label: 'Mont Blanc hike', color: 'bg-emerald-500' }],
|
||||
9: [{ label: 'Rest / explore town', color: 'bg-slate-500' }],
|
||||
10: [{ label: 'Mer de Glace trek', color: 'bg-emerald-500' }],
|
||||
11: [{ label: 'Via ferrata', color: 'bg-amber-500' }],
|
||||
12: [{ label: 'Train to Zermatt', color: 'bg-cyan-500' }],
|
||||
13: [{ label: 'Matterhorn viewpoint', color: 'bg-emerald-500' }],
|
||||
14: [{ label: 'Mountain biking', color: 'bg-violet-500' }],
|
||||
15: [{ label: 'Gorner Gorge hike', color: 'bg-emerald-500' }],
|
||||
16: [{ label: 'Paragliding!', color: 'bg-rose-500' }],
|
||||
17: [{ label: 'Travel to Dolomites', color: 'bg-cyan-500' }],
|
||||
18: [{ label: 'Tre Cime circuit', color: 'bg-emerald-500' }],
|
||||
19: [{ label: 'Lake Braies kayaking', color: 'bg-blue-500' }],
|
||||
20: [{ label: 'Fly home', color: 'bg-teal-500' }],
|
||||
}
|
||||
|
||||
const polls = [
|
||||
{
|
||||
question: 'Day 5 Activity?',
|
||||
options: [
|
||||
{ label: 'Via Ferrata', votes: 4, color: 'bg-amber-500' },
|
||||
{ label: 'Kayaking', votes: 1, color: 'bg-blue-500' },
|
||||
{ label: 'Rest day', votes: 1, color: 'bg-slate-500' },
|
||||
],
|
||||
totalVotes: 6,
|
||||
},
|
||||
{
|
||||
question: 'Dinner tonight?',
|
||||
options: [
|
||||
{ label: 'Fondue place', votes: 3, color: 'bg-amber-500' },
|
||||
{ label: 'Pizza by the lake', votes: 2, color: 'bg-rose-500' },
|
||||
{ label: 'Cook at Airbnb', votes: 1, color: 'bg-emerald-500' },
|
||||
],
|
||||
totalVotes: 6,
|
||||
},
|
||||
]
|
||||
|
||||
const expenses = [
|
||||
{ desc: 'Geneva → Chamonix shuttle', who: 'Alex', amount: 186, split: 6 },
|
||||
{ desc: 'Mountain hut (2 nights)', who: 'Sam', amount: 420, split: 6 },
|
||||
{ desc: 'Via ferrata gear rental', who: 'Jordan', amount: 144, split: 4 },
|
||||
{ desc: 'Groceries (Zermatt)', who: 'Casey', amount: 93, split: 6 },
|
||||
{ desc: 'Paragliding deposit', who: 'Riley', amount: 360, split: 3 },
|
||||
]
|
||||
|
||||
const cartItems = [
|
||||
{ item: 'Group first-aid kit', target: 85, funded: 85, status: 'Purchased' as const },
|
||||
{ item: 'Portable water filter', target: 45, funded: 45, status: 'Purchased' as const },
|
||||
{ item: 'Bear canister (2x)', target: 120, funded: 90, status: 'Funding' as const },
|
||||
{ item: 'Camp stove + fuel', target: 65, funded: 65, status: 'Purchased' as const },
|
||||
{ item: 'Drone (group footage)', target: 350, funded: 210, status: 'Funding' as const },
|
||||
{ item: 'Starlink Mini rental', target: 200, funded: 80, status: 'Funding' as const },
|
||||
]
|
||||
|
||||
/* ─── Card Wrapper ──────────────────────────────────────────── */
|
||||
|
||||
function CardWrapper({
|
||||
icon,
|
||||
title,
|
||||
service,
|
||||
href,
|
||||
span = 1,
|
||||
children,
|
||||
}: {
|
||||
icon: string
|
||||
title: string
|
||||
service: string
|
||||
href: string
|
||||
span?: 1 | 2
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`bg-slate-800/50 rounded-2xl border border-slate-700/50 overflow-hidden ${
|
||||
span === 2 ? 'md:col-span-2' : ''
|
||||
}`}
|
||||
>
|
||||
<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">{icon}</span>
|
||||
<span className="font-semibold text-sm">{title}</span>
|
||||
</div>
|
||||
<a
|
||||
href={`https://${href}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs px-3 py-1.5 bg-slate-700/60 hover:bg-slate-600/60 rounded-lg text-slate-300 hover:text-white transition-colors"
|
||||
>
|
||||
Open in {service} ↗
|
||||
</a>
|
||||
</div>
|
||||
<div className="p-5">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Card: rMaps ───────────────────────────────────────────── */
|
||||
|
||||
function RMapsCard() {
|
||||
return (
|
||||
<CardWrapper icon="🗺️" title="Route Map" service="rMaps" href="rmaps.online" span={2}>
|
||||
<div className="relative w-full h-64 rounded-xl bg-slate-900/60 overflow-hidden">
|
||||
<svg viewBox="0 0 800 300" className="w-full h-full" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* Mountain silhouettes */}
|
||||
<path
|
||||
d="M0 280 L60 200 L100 240 L160 160 L200 210 L260 130 L320 180 L380 100 L420 150 L480 80 L540 140 L600 110 L660 160 L720 120 L800 180 L800 300 L0 300 Z"
|
||||
fill="rgba(30,41,59,0.8)"
|
||||
/>
|
||||
<path
|
||||
d="M0 280 L80 220 L140 250 L200 190 L280 230 L340 170 L400 200 L460 150 L520 190 L580 160 L640 200 L700 170 L800 220 L800 300 L0 300 Z"
|
||||
fill="rgba(51,65,85,0.6)"
|
||||
/>
|
||||
{/* Snow caps */}
|
||||
<path d="M370 100 L380 100 L390 108 L375 105 Z" fill="rgba(255,255,255,0.4)" />
|
||||
<path d="M470 80 L480 80 L492 90 L476 86 Z" fill="rgba(255,255,255,0.5)" />
|
||||
<path d="M590 110 L600 110 L612 120 L598 116 Z" fill="rgba(255,255,255,0.4)" />
|
||||
|
||||
{/* Route line */}
|
||||
<path
|
||||
d="M160 180 C250 160, 350 200, 430 150 C510 100, 560 160, 650 140"
|
||||
fill="none"
|
||||
stroke="rgba(94,234,212,0.7)"
|
||||
strokeWidth="3"
|
||||
strokeDasharray="10 6"
|
||||
/>
|
||||
|
||||
{/* Destination pins */}
|
||||
{/* Chamonix */}
|
||||
<circle cx="160" cy="180" r="8" fill="#14b8a6" stroke="#0d9488" strokeWidth="2" />
|
||||
<text x="160" y="210" textAnchor="middle" fill="#94a3b8" fontSize="12" fontWeight="600">
|
||||
Chamonix
|
||||
</text>
|
||||
<text x="160" y="224" textAnchor="middle" fill="#64748b" fontSize="10">
|
||||
Jul 6–11
|
||||
</text>
|
||||
|
||||
{/* Zermatt */}
|
||||
<circle cx="430" cy="150" r="8" fill="#06b6d4" stroke="#0891b2" strokeWidth="2" />
|
||||
<text x="430" y="180" textAnchor="middle" fill="#94a3b8" fontSize="12" fontWeight="600">
|
||||
Zermatt
|
||||
</text>
|
||||
<text x="430" y="194" textAnchor="middle" fill="#64748b" fontSize="10">
|
||||
Jul 12–16
|
||||
</text>
|
||||
|
||||
{/* Dolomites */}
|
||||
<circle cx="650" cy="140" r="8" fill="#8b5cf6" stroke="#7c3aed" strokeWidth="2" />
|
||||
<text x="650" y="170" textAnchor="middle" fill="#94a3b8" fontSize="12" fontWeight="600">
|
||||
Dolomites
|
||||
</text>
|
||||
<text x="650" y="184" textAnchor="middle" fill="#64748b" fontSize="10">
|
||||
Jul 17–20
|
||||
</text>
|
||||
|
||||
{/* Activity icons along route */}
|
||||
<text x="280" y="168" fontSize="16">🥾</text>
|
||||
<text x="350" y="188" fontSize="16">🧗</text>
|
||||
<text x="500" y="128" fontSize="16">🚵</text>
|
||||
<text x="560" y="148" fontSize="16">🪂</text>
|
||||
<text x="620" y="158" fontSize="16">🛶</text>
|
||||
</svg>
|
||||
|
||||
{/* Legend */}
|
||||
<div className="absolute bottom-3 left-3 flex gap-3 text-xs text-slate-400">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 rounded-full bg-teal-500" /> France
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 rounded-full bg-cyan-500" /> Switzerland
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 rounded-full bg-violet-500" /> Italy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Card: rNotes ──────────────────────────────────────────── */
|
||||
|
||||
function RNotesCard() {
|
||||
return (
|
||||
<CardWrapper icon="📝" title="Trip Notes" service="rNotes" href="rnotes.online">
|
||||
<div className="space-y-4">
|
||||
{/* Packing list */}
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">
|
||||
Packing Checklist
|
||||
</h4>
|
||||
<ul className="space-y-1.5">
|
||||
{packingList.map((item) => (
|
||||
<li key={item.item} className="flex items-center gap-2 text-sm">
|
||||
<span
|
||||
className={`w-4 h-4 rounded border flex-shrink-0 flex items-center justify-center ${
|
||||
item.checked
|
||||
? 'bg-teal-500/20 border-teal-500 text-teal-400'
|
||||
: 'border-slate-600'
|
||||
}`}
|
||||
>
|
||||
{item.checked && (
|
||||
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
<span className={item.checked ? 'text-slate-400 line-through' : 'text-slate-200'}>
|
||||
{item.item}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Trip rules */}
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">
|
||||
Trip Rules
|
||||
</h4>
|
||||
<ol className="space-y-1 text-sm text-slate-300 list-decimal list-inside">
|
||||
{tripRules.map((rule) => (
|
||||
<li key={rule}>{rule}</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</CardWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Card: rCal ────────────────────────────────────────────── */
|
||||
|
||||
function RCalCard() {
|
||||
const daysInJuly = 31
|
||||
const startDay = 0 // July 2026 starts on Wednesday (0=Mon grid: Wed=2, but let's use simple offset)
|
||||
// July 1, 2026 is a Wednesday. In a Mon-start grid, offset = 2
|
||||
const offset = 2
|
||||
const days = Array.from({ length: daysInJuly }, (_, i) => i + 1)
|
||||
const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
|
||||
return (
|
||||
<CardWrapper icon="📅" title="Group Calendar" service="rCal" href="rtrips.online" span={2}>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-semibold text-slate-200">July 2026</h4>
|
||||
<span className="text-xs text-slate-400">15 days • 3 countries</span>
|
||||
</div>
|
||||
|
||||
{/* Day headers */}
|
||||
<div className="grid grid-cols-7 gap-1 mb-1">
|
||||
{dayNames.map((d) => (
|
||||
<div key={d} className="text-center text-xs text-slate-500 font-medium py-1">
|
||||
{d}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Calendar grid */}
|
||||
<div className="grid grid-cols-7 gap-1">
|
||||
{/* Empty offset cells */}
|
||||
{Array.from({ length: offset }).map((_, i) => (
|
||||
<div key={`empty-${i}`} className="h-14" />
|
||||
))}
|
||||
|
||||
{days.map((day) => {
|
||||
const events = calendarEvents[day]
|
||||
const isTrip = day >= 6 && day <= 20
|
||||
return (
|
||||
<div
|
||||
key={day}
|
||||
className={`h-14 rounded-lg p-1 text-xs ${
|
||||
isTrip
|
||||
? 'bg-slate-700/40 border border-slate-600/40'
|
||||
: 'bg-slate-800/30'
|
||||
}`}
|
||||
>
|
||||
<span className={`${isTrip ? 'text-slate-200 font-medium' : 'text-slate-500'}`}>
|
||||
{day}
|
||||
</span>
|
||||
{events?.map((e) => (
|
||||
<div
|
||||
key={e.label}
|
||||
className={`${e.color} rounded px-1 py-0.5 text-white truncate mt-0.5`}
|
||||
style={{ fontSize: '9px' }}
|
||||
>
|
||||
{e.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Activity legend */}
|
||||
<div className="flex flex-wrap gap-3 mt-3 text-xs text-slate-400">
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-teal-500" /> Travel</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-emerald-500" /> Hiking</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-amber-500" /> Adventure</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-violet-500" /> Biking</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-rose-500" /> Extreme</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-blue-500" /> Water</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2 h-2 rounded-full bg-cyan-500" /> Transit</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Card: rVote ───────────────────────────────────────────── */
|
||||
|
||||
function RVoteCard() {
|
||||
return (
|
||||
<CardWrapper icon="🗳️" title="Group Polls" service="rVote" href="rvote.online">
|
||||
<div className="space-y-5">
|
||||
{polls.map((poll) => (
|
||||
<div key={poll.question}>
|
||||
<h4 className="text-sm font-medium text-slate-200 mb-2">{poll.question}</h4>
|
||||
<div className="space-y-2">
|
||||
{poll.options.map((opt) => {
|
||||
const pct = Math.round((opt.votes / poll.totalVotes) * 100)
|
||||
return (
|
||||
<div key={opt.label}>
|
||||
<div className="flex items-center justify-between text-xs mb-1">
|
||||
<span className="text-slate-300">{opt.label}</span>
|
||||
<span className="text-slate-400">
|
||||
{opt.votes} vote{opt.votes !== 1 ? 's' : ''} ({pct}%)
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 bg-slate-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full ${opt.color} rounded-full transition-all`}
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-1">{poll.totalVotes} votes cast</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Card: rFunds ──────────────────────────────────────────── */
|
||||
|
||||
function RFundsCard() {
|
||||
const totalSpent = expenses.reduce((s, e) => s + e.amount, 0)
|
||||
|
||||
// Calculate balances per member
|
||||
const balances: Record<string, number> = {}
|
||||
members.forEach((m) => (balances[m.name] = 0))
|
||||
expenses.forEach((e) => {
|
||||
const share = e.amount / e.split
|
||||
balances[e.who] = (balances[e.who] || 0) + e.amount // they paid
|
||||
// Everyone who splits owes their share
|
||||
members.slice(0, e.split).forEach((m) => {
|
||||
balances[m.name] = (balances[m.name] || 0) - share
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<CardWrapper icon="💰" title="Group Expenses" service="rFunds" href="rfunds.online">
|
||||
<div className="space-y-4">
|
||||
{/* Total */}
|
||||
<div className="text-center py-2">
|
||||
<p className="text-2xl font-bold text-white">€{totalSpent.toLocaleString()}</p>
|
||||
<p className="text-xs text-slate-400">Total group spending</p>
|
||||
</div>
|
||||
|
||||
{/* Recent transactions */}
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">
|
||||
Recent
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{expenses.slice(0, 4).map((e) => (
|
||||
<div key={e.desc} className="flex items-center justify-between text-sm">
|
||||
<div className="min-w-0">
|
||||
<p className="text-slate-200 truncate">{e.desc}</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
{e.who} • split {e.split} ways
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-slate-300 font-medium ml-2 flex-shrink-0">€{e.amount}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Balances */}
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">
|
||||
Balances
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
{members.map((m) => {
|
||||
const bal = balances[m.name] || 0
|
||||
return (
|
||||
<div key={m.name} className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-300">{m.name}</span>
|
||||
<span className={bal >= 0 ? 'text-emerald-400' : 'text-rose-400'}>
|
||||
{bal >= 0 ? '+' : ''}€{Math.round(bal)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Card: rCart ────────────────────────────────────────────── */
|
||||
|
||||
function RCartCard() {
|
||||
const totalFunded = cartItems.reduce((s, i) => s + i.funded, 0)
|
||||
const totalTarget = cartItems.reduce((s, i) => s + i.target, 0)
|
||||
|
||||
return (
|
||||
<CardWrapper icon="🛒" title="Shared Gear" service="rCart" href="rcart.online" span={2}>
|
||||
<div className="space-y-3">
|
||||
{/* Summary bar */}
|
||||
<div className="flex items-center justify-between text-sm mb-1">
|
||||
<span className="text-slate-300">
|
||||
€{totalFunded} / €{totalTarget} funded
|
||||
</span>
|
||||
<span className="text-xs text-slate-400">
|
||||
{cartItems.filter((i) => i.status === 'Purchased').length}/{cartItems.length} purchased
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 bg-slate-700 rounded-full overflow-hidden mb-4">
|
||||
<div
|
||||
className="h-full bg-teal-500 rounded-full"
|
||||
style={{ width: `${Math.round((totalFunded / totalTarget) * 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Items grid */}
|
||||
<div className="grid sm:grid-cols-2 gap-3">
|
||||
{cartItems.map((item) => {
|
||||
const pct = Math.round((item.funded / item.target) * 100)
|
||||
return (
|
||||
<div key={item.item} className="bg-slate-700/30 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<span className="text-sm text-slate-200">{item.item}</span>
|
||||
{item.status === 'Purchased' ? (
|
||||
<span className="text-xs px-2 py-0.5 bg-emerald-500/20 text-emerald-400 rounded-full">
|
||||
✓ Bought
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-slate-400">€{item.funded}/€{item.target}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-1.5 bg-slate-600 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full ${
|
||||
pct === 100 ? 'bg-emerald-500' : 'bg-teal-500'
|
||||
}`}
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</CardWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── 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">
|
||||
<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-teal-400 to-cyan-500 rounded-lg flex items-center justify-center font-bold text-slate-900 text-sm">
|
||||
rT
|
||||
</div>
|
||||
<span className="font-semibold text-lg">rTrips</span>
|
||||
</Link>
|
||||
<span className="text-slate-600">/</span>
|
||||
<span className="text-sm text-slate-400">Demo</span>
|
||||
</div>
|
||||
<Link
|
||||
href="/trips/new"
|
||||
className="text-sm px-4 py-2 bg-teal-600 hover:bg-teal-500 rounded-lg transition-colors font-medium"
|
||||
>
|
||||
Plan Your Own Trip
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Trip Header */}
|
||||
<section className="max-w-7xl mx-auto px-6 pt-12 pb-8">
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold mb-4 bg-gradient-to-r from-teal-300 via-cyan-300 to-blue-300 bg-clip-text text-transparent">
|
||||
Alpine Explorer 2026
|
||||
</h1>
|
||||
<p className="text-lg text-slate-300 mb-2">
|
||||
Chamonix → Zermatt → Dolomites
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-slate-400 mb-6">
|
||||
<span>📅 Jul 6–20, 2026</span>
|
||||
<span>💶 ~€4,500 budget</span>
|
||||
<span>🏔️ 3 countries</span>
|
||||
</div>
|
||||
|
||||
{/* Member avatars */}
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{members.map((m) => (
|
||||
<div
|
||||
key={m.name}
|
||||
className={`w-10 h-10 ${m.color} rounded-full flex items-center justify-center text-sm font-bold text-white ring-2 ring-slate-800`}
|
||||
title={m.name}
|
||||
>
|
||||
{m.name[0]}
|
||||
</div>
|
||||
))}
|
||||
<span className="text-sm text-slate-400 ml-2">6 explorers</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* rStack Intro */}
|
||||
<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">
|
||||
Every trip is powered by the <span className="text-slate-200 font-medium">rStack</span> — a suite
|
||||
of collaborative tools that handle routes, notes, schedules, voting, expenses, and shared
|
||||
purchases. Each card below is a live preview with a link to the full tool.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Canvas Grid */}
|
||||
<section className="max-w-7xl mx-auto px-6 pb-16">
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<RMapsCard />
|
||||
<RNotesCard />
|
||||
<RCalCard />
|
||||
<RVoteCard />
|
||||
<RFundsCard />
|
||||
<RCartCard />
|
||||
</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">Plan Your Own Group Adventure</h2>
|
||||
<p className="text-slate-400 mb-6 max-w-lg mx-auto">
|
||||
The rStack gives your group everything you need — routes, schedules, polls, shared
|
||||
expenses, and gear lists — all connected in one trip canvas.
|
||||
</p>
|
||||
<Link
|
||||
href="/trips/new"
|
||||
className="inline-block px-8 py-4 bg-teal-600 hover:bg-teal-500 rounded-xl text-lg font-medium transition-all shadow-lg shadow-teal-900/30"
|
||||
>
|
||||
Start Planning
|
||||
</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">rStack</span>
|
||||
<a href="https://rtrips.online" className="hover:text-slate-300 transition-colors">✈️ rTrips</a>
|
||||
<a href="https://rmaps.online" className="hover:text-slate-300 transition-colors">🗺️ 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://rcart.online" className="hover:text-slate-300 transition-colors">🛒 rCart</a>
|
||||
<a href="https://rspace.online" className="hover:text-slate-300 transition-colors">🌌 rSpace</a>
|
||||
<a href="https://rfiles.online" className="hover:text-slate-300 transition-colors">📁 rFiles</a>
|
||||
<a href="https://rwallet.online" className="hover:text-slate-300 transition-colors">💰 rWallet</a>
|
||||
</div>
|
||||
<p className="text-center text-xs text-slate-600">
|
||||
Collaborative trip planning for groups that love adventure.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -13,6 +13,12 @@ export default function Home() {
|
|||
<span className="font-semibold text-lg">rTrips</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
href="/demo"
|
||||
className="text-sm text-slate-300 hover:text-white transition-colors"
|
||||
>
|
||||
Demo
|
||||
</Link>
|
||||
<Link
|
||||
href="/trips"
|
||||
className="text-sm text-slate-300 hover:text-white transition-colors"
|
||||
|
|
|
|||
Loading…
Reference in New Issue