← back to Stayclaim
src/app/stars/walk/page.tsx
128 lines
/**
* /stars/walk — Hollywood Walk of Fame
*
* Sidewalk stars on Hollywood Blvd + Vine St. Distinct from /stars (residences).
* Each star: name, category (motion pictures / TV / recording / radio / theater),
* exact address + lat/lng, dedicated date.
*/
import Link from 'next/link';
import type { Metadata } from 'next';
import { pool } from '@/lib/db';
import { BreadcrumbArchive } from '@/components/BreadcrumbArchive';
import { InteractiveMapMount, type MapPin } from '@/components/InteractiveMapMount';
export const metadata: Metadata = {
title: 'Hollywood Walk of Fame',
description: 'Sidewalk stars on Hollywood Blvd and Vine St — names, categories, addresses, dedication dates. Mapped.',
alternates: { canonical: '/stars/walk' },
};
type WalkStar = {
honoree_slug: string;
honoree_name: string;
category: string;
address: string | null;
lat: number | null;
lng: number | null;
dedicated_date: string | null;
};
const CATEGORY_LABEL: Record<string, string> = {
motion_pictures: 'Motion Pictures',
television: 'Television',
recording: 'Recording',
radio: 'Radio',
theater: 'Theater',
sports: 'Sports',
special: 'Special',
};
async function getStars(): Promise<WalkStar[]> {
try {
const { rows } = await pool.query<WalkStar>(`
SELECT honoree_slug, honoree_name, category, address,
lat::float AS lat, lng::float AS lng,
dedicated_date::text
FROM walk_of_fame
WHERE lat IS NOT NULL AND lng IS NOT NULL
ORDER BY honoree_name
LIMIT 5000
`);
return rows;
} catch {
return [];
}
}
export default async function WalkPage() {
const stars = await getStars();
const pins: MapPin[] = stars.map(s => ({
slug: s.honoree_slug,
title: s.honoree_name,
subtitle: `${CATEGORY_LABEL[s.category] ?? s.category} · ${s.address ?? ''}`,
lat: Number(s.lat),
lng: Number(s.lng),
href: null,
}));
// Group by category
const byCategory: Record<string, WalkStar[]> = {};
for (const s of stars) (byCategory[s.category] ??= []).push(s);
return (
<>
<div className="max-w-6xl mx-auto px-6 pt-6">
<BreadcrumbArchive items={[
{ label: 'Archive', href: '/' },
{ label: 'Map of the stars', href: '/stars' },
{ label: 'Walk of Fame' },
]} />
</div>
<header className="max-w-3xl mx-auto px-6 py-12 border-b border-ink/10">
<p className="text-[10px] uppercase tracking-[0.18em] text-ink/55 mb-3 font-mono">
Sidewalk · Hollywood & Vine
</p>
<h1 className="font-display text-5xl md:text-6xl text-ink tracking-[-0.02em] leading-[1.02]">
Hollywood Walk of Fame.
</h1>
<p className="mt-4 font-display italic text-xl text-ink/70 max-w-prose">
{stars.length} stars on the map — addresses verified against the
Hollywood Chamber of Commerce list, coordinates anchored to the
actual sidewalk inscription.
</p>
<p className="mt-4 text-sm text-ink/55">
On your phone? <Link href="/stars/nearby" className="underline hover:text-coral">Find stars near you →</Link>
</p>
</header>
{pins.length > 0 && (
<section className="max-w-6xl mx-auto px-6 py-8">
<InteractiveMapMount pins={pins} height={520} variant="positron" />
</section>
)}
<section className="max-w-3xl mx-auto px-6 py-10">
{Object.entries(byCategory).sort().map(([cat, list]) => (
<div key={cat} className="mb-12">
<h2 className="font-display text-3xl text-ink mb-4 pb-2 border-b border-ink/10">
{CATEGORY_LABEL[cat] ?? cat}
<span className="ml-3 font-mono text-[11px] uppercase tracking-[0.15em] text-ink/45">
{list.length}
</span>
</h2>
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-3">
{list.map(s => (
<li key={s.honoree_slug} className="flex items-baseline justify-between gap-3">
<span className="font-display text-lg text-ink truncate">{s.honoree_name}</span>
<span className="font-mono text-[10px] text-ink/45 shrink-0">{s.address?.replace(/ Hollywood Blvd/i, ' Hollywood').replace(/ Vine St/i, ' Vine')}</span>
</li>
))}
</ul>
</div>
))}
</section>
</>
);
}