1'use client';2import { ChevronDown, Menu, Search, X } from 'lucide-react';3import Link from 'next/link';4import { usePathname } from 'next/navigation';5import { useEffect, useRef, useState } from 'react';6import { Wordmark } from '@/components/brand/logo';7import { cn } from '@/lib/cn';8import { exploreNav, moreNav, primaryNav, routes } from '@/lib/site';9import { useOpenSearch } from './search-context';10import { ThemeToggle } from './theme';1112const EXPLORE_PREFIXES = exploreNav.map((n) => n.href);1314export function SiteHeader() {15 const pathname = usePathname();16 const openSearch = useOpenSearch();17 const [menu, setMenu] = useState(false);18 const [explore, setExplore] = useState(false);19 const exploreRef = useRef<HTMLDivElement>(null);20 useEffect(() => {21 setMenu(false);22 setExplore(false);23 }, [pathname]);24 useEffect(() => {25 if (!explore) return;26 const onDoc = (e: MouseEvent) => {27 if (!exploreRef.current?.contains(e.target as Node)) setExplore(false);28 };29 const onKey = (e: KeyboardEvent) => e.key === 'Escape' && setExplore(false);30 document.addEventListener('mousedown', onDoc);31 document.addEventListener('keydown', onKey);32 return () => {33 document.removeEventListener('mousedown', onDoc);34 document.removeEventListener('keydown', onKey);35 };36 }, [explore]);3738 const exploreActive = pathname === '/explore' || EXPLORE_PREFIXES.some((p) => pathname === p || pathname.startsWith(p + '/'));39 const navCls = (active: boolean) => cn('flex h-9 items-center gap-1 rounded-sm px-2.5 text-[13.5px] transition-colors', active ? 'text-ink font-medium' : 'text-ink-2 hover:text-ink');4041 return (42 <header className="sticky top-0 z-40 border-b border-rule bg-canvas/85 backdrop-blur-md">43 <div className="container-x mx-auto flex h-[var(--header-h)] max-w-[1600px] items-center gap-3">44 <Link href={routes.home()} className="flex h-11 items-center" aria-label="AI Atlas home">45 <Wordmark />46 </Link>47 <nav aria-label="Primary" className="ml-3 hidden items-center gap-0.5 lg:flex">48 <div className="relative" ref={exploreRef}>49 <button type="button" onClick={() => setExplore((v) => !v)} aria-expanded={explore} aria-haspopup="menu" className={navCls(exploreActive)}>50 Explore <ChevronDown className={cn('size-3.5 transition-transform', explore && 'rotate-180')} aria-hidden />51 </button>52 {explore && (53 <div role="menu" className="panel absolute left-0 top-full mt-1 w-[520px] p-2 shadow-lg">54 <ul className="grid grid-cols-2 gap-0.5">55 {exploreNav.map((n) => (56 <li key={n.href}>57 <Link role="menuitem" href={n.href} className={cn('block rounded-sm px-3 py-2 text-sm hover:bg-surface-2', pathname.startsWith(n.href) ? 'text-ink font-medium' : 'text-ink-2 hover:text-ink')}>58 {n.label}59 </Link>60 </li>61 ))}62 <li className="col-span-2 mt-1 border-t border-rule pt-1">63 <Link role="menuitem" href={routes.explore()} className="block rounded-sm px-3 py-2 text-sm text-accent hover:bg-surface-2">64 All entity types with live counts →65 </Link>66 </li>67 </ul>68 </div>69 )}70 </div>71 {primaryNav.map((n) => {72 const active = pathname === n.href || pathname.startsWith(n.href + '/');73 return (74 <Link key={n.href} href={n.href} aria-current={active ? 'page' : undefined} className={navCls(active)}>75 {n.label}76 </Link>77 );78 })}79 </nav>80 <div className="ml-auto flex items-center gap-1">81 <button type="button" onClick={openSearch} className="hidden h-9 min-w-[280px] items-center gap-2 rounded-sm border border-rule bg-surface px-3 text-sm text-ink-3 hover:border-rule-strong hover:text-ink-2 md:flex" aria-label="Open search">82 <Search className="size-4" aria-hidden />83 <span className="flex-1 text-left">Search the AI ecosystem…</span>84 <kbd className="mono rounded-[3px] border border-rule px-1.5 py-0.5 text-[10px]">⌘K</kbd>85 </button>86 <button type="button" onClick={openSearch} className="flex size-11 items-center justify-center rounded-sm text-ink-2 hover:bg-surface-2 hover:text-ink md:hidden" aria-label="Open search">87 <Search className="size-5" aria-hidden />88 </button>89 <ThemeToggle />90 <button type="button" onClick={() => setMenu((m) => !m)} className="flex size-11 items-center justify-center rounded-sm text-ink-2 hover:bg-surface-2 hover:text-ink lg:hidden" aria-label={menu ? 'Close menu' : 'Open menu'} aria-expanded={menu}>91 {menu ? <X className="size-5" aria-hidden /> : <Menu className="size-5" aria-hidden />}92 </button>93 </div>94 </div>95 {menu && (96 <div className="border-t border-rule bg-canvas lg:hidden">97 <div className="container-x mx-auto grid max-w-[1600px] gap-6 py-5 sm:grid-cols-3">98 <div>99 <p className="eyebrow mb-2">Explore</p>100 <ul className="grid grid-cols-2 gap-0.5 sm:grid-cols-1">101 {exploreNav.map((n) => (102 <li key={n.href}>103 <Link href={n.href} className="block py-2.5 text-[15px] text-ink-2 hover:text-ink">104 {n.label}105 </Link>106 </li>107 ))}108 </ul>109 </div>110 <div>111 <p className="eyebrow mb-2">Track</p>112 <ul className="grid grid-cols-2 gap-0.5 sm:grid-cols-1">113 {primaryNav.map((n) => (114 <li key={n.href}>115 <Link href={n.href} className="block py-2.5 text-[15px] text-ink-2 hover:text-ink">116 {n.label}117 </Link>118 </li>119 ))}120 </ul>121 </div>122 <div>123 <p className="eyebrow mb-2">About</p>124 <ul className="grid grid-cols-2 gap-0.5 sm:grid-cols-1">125 {moreNav.map((n) => (126 <li key={n.href}>127 <Link href={n.href} className="block py-2.5 text-[15px] text-ink-2 hover:text-ink">128 {n.label}129 </Link>130 </li>131 ))}132 </ul>133 </div>134 </div>135 </div>136 )}137 </header>138 );139}140