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:
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
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user