132 lines
5.0 KiB
TypeScript
132 lines
5.0 KiB
TypeScript
import { ArrowLeft, CalendarDays, FolderKanban, Tag } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
|
|
import { MotionFade } from "@/components/motion-fade";
|
|
import { routing } from "@/i18n/routing";
|
|
import { getPortfolioItem, pickText, portfolioItems, resolveLocale } from "@/lib/site-data";
|
|
|
|
type PortfolioItemPageProps = {
|
|
params: {
|
|
locale: string;
|
|
slug: string;
|
|
};
|
|
};
|
|
|
|
export function generateStaticParams() {
|
|
return routing.locales.flatMap((locale) =>
|
|
portfolioItems.map((item) => ({
|
|
locale,
|
|
slug: item.slug,
|
|
})),
|
|
);
|
|
}
|
|
|
|
export default function PortfolioItemPage({
|
|
params: { locale, slug },
|
|
}: PortfolioItemPageProps) {
|
|
const localeKey = resolveLocale(locale);
|
|
const item = getPortfolioItem(slug);
|
|
|
|
if (!item) {
|
|
notFound();
|
|
}
|
|
|
|
const copy =
|
|
localeKey === "de"
|
|
? {
|
|
back: "Zurueck zum Portfolio",
|
|
challenge: "Herausforderung",
|
|
solution: "Loesung",
|
|
outcome: "Ergebnis",
|
|
challengeText:
|
|
"Das Projekt brauchte eine klare Informationsarchitektur und schnellere Ladezeiten.",
|
|
solutionText:
|
|
"Wir haben Design, Komponenten und Content in einem modularen System aufgebaut.",
|
|
outcomeText:
|
|
"Das Team kann Inhalte schneller ausrollen und Nutzer finden schneller zum Ziel.",
|
|
}
|
|
: {
|
|
back: "Back to portfolio",
|
|
challenge: "Challenge",
|
|
solution: "Solution",
|
|
outcome: "Outcome",
|
|
challengeText:
|
|
"The project needed clearer information architecture and faster performance.",
|
|
solutionText:
|
|
"We built design, components and content in a modular system.",
|
|
outcomeText:
|
|
"The team ships content faster and users reach goals more quickly.",
|
|
};
|
|
|
|
return (
|
|
<div className="mx-auto flex w-full max-w-5xl flex-col gap-8 px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
|
|
<MotionFade>
|
|
<section className="rounded-3xl border border-zinc-200 bg-white p-6 dark:border-zinc-800 dark:bg-zinc-950 lg:p-10">
|
|
<Link
|
|
href={`/${localeKey}/portfolio`}
|
|
className="inline-flex items-center gap-2 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-300 dark:hover:text-zinc-100"
|
|
>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
{copy.back}
|
|
</Link>
|
|
<h1 className="mt-5 text-3xl font-semibold text-zinc-900 dark:text-zinc-100 sm:text-4xl">
|
|
{pickText(item.title, localeKey)}
|
|
</h1>
|
|
<p className="mt-4 text-base text-zinc-600 dark:text-zinc-300 sm:text-lg">
|
|
{pickText(item.summary, localeKey)}
|
|
</p>
|
|
|
|
<div className="mt-6 flex flex-wrap gap-3 text-sm text-zinc-600 dark:text-zinc-300">
|
|
<span className="inline-flex items-center gap-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 dark:border-zinc-800 dark:bg-zinc-900">
|
|
<Tag className="h-4 w-4" />
|
|
{pickText(item.category, localeKey)}
|
|
</span>
|
|
<span className="inline-flex items-center gap-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 dark:border-zinc-800 dark:bg-zinc-900">
|
|
<CalendarDays className="h-4 w-4" />
|
|
{item.year}
|
|
</span>
|
|
<span className="inline-flex items-center gap-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 dark:border-zinc-800 dark:bg-zinc-900">
|
|
<FolderKanban className="h-4 w-4" />
|
|
{item.slug}
|
|
</span>
|
|
</div>
|
|
</section>
|
|
</MotionFade>
|
|
|
|
<div className="grid gap-4 md:grid-cols-3">
|
|
<MotionFade delay={0.05}>
|
|
<article className="rounded-2xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-950">
|
|
<h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
|
{copy.challenge}
|
|
</h2>
|
|
<p className="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
|
|
{copy.challengeText}
|
|
</p>
|
|
</article>
|
|
</MotionFade>
|
|
<MotionFade delay={0.1}>
|
|
<article className="rounded-2xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-950">
|
|
<h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
|
{copy.solution}
|
|
</h2>
|
|
<p className="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
|
|
{copy.solutionText}
|
|
</p>
|
|
</article>
|
|
</MotionFade>
|
|
<MotionFade delay={0.15}>
|
|
<article className="rounded-2xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-950">
|
|
<h2 className="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
|
{copy.outcome}
|
|
</h2>
|
|
<p className="mt-2 text-sm text-zinc-600 dark:text-zinc-300">
|
|
{copy.outcomeText}
|
|
</p>
|
|
</article>
|
|
</MotionFade>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|