← back to Stayclaim
src/app/neighborhoods/page.tsx
98 lines
import Link from 'next/link';
import type { Metadata } from 'next';
import { listNeighborhoodsWithGeo, searchListings } from '@/lib/db';
import { BreadcrumbArchive } from '@/components/BreadcrumbArchive';
import { InteractiveMapMount, type MapPin } from '@/components/InteractiveMapMount';
import { CrossPromo } from '@/components/CrossPromo';
import { FilmsGridServer } from '@/components/FilmsGridServer';
import { HeroCollage } from '@/components/HeroCollage';
import { StarsCtaRow } from '@/components/StarsCtaRow';
import { currentSurface } from '@/lib/site';
export const metadata: Metadata = {
title: 'Neighborhoods',
description: 'Where the archive lives — Beverly Hills first, concentric rings outward.',
alternates: { canonical: '/neighborhoods' },
};
function slugify(s: string) {
return s.toLowerCase().replace(/\s+/g, '-');
}
export default async function NeighborhoodsIndex() {
const surface = await currentSurface();
const items = await listNeighborhoodsWithGeo();
// Pull all listings with lat/lng for a master map of every place we have.
const allListings = await searchListings({ limit: 100 });
const pins: MapPin[] = allListings.flatMap(l => {
const lat = Number(l.latitude);
const lng = Number(l.longitude);
if (!Number.isFinite(lat) || !Number.isFinite(lng)) return [];
if (lat === 0 && lng === 0) return [];
return [{
slug: l.slug,
title: l.title,
subtitle: l.city ?? undefined,
lat,
lng,
}];
});
return (
<>
<HeroCollage />
<StarsCtaRow surface={surface.key} />
<div className="max-w-5xl mx-auto px-6 pt-6">
<BreadcrumbArchive items={[{ label: 'Archive', href: '/' }, { label: 'Neighborhoods' }]} />
</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">Neighborhoods</p>
<h1 className="font-display text-5xl md:text-6xl text-ink tracking-[-0.02em] leading-[1.02]">
Where the archive lives.
</h1>
<p className="mt-4 font-display italic text-xl text-ink/70 max-w-prose">
Beverly Hills first. Concentric rings outward as ingestion stabilizes.
</p>
</header>
{pins.length > 0 && (
<div className="max-w-5xl mx-auto px-6 mt-10">
<InteractiveMapMount pins={pins} height={420} variant="positron" />
<p className="mt-2 font-mono text-[10px] uppercase tracking-[0.15em] text-ink/50">
{pins.length} addresses across {items.length} {items.length === 1 ? 'neighborhood' : 'neighborhoods'}
</p>
</div>
)}
<section className="max-w-5xl mx-auto px-6 py-12">
<ul className="divide-y divide-ink/10">
{items.map(n => (
<li key={n.city}>
<Link
href={`/neighborhood/${slugify(n.city)}`}
className="flex items-baseline justify-between py-5 hover:text-coral transition group"
>
<span className="font-display text-3xl text-ink group-hover:text-coral tracking-[-0.01em]">
{n.city}
</span>
<span className="font-mono text-xs text-ink/50">
{n.count} {n.count === 1 ? 'address' : 'addresses'} · {n.state}
</span>
</Link>
</li>
))}
</ul>
</section>
<FilmsGridServer
heading="The block, before you knew it"
kicker="Neighborhood screenings"
limit={48}
/>
<CrossPromo current={surface.key} />
</>
);
}