spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { StatsComposition } from '@/components/stats/stats-composition';4import { StatsDensity } from '@/components/stats/stats-density';5import { StatsHeadline } from '@/components/stats/stats-headline';6import { StatsHistory } from '@/components/stats/stats-history';7import { ConnectorStrip, Note } from '@/components/stats/shared';8import { Container, PageHeader, Section } from '@/components/ui/section';9import { Unavailable } from '@/components/ui/unavailable';10import { api, safe } from '@/lib/api';11import { fmtAgo, fmtDateTime, fmtInt } from '@/lib/format';12import { routes, SITE_NAME, SITE_URL } from '@/lib/site';1314export const revalidate = 300;1516const TITLE = 'Global statistics — satellites, debris, launches and orbital density';17const DESC = 'Live statistics of everything in Earth orbit: active satellites, objects on orbit, debris, rocket bodies, launches since 1957, constellation shares and orbital density by altitude and inclination. Real catalogue data with transparent sources.';1819export async function generateMetadata(): Promise<Metadata> {20 const url = `${SITE_URL}${routes.stats()}`;21 return {22 title: TITLE,23 description: DESC,24 alternates: { canonical: url },25 openGraph: { title: `${TITLE} | ${SITE_NAME}`, description: DESC, url, type: 'website', siteName: SITE_NAME },26 twitter: { card: 'summary_large_image', title: `${TITLE} | ${SITE_NAME}`, description: DESC },27 };28}2930export default async function StatsPage() {31 const [stats, density] = await Promise.all([safe(api.stats()), safe(api.density())]);32 const snapshot = stats?.data ?? null;3334 return (35 <Container>36 <PageHeader eyebrow="Statistics" title="Everything in Earth orbit, by the numbers" lede="A dashboard of the whole catalogue: how many objects fly, who operates them, how fast the population grows and where it concentrates. Every number is read from the latest SatelliteIndex snapshot — nothing is estimated or hardcoded.">37 {snapshot && (38 <p className="mono mt-4 text-xs text-ink-3">39 Computed {fmtAgo(snapshot.computed_at)} · {fmtDateTime(snapshot.computed_at)} · {fmtInt(snapshot.connectors.length)} connectors40 </p>41 )}42 </PageHeader>4344 {!snapshot ? (45 <div className="py-8">46 <Unavailable what="Statistics snapshot" />47 <Note className="mt-3">The statistics API did not answer. Live catalogue browsing may still work on <Link href={routes.satellites()} className="text-accent hover:underline">/satellites</Link>.</Note>48 </div>49 ) : (50 <>51 <Section eyebrow="Headline" title="Catalogue at a glance" className="pt-0 md:pt-0">52 <StatsHeadline snapshot={snapshot} />53 </Section>5455 <Section eyebrow="Composition" title="What is up there" action={{ href: routes.satellites('on_orbit=true'), label: 'Browse objects' }}>56 <StatsComposition snapshot={snapshot} />57 </Section>5859 <Section eyebrow="History" title="Seven decades of launches and decays" action={{ href: routes.launches(), label: 'All launches' }}>60 <StatsHistory snapshot={snapshot} />61 </Section>6263 <Section eyebrow="Orbital density" title="Where objects concentrate" action={{ href: routes.debris(), label: 'Debris growth' }}>64 <StatsDensity density={density?.data ?? null} fallbackBuckets={snapshot.orbital_buckets} />65 </Section>6667 <Section eyebrow="Rankings" title="Leaders and movers" action={{ href: routes.rankings(), label: 'All rankings' }}>68 <ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">69 {[70 { href: routes.rankings('constellations'), label: 'Largest constellations', hint: 'by active satellites' },71 { href: routes.rankings('operators'), label: 'Largest operators', hint: 'by active payloads' },72 { href: routes.rankings('countries'), label: 'Countries', hint: 'by active payloads' },73 { href: routes.rankings('countries-debris'), label: 'Debris by country', hint: 'objects on orbit' },74 { href: routes.rankings('launches'), label: 'Busiest launch sites', hint: 'launches since 1957' },75 { href: routes.rankings('fastest-growing'), label: 'Fastest-growing constellations', hint: 'launched last 365 d vs fleet' },76 { href: routes.rankings('congested-shells'), label: 'Most populated LEO shells', hint: '50 km bands, object counts' },77 { href: routes.rankings('launch-years'), label: 'Launch years', hint: 'launches and payloads per year' },78 ].map((r) => (79 <li key={r.href}>80 <Link href={r.href} className="flex min-h-[64px] flex-col justify-center border-t border-rule py-3 transition-colors hover:border-accent/60">81 <span className="text-sm font-medium text-ink">{r.label} →</span>82 <span className="text-xs text-ink-3">{r.hint}</span>83 </Link>84 </li>85 ))}86 </ul>87 </Section>8889 <Section eyebrow="Sources" title="Connector freshness" action={{ href: routes.sources(), label: 'All sources' }}>90 <ConnectorStrip connectors={snapshot.connectors} />91 <Note className="mt-4">92 Fresh = last success within 3× the connector interval · aging = within 12× · stale beyond that. Orbit class, mission type, constellation membership, activity score and orbital density are derived metrics documented on{' '}93 <Link href={routes.methodology()} className="text-accent hover:underline">/methodology</Link>.94 </Note>95 </Section>96 </>97 )}98 </Container>99 );100}101