Merge pull request #56 from trails-cool/implement-planner-landing-page

This commit is contained in:
github-actions[bot] 2026-03-25 03:07:58 +00:00 committed by GitHub
commit a9e29b2950
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 151 additions and 25 deletions

View file

@ -3,20 +3,87 @@ import type { Route } from "./+types/home";
export function meta(_args: Route.MetaArgs) {
return [
{ title: "trails.cool Planner" },
{ name: "description", content: "Collaborative route planning" },
{ title: "trails.cool Planner — Collaborative Route Planning" },
{ name: "description", content: "Plan hiking and cycling routes together in real-time. No account needed." },
];
}
const features = [
{ key: "featureCollaborative", icon: "👥" },
{ key: "featureRouting", icon: "🗺️" },
{ key: "featureElevation", icon: "⛰️" },
{ key: "featureExport", icon: "📥" },
{ key: "featureNoAccount", icon: "🔓" },
] as const;
export default function Home() {
const { t } = useTranslation("planner");
return (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900">{t("title")}</h1>
<p className="mt-4 text-lg text-gray-600">{t("subtitle")}</p>
</div>
<div className="flex h-full flex-col overflow-y-auto">
{/* Hero */}
<section className="flex flex-1 flex-col items-center justify-center px-4 py-16">
<h1 className="text-5xl font-bold tracking-tight text-gray-900 sm:text-6xl">
{t("title")}
</h1>
<p className="mt-4 max-w-lg text-center text-lg text-gray-600">
{t("landing.heroDescription")}
</p>
<a
href="/new"
className="mt-8 rounded-lg bg-blue-600 px-8 py-3 text-lg font-medium text-white shadow-sm hover:bg-blue-700"
>
{t("landing.startPlanning")}
</a>
</section>
{/* Features */}
<section className="border-t border-gray-200 bg-gray-50 px-4 py-16">
<div className="mx-auto grid max-w-4xl gap-8 sm:grid-cols-2 lg:grid-cols-3">
{features.map(({ key, icon }) => (
<div key={key} className="rounded-lg bg-white p-6 shadow-sm">
<div className="text-3xl">{icon}</div>
<h3 className="mt-3 font-semibold text-gray-900">
{t(`landing.${key}`)}
</h3>
<p className="mt-2 text-sm text-gray-600">
{t(`landing.${key}Desc`)}
</p>
</div>
))}
</div>
</section>
{/* Secondary CTA */}
<section className="border-t border-gray-200 px-4 py-12 text-center">
<h2 className="text-xl font-semibold text-gray-900">
{t("landing.saveRoutes")}
</h2>
<p className="mt-2 text-gray-600">
{t("landing.saveRoutesDesc")}
</p>
<a
href="https://trails.cool"
className="mt-4 inline-block rounded-md border border-gray-300 px-6 py-2 text-gray-700 hover:bg-gray-50"
>
{t("landing.goToJournal")}
</a>
</section>
{/* Footer */}
<footer className="border-t border-gray-200 px-4 py-6">
<div className="mx-auto flex max-w-4xl flex-wrap items-center justify-between gap-4 text-sm text-gray-400">
<span>{t("landing.footer.builtWith")}</span>
<div className="flex gap-4">
<a href="https://trails.cool/privacy" className="hover:text-gray-600">
{t("landing.footer.privacy")}
</a>
<a href="https://github.com/trails-cool/trails" className="hover:text-gray-600">
{t("landing.footer.source")}
</a>
</div>
</div>
</footer>
</div>
);
}

View file

