SPB Git forge
3commits 1branches 0releases
417.0 KBsize
maindefault branch
10 days agolast push
TypeScript 66.5% Python 30.9% JavaScript 1.4% CSS 0.7%
4.8 KB · 90 lines tsx
Raw Blame History
1'use client';2import { Menu, Search, X } from 'lucide-react';3import Link from 'next/link';4import { usePathname } from 'next/navigation';5import { useEffect, useState } from 'react';6import { Wordmark } from '@/components/brand/logo';7import { cn } from '@/lib/cn';8import { moreNav, primaryNav, routes } from '@/lib/site';9import { useOpenSearch } from './search-context';1011export function SiteHeader() {12  const pathname = usePathname();13  const openSearch = useOpenSearch();14  const [menu, setMenu] = useState(false);15  useEffect(() => setMenu(false), [pathname]);16  const isHome = pathname === '/';17  const isExplore = pathname === '/explore';1819  return (20    <header className={cn('sticky top-0 z-40 border-b border-rule backdrop-blur-md', isHome || isExplore ? 'bg-space/70' : 'bg-space/85')}>21      <div className="container-x mx-auto flex h-[60px] max-w-[1600px] items-center gap-4">22        <Link href={routes.home()} className="flex h-11 items-center" aria-label="SatelliteIndex home">23          <Wordmark />24        </Link>25        <nav aria-label="Primary" className="ml-2 hidden items-center gap-0.5 lg:flex">26          {primaryNav.map((n) => {27            const active = pathname === n.href || (n.href !== '/' && pathname.startsWith(n.href + '/')) || (n.href === '/satellites' && pathname.startsWith('/satellite/')) || (n.href === '/constellations' && pathname.startsWith('/constellation/')) || (n.href === '/operators' && pathname.startsWith('/operator/')) || (n.href === '/countries' && pathname.startsWith('/country/')) || (n.href === '/launches' && pathname.startsWith('/launch/'));28            return (29              <Link key={n.href} href={n.href} aria-current={active ? 'page' : undefined} className={cn('rounded-md px-3 py-2 text-[13.5px] transition-colors', active ? 'bg-plane-2 text-ink' : 'text-ink-2 hover:bg-plane-2 hover:text-ink')}>30                {n.label}31              </Link>32            );33          })}34        </nav>35        <div className="ml-auto flex items-center gap-2">36          <button37            type="button"38            onClick={openSearch}39            className="hidden h-10 min-w-[260px] items-center gap-2 rounded-md border border-rule bg-plane-2/70 px-3 text-sm text-ink-3 hover:border-rule-strong hover:text-ink-2 md:flex"40            aria-label="Open search"41          >42            <Search className="size-4" aria-hidden />43            <span className="flex-1 text-left">Search satellites, operators, IDs…</span>44            <kbd className="mono rounded border border-rule px-1.5 py-0.5 text-[10px]">⌘K</kbd>45          </button>46          <button type="button" onClick={openSearch} className="flex size-11 items-center justify-center rounded-md text-ink-2 hover:bg-plane-2 hover:text-ink md:hidden" aria-label="Open search">47            <Search className="size-5" aria-hidden />48          </button>49          <button type="button" onClick={() => setMenu((m) => !m)} className="flex size-11 items-center justify-center rounded-md text-ink-2 hover:bg-plane-2 hover:text-ink lg:size-10" aria-label={menu ? 'Close menu' : 'Open menu'} aria-expanded={menu}>50            {menu ? <X className="size-5" aria-hidden /> : <Menu className="size-5" aria-hidden />}51          </button>52        </div>53      </div>54      {menu && (55        <div className="border-t border-rule bg-plane/95 backdrop-blur-md">56          <div className="container-x mx-auto grid max-w-[1600px] gap-6 py-5 md:grid-cols-3">57            <div>58              <p className="eyebrow mb-2">Explore</p>59              <ul className="grid grid-cols-2 gap-1 md:grid-cols-1">60                {primaryNav.map((n) => (61                  <li key={n.href}>62                    <Link href={n.href} className="block rounded-md px-2 py-2.5 text-[15px] text-ink-2 hover:bg-plane-2 hover:text-ink">{n.label}</Link>63                  </li>64                ))}65              </ul>66            </div>67            <div>68              <p className="eyebrow mb-2">More</p>69              <ul className="grid grid-cols-2 gap-1 md:grid-cols-1">70                {moreNav.map((n) => (71                  <li key={n.href}>72                    <Link href={n.href} className="block rounded-md px-2 py-2.5 text-[15px] text-ink-2 hover:bg-plane-2 hover:text-ink">{n.label}</Link>73                  </li>74                ))}75              </ul>76            </div>77            <div className="hidden md:block">78              <p className="eyebrow mb-2">About the index</p>79              <p className="text-sm leading-relaxed text-ink-2">80                SatelliteIndex aggregates public orbital, catalog and operator data into one canonical, source-attributed database. Positions are propagated with SGP4 from public element sets.81              </p>82              <Link href={routes.methodology()} className="mt-3 inline-block text-sm text-accent hover:underline">Read the methodology →</Link>83            </div>84          </div>85        </div>86      )}87    </header>88  );89}90