1f3af8f7f5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import Frame from "@/components/Frame";
|
|
import { projects, site } from "@/content/site";
|
|
|
|
export const dynamicParams = false;
|
|
|
|
export function generateStaticParams() {
|
|
return projects.map((p) => ({ slug: p.slug }));
|
|
}
|
|
|
|
export async function generateMetadata({ params }: PageProps<"/work/[slug]">): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const project = projects.find((p) => p.slug === slug);
|
|
if (!project) return {};
|
|
return { title: project.title, description: project.summary };
|
|
}
|
|
|
|
function Video({ src, title }: { src: string; title: string }) {
|
|
if (/vimeo\.com|youtube\.com|youtu\.be/.test(src)) {
|
|
return (
|
|
<iframe
|
|
src={src}
|
|
title={title}
|
|
className="aspect-video w-full"
|
|
allow="autoplay; fullscreen; picture-in-picture"
|
|
allowFullScreen
|
|
/>
|
|
);
|
|
}
|
|
return <video src={src} className="aspect-video w-full bg-black" controls playsInline />;
|
|
}
|
|
|
|
export default async function ProjectPage({ params }: PageProps<"/work/[slug]">) {
|
|
const { slug } = await params;
|
|
const index = projects.findIndex((p) => p.slug === slug);
|
|
if (index === -1) notFound();
|
|
const project = projects[index];
|
|
const next = projects[(index + 1) % projects.length];
|
|
|
|
const meta = [
|
|
{ k: "Client", v: project.client },
|
|
{ k: "Year", v: String(project.year) },
|
|
{ k: "Type", v: project.category },
|
|
...(project.shots ? [{ k: "Shots", v: String(project.shots) }] : []),
|
|
];
|
|
|
|
return (
|
|
<article className="pt-28 md:pt-36">
|
|
<div className="mx-auto max-w-7xl px-5 md:px-8">
|
|
<Link
|
|
href="/work/"
|
|
className="font-mono text-xs tracking-[0.2em] text-muted uppercase transition-colors hover:text-fg"
|
|
>
|
|
← All work
|
|
</Link>
|
|
<h1 className="mt-8 font-display text-5xl leading-[0.9] font-thin uppercase md:text-8xl">{project.title}</h1>
|
|
</div>
|
|
|
|
<div className="mx-auto mt-12 max-w-7xl px-5 md:px-8">
|
|
{project.video ? <Video src={project.video} title={project.title} /> : <Frame project={project} priority />}
|
|
</div>
|
|
|
|
<div className="mx-auto grid max-w-7xl gap-12 px-5 py-20 md:grid-cols-[1fr_2fr] md:px-8 md:py-28">
|
|
<dl className="grid grid-cols-2 gap-6 self-start md:grid-cols-1">
|
|
{meta.map((m) => (
|
|
<div key={m.k}>
|
|
<dt className="font-mono text-[11px] tracking-[0.2em] text-muted uppercase">{m.k}</dt>
|
|
<dd className="mt-1 text-lg">{m.v}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
<div>
|
|
<p className="text-xl leading-relaxed font-light md:text-2xl">{project.summary}</p>
|
|
<ul className="mt-10 flex flex-wrap gap-2">
|
|
{project.credits.map((c) => (
|
|
<li
|
|
key={c}
|
|
className="border border-line px-3 py-1.5 font-mono text-[11px] tracking-[0.15em] text-muted uppercase"
|
|
>
|
|
{c}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<a
|
|
href={`mailto:${site.email}?subject=${encodeURIComponent(`Enquiry: ${project.title}`)}`}
|
|
className="mt-12 inline-block font-mono text-xs tracking-[0.2em] text-accent uppercase hover:text-fg"
|
|
>
|
|
Talk to us about a project like this →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<Link href={`/work/${next.slug}/`} className="group block border-t border-line">
|
|
<div className="mx-auto flex max-w-7xl items-center justify-between gap-6 px-5 py-14 md:px-8 md:py-20">
|
|
<div>
|
|
<p className="font-mono text-xs tracking-[0.3em] text-muted uppercase">Next project</p>
|
|
<p className="mt-3 font-display text-3xl font-thin uppercase transition-colors group-hover:text-accent md:text-6xl">
|
|
{next.title}
|
|
</p>
|
|
</div>
|
|
<span className="font-display text-4xl font-thin transition-transform group-hover:translate-x-2 md:text-6xl">
|
|
→
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
</article>
|
|
);
|
|
}
|