@ -3,8 +3,8 @@ import { test, expect } from "@playwright/test";
test.describe("Planner", () => {
test("loads the home page", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle("trails.cool Planner");
await expect(page.getByText("Collaborative route planning")).toBeVisible();
await expect(page).toHaveTitle(/trails\.cool Planner/);
await expect(page.getByRole("link", { name: "Start Planning" })).toBeVisible();
});
test("can create a session via API", async ({ request }) => {

View file

@ -0,0 +1,16 @@
## 1. Landing Page Content
- [x] 1.1 Rewrite Planner home.tsx with hero section: heading, subtitle, "Start Planning" CTA linking to /new
- [x] 1.2 Add feature highlights section (4-5 cards): collaborative editing, routing profiles, elevation profile, GPX export, no account needed
- [x] 1.3 Add secondary CTA: "Save your routes on trails.cool" linking to Journal
- [x] 1.4 Add footer: privacy link, GitHub repo, BRouter/OSM attribution
## 2. i18n
- [x] 2.1 Add all landing page strings to en.ts and de.ts (hero, features, CTAs, footer)
## 3. Verify
- [x] 3.1 Verify landing page renders correctly at planner.trails.cool
- [x] 3.2 Verify "Start Planning" creates a session and redirects
- [x] 3.3 Update E2E planner test if home page assertions changed

View file

@ -1,16 +0,0 @@
## 1. Landing Page Content
- [ ] 1.1 Rewrite Planner home.tsx with hero section: heading, subtitle, "Start Planning" CTA linking to /new
- [ ] 1.2 Add feature highlights section (4-5 cards): collaborative editing, routing profiles, elevation profile, GPX export, no account needed
- [ ] 1.3 Add secondary CTA: "Save your routes on trails.cool" linking to Journal
- [ ] 1.4 Add footer: privacy link, GitHub repo, BRouter/OSM attribution
## 2. i18n
- [ ] 2.1 Add all landing page strings to en.ts and de.ts (hero, features, CTAs, footer)
## 3. Verify
- [ ] 3.1 Verify landing page renders correctly at planner.trails.cool
- [ ] 3.2 Verify "Start Planning" creates a session and redirects
- [ ] 3.3 Update E2E planner test if home page assertions changed

View file

@ -73,6 +73,21 @@ The Planner SHALL display presence indicators showing which users are currently
- **WHEN** a user disconnects from the session
- **THEN** their cursor disappears from all other participants' maps within 5 seconds
### Requirement: Planner home page
The Planner home page SHALL explain the tool's purpose and provide a one-click way to start planning.
#### Scenario: First-time visitor
- **WHEN** a user visits planner.trails.cool for the first time
- **THEN** they see a landing page explaining collaborative route planning, key features, and a prominent "Start Planning" button
#### Scenario: Start a session
- **WHEN** a user clicks "Start Planning"
- **THEN** a new anonymous session is created and the user is redirected to the session view
#### Scenario: Journal link
- **WHEN** a user wants to save routes permanently
- **THEN** a secondary CTA links to trails.cool for account creation
### Requirement: No user data collection
The Planner SHALL NOT collect, store, or track any personal user data. Sessions are anonymous by default.

View file

@ -39,6 +39,28 @@ export default {
loss: "Höhenmeter abwärts",
profile: "Höhenprofil",
},
landing: {
startPlanning: "Planung starten",
heroDescription: "Plane Wander- und Radrouten gemeinsam in Echtzeit. Kein Konto nötig.",
featureCollaborative: "Echtzeit-Zusammenarbeit",
featureCollaborativeDesc: "Wegpunkte gemeinsam bearbeiten — Änderungen werden sofort synchronisiert.",
featureRouting: "Intelligentes Routing",
featureRoutingDesc: "BRouter berechnet optimale Routen für Wandern, Radfahren oder Autofahren mit Höhenprofil.",
featureElevation: "Höhenprofil",
featureElevationDesc: "Das vollständige Höhenprofil deiner Route mit Auf- und Abstiegsstatistiken.",
featureExport: "GPX-Export",
featureExportDesc: "Exportiere deine Route als GPX für jedes GPS-Gerät oder jede App.",
featureNoAccount: "Kein Konto nötig",
featureNoAccountDesc: "Sofort loslegen. Teile einen Link und jeder kann deiner Sitzung beitreten.",
saveRoutes: "Routen speichern?",
saveRoutesDesc: "Erstelle ein kostenloses Konto auf trails.cool, um Routen zu speichern, Aktivitäten zu verfolgen und mehr.",
goToJournal: "Zu trails.cool",
footer: {
privacy: "Datenschutz",
source: "Quellcode",
builtWith: "Erstellt mit BRouter und OpenStreetMap",
},
},
},
journal: {
title: "trails.cool",

View file

@ -39,6 +39,28 @@ export default {
loss: "Elevation Loss",
profile: "Elevation Profile",
},
landing: {
startPlanning: "Start Planning",
heroDescription: "Plan hiking and cycling routes together in real-time. No account needed.",
featureCollaborative: "Real-Time Collaboration",
featureCollaborativeDesc: "Edit waypoints together — changes sync instantly across all participants.",
featureRouting: "Smart Routing",
featureRoutingDesc: "BRouter computes optimal routes for hiking, cycling, or driving with elevation awareness.",
featureElevation: "Elevation Profile",
featureElevationDesc: "See the full elevation profile of your route with gain and loss statistics.",
featureExport: "GPX Export",
featureExportDesc: "Export your route as GPX for use in any GPS device or app.",
featureNoAccount: "No Account Needed",
featureNoAccountDesc: "Start planning immediately. Share a link and anyone can join your session.",
saveRoutes: "Want to save your routes?",
saveRoutesDesc: "Create a free account on trails.cool to keep your routes, track activities, and more.",
goToJournal: "Go to trails.cool",
footer: {
privacy: "Privacy",
source: "Source Code",
builtWith: "Built with BRouter and OpenStreetMap",
},
},
},
journal: {
title: "trails.cool",