spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import Link from 'next/link';2import { Section } from '@/components/ui/section';3import { Unavailable } from '@/components/ui/unavailable';4import { fmtInt, num, titleCase } from '@/lib/format';5import { ORBIT_CLASS_COLORS, routes } from '@/lib/site';6import type { HomePayload } from '@/lib/types';78interface RankRow {9 key: string;10 href: string;11 label: string;12 sub?: string | null;13 value: number;14 color?: string;15}1617/** Ranked list with proportional bars where every label is a link (the shared HBars is not linkable). */18export function RankedBars({ rows, valueLabel }: { rows: RankRow[]; valueLabel: string }) {19 if (!rows.length) return <Unavailable what="Ranking" />;20 const max = Math.max(1, ...rows.map((r) => r.value));21 return (22 <ol className="divide-y divide-rule border-t border-rule">23 {rows.map((r, i) => (24 <li key={r.key}>25 <Link href={r.href} className="group grid min-h-[48px] grid-cols-[1.6rem_minmax(0,1fr)_auto] items-center gap-x-3 py-2">26 <span className="mono text-2xs text-ink-3">{String(i + 1).padStart(2, '0')}</span>27 <span className="min-w-0">28 <span className="flex items-baseline gap-2">29 <span className="truncate text-sm text-ink group-hover:text-accent">{r.label}</span>30 {r.sub && <span className="hidden truncate text-2xs text-ink-3 sm:inline">{r.sub}</span>}31 </span>32 <span className="mt-1 block h-[4px] w-full overflow-hidden rounded-full bg-plane-2">33 <span className="block h-full rounded-full" style={{ width: `${Math.max(1.5, (r.value / max) * 100)}%`, background: r.color ?? 'var(--series-1)' }} />34 </span>35 </span>36 <span className="tnum text-right text-sm text-ink">37 {fmtInt(r.value)} <span className="sr-only">{valueLabel}</span>38 </span>39 </Link>40 </li>41 ))}42 </ol>43 );44}4546export function TopConstellations({ items }: { items: HomePayload['top_constellations'] }) {47 const rows: RankRow[] = items.map((c) => ({48 key: c.id,49 href: routes.constellation(c.slug),50 label: c.name,51 sub: [c.service_type ? titleCase(c.service_type) : null, c.orbit_class].filter(Boolean).join(' · '),52 value: num(c.active) ?? 0,53 color: ORBIT_CLASS_COLORS[c.orbit_class ?? 'OTHER'] ?? 'var(--series-1)',54 }));55 return (56 <Section eyebrow="Constellations" title="Largest constellations" action={{ href: routes.constellations(), label: 'All constellations' }} className="py-0">57 <p className="mb-3 text-xs text-ink-3">Active satellites · membership is derived (see <Link href={routes.methodology()} className="link">methodology</Link>)</p>58 <RankedBars rows={rows} valueLabel="active satellites" />59 </Section>60 );61}6263export function TopCountries({ items }: { items: HomePayload['top_countries'] }) {64 const rows: RankRow[] = items.slice(0, 10).map((c) => ({65 key: c.code,66 href: routes.country(c.slug),67 label: c.name,68 sub: `${fmtInt(c.objects_on_orbit)} objects on orbit`,69 value: num(c.active_payloads) ?? 0,70 color: 'var(--series-6)',71 }));72 return (73 <Section eyebrow="Countries" title="Active payloads by country" action={{ href: routes.countries(), label: 'All countries' }} className="py-0">74 <RankedBars rows={rows} valueLabel="active payloads" />75 </Section>76 );77}7879export function TopOperators({ items }: { items: HomePayload['top_operators'] }) {80 const rows: RankRow[] = items.slice(0, 10).map((o) => ({81 key: o.id,82 href: routes.operator(o.slug),83 label: o.name,84 sub: [o.country_code, num(o.payloads_last_365d) ? `${fmtInt(o.payloads_last_365d)} launched in 365 d` : null].filter(Boolean).join(' · '),85 value: num(o.active_payloads) ?? 0,86 color: 'var(--series-2)',87 }));88 return (89 <Section eyebrow="Operators" title="Largest operators" action={{ href: routes.operators(), label: 'All operators' }} className="py-0">90 <RankedBars rows={rows} valueLabel="active payloads" />91 </Section>92 );93}94