Files
ttvfx/app/work/[slug]/page.tsx
T
twotalesanimation 1f3af8f7f5 Initial ttvfx.com site: Next.js static export
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:17:41 +02:00

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>
);
}