Add site-wide 404 page

app/not-found.tsx — root not-found handles all unmatched routes
automatically (Next 16, no global-not-found flag needed for a single
root layout). Hero (Ups. / subheading / CTA) with a natively-drawn
sticky-note illustration (Lora "404" + Caveat handwritten line, no
image asset needed), 3 help links (Startseite/Blog/Werkzeuge), a
decorative search bar (no search backend exists yet to wire it to),
photo+quote testimonial band, TrustRow, Footer. Sets robots: noindex.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-19 20:16:22 +00:00
parent f5df384b13
commit 0664e11d3b
+214
View File
@@ -0,0 +1,214 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Reveal, RevealGroup, RevealItem } from "./components/Reveal";
import { Footer } from "./components/Footer";
import { TrustRow } from "./components/TrustRow";
export const metadata: Metadata = {
title: "Seite nicht gefunden",
description: "Diese Seite ist gerade nicht dort, wo sie sein sollte.",
robots: { index: false, follow: true },
};
const helpLinks = [
{
href: "/",
title: "Startseite",
desc: "Zurück zu den wichtigsten Inhalten und Angeboten.",
cta: "Zur Startseite",
icon: (
<svg viewBox="0 0 24 24" className="size-6" fill="none" aria-hidden="true">
<path
d="M4 11.5 12 4l8 7.5M6 9.5V20h5v-5.5h2V20h5V9.5"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
),
},
{
href: "/blog",
title: "Im Blog stöbern",
desc: "Lass dich von Impulsen, Methoden und Gedanken inspirieren.",
cta: "Zum Blog",
icon: (
<svg viewBox="0 0 24 24" className="size-6" fill="none" aria-hidden="true">
<path
d="M6 3.5h9.5L20 8v12.5a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-16a1 1 0 0 1 1-1Z"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
<path d="M8.5 9.5h7M8.5 13h7M8.5 16.5h4.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
</svg>
),
},
{
href: "/#werkzeuge",
title: "Werkzeuge entdecken",
desc: "Praktische Tools für mehr Fokus, Struktur und Klarheit.",
cta: "Zu den Werkzeugen",
icon: (
<svg viewBox="0 0 24 24" className="size-6" fill="none" aria-hidden="true">
<path
d="M4 9.5h16a1 1 0 0 1 1 1V19a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-8.5a1 1 0 0 1 1-1Z"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
<path
d="M8.5 9.5V7a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v2.5"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
</svg>
),
},
];
export default function NotFound() {
return (
<>
<main className="flex flex-col flex-1 bg-bg-base">
{/* Hero — eyebrow / Ups. / subheading / copy / CTA, sticky note beside it */}
<div className="flex flex-col lg:flex-row gap-12 lg:gap-16 items-center pt-16 pb-20 px-[var(--layout-padding-x)] w-full">
<Reveal className="flex flex-col gap-4 items-start w-full lg:flex-1 max-w-[32rem]">
<p className="font-semibold text-body-sm text-brand tracking-wide">404 Seite nicht gefunden</p>
<p
className="font-semibold text-[3.5rem] leading-[1.05] text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Ups.
</p>
<p
className="font-semibold text-h-feature text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Diese Seite ist gerade nicht dort, wo sie sein sollte.
</p>
<div className="h-[0.125rem] w-8 bg-brand" />
<p className="text-body text-text-muted">
Vielleicht wurde sie verschoben, umbenannt oder es hat sich ein kleiner Tippfehler eingeschlichen.
</p>
<p className="text-body text-text-muted">Aber kein Problem wir bringen dich gerne zurück auf Kurs.</p>
<Link
href="/"
className="flex items-center gap-2 mt-2 px-7 py-4 rounded-sm bg-brand text-h4 font-bold text-text-primary hover:bg-brand-hover active:scale-[0.97] transition-all"
>
Zur Startseite
<svg viewBox="0 0 20 20" className="size-4" fill="none" aria-hidden="true">
<path d="M4 10h12m0 0-5-5m5 5-5 5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Link>
</Reveal>
{/* Sticky note — drawn natively (rotated card + tape + Caveat
handwriting) rather than a cropped photo asset, since there's
no such image in /public and this reads just as well as a
coded illustration built from the same tokens (Lora for the
"404", Caveat for the handwritten line) the rest of the site
already uses for handwritten-style accents (see About.tsx's
signature quote). */}
<Reveal delay={0.15} className="w-full lg:flex-1 flex items-center justify-center">
<div className="relative w-full max-w-[22rem] aspect-square rotate-2 rounded-sm bg-[#EDE3D0] shadow-modal p-8 flex flex-col items-center text-center">
<div className="absolute -top-3 left-1/2 -translate-x-1/2 -rotate-3 w-16 h-6 bg-[#d8cdb8]/80" />
<p
className="font-bold text-text-primary text-[4.5rem] leading-none mt-6"
style={{ fontFamily: "var(--font-lora)" }}
>
404
</p>
<div className="h-[0.1875rem] w-24 bg-brand mt-3 mb-4" />
<p
className="text-text-primary text-[1.75rem] leading-[1.15]"
style={{ fontFamily: "var(--font-caveat)" }}
>
Nicht hier.
<br />
Aber dein Ziel ist
<br />
sicher nicht weit.
</p>
<svg viewBox="0 0 32 32" className="size-8 mt-4 text-text-primary" fill="none" aria-hidden="true">
<circle cx="11" cy="13" r="1.6" fill="currentColor" />
<circle cx="21" cy="13" r="1.6" fill="currentColor" />
<path d="M9 19c1.8 2.4 4.4 3.6 7 3.6s5.2-1.2 7-3.6" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
</svg>
</div>
</Reveal>
</div>
{/* Help links + search */}
<div className="flex flex-col gap-10 items-center pb-16 px-[var(--layout-padding-x)] w-full">
<Reveal className="flex flex-col items-center gap-2">
<p
className="font-semibold text-h-small text-text-primary"
style={{ fontFamily: "var(--font-lora)" }}
>
Vielleicht hilft dir das weiter:
</p>
<div className="h-[0.125rem] w-8 bg-brand" />
</Reveal>
<RevealGroup className="grid grid-cols-1 md:grid-cols-3 gap-10 md:gap-8 w-full max-w-[64rem] divide-y md:divide-y-0 md:divide-x divide-border">
{helpLinks.map((link) => (
<RevealItem key={link.href} className="flex flex-col items-center text-center gap-3 pt-10 md:pt-0 first:pt-0 px-4">
<div className="flex size-14 items-center justify-center rounded-full bg-bg-muted text-brand">
{link.icon}
</div>
<p className="font-semibold text-body text-text-primary">{link.title}</p>
<p className="text-body-sm text-text-muted">{link.desc}</p>
<Link href={link.href} className="flex items-center gap-1.5 text-body-sm font-semibold text-brand hover:underline">
{link.cta}
<svg viewBox="0 0 20 20" className="size-3.5" fill="none" aria-hidden="true">
<path d="M4 10h12m0 0-5-5m5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Link>
</RevealItem>
))}
</RevealGroup>
<Reveal className="w-full max-w-[28rem]">
<div className="flex items-center gap-3 bg-bg-white border border-border rounded-sm px-6 py-3">
<svg viewBox="0 0 20 20" className="size-4 shrink-0 text-text-muted" fill="none" aria-hidden="true">
<circle cx="9" cy="9" r="6.5" stroke="currentColor" strokeWidth="1.6" />
<path d="m18 18-4-4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
</svg>
<span className="text-body text-text-muted">Suche nach Impulsen, Tools, Themen </span>
</div>
</Reveal>
</div>
{/* Testimonial band — photo bleeds to the viewport edge, gradient
fade into bg-base on its trailing edge instead of a hard cut,
same idea as page-bestellbestaetigung's photo+quote pattern. */}
<Reveal className="relative w-full flex items-stretch min-h-[16rem] bg-bg-muted overflow-hidden">
<div className="relative w-full md:w-[45%] shrink-0">
<img
alt=""
src="/newsletter-modal-photo.jpg"
className="absolute inset-0 w-full h-full object-cover"
/>
<div className="hidden md:block absolute inset-y-0 right-0 w-40 bg-gradient-to-l from-bg-muted to-transparent" />
</div>
<div className="flex-1 flex flex-col justify-center gap-3 px-8 md:px-16 py-12">
<p
className="font-semibold text-h-section text-text-primary leading-[1.3] max-w-[28rem]"
style={{ fontFamily: "var(--font-lora)" }}
>
Manchmal führt der falsche Weg zu neuen klaren Gedanken.
</p>
<div className="h-[0.125rem] w-8 bg-brand" />
<p className="text-body text-text-muted">Björn</p>
</div>
</Reveal>
<TrustRow />
</main>
<Footer />
</>
);
}