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