Untitled
unknown
plain_text
a year ago
15 kB
17
Indexable
import React, { useMemo, useState } from "react";
import { motion } from "framer-motion";
import { MapPin, Calendar, Coins, Compass, Plus, Trash2, CheckCircle2, ExternalLink, Star, Layers, Rocket, Smartphone, Shield } from "lucide-react";
// Simple UI atoms (to avoid external UI deps while keeping clean design)
const Container = ({ children, className = "" }) => (
<div className={`max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 ${className}`}>{children}</div>
);
const Pill = ({ children, className = "" }) => (
<span className={`inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium ${className}`}>{children}</span>
);
const Card = ({ children, className = "" }) => (
<div className={`rounded-2xl border shadow-sm bg-white/80 backdrop-blur p-5 ${className}`}>{children}</div>
);
const Button = ({ children, className = "", ...props }) => (
<button
{...props}
className={`inline-flex items-center gap-2 rounded-2xl border px-4 py-2 font-medium shadow-sm hover:shadow transition ${className}`}
>
{children}
</button>
);
const Input = ({ className = "", ...props }) => (
<input
{...props}
className={`w-full rounded-xl border px-3 py-2 outline-none focus:ring focus:ring-black/10 ${className}`}
/>
);
const Select = ({ className = "", ...props }) => (
<select {...props} className={`w-full rounded-xl border px-3 py-2 outline-none focus:ring focus:ring-black/10 ${className}`} />
);
const SectionTitle = ({ kicker, title, subtitle }) => (
<div className="text-center mb-10">
{kicker && (
<Pill className="border-black/10 text-black/70">{kicker}</Pill>
)}
<h2 className="text-3xl sm:text-4xl font-bold mt-4 tracking-tight">{title}</h2>
{subtitle && (
<p className="text-black/70 max-w-2xl mx-auto mt-3 leading-relaxed">{subtitle}</p>
)}
</div>
);
// --- Itinerary Builder (lightweight demo) ---
function useItinerary() {
const [legs, setLegs] = useState([
{ id: 1, place: "Hanoi, Vietnam", nights: 3, stayCostPerNight: 18, transportCost: 45 },
{ id: 2, place: "Luang Prabang, Laos", nights: 4, stayCostPerNight: 16, transportCost: 35 },
]);
const addLeg = (leg) => setLegs((prev) => [...prev, { id: Date.now(), ...leg }]);
const removeLeg = (id) => setLegs((prev) => prev.filter((l) => l.id !== id));
const reset = () => setLegs([]);
const totals = useMemo(() => {
const days = legs.reduce((a, l) => a + Number(l.nights || 0), 0);
const stay = legs.reduce((a, l) => a + Number(l.nights || 0) * Number(l.stayCostPerNight || 0), 0);
const transport = legs.reduce((a, l) => a + Number(l.transportCost || 0), 0);
return { days, stay, transport, total: stay + transport };
}, [legs]);
return { legs, addLeg, removeLeg, reset, totals };
}
function ItineraryBuilder() {
const { legs, addLeg, removeLeg, reset, totals } = useItinerary();
const [form, setForm] = useState({ place: "", nights: 3, stayCostPerNight: 20, transportCost: 25 });
const onChange = (e) => setForm((f) => ({ ...f, [e.target.name]: e.target.value }));
const onAdd = () => {
if (!form.place) return;
addLeg({
place: form.place,
nights: Number(form.nights),
stayCostPerNight: Number(form.stayCostPerNight),
transportCost: Number(form.transportCost),
});
setForm({ place: "", nights: 3, stayCostPerNight: 20, transportCost: 25 });
};
return (
<div className="grid lg:grid-cols-3 gap-6">
{/* Builder form */}
<Card className="lg:col-span-1">
<div className="flex items-center gap-2 mb-4">
<Compass className="w-5 h-5" />
<h3 className="font-semibold">Add a trip leg</h3>
</div>
<div className="space-y-3">
<div>
<label className="text-sm text-black/60">Destination</label>
<Input name="place" placeholder="e.g., Chiang Mai, Thailand" value={form.place} onChange={onChange} />
</div>
<div className="grid grid-cols-3 gap-3">
<div>
<label className="text-sm text-black/60">Nights</label>
<Input type="number" min={1} name="nights" value={form.nights} onChange={onChange} />
</div>
<div>
<label className="text-sm text-black/60">Stay £/night</label>
<Input type="number" min={0} name="stayCostPerNight" value={form.stayCostPerNight} onChange={onChange} />
</div>
<div>
<label className="text-sm text-black/60">Transport £</label>
<Input type="number" min={0} name="transportCost" value={form.transportCost} onChange={onChange} />
</div>
</div>
<div className="flex gap-3">
<Button onClick={onAdd} className="bg-black text-white border-black hover:bg-black/90"><Plus className="w-4 h-4"/>Add leg</Button>
<Button onClick={reset} className="border-black/20">Clear</Button>
</div>
<p className="text-xs text-black/60">This demo calculates days & budget locally. In the full app you’ll be able to book hostels, transport & activities from here.</p>
</div>
</Card>
{/* Itinerary list */}
<Card className="lg:col-span-2">
<div className="flex items-center justify-between gap-2 mb-4">
<div className="flex items-center gap-2"><Calendar className="w-5 h-5"/><h3 className="font-semibold">Your itinerary</h3></div>
<Pill className="border-emerald-500/30 text-emerald-700">Long-trip mode</Pill>
</div>
<div className="space-y-3">
{legs.length === 0 && (
<div className="text-black/60">No legs added yet — start by adding a destination on the left.</div>
)}
{legs.map((leg, idx) => (
<div key={leg.id} className="grid grid-cols-12 items-center gap-3 p-3 rounded-xl border">
<div className="col-span-6 sm:col-span-5 flex items-center gap-2">
<MapPin className="w-4 h-4"/>
<div className="font-medium">{idx + 1}. {leg.place}</div>
</div>
<div className="col-span-6 sm:col-span-2 text-sm text-black/70">{leg.nights} nights</div>
<div className="col-span-6 sm:col-span-3 text-sm text-black/70">Stay £{(leg.nights * leg.stayCostPerNight).toFixed(0)} · Transport £{Number(leg.transportCost).toFixed(0)}</div>
<div className="col-span-6 sm:col-span-2 flex justify-end">
<Button onClick={() => removeLeg(leg.id)} className="border-red-200 hover:bg-red-50"><Trash2 className="w-4 h-4"/>Remove</Button>
</div>
</div>
))}
</div>
{/* Totals */}
<div className="mt-5 grid sm:grid-cols-3 gap-3">
<Card className="sm:col-span-1 border-emerald-200">
<div className="text-sm text-black/60">Total days</div>
<div className="text-2xl font-bold">{totals.days}</div>
</Card>
<Card className="sm:col-span-1 border-amber-200">
<div className="text-sm text-black/60">Stay cost</div>
<div className="text-2xl font-bold">£{totals.stay.toFixed(0)}</div>
</Card>
<Card className="sm:col-span-1 border-blue-200">
<div className="text-sm text-black/60">Transport + stay</div>
<div className="text-2xl font-bold">£{totals.total.toFixed(0)}</div>
</Card>
</div>
{/* CTA row */}
<div className="mt-6 flex flex-wrap gap-3">
<Button className="bg-black text-white border-black hover:bg-black/90"><CheckCircle2 className="w-4 h-4"/>Save itinerary</Button>
<Button className="border-black/20"><ExternalLink className="w-4 h-4"/>Get share link</Button>
<Button className="border-black/20"><Star className="w-4 h-4"/>Use a template</Button>
</div>
</Card>
</div>
);
}
export default function App() {
return (
<div className="font-sans bg-gradient-to-b from-white to-slate-50 text-slate-900">
{/* NAV */}
<header className="sticky top-0 z-40 bg-white/70 backdrop-blur border-b">
<Container className="flex items-center justify-between h-16">
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-xl bg-black"/>
<span className="font-bold tracking-tight">TrailForge</span>
</div>
<nav className="hidden sm:flex items-center gap-6 text-sm">
<a href="#features" className="hover:opacity-70">Features</a>
<a href="#builder" className="hover:opacity-70">Itinerary Builder</a>
<a href="#pricing" className="hover:opacity-70">Pricing</a>
<a href="#faq" className="hover:opacity-70">FAQ</a>
</nav>
<div className="flex items-center gap-3">
<Button className="border-black/20 hidden sm:inline-flex">Sign in</Button>
<Button className="bg-black text-white border-black hover:bg-black/90">Get started</Button>
</div>
</Container>
</header>
{/* HERO */}
<section className="pt-16 sm:pt-24 pb-14">
<Container>
<div className="grid lg:grid-cols-2 gap-10 items-center">
<div>
<Pill className="border-black/10 text-black/70">For backpackers & slow travelers</Pill>
<h1 className="text-4xl sm:text-6xl font-extrabold tracking-tight mt-4 leading-tight">
Build, book, and budget your <span className="underline decoration-emerald-300 decoration-4 underline-offset-4">long trip</span> in one place
</h1>
<p className="text-black/70 mt-5 text-lg leading-relaxed">
Ditch the spreadsheets. Drag-and-drop your route, compare transport, book hostels and activities, and keep your budget on track — online and offline.
</p>
<div className="flex flex-wrap gap-3 mt-6">
<Button className="bg-black text-white border-black hover:bg-black/90">Start free</Button>
<Button className="border-black/20">Watch demo</Button>
</div>
<div className="flex flex-wrap gap-3 mt-6">
<Pill className="border-emerald-300/70">Offline access</Pill>
<Pill className="border-blue-300/70">Templates for SE Asia, Europe</Pill>
<Pill className="border-amber-300/70">Budget alerts</Pill>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className=""
>
<Card className="p-0 overflow-hidden">
<div className="bg-gradient-to-r from-emerald-100 via-blue-100 to-amber-100 h-3"/>
<div className="p-5">
<div className="flex items-center gap-2 mb-3"><Layers className="w-4 h-4"/><div className="font-semibold">Live Itinerary Preview</div></div>
<ItineraryBuilder />
</div>
</Card>
</motion.div>
</div>
</Container>
</section>
{/* FEATURES */}
<section id="features" className="py-16">
<Container>
<SectionTitle
kicker="Why TrailForge"
title="Designed for long trips, not weekend breaks"
subtitle="Everything you need to plan months on the road — and nothing you don’t."
/>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{[{
icon: <Compass className="w-5 h-5"/>,
title: "Drag‑and‑drop route",
text: "Build multi-country routes in minutes with smart travel times and border tips.",
},{
icon: <Coins className="w-5 h-5"/>,
title: "Budget intelligence",
text: "Set a daily target and get alerts, currency-aware totals, and cost forecasts.",
},{
icon: <Star className="w-5 h-5"/>,
title: "Social-friendly stays",
text: "Hostels and budget hotels curated for backpackers — with vibe-first reviews.",
},{
icon: <Rocket className="w-5 h-5"/>,
title: "One-click bookings",
text: "Book stays, transport, and activities without leaving your plan.",
},{
icon: <Smartphone className="w-5 h-5"/>,
title: "Offline mode",
text: "Access addresses, tickets, and maps with or without signal.",
},{
icon: <Shield className="w-5 h-5"/>,
title: "Peace of mind",
text: "Flight changes, visa reminders, and safety tips built in.",
}].map((f, i) => (
<Card key={i}>
<div className="flex items-center gap-2 text-sm text-black/70 mb-2">{f.icon}<span>{f.title}</span></div>
<div className="font-medium">{f.text}</div>
</Card>
))}
</div>
</Container>
</section>
{/* BUILDER */}
<section id="builder" className="py-16 bg-white">
<Container>
<SectionTitle
kicker="Try it"
title="Plan a demo trip in 60 seconds"
subtitle="Add destinations, tweak nights and see an instant budget. The full version plugs into hostels, buses and tours."
/>
<ItineraryBuilder />
</Container>
</section>
{/* PRICING */}
<section id="pricing" className="py-16">
<Container>
<SectionTitle
kicker="Simple pricing"
title="Start free. Upgrade when you’re rolling."
subtitle="Most travelers can plan an entire trip on the free tier. Power features unlock with Pro."
/>
<div className="grid md:grid-cols-2 gap-6">
<Card>
<div className="text-sm text-black/60">Free</div>
<div className="text-4xl font-extrabold mt-1">£0</div>
<ul className="mt-4 space-y-2 text-sm">
<li className="flex items-center gap-2"><CheckCircle2 className="w-4 h-4"/> Unlimited trip legs</li>
<li className="flex items-center gap-2"><CheckCircle2 className="w-4 h-4"/> Shareable itineraries</li>
<li className="flex items-center gap-2"><CheckCircle2 className="w-4 h-4"/> Starter templates</li>
</ul>
<Button className="mt-5 bg-black text-white border-black hover:bg-black/90">Get started</Button>
</Card>
<Card>
<div className="text-sm text-black/60">Pro</div>
<div className="text-4xl font-extrabold mt-1">£6<span className="text-base font-medium">/month</span></div>
<ul className="mt-4 space-y-2 text-sm">
<li className="flex items-center gap-2"><CheckCircle2 className="w-4 h-4"/> Offline access</li>
<li className="flex items-center gap-2"><CheckCircle2 className="w-4 h-4"/> Budget alerts & analytics</li>
<li className="flex items-center gap-2"><CheckCircle2 className="w-4 h-4"/> Booking integrations</li>
<li className="flex items-center gap-2"><CheckCircle2 className="w-4 h-4"/> Priority support</li>
</ul>
<Button className="mt-5 border-black/20">Go Pro</Button>
</Card>
</div>
</Container>
</section>
{/* FAQ */}
<section id="faq" className="py-16 bg-white">
<Container>
<SectionTitle kicker="FAQ" title="Your questions, answered" />
<div className="grid md:grid-cols-2 gap-6 text-sm">
{[{
q: "Can I book hostels and buses inside the app?",
a: "Yes — the production version integrates with major partners so you can book without leaving your plan.",
},{
q: "Does it work offline?",
a: "Pro users can save their itinerary, tickets and maps for offline access.",
},{
q: "Is it only for backpackers?",
a: "It’s optimised for long trips and budget travel, but works great for anyone planning multi-stop journeys.",
},{
q: "How do shared itineraries work?",
a: "Create a private or public link so friends can comment or duplicate your route.",
}].map((item, i) => (
<Card key={i}>
<div className="font-semibold mb-1">{item.q}</div>
<div className="text-black/70">{item.a}</div>
</Card>
))}
</div>
</Container>
</section>
{/* FOOTER */}
<footer className="py-12 border-t">
<Container>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-xl bg-black"/>
<span className="font-bold tracking-tight">TrailForge</span>
</div>
<div className="text-sm text-black/60">© {new Date().getFullYear()} TrailForge Ltd. All rights reserved.</div>
</div>
</Container>
</footer>
</div>
);
}Editor is loading...
Leave a Comment