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.0 KB · 99 lines tsx
Raw Blame History
1import Link from 'next/link';2import { cn } from '@/lib/cn';3import { EVENT_TYPE_LABELS, routes } from '@/lib/site';4import type { EventEntity } from '@/lib/types';56/** Event type → colour token. Launch = accent, decay/reentry = warn, orbit change = accent-2, decommission = danger, activation = active. */7export const EVENT_TYPE_COLORS: Record<string, string> = {8  SATELLITE_LAUNCH: 'var(--accent)',9  DECAY: 'var(--warn)',10  REENTRY: 'var(--warn)',11  ORBIT_CHANGE: 'var(--accent-2)',12  SATELLITE_DECOMMISSION: 'var(--danger)',13  SATELLITE_ACTIVATION: 'var(--active)',14  CONSTELLATION_EXPANSION: 'var(--accent)',15  REGULATORY_APPROVAL: 'var(--series-4)',16  NEW_LICENSE: 'var(--series-4)',17  COMPANY_ANNOUNCEMENT: 'var(--inactive)',18};1920export function eventColor(type: string): string {21  return EVENT_TYPE_COLORS[type] ?? 'var(--inactive)';22}23export function eventLabel(type: string): string {24  return EVENT_TYPE_LABELS[type] ?? type.replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase());25}2627export function EventTypeBadge({ type, className, size = 'sm' }: { type: string; className?: string; size?: 'sm' | 'md' }) {28  const color = eventColor(type);29  return (30    <span className={cn('inline-flex items-center gap-1.5 whitespace-nowrap rounded border font-medium', size === 'sm' ? 'px-1.5 py-0.5 text-[11px]' : 'px-2.5 py-1 text-xs', className)} style={{ color, borderColor: `color-mix(in oklab, ${color} 35%, transparent)`, background: `color-mix(in oklab, ${color} 10%, transparent)` }}>31      <span className="dot" aria-hidden />32      {eventLabel(type)}33    </span>34  );35}3637/** Confidence as a mono percentage; tooltip explains it is derived. */38export function Confidence({ value, className }: { value: number | null | undefined; className?: string }) {39  if (value === null || value === undefined || !Number.isFinite(value)) return <span className={cn('mono text-xs text-ink-3', className)}>—</span>;40  const pct = Math.round(value * 100);41  const color = pct >= 90 ? 'var(--active)' : pct >= 60 ? 'var(--warn)' : 'var(--danger)';42  return (43    <span className={cn('mono text-xs', className)} title="Derived confidence: how sure SatelliteIndex is about this event given its source and dedupe rules" style={{ color }}>44      {pct}%45    </span>46  );47}4849export function entityHref(e: EventEntity): string | null {50  if (!e.slug) return null;51  switch (e.type) {52    case 'satellite':53      return routes.satellite(e.slug);54    case 'launch':55      return routes.launch(e.slug);56    case 'constellation':57      return routes.constellation(e.slug);58    case 'operator':59      return routes.operator(e.slug);60    case 'country':61      return routes.country(e.slug);62    case 'launch_site':63      return routes.launchSite(e.slug);64    default:65      return null;66  }67}6869export function entityName(e: EventEntity): string {70  if (e.type === 'launch') return `Launch ${e.slug ?? e.id}`;71  return e.name ?? e.slug ?? e.id;72}7374/** Inline list of entity links: satellite → /satellite/<slug>, launch → /launch/<cospar>. */75export function EntityLinks({ entities, className, max = 6 }: { entities: EventEntity[] | null | undefined; className?: string; max?: number }) {76  if (!entities?.length) return null;77  const shown = entities.slice(0, max);78  return (79    <ul className={cn('flex flex-wrap items-center gap-x-2 gap-y-1 text-xs', className)}>80      {shown.map((e) => {81        const href = entityHref(e);82        const label = entityName(e);83        return (84          <li key={`${e.type}-${e.id}`} className="inline-flex items-center gap-1">85            <span className="mono text-[10px] uppercase tracking-wider text-ink-3">{e.type.replace('_', ' ')}</span>86            {href ? (87              <Link href={href} className="link">{label}</Link>88            ) : (89              <span className="text-ink-2">{label}</span>90            )}91            {e.norad_id ? <span className="mono text-ink-3">#{e.norad_id}</span> : null}92          </li>93        );94      })}95      {entities.length > max && <li className="text-ink-3">+{entities.length - max} more</li>}96    </ul>97  );98}99