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