feat(landing): add header, app preview, feature overview, and footer

The new public landing page (/) was hero-only — missing a language
switcher, legal links, and anything showing what the product actually
looks like. Adds:
- A slim header with the wordmark and a flag-based language switcher
  (LanguageSwitcher now shows 🇬🇧/🇩🇪 instead of English/Deutsch text,
  used here and everywhere else it's already mounted)
- An app-preview section: a real screenshot of the live feed inside a
  browser-chrome mockup card
- A 4-item feature overview (pet-first profiles, posts/stories/video,
  health tracking, pet-to-pet messaging)
- A footer with Impressum/Datenschutz/AGB links, matching what the
  authenticated sidebar already links to
This commit is contained in:
admin committed 2026-08-11 19:58:59 +02:00
1 parent 07a1cb7768
commit 1eb27dfeb3
3 files changed
+135 -47

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

+127 -41
View File
@@ -1,65 +1,151 @@
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
import Link from "next/link";
import { PawPrint } from "lucide-react";
import Image from "next/image";
import { PawPrint, Sparkles, HeartPulse, MessageCircle, Images } from "lucide-react";
import { LanguageSwitcher } from "@/components/i18n/LanguageSwitcher";
/**
* Root route (/) — public landing page for logged-out visitors, redirects
* to /feed for authenticated ones (D-04).
*
* Previously an unconditional redirect("/feed"), gated entirely by
* proxy.ts's auth.protect() — meaning logged-out visitors (and social-media
* crawlers scraping og:image for link previews) got bounced straight to
* Clerk's hosted sign-in page before Next.js ever rendered this route's
* metadata. proxy.ts now leaves "/" out of the protected matcher so this
* page can render for everyone; the redirect-when-authenticated check
* happens here instead via a plain auth() read (not .protect()).
* proxy.ts's auth.protect() — so logged-out visitors AND social-media
* crawlers scraping og:image for link previews got bounced to Clerk's
* hosted sign-in before Next.js ever rendered this route's metadata.
* proxy.ts now leaves "/" out of the protected matcher so this page can
* render for everyone; the redirect-when-authenticated check happens here
* instead via a plain auth() read (not .protect()).
*/
export default async function RootPage() {
const { userId } = await auth();
if (userId) redirect("/feed");
const features = [
{
icon: PawPrint,
title: "Pets, not humans, are the profile",
body: "Every dog, cat, or bird gets its own identity — its own posts, followers, and story, separate from its owner's.",
},
{
icon: Images,
title: "Posts, stories & short video",
body: "Photos, short-form video, and 24-hour stories — all built around what your pet is up to.",
},
{
icon: HeartPulse,
title: "Health tracking",
body: "Weight logs, vet visits, and vaccine records in one place — shareable with a vet via a private link.",
},
{
icon: MessageCircle,
title: "Pet-to-pet messaging",
body: "Direct messages happen between pets, not owners — matching how the whole platform is built.",
},
] as const;
return (
<div className="relative min-h-screen overflow-hidden bg-gradient-to-br from-orange-400 via-orange-500 to-orange-700">
{/* Decorative oversized paw watermark — depth/layering, matches the
OG image's visual language */}
<PawPrint
className="pointer-events-none absolute -right-24 -top-20 h-[560px] w-[560px] text-white opacity-[0.14] sm:-right-16"
fill="white"
strokeWidth={0}
aria-hidden="true"
/>
<div className="min-h-screen bg-background">
{/* Header */}
<header className="flex items-center justify-between px-6 py-5 sm:px-10">
<div className="flex items-center gap-2">
<PawPrint className="h-6 w-6 text-orange-500" fill="currentColor" strokeWidth={0} aria-hidden="true" />
<span className="text-lg font-bold text-foreground">PawFeed</span>
</div>
<LanguageSwitcher />
</header>
<div className="relative z-10 flex min-h-screen items-center justify-center px-6 py-20">
<div className="max-w-2xl text-center sm:text-left">
<div className="mb-6 flex justify-center sm:justify-start">
<PawPrint className="h-14 w-14 text-white" fill="white" strokeWidth={0} aria-hidden="true" />
</div>
{/* Hero */}
<section className="relative overflow-hidden bg-gradient-to-br from-orange-400 via-orange-500 to-orange-700">
<PawPrint
className="pointer-events-none absolute -right-24 -top-20 h-[480px] w-[480px] text-white opacity-[0.14] sm:-right-16"
fill="white"
strokeWidth={0}
aria-hidden="true"
/>
<h1 className="text-6xl font-extrabold tracking-tight text-white sm:text-7xl">
PawFeed
</h1>
<div className="relative z-10 mx-auto max-w-5xl px-6 py-20 sm:px-10 sm:py-28">
<div className="max-w-2xl text-center sm:text-left">
<h1 className="text-6xl font-extrabold tracking-tight text-white sm:text-7xl">
PawFeed
</h1>
<p className="mx-auto mt-5 max-w-lg text-xl font-medium text-orange-50 sm:mx-0 sm:text-2xl">
Where pets are the stars. Profiles, posts, and stories — built for dogs, cats, and birds.
</p>
<p className="mx-auto mt-5 max-w-lg text-xl font-medium text-orange-50 sm:mx-0 sm:text-2xl">
Where pets are the stars. Profiles, posts, and stories — built for dogs, cats, and birds.
</p>
<div className="mt-10 flex flex-col justify-center gap-3 sm:flex-row sm:justify-start">
<Link
href="/sign-up"
className="inline-flex items-center justify-center rounded-xl bg-white px-7 py-3.5 text-base font-semibold text-orange-600 shadow-lg transition-colors hover:bg-orange-50"
>
Get started
</Link>
<Link
href="/log-in"
className="inline-flex items-center justify-center rounded-xl border-2 border-white/70 px-7 py-3.5 text-base font-semibold text-white transition-colors hover:bg-white/10"
>
Log in
</Link>
<div className="mt-10 flex flex-col justify-center gap-3 sm:flex-row sm:justify-start">
<Link
href="/sign-up"
className="inline-flex items-center justify-center rounded-xl bg-white px-7 py-3.5 text-base font-semibold text-orange-600 shadow-lg transition-colors hover:bg-orange-50"
>
Get started
</Link>
<Link
href="/log-in"
className="inline-flex items-center justify-center rounded-xl border-2 border-white/70 px-7 py-3.5 text-base font-semibold text-white transition-colors hover:bg-white/10"
>
Log in
</Link>
</div>
</div>
</div>
</div>
</section>
{/* App preview — browser-chrome mockup around a real feed screenshot */}
<section className="px-6 py-20 sm:px-10">
<div className="mx-auto max-w-4xl">
<div className="overflow-hidden rounded-2xl border shadow-2xl">
<div className="flex items-center gap-1.5 border-b bg-muted/60 px-4 py-2.5">
<span className="h-2.5 w-2.5 rounded-full bg-red-400" />
<span className="h-2.5 w-2.5 rounded-full bg-amber-400" />
<span className="h-2.5 w-2.5 rounded-full bg-green-400" />
</div>
<div className="relative aspect-[16/10] w-full bg-muted">
<Image
src="/app-preview-feed.jpg"
alt="The PawFeed feed, showing pet posts from Noctis and Bubi"
fill
sizes="(min-width: 768px) 896px, 100vw"
className="object-cover object-left-top"
priority
/>
</div>
</div>
</div>
</section>
{/* Feature overview */}
<section className="bg-muted/30 px-6 py-20 sm:px-10">
<div className="mx-auto max-w-5xl">
<h2 className="text-center text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Built for the pet, not the person
</h2>
<div className="mt-14 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
{features.map(({ icon: Icon, title, body }) => (
<div key={title} className="flex flex-col items-start">
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-orange-100 text-orange-600">
<Icon className="h-5.5 w-5.5" aria-hidden="true" />
</div>
<h3 className="mt-4 text-base font-semibold text-foreground">{title}</h3>
<p className="mt-1.5 text-sm leading-relaxed text-muted-foreground">{body}</p>
</div>
))}
</div>
</div>
</section>
{/* Footer */}
<footer className="border-t px-6 py-10 sm:px-10">
<div className="mx-auto flex max-w-5xl flex-col items-center justify-between gap-4 text-sm text-muted-foreground sm:flex-row">
<span>© {new Date().getFullYear()} PawFeed</span>
<nav className="flex gap-5">
<Link href="/impressum" className="hover:text-foreground">Impressum</Link>
<Link href="/datenschutz" className="hover:text-foreground">Datenschutz</Link>
<Link href="/nutzungsbedingungen" className="hover:text-foreground">AGB</Link>
</nav>
</div>
</footer>
</div>
);
}
+8 -6
View File
@@ -6,8 +6,8 @@ import { useTransition } from "react";
import { Globe } from "lucide-react";
const LOCALES = [
{ code: "en", label: "English" },
{ code: "de", label: "Deutsch" },
{ code: "en", label: "English", flag: "🇬🇧" },
{ code: "de", label: "Deutsch", flag: "🇩🇪" },
] as const;
export function LanguageSwitcher() {
@@ -32,13 +32,15 @@ export function LanguageSwitcher() {
key={l.code}
onClick={() => switchLocale(l.code)}
disabled={isPending || locale === l.code}
className={`text-xs px-1.5 py-0.5 rounded transition-colors ${
aria-label={l.label}
title={l.label}
className={`flex items-center justify-center text-base leading-none px-1.5 py-1 rounded transition-colors ${
locale === l.code
? "text-foreground font-medium bg-accent"
: "hover:text-foreground hover:bg-accent/60"
? "bg-accent ring-1 ring-inset ring-foreground/20"
: "opacity-60 hover:opacity-100 hover:bg-accent/60"
}`}
>
{l.label}
<span aria-hidden="true">{l.flag}</span>
</button>
))}
</div>