spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1import { ArrowRight, Code2 } from 'lucide-react';2import Link from 'next/link';3import { Suspense } from 'react';4import { Sparkline } from '@/components/charts/sparkline';5import { WorldMap } from '@/components/charts/world-map';6import { CompanyMiniList } from '@/components/company/company-table';7import { EventList } from '@/components/events/event-row';8import { LiveCounters } from '@/components/live/counters';9import { LiveFeed } from '@/components/live/live-feed';10import { Chip, CountryChip } from '@/components/ui/badges';11import { Container, Empty, Note, Section, Stat, StatGrid, Unavailable } from '@/components/ui/section';12import { api, liveItems, safe } from '@/lib/api';13import { countryName } from '@/lib/countries';14import { fmtBytes, fmtDate, fmtDays, fmtInt, fmtPctSigned, fmtScore } from '@/lib/format';15import { ALT_TAGLINE, DESCRIPTION, PUBLIC_API_BASE, routes, TAGLINE } from '@/lib/site';16import type { Event, Pulse } from '@/lib/types';1718export const revalidate = 60;1920export default async function HomePage() {21 const [pulse, live, index, signals] = await Promise.all([safe(api.pulse()), safe(api.live({ limit: 40 })), safe(api.index()), safe(api.signals({ limit: 8 }))]);22 const initialLive: Event[] = liveItems(live).length ? liveItems(live) : (pulse?.live ?? []);23 const stats = pulse?.stats ?? (await safe(api.stats()));24 const p: Partial<Pulse> = pulse ?? {};2526 return (27 <>28 {/* Hero */}29 <div className="relative overflow-hidden border-b border-rule">30 <div className="grid-bg pointer-events-none absolute inset-0" aria-hidden />31 <Container className="relative pb-8 pt-10 md:pb-12 md:pt-16">32 <p className="eyebrow flex items-center gap-2">33 <span className="dot pulse" aria-hidden /> Continuous corporate observation network34 </p>35 <h1 className="display mt-3 max-w-4xl text-[34px] md:text-[60px]">{TAGLINE}</h1>36 <p className="mt-4 max-w-2xl text-[16px] leading-relaxed text-ink-2 md:text-[18px]">{DESCRIPTION}</p>37 <p className="mt-2 max-w-2xl text-sm text-ink-3">{ALT_TAGLINE}</p>38 <div className="mt-6 flex flex-wrap gap-2">39 <Link href={routes.live()} className="btn btn-primary">40 Open the live feed <ArrowRight className="size-4" aria-hidden />41 </Link>42 <Link href={routes.companies()} className="btn">43 Browse companies44 </Link>45 <Link href={routes.api()} className="btn">46 <Code2 className="size-4" aria-hidden /> API & data47 </Link>48 </div>49 <LiveCounters stats={stats} className="mt-8 md:mt-10" />50 </Container>51 </div>5253 <Container>54 {/* Live feed + movers */}55 <div className="grid gap-8 lg:grid-cols-12">56 <Section eyebrow="Live activity feed" title="What changed in the last minutes" action={{ href: routes.live(), label: 'Full feed' }} className="lg:col-span-7" hairline={false}>57 {live || pulse ? (58 <Suspense fallback={<EventList events={initialLive.slice(0, 18)} variant="feed" />}>59 <LiveFeed initial={initialLive} limit={18} compact showControls={false} />60 </Suspense>61 ) : (62 <Unavailable what="The live feed" />63 )}64 </Section>65 <Section eyebrow="Companies moving fastest" title="Highest Activity Score (7 days)" action={{ href: routes.rankings('most_active', '7d'), label: 'Rankings' }} className="lg:col-span-5" hairline={false}>66 {p.movers ? <CompanyMiniList items={p.movers} metric="activity_score" label="Activity" sparkline /> : <Unavailable what="Movers" compact />}67 <Note className="mt-3">Activity Score = decayed weight of meaningful changes and structured events over 30 days, normalised per active sensor (0–100, see methodology).</Note>68 </Section>69 </div>7071 {/* Map */}72 <Section eyebrow="Global activity map" title="Where monitored companies are changing" lede="Bubbles are sized by structured events detected in the last 30 days, clustered by headquarters city. Hover for detail, click to open the country atlas." action={{ href: routes.countries(), label: 'Country atlas' }}>73 {p.map && p.map.length ? (74 <div className="grid gap-6 lg:grid-cols-[minmax(0,2.2fr)_minmax(0,1fr)]">75 <WorldMap buckets={p.map} />76 <div>77 <p className="eyebrow mb-1">Most active countries (30 d)</p>78 <ol className="divide-y divide-rule border-y border-rule text-sm">79 {(p.countries ?? []).slice(0, 8).map((c) => (80 <li key={c.code}>81 <Link href={routes.country(c.code)} className="flex items-center gap-2 py-1.5 hover:text-accent">82 <CountryChip code={c.code} link={false} />83 <span className="min-w-0 flex-1 truncate">{c.name}</span>84 <span className="tnum text-xs text-ink-3">{fmtInt(c.companies)} co.</span>85 <span className="tnum w-12 text-right font-medium">{fmtInt(c.events_30d)}</span>86 </Link>87 </li>88 ))}89 </ol>90 </div>91 </div>92 ) : (93 <Unavailable what="The activity map" />94 )}95 </Section>9697 {/* Hiring / launches / pricing */}98 <div className="grid gap-8 md:grid-cols-3">99 <Section eyebrow="Hiring momentum" title="Open listings, 30-day change" action={{ href: routes.rankings('hiring_growth', '30d'), label: 'All' }} className="md:border-t">100 {p.hiring ? <CompanyMiniList items={p.hiring} metric="hiring_momentum_30d" /> : <Unavailable what="Hiring momentum" compact />}101 </Section>102 <Section eyebrow="Product launches" title="New products detected" action={{ href: routes.events({ event_type: 'PRODUCT' }), label: 'All' }}>103 {p.launches ? <EventList events={p.launches.slice(0, 6)} variant="feed" emptyLabel="No product launches detected in the current window." /> : <Unavailable what="Launches" compact />}104 </Section>105 <Section eyebrow="Pricing changes" title="Public pricing pages that changed" action={{ href: routes.events({ event_type: 'PRICING' }), label: 'All' }}>106 {p.pricing ? <EventList events={p.pricing.slice(0, 6)} variant="feed" emptyLabel="No pricing changes detected in the current window." /> : <Unavailable what="Pricing changes" compact />}107 </Section>108 </div>109110 {/* AI / industries / countries */}111 <div className="grid gap-8 md:grid-cols-3">112 <Section eyebrow="AI adoption" title="Observable public AI signals" action={{ href: routes.rankings('ai_active', '30d'), label: 'All' }}>113 {p.ai ? <CompanyMiniList items={p.ai} metric="ai_adoption" label="score" /> : <Unavailable what="AI adoption" compact />}114 <Note className="mt-2">Scored from public evidence only (AI listings, products, documentation, communications). Never a claim about internal use.</Note>115 </Section>116 <Section eyebrow="Industries" title="Industry atlas" action={{ href: routes.industries(), label: 'All industries' }}>117 {p.industries ? (118 <ol className="divide-y divide-rule border-y border-rule text-sm">119 {p.industries.slice(0, 10).map((i) => (120 <li key={i.slug}>121 <Link href={routes.industry(i.slug)} className="flex items-center gap-2 py-1.5 hover:text-accent">122 <span className="min-w-0 flex-1 truncate">{i.name}</span>123 <span className="tnum text-xs text-ink-3">{fmtInt(i.companies)} co.</span>124 <span className="tnum w-14 text-right text-xs">{fmtScore(i.activity_score)}</span>125 <span className="tnum w-12 text-right font-medium">{fmtInt(i.events_30d)}</span>126 </Link>127 </li>128 ))}129 </ol>130 ) : (131 <Unavailable what="Industries" compact />132 )}133 <p className="mt-1 text-right text-[10px] uppercase tracking-wider text-ink-3">activity · events 30 d</p>134 </Section>135 <Section eyebrow="Countries" title="Country atlas" action={{ href: routes.countries(), label: 'All countries' }}>136 {p.countries ? (137 <ol className="divide-y divide-rule border-y border-rule text-sm">138 {p.countries.slice(0, 10).map((c) => (139 <li key={c.code}>140 <Link href={routes.country(c.code)} className="flex items-center gap-2 py-1.5 hover:text-accent">141 <CountryChip code={c.code} link={false} />142 <span className="min-w-0 flex-1 truncate">{c.name}</span>143 <span className={`tnum w-16 text-right text-xs ${(c.hiring_momentum_30d ?? 0) > 0 ? 'text-positive' : (c.hiring_momentum_30d ?? 0) < 0 ? 'text-danger' : 'text-ink-3'}`}>{fmtPctSigned(c.hiring_momentum_30d, 0)}</span>144 <span className="tnum w-12 text-right font-medium">{fmtInt(c.events_30d)}</span>145 </Link>146 </li>147 ))}148 </ol>149 ) : (150 <Unavailable what="Countries" compact />151 )}152 <p className="mt-1 text-right text-[10px] uppercase tracking-wider text-ink-3">hiring 30 d · events 30 d</p>153 </Section>154 </div>155156 {/* Trending + index */}157 <div className="grid gap-8 lg:grid-cols-12">158 <Section eyebrow="Trending signals" title="Terms and patterns gaining momentum" action={{ href: routes.events(), label: 'Events' }} className="lg:col-span-7">159 <div className="grid gap-6 md:grid-cols-2">160 <div>161 <p className="eyebrow mb-1">Trending terms (7 d)</p>162 {p.trending?.length ? (163 <ul className="divide-y divide-rule border-y border-rule text-sm">164 {p.trending.slice(0, 8).map((t) => (165 <li key={t.term} className="flex items-center gap-2 py-1.5">166 <Link href={routes.search(t.term)} className="min-w-0 flex-1 truncate hover:text-accent">167 {t.term}168 </Link>169 <Sparkline values={t.series} width={56} height={16} />170 <span className="tnum text-xs text-ink-3">{fmtInt(t.companies)} co.</span>171 <span className={`tnum w-14 text-right text-xs font-medium ${(t.momentum ?? 0) > 0 ? 'text-positive' : 'text-ink-3'}`}>{t.momentum === null ? '—' : fmtPctSigned(t.momentum, 0)}</span>172 </li>173 ))}174 </ul>175 ) : (176 <Empty compact />177 )}178 </div>179 <div>180 <p className="eyebrow mb-1">Cross-company signals</p>181 {signals?.items.length ? (182 <ul className="divide-y divide-rule border-y border-rule text-sm">183 {signals.items.slice(0, 6).map((s) => (184 <li key={s.id} className="py-1.5">185 <div className="flex items-center gap-2">186 <Chip tone="accent">signal</Chip>187 <span className="min-w-0 flex-1 truncate text-ink">{s.title}</span>188 <span className="tnum text-xs text-ink-3">{Math.round(s.confidence * 100)} %</span>189 </div>190 {s.scope_key && <p className="text-[11px] text-ink-3">{s.scope} · {s.scope === 'country' ? countryName(s.scope_key) : s.scope_key}</p>}191 </li>192 ))}193 </ul>194 ) : (195 <Empty compact title="No cross-company signals detected yet." />196 )}197 </div>198 </div>199 </Section>200 <Section eyebrow="Global Corporate Activity Index" title="Baseline 100 = normalised historical activity" action={{ href: routes.methodology(), label: 'Methodology' }} className="lg:col-span-5">201 {p.activity_index ? (202 <div>203 <div className="flex items-end gap-4">204 <p className="tnum text-[44px] font-semibold leading-none tracking-tight">{p.activity_index.value === null ? '—' : p.activity_index.value.toFixed(1)}</p>205 <div className="pb-1 text-xs text-ink-3">206 <p className={`tnum font-medium ${(p.activity_index.delta_7d ?? 0) > 0 ? 'text-positive' : (p.activity_index.delta_7d ?? 0) < 0 ? 'text-danger' : ''}`}>{fmtPctSigned(p.activity_index.delta_7d)} 7 d</p>207 {index && <p className={`tnum ${(index.delta_30d ?? 0) > 0 ? 'text-positive' : (index.delta_30d ?? 0) < 0 ? 'text-danger' : ''}`}>{fmtPctSigned(index.delta_30d)} 30 d</p>}208 </div>209 </div>210 <Sparkline values={(index?.series ?? p.activity_index.series).slice(-90).map((x) => x.value)} width={420} height={72} tone="accent" baseline={100} className="mt-3 h-auto w-full" />211 <p className="mt-1 text-[11px] text-ink-3">Last 90 days · coverage-normalised · {index?.formula_version ?? 'v1'}</p>212 </div>213 ) : (214 <Unavailable what="The index" compact />215 )}216 </Section>217 </div>218219 {/* Platform statistics + API CTA */}220 <Section eyebrow="Platform statistics" title="The dataset gets harder to reproduce every day">221 {stats ? (222 <StatGrid cols={6}>223 <Stat label="Dataset age" value={fmtDays(stats.dataset_age_days)} hint={stats.dataset_started_at ? `since ${fmtDate(stats.dataset_started_at)}` : undefined} size="sm" />224 <Stat label="Oldest continuous history" value={fmtDays(stats.oldest_history_days)} size="sm" />225 <Stat label="Observations today" value={fmtInt(stats.observations_today)} size="sm" />226 <Stat label="Changes today" value={fmtInt(stats.changes_today)} hint={`${fmtInt(stats.events_today)} events`} size="sm" />227 <Stat label="Countries · industries" value={`${fmtInt(stats.countries)} · ${fmtInt(stats.industries)}`} size="sm" />228 <Stat label="Archive" value={fmtBytes(stats.archive?.bytes)} hint={`${fmtInt(stats.archive?.objects)} content objects`} size="sm" />229 </StatGrid>230 ) : (231 <Unavailable what="Platform statistics" />232 )}233 <div className="mt-6 grid gap-4 border border-rule p-4 md:grid-cols-[minmax(0,1fr)_auto] md:items-center md:p-6">234 <div>235 <p className="eyebrow">API & data</p>236 <p className="mt-1 text-lg font-semibold tracking-tight">Every event, metric and history version is available through a public JSON API.</p>237 <p className="mt-1 text-sm text-ink-2">238 Companies, events with filters, timelines, rankings, industries, countries, search, streaming (SSE) and exports (JSON, NDJSON, CSV). <span className="mono text-xs text-ink-3">{PUBLIC_API_BASE}</span>239 </p>240 </div>241 <div className="flex flex-wrap gap-2">242 <Link href={routes.api()} className="btn btn-primary">243 Read the API docs244 </Link>245 <Link href={routes.methodology()} className="btn">246 Methodology247 </Link>248 </div>249 </div>250 </Section>251 </Container>252 </>253 );254}255