SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
7.7 KB · 121 lines tsx
Raw Blame History
1import Link from "next/link";2import { ChangesPanel } from "@/components/market/changes-panel";3import { HealthPanel } from "@/components/market/health-panel";4import { InstrumentTable } from "@/components/market/instrument-table";5import { LiveEvents } from "@/components/market/live-events";6import { PulseRow } from "@/components/market/pulse-grid";7import { RedundancyPanel } from "@/components/market/redundancy-panel";8import { LiveTape } from "@/components/market/tape";9import { TelemetryStrip } from "@/components/market/telemetry-strip";10import { WorldMap } from "@/components/market/world-map";11import { SearchButton } from "@/components/layout/search-dialog";12import { Section } from "@/components/ui/section";13import { api, apiOptional } from "@/lib/api";14import type { Changes, DataHealth, MarketEvent, MarketsOverview, Stats } from "@/lib/types";1516export const dynamic = "force-dynamic";1718export default async function HomePage() {19  const [stats, markets, events, health, changes] = await Promise.all([20    api<Stats>("/v1/stats"),21    api<MarketsOverview>("/v1/markets"),22    apiOptional<MarketEvent[]>("/v1/events?limit=30"),23    apiOptional<DataHealth>("/v1/data-health"),24    apiOptional<Changes>("/v1/changes?window=1h"),25  ]);26  const movers = [...markets.gainers.slice(0, 5), ...markets.losers.slice(0, 5)];27  return (28    <div className="mx-auto max-w-[1440px] px-3 sm:px-5">29      {/* Hero */}30      <section className="pb-6 pt-8 sm:pt-12">31        <p className="text-[11px] font-medium uppercase tracking-[0.14em] text-ink-3">Market Atlas</p>32        <h1 className="mt-2 max-w-3xl text-3xl font-semibold leading-[1.05] tracking-tight sm:text-5xl">The live map of global markets.</h1>33        <p className="mt-3 max-w-2xl text-[15px] text-ink-2 sm:text-base">34          Thousands of instruments. Independent sources compared in real time. One continuously updating, provenance-backed market graph — every quote carries its freshness, its sources and its confidence.35        </p>36        <div className="mt-5 flex flex-wrap items-center gap-2">37          <SearchButton className="h-12 !text-[15px] sm:min-w-[360px]" />38          <Link href="/live" className="inline-flex h-12 items-center gap-2 rounded-md bg-ink px-4 text-sm font-medium text-canvas hover:opacity-90">39            <span className="live-dot inline-block h-2 w-2 rounded-full bg-positive" /> Open the live feed40          </Link>41          <Link href="/methodology" className="inline-flex h-12 items-center px-3 text-sm text-ink-2 hover:text-ink">42            How prices are formed →43          </Link>44        </div>45        <TelemetryStrip initial={stats} className="mt-7" />46      </section>4748      {/* Pulse */}49      <Section title="Global market pulse" hint="canonical quotes · live where sources allow, delayed or end-of-day otherwise" href="/markets">50        <PulseRow title="Indices" href="/indices" items={markets.indices} />51        <PulseRow title="Crypto" href="/crypto" items={markets.crypto} />52        <PulseRow title="Forex" href="/forex" items={markets.forex} />53        <PulseRow title="Rates & yields" href="/rates" items={markets.rates} />54        <PulseRow title="Commodities & futures" href="/commodities" items={markets.commodities} />55        <PulseRow title="Equities" href="/stocks" items={markets.equities} className="border-b-0" />56      </Section>5758      {/* Source redundancy — the mesh is the product */}59      {stats.coverage && (60        <div className="mt-8 grid grid-cols-1 [&>*]:min-w-0 gap-6 lg:grid-cols-[1fr_1.1fr]">61          <RedundancyPanel coverage={stats.coverage} prominent className="!mt-0" />62          <div className="flex flex-col justify-center rounded-md border border-rule bg-surface p-4 text-sm text-ink-2">63            <h3 className="text-[11px] font-medium uppercase tracking-wide text-ink-3">One instrument → many observers → one canonical state</h3>64            <p className="mt-2">Market Atlas does not trust a quote. Each instrument is observed through independent paths — exchange feeds, official publishers, public quote pages — and only comparable observations are compared: a live trade is never measured against an official fixing or last week&apos;s close.</p>65            <p className="mt-2">Real markets <span className="font-medium text-ink">vote</span>; stablecoin markets and crosses <span className="font-medium text-ink">confirm</span>; restricted-rights sources <span className="font-medium text-ink">validate</span> without ever setting or showing a value. Sources that statistically mirror each other are counted as one family.</p>66            <p className="mt-3 text-xs text-ink-3">67              Targets: tier A majors ≥ 3 independent families and 5 observations · tier B ≥ 2 / 3 · tier C 1 / 2 · tier D one official source. <Link href="/coverage" className="text-accent hover:underline">See the expansion queue →</Link>68            </p>69          </div>70        </div>71      )}7273      {/* Tape + events */}74      <div className="mt-8 grid grid-cols-1 [&>*]:min-w-0 gap-6 lg:grid-cols-[1.1fr_1fr]">75        <Section title="Live tape" hint="every canonical price change" className="!mt-0">76          <LiveTape />77        </Section>78        <Section title="Live events" hint="halts, filings, moves, source incidents" href="/events" className="!mt-0">79          <LiveEvents initial={events ?? []} max={18} dense className="max-h-[470px] overflow-y-auto" />80        </Section>81      </div>8283      {/* Map */}84      <Section title="World market map" hint="venues by session state · hover a marker for local time" href="/exchanges" hrefLabel="All exchanges">85        <WorldMap exchanges={markets.exchanges} />86      </Section>8788      {/* Movers + health + changes */}89      <div className="mt-8 grid grid-cols-1 [&>*]:min-w-0 gap-6 lg:grid-cols-[1.3fr_1fr]">90        <Section title="Largest moves · US equities" hint="Cboe delayed quotes, 15 min" href="/stocks" className="!mt-0">91          <InstrumentTable rows={movers} compact defaultSort="change" emptyText="No equity quotes yet — the US session has not produced observations." />92          {markets.breadth.instruments > 0 && (93            <p className="mt-2 text-xs text-ink-3">94              Breadth: <span className="text-positive">{markets.breadth.advancers} advancing</span> · <span className="text-negative">{markets.breadth.decliners} declining</span> · median {markets.breadth.median_change_percent?.toFixed(2)}% across {markets.breadth.instruments} quoted names.95            </p>96          )}97        </Section>98        <div className="space-y-4">99          {health && <HealthPanel health={health} />}100          {changes && <ChangesPanel changes={changes} />}101        </div>102      </div>103104      <section className="mt-12 grid grid-cols-1 [&>*]:min-w-0 gap-6 border-t border-rule pt-8 text-sm text-ink-2 md:grid-cols-3">105        <div>106          <h3 className="font-semibold text-ink">Multi-source by design</h3>107          <p className="mt-1">Prices are a weighted median of comparable, independent observations — one vote per upstream family, proxies and validators as confirmations. Open “Why this price?” on any instrument to see the full source coverage matrix.</p>108        </div>109        <div>110          <h3 className="font-semibold text-ink">Honest freshness</h3>111          <p className="mt-1">Live, delayed, at-close, end-of-day or stale — every value is labelled with its status and the age of its newest observation. Nothing stale is ever shown as live.</p>112        </div>113        <div>114          <h3 className="font-semibold text-ink">A growing memory</h3>115          <p className="mt-1">Every observation, event and filing is recorded with its provenance. The historical dataset grows every second the atlas runs.</p>116        </div>117      </section>118    </div>119  );120}121