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%
5.7 KB · 101 lines tsx
Raw Blame History
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