spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import Link from "next/link";2import { cx } from "@/lib/format";3import type { CoverageSummary } from "@/lib/types";45/** Source Redundancy block (server component). Numbers come from the coverage engine, never hard-coded. */6export function RedundancyPanel({ coverage: c, className, prominent }: { coverage: CoverageSummary; className?: string; prominent?: boolean }) {7 const f = c.byFamilies;8 const bars: Array<[string, number, string]> = [9 ["≥ 5 sources", f[">=5"], "bg-positive"],10 ["≥ 3", f[">=3"] - f[">=5"], "bg-accent"],11 ["2", f[">=2"] - f[">=3"], "bg-warning"],12 ["single source", f["1"], "bg-stale"],13 ];14 const pct = (n: number) => (c.quoted ? (n / c.quoted) * 100 : 0);15 const tierA = c.tiers.A;16 return (17 <div className={cx("rounded-md border border-rule bg-surface p-4", className)}>18 <div className="flex items-baseline justify-between gap-3">19 <h3 className="text-[11px] font-medium uppercase tracking-wide text-ink-3">Source redundancy</h3>20 <Link href="/coverage" className="text-xs text-accent hover:underline">21 Source coverage →22 </Link>23 </div>24 <div className={cx("mt-1 flex flex-wrap items-baseline gap-x-2", prominent && "gap-x-3")}>25 <span className={cx("mono font-semibold tnum", prominent ? "text-3xl" : "text-2xl")}>{c.multiSource}</span>26 <span className="text-sm text-ink-2">27 of <span className="mono tnum">{c.quoted}</span> quoted instruments observed through ≥ 2 independent source families28 </span>29 </div>30 <div className="mt-3 flex h-2 overflow-hidden rounded-full bg-surface-3" aria-hidden>31 {bars.map(([label, n, cls]) => (n > 0 ? <span key={label} className={cls} style={{ width: `${pct(n)}%` }} title={`${n} ${label}`} /> : null))}32 </div>33 <div className="mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-[11.5px] sm:grid-cols-4">34 {bars.map(([label, n, cls]) => (35 <span key={label} className="inline-flex items-center gap-1.5 text-ink-2">36 <span className={cx("inline-block h-1.5 w-1.5 rounded-full", cls)} />37 <span className="mono tnum">{n}</span> {label}38 </span>39 ))}40 </div>41 <div className="mt-3 border-t border-rule pt-3">42 <div className="flex items-baseline justify-between text-xs">43 <span className="text-ink-3">Weighted redundancy score</span>44 <span className="mono tnum font-medium">{c.weightedScore.toFixed(1)}%</span>45 </div>46 <div className="mt-1 h-1.5 overflow-hidden rounded-full bg-surface-3">47 <span className="block h-full rounded-full bg-accent" style={{ width: `${Math.min(100, c.weightedScore)}%` }} />48 </div>49 <dl className="mt-2 grid grid-cols-2 gap-x-4 text-xs">50 <Row k="Tier A majors covered" v={`${tierA.covered} / ${tierA.instruments}`} sub={`${tierA.attainment.toFixed(0)}% · target ≥ 3 families`} />51 <Row k="Tier B liquid covered" v={`${c.tiers.B.covered} / ${c.tiers.B.instruments}`} sub={`${c.tiers.B.attainment.toFixed(0)}% · target ≥ 2 families`} />52 <Row k="Proxies confirming" v={`${c.proxiesConfirming}`} sub="stablecoin markets, crosses" />53 <Row k="Validators confirming" v={`${c.validatorsConfirming}`} sub="restricted-rights cross-checks" />54 </dl>55 </div>56 </div>57 );58}5960function Row({ k, v, sub }: { k: string; v: string; sub?: string }) {61 return (62 <div className="border-b border-rule py-1">63 <div className="flex items-baseline justify-between">64 <dt className="text-ink-3">{k}</dt>65 <dd className="mono tnum">{v}</dd>66 </div>67 {sub && <div className="text-[10.5px] text-ink-3">{sub}</div>}68 </div>69 );70}71