← back to Stayclaim
src/app/people/page.tsx
121 lines
import Link from 'next/link';
import type { Metadata } from 'next';
import { listEntitiesWithCounts } from '@/lib/db';
import { BreadcrumbArchive } from '@/components/BreadcrumbArchive';
import { CrossPromo } from '@/components/CrossPromo';
import { currentSurface } from '@/lib/site';
export const metadata: Metadata = {
title: 'People',
description: 'Architects, residents, and studios in the archive — historical only.',
alternates: { canonical: '/people' },
};
const KIND_LABEL: Record<string, string> = {
architect: 'Architects',
studio: 'Studios',
person: 'People',
business: 'Businesses',
};
export default async function PeopleIndex() {
const surface = await currentSurface();
const all = await listEntitiesWithCounts();
// Featured = entities with the most public associations; max 4 across kinds.
const featured = [...all].sort((a, b) => b.oeuvre_count - a.oeuvre_count).slice(0, 4);
const grouped: Record<string, typeof all> = {};
for (const e of all) {
if (!grouped[e.kind]) grouped[e.kind] = [];
grouped[e.kind]!.push(e);
}
return (
<>
<div className="max-w-5xl mx-auto px-6 pt-6">
<BreadcrumbArchive items={[{ label: 'Archive', href: '/' }, { label: 'People' }]} />
</div>
<header className="max-w-5xl mx-auto px-6 py-12 border-b border-ink/10">
<p className="text-xs uppercase tracking-[0.15em] text-ink/50 mb-2">People</p>
<h1 className="font-display text-5xl md:text-6xl text-ink tracking-[-0.02em] leading-[1.02]">
The people in the archive.
</h1>
<p className="mt-4 font-display italic text-xl text-ink/70 max-w-prose">
Historical only. Architects, studios, and prior occupants whose ties to a place are documented.
</p>
</header>
{featured.length > 0 && featured[0]!.oeuvre_count > 0 && (
<section className="max-w-5xl mx-auto px-6 py-10 border-b border-ink/10">
<h2 className="font-display text-2xl text-ink tracking-[-0.01em] mb-5">Most cited</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{featured.map(e => (
<Link
key={e.id}
href={`/entity/${encodeURIComponent(e.slug)}`}
className="block border border-ink/10 bg-sand p-5 hover:border-ink/40 transition group"
>
<p className="font-mono text-[10px] uppercase tracking-[0.15em] text-ink/45">
{e.kind}
</p>
<h3 className="font-display text-xl text-ink leading-tight mt-1 group-hover:text-coral transition">
{e.display_name}
</h3>
<p className="font-mono text-xs text-ink/50 mt-1">
{e.birth_year ?? '?'}–{e.death_year ?? '?'}
</p>
<p className="mt-3 font-display text-3xl text-ink/80 group-hover:text-coral">
{e.oeuvre_count}
<span className="ml-1 font-sans text-[10px] uppercase tracking-[0.15em] text-ink/40">
{e.oeuvre_count === 1 ? 'work' : 'works'}
</span>
</p>
</Link>
))}
</div>
</section>
)}
<section className="max-w-5xl mx-auto px-6 py-12">
{Object.entries(grouped).map(([kind, list]) => (
<div key={kind} className="mb-12">
<h2 className="font-display text-3xl text-ink tracking-[-0.01em] mb-5">{KIND_LABEL[kind] ?? kind}</h2>
<ul className="grid md:grid-cols-2 gap-3">
{list.map(e => (
<li key={e.id}>
<Link
href={`/entity/${encodeURIComponent(e.slug)}`}
className="block border border-ink/10 bg-sand p-4 hover:border-ink/40 transition group"
>
<div className="flex items-baseline justify-between gap-3">
<div>
<div className="font-display text-2xl text-ink leading-tight group-hover:text-coral">
{e.display_name}
</div>
<div className="mt-1 text-xs text-ink/60 font-mono">
{e.birth_year ?? '?'}–{e.death_year ?? '?'} · {e.kind}
</div>
</div>
<span className="font-mono text-xs text-ink/50 whitespace-nowrap">
{e.oeuvre_count} {e.oeuvre_count === 1 ? 'addr' : 'addrs'}
</span>
</div>
</Link>
</li>
))}
</ul>
</div>
))}
<p className="mt-12 text-xs text-ink/40 max-w-prose leading-relaxed">
We do not include living persons. The privacy gate enforces this at the database level — any
association with a living person ending within the last 25 years cannot be made public, no
matter how it’s entered.
</p>
</section>
<CrossPromo current={surface.key} />
</>
);
}