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:
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user