spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import Link from 'next/link';2import type { ReactNode } from 'react';3import { cn } from '@/lib/cn';4import { routes } from '@/lib/site';56/** Small building blocks shared by the satellite / launch pages (local variants — shared ui/* is read-only). */78export function DL({ children, className }: { children: ReactNode; className?: string }) {9 return <dl className={cn('divide-y divide-[color:var(--rule)] text-sm', className)}>{children}</dl>;10}1112export function Row({ label, value, mono = true, hint }: { label: ReactNode; value: ReactNode; mono?: boolean; hint?: ReactNode }) {13 return (14 <div className="grid grid-cols-[minmax(0,42%)_minmax(0,1fr)] items-baseline gap-3 py-2">15 <dt className="text-ink-3">16 {label}17 {hint && <span className="ml-1 text-2xs text-ink-3/70">{hint}</span>}18 </dt>19 <dd className={cn('min-w-0 break-words text-right text-ink', mono && 'mono tnum')}>{value}</dd>20 </div>21 );22}2324export function Derived({ className }: { className?: string }) {25 return (26 <Link href={routes.methodology()} className={cn('inline-flex items-center rounded border border-dashed border-rule-strong px-1.5 py-px text-[10px] uppercase tracking-wider text-ink-3 hover:text-accent', className)} title="Derived by SatelliteIndex — see methodology">27 derived28 </Link>29 );30}3132export function Note({ children, className }: { children: ReactNode; className?: string }) {33 return <p className={cn('rounded-md border border-dashed border-rule px-3 py-2.5 text-xs leading-relaxed text-ink-3', className)}>{children}</p>;34}3536export function Chip({ href, children, active = false, count, className }: { href: string; children: ReactNode; active?: boolean; count?: ReactNode; className?: string }) {37 return (38 <Link39 href={href}40 className={cn(41 'inline-flex min-h-9 items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs transition-colors',42 active ? 'border-accent/40 bg-accent-soft text-accent' : 'border-rule bg-plane text-ink-2 hover:border-rule-strong hover:text-ink',43 className,44 )}45 >46 <span className="truncate">{children}</span>47 {count !== undefined && <span className="tnum text-[11px] text-ink-3">{count}</span>}48 </Link>49 );50}5152export function Head({ eyebrow, title, action, children }: { eyebrow?: string; title: ReactNode; action?: { href: string; label: string }; children?: ReactNode }) {53 return (54 <div className="mb-4 flex items-end justify-between gap-4 border-b border-rule pb-3">55 <div>56 {eyebrow && <p className="eyebrow">{eyebrow}</p>}57 <h2 className="mt-0.5 text-lg font-semibold tracking-tight md:text-xl">{title}</h2>58 {children}59 </div>60 {action && (61 <Link href={action.href} className="shrink-0 py-1 text-sm text-accent hover:underline">62 {action.label} →63 </Link>64 )}65 </div>66 );67}6869/** Wraps a page section; `id` allows deep links. */70export function Block({ id, children, className }: { id: string; children: ReactNode; className?: string }) {71 return (72 <section id={id} className={cn('scroll-mt-20 py-6 md:py-8', className)}>73 {children}74 </section>75 );76}7778export function Empty({ children }: { children: ReactNode }) {79 return <p className="text-sm text-ink-3">{children}</p>;80}81