diff --git a/.env.example b/.env.example index 4ccec424..58bc57b8 100644 --- a/.env.example +++ b/.env.example @@ -25,3 +25,9 @@ NEXT_PUBLIC_COOKIE_CONSENT=on # GraphQL indexer URL for org.plresearch.* records # Default: https://api.hi.gainforest.app/graphql # INDEXER_URL=http://localhost:4000/graphql + +# Metaculus API token — enables live community-forecast readings on the /impact +# dashboard (e.g. the whole-brain-emulation question). Without it the question +# is still linked, just without a live number. Create a token at +# https://www.metaculus.com/accounts/settings/ +# METACULUS_API_TOKEN= diff --git a/docs/screenshots/README-impact-v2.md b/docs/screenshots/README-impact-v2.md new file mode 100644 index 00000000..f0abb745 --- /dev/null +++ b/docs/screenshots/README-impact-v2.md @@ -0,0 +1,23 @@ +# Impact dashboard — v1 vs v2 (side-by-side) + +Screenshots for the stacked PR that adds the acceleration-centered `/impact-v2` +alongside the unchanged inflection-point `/impact` (PR #29). Captured at ~1280×600. + +## v2 — `/impact-v2` (acceleration reframe) + +| File | What it shows | +|---|---| +| `v2-light-cards.png` / `v2-dark-cards.png` | Field card leads with the **velocity basket**; hand card shows tool → condition → observed movement | +| `v2-light-sensors.png` / `v2-dark-sensors.png` | "Our sensors: inflection points" list with status chips (incl. **RETIRED**, illustrative-draft caption) | +| `v2-light-sensor-modal.png` | **Retired** sensor modal — status + logged post-mortem | +| `v2-dark-sensor-modal.png` | **Missed · field moved** sensor modal — distinct amber post-mortem | +| `v2-dark-toolkit.png` | Methodology: the seven-tool toolkit with `releases:` tags + "needs co-funders" markers | +| `v2-dark-toolkit-modal.png` | Toolkit modal (Culture) — full description + what-changed + co-funder gap | +| `v2-light-howtoread-modal.png` | How-to-read v2 — X→Y contract + two-axis legend + five sensor-status legend | + +## v1 — `/impact` (unchanged from PR #29) + +| File | What it shows | +|---|---| +| `v1-light-cards.png` / `v1-dark-cards.png` | Original inflection-point cards (The field / Our hand / Live signal) | +| `v1-light-modal.png` / `v1-dark-modal.png` | Original stacked grey-card detail modal | diff --git a/docs/screenshots/impact-modal-restructured.png b/docs/screenshots/impact-modal-restructured.png new file mode 100644 index 00000000..6671c3f7 Binary files /dev/null and b/docs/screenshots/impact-modal-restructured.png differ diff --git a/docs/screenshots/impact-signals-grid.png b/docs/screenshots/impact-signals-grid.png new file mode 100644 index 00000000..510e6419 Binary files /dev/null and b/docs/screenshots/impact-signals-grid.png differ diff --git a/docs/screenshots/impact-signals-livebar.png b/docs/screenshots/impact-signals-livebar.png new file mode 100644 index 00000000..c938d6af Binary files /dev/null and b/docs/screenshots/impact-signals-livebar.png differ diff --git a/docs/screenshots/impact-signals-modal.png b/docs/screenshots/impact-signals-modal.png new file mode 100644 index 00000000..1f841009 Binary files /dev/null and b/docs/screenshots/impact-signals-modal.png differ diff --git a/docs/screenshots/impact-signals-platforms.png b/docs/screenshots/impact-signals-platforms.png new file mode 100644 index 00000000..0b9e5eb3 Binary files /dev/null and b/docs/screenshots/impact-signals-platforms.png differ diff --git a/docs/screenshots/v1-dark-cards.png b/docs/screenshots/v1-dark-cards.png new file mode 100644 index 00000000..655b5d53 Binary files /dev/null and b/docs/screenshots/v1-dark-cards.png differ diff --git a/docs/screenshots/v1-dark-modal.png b/docs/screenshots/v1-dark-modal.png new file mode 100644 index 00000000..7e1d3300 Binary files /dev/null and b/docs/screenshots/v1-dark-modal.png differ diff --git a/docs/screenshots/v1-light-cards.png b/docs/screenshots/v1-light-cards.png new file mode 100644 index 00000000..45d6fbaf Binary files /dev/null and b/docs/screenshots/v1-light-cards.png differ diff --git a/docs/screenshots/v1-light-modal.png b/docs/screenshots/v1-light-modal.png new file mode 100644 index 00000000..c6f95f5d Binary files /dev/null and b/docs/screenshots/v1-light-modal.png differ diff --git a/docs/screenshots/v2-dark-cards.png b/docs/screenshots/v2-dark-cards.png new file mode 100644 index 00000000..22e9217c Binary files /dev/null and b/docs/screenshots/v2-dark-cards.png differ diff --git a/docs/screenshots/v2-dark-sensor-modal.png b/docs/screenshots/v2-dark-sensor-modal.png new file mode 100644 index 00000000..3b0ec87f Binary files /dev/null and b/docs/screenshots/v2-dark-sensor-modal.png differ diff --git a/docs/screenshots/v2-dark-sensors.png b/docs/screenshots/v2-dark-sensors.png new file mode 100644 index 00000000..5c3d8b46 Binary files /dev/null and b/docs/screenshots/v2-dark-sensors.png differ diff --git a/docs/screenshots/v2-dark-toolkit-modal.png b/docs/screenshots/v2-dark-toolkit-modal.png new file mode 100644 index 00000000..32af31bf Binary files /dev/null and b/docs/screenshots/v2-dark-toolkit-modal.png differ diff --git a/docs/screenshots/v2-dark-toolkit.png b/docs/screenshots/v2-dark-toolkit.png new file mode 100644 index 00000000..d8856d85 Binary files /dev/null and b/docs/screenshots/v2-dark-toolkit.png differ diff --git a/docs/screenshots/v2-light-cards.png b/docs/screenshots/v2-light-cards.png new file mode 100644 index 00000000..d5022c1e Binary files /dev/null and b/docs/screenshots/v2-light-cards.png differ diff --git a/docs/screenshots/v2-light-howtoread-modal.png b/docs/screenshots/v2-light-howtoread-modal.png new file mode 100644 index 00000000..67f27062 Binary files /dev/null and b/docs/screenshots/v2-light-howtoread-modal.png differ diff --git a/docs/screenshots/v2-light-sensor-modal.png b/docs/screenshots/v2-light-sensor-modal.png new file mode 100644 index 00000000..53e129f5 Binary files /dev/null and b/docs/screenshots/v2-light-sensor-modal.png differ diff --git a/docs/screenshots/v2-light-sensors.png b/docs/screenshots/v2-light-sensors.png new file mode 100644 index 00000000..3e745ffb Binary files /dev/null and b/docs/screenshots/v2-light-sensors.png differ diff --git a/src/app/globals.css b/src/app/globals.css index 1548fae9..99c69c6f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -95,6 +95,15 @@ body { background-color: var(--color-white); } +/* Impact dashboard two-axis accents — theme-aware so the “field” ink and the + progress track stay legible in dark mode (ImpactDashboard / MeasuringQuestions). */ +:root { + --impact-field: #131316; /* the field axis (neutral ink) */ + --impact-field-ink: #ffffff; /* text on a field-accent fill */ + --impact-field-track: #e5e7eb; /* empty progress segment */ + --impact-hand: #1982f4; /* our-hand axis (blue) */ +} + /* ===== Dark mode ===== Rather than annotating every component, we remap the semantic color tokens. Because Tailwind v4 utilities compile to var(--color-*), @@ -120,6 +129,13 @@ html.dark { --color-gray: #9A9EAB; --color-blue: #3B96F6; /* slightly brighter accents on dark */ + + /* Two-axis accents: light ink reads on dark surfaces; the field fill flips + to light so white badge text is swapped for dark ink. */ + --impact-field: #e9e9ee; + --impact-field-ink: #15171c; + --impact-field-track: #3a3f4b; + --impact-hand: #3b96f6; } /* Keep the footer's intentional dark design in both themes by diff --git a/src/app/impact-v2/page.tsx b/src/app/impact-v2/page.tsx new file mode 100644 index 00000000..d4ead56f --- /dev/null +++ b/src/app/impact-v2/page.tsx @@ -0,0 +1,87 @@ +import type { Metadata } from 'next' +import Breadcrumb from '@/components/Breadcrumb' +import ImpactDashboardV2 from '@/components/ImpactDashboardV2' +import MeasuringQuestionsV2 from '@/components/MeasuringQuestionsV2' +import { resolveAllSignals } from '@/lib/market-signals' +import { FIELD_COLOR, HAND_COLOR } from '@/lib/field-velocity' + +// v2 of the impact dashboard — acceleration-centered. Reuses the same live +// crowd-forecast integration as v1 (resolveAllSignals) on the field axis. +export const revalidate = 60 + +export const metadata: Metadata = { + title: 'Impact (v2 — acceleration)', + description: + 'A draft reframe of the impact dashboard: the target is field acceleration itself, measured by a velocity basket per focus area. Inflection points are demoted to pre-registered sensors.', +} + +export default async function ImpactV2Page() { + const marketSignals = await resolveAllSignals() + return ( +
+ {/* Hero */} +
+ +
+ + Draft for review · acceleration reframe + +

+ Is the field accelerating? +

+

+ The target is field acceleration itself — + measured by a basket of velocity signals per focus area. Our{' '} + inflection points are + demoted to pre-registered sensors: dated, + falsifiable markers we expect acceleration to produce. Being wrong about a marker is a logged + learning event, not a failure — the markers stay on the board. +

+ + How we read this + + + + + {/* Low-key cross-link back to the original methodology (v2 only). */} +

+ Original methodology (inflection-point framing):{' '} + + /impact + +

+
+
+ + {/* Dashboard — grey full-bleed section to set it apart from the rest of the site */} +
+
+

+ Field acceleration, focus area by focus area +

+

+ Select a focus area. The field card leads with the velocity basket — is the field moving + faster — then lists our inflection-point sensors. The hand card shows the conditions we are + pushing on, and whether they moved. +

+ +
+
+ + {/* Methodology */} +
+

Our methodology

+

+ Three questions we hold apart — the field ( + Y), our hand ( + X), and the attribution + that connects them. +

+ +
+
+ ) +} diff --git a/src/app/impact/page.tsx b/src/app/impact/page.tsx new file mode 100644 index 00000000..ec39b16d --- /dev/null +++ b/src/app/impact/page.tsx @@ -0,0 +1,124 @@ +import type { Metadata } from 'next' +import Breadcrumb from '@/components/Breadcrumb' +import ImpactDashboard, { type LiveMetric, type LiveOutputs } from '@/components/ImpactDashboard' +import MeasuringQuestions from '@/components/MeasuringQuestions' +import { fetchSimocracyStats } from '@/lib/simocracy' +import { fetchGainforestStats } from '@/lib/gainforest' +import { fetchGlowStats } from '@/lib/glow' +import { resolveAllSignals } from '@/lib/market-signals' +import { FIELD_COLOR, HAND_COLOR } from '@/lib/inflection-points' + +// Pull live output metrics for the Economies & Governance inflection points from +// the same sources as the FA2 live dashboard. These are Q3 OUTPUTS (the work +// PL-backed teams have produced) — never Q2 progress toward a threshold. +export const revalidate = 60 + +async function fetchLiveOutputs(): Promise { + const compact = (n: number) => + new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1 }).format(n) + const out: LiveOutputs = {} + + const [sim, gf, glow] = await Promise.allSettled([ + fetchSimocracyStats(), + fetchGainforestStats(), + fetchGlowStats(), + ]) + + // A binding decision at scale — Simocracy (a PL-supported deliberation mechanism). + if (sim.status === 'fulfilled' && !sim.value.degraded) { + const t = sim.value.totals + const metrics: LiveMetric[] = [ + { n: t.uniqueHumans, label: 'participants' }, + { n: t.totalSims, label: 'simulations' }, + { n: t.totalGatherings, label: 'gatherings' }, + ] + .filter((m) => m.n > 0) + .map((m) => ({ value: compact(m.n), label: m.label })) + if (metrics.length) out['A binding decision at scale'] = metrics + } + + // Capital that pays on verified outcomes — GainForest + Glow (PL-backed MRV teams). + const verified: LiveMetric[] = [] + if (gf.status === 'fulfilled' && !gf.value.degraded) { + const g = gf.value + if (g.observations > 0) verified.push({ value: compact(g.observations), label: 'species observations' }) + if (g.certifiedOrgs > 0) verified.push({ value: compact(g.certifiedOrgs), label: 'certified orgs' }) + } + if (glow.status === 'fulfilled' && !glow.value.degraded) { + const gl = glow.value + if (gl.activeFarms > 0) verified.push({ value: compact(gl.activeFarms), label: 'active solar farms' }) + if (gl.carbon > 0) verified.push({ value: compact(gl.carbon), label: 'tCO₂ / wk' }) + } + if (verified.length) out['Capital that pays on verified outcomes'] = verified + + return out +} + +export const metadata: Metadata = { + title: 'Impact', + description: + 'How we measure whether PL R&D’s work matters: the inflection points we have pre-registered across every focus area, and how we will know if they happen.', +} + +export default async function ImpactPage() { + const [liveOutputs, marketSignals] = await Promise.all([ + fetchLiveOutputs(), + resolveAllSignals(), + ]) + return ( +
+ {/* Hero */} +
+ +
+

+ Our impact +

+

+ Across our four focus areas we have named a small set of{' '} + inflection points — specific, + observable, falsifiable shifts we believe would be catalytic, and that have not yet + happened. +

+ + Our methodology + + + + +
+
+ + {/* Inflection points — grey full-bleed section to set it apart from the rest of the site */} +
+
+

+ Inflection points we are tracking +

+

+ Select a focus area. Each card shows the defined threshold (did it happen), the cascade we + expect if it matters, and the PL contribution we would trace. +

+ +
+
+ + {/* Our methodology */} +
+

Our methodology

+

+ For every inflection point we ask three questions — different jobs that should not be + collapsed into a single score. We hold two axes apart:{' '} + our hand (the work + we control) and{' '} + the field (the + change that follows, with or without us). +

+ +
+
+ ) +} diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 8ee97b26..a1aa6d40 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -28,7 +28,12 @@ const FALLBACK_CARDS = { 'card-blog': { title: 'Blog' }, } -export default async function InsightsPage() { +export default async function InsightsPage({ + searchParams, +}: { + searchParams: Promise<{ area?: string }> +}) { + const { area: initialArea } = await searchParams const page = await fetchPage('insights') const hero = getSection(page, 'hero') const heroTitle = hero?.title || FALLBACK_HERO_TITLE @@ -223,7 +228,7 @@ export default async function InsightsPage() { )} - + diff --git a/src/components/ImpactDashboard.tsx b/src/components/ImpactDashboard.tsx new file mode 100644 index 00000000..3c03bff0 --- /dev/null +++ b/src/components/ImpactDashboard.tsx @@ -0,0 +1,699 @@ +'use client' + +import { useEffect, useMemo, useState } from 'react' +import { + ROLE_META, + PL_ROLE_ORDER, + FIELD_COLOR, + FIELD_TRACK, + HAND_COLOR, + LIVE_COLOR, + FIELD_STAGES, + FOCUS_AREAS, + INFLECTION_POINTS, + TEAM_LINKS, + HOW_TO_READ, + FIELD_INK, + stageIndexForStatus, + type FocusAreaKey, + type InflectionPoint, + type PLRole, +} from '@/lib/inflection-points' +import { AreaIcon, type AreaIconType } from '@/components/AreaIcons' +import type { MarketSignal } from '@/lib/market-signals' + +type Filter = FocusAreaKey + +/** Live output metrics for a point, keyed by the point's title. Fetched server-side. */ +export type LiveMetric = { value: string; label: string } +export type LiveOutputs = Record +/** Prediction-market crowd signal per point, keyed by title. Fetched server-side. */ +export type MarketSignals = Record + +const PLATFORM_LABEL: Record<'polymarket' | 'kalshi' | 'metaculus', string> = { + polymarket: 'Polymarket', + kalshi: 'Kalshi', + metaculus: 'Metaculus', +} + +const FA_ICON: Record = { + 'digital-human-rights': 'shield', + 'economies-governance': 'hexagon', + 'ai-robotics': 'neural', + neurotech: 'brain', +} + +export default function ImpactDashboard({ + liveOutputs = {}, + marketSignals = {}, +}: { + liveOutputs?: LiveOutputs + marketSignals?: MarketSignals +}) { + const [filter, setFilter] = useState('digital-human-rights') + const [active, setActive] = useState(null) + const [howToOpen, setHowToOpen] = useState(false) + + const visible = useMemo( + () => INFLECTION_POINTS.filter((p) => p.area === filter), + [filter], + ) + + return ( + <> + {/* “How to read this” — a persistent affordance above the grid (never per-card). */} +
+ +
+
+ {/* Vertical tabs */} +
+ {FOCUS_AREAS.map((fa) => ( + p.area === fa.key).length} + forthcoming={fa.forthcoming} + icon={FA_ICON[fa.key]} + active={filter === fa.key} + onClick={() => setFilter(fa.key)} + /> + ))} +
+ + {/* Content */} +
+ {visible.length > 0 ? ( +
+ {visible.map((p) => ( + setActive(p)} + /> + ))} +
+ ) : ( + + )} +
+ + {active && ( + setActive(null)} + /> + )} +
+ {howToOpen && setHowToOpen(false)} />} + + ) +} + +// Turn any team/venture name present in TEAM_LINKS into a link, wherever it +// appears in contribution copy. Names are matched longest-first so multi-word +// names (e.g. "Funding the Commons") win over any shorter substring. +const TEAM_LINK_PATTERN = new RegExp( + '(' + + Object.keys(TEAM_LINKS) + .sort((a, b) => b.length - a.length) + .map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + .join('|') + + ')', + 'g', +) + +function Linkify({ text }: { text: string }) { + const parts = text.split(TEAM_LINK_PATTERN) + return ( + <> + {parts.map((part, i) => + TEAM_LINKS[part] ? ( + + {part} + + ) : ( + {part} + ), + )} + + ) +} + +function Tab({ + label, + count, + forthcoming = false, + active, + icon, + onClick, +}: { + label: string + count: number + forthcoming?: boolean + active: boolean + icon?: AreaIconType + onClick: () => void +}) { + return ( + + ) +} + +/** Field-progress lifecycle meter — the “field” axis (teal). */ +function FieldMeter({ + status, + compact = false, +}: { + status: InflectionPoint['status'] + compact?: boolean +}) { + const reached = stageIndexForStatus(status) + return ( +
+
+ {FIELD_STAGES.map((_, i) => ( + + ))} +
+ {!compact && ( +
+ {FIELD_STAGES.map((s, i) => ( + + {s} + + ))} +
+ )} +
+ ) +} + +/** One pill per PL role, in canonical order. */ +function RoleChips({ roles, eyebrow = true }: { roles: PLRole[]; eyebrow?: boolean }) { + const ordered = PL_ROLE_ORDER.filter((r) => roles.includes(r)) + return ( + + {eyebrow && ( + PL role + )} + {ordered.map((r) => ( + + + {ROLE_META[r].label} + + + {ROLE_META[r].label} — {ROLE_META[r].description} + + + ))} + + ) +} + +function InflectionCard({ + point, + metrics, + signal, + onOpen, +}: { + point: InflectionPoint + metrics?: LiveMetric[] + signal?: MarketSignal + onOpen: () => void +}) { + const fa = FOCUS_AREAS.find((f) => f.key === point.area)! + const stageLabel = FIELD_STAGES[stageIndexForStatus(point.status)] + // Whether this point has any live signal worth opening the card for. The + // preview only hints that something exists — the detail lives in the modal. + const hasLiveSignal = !!( + point.liveEvidence?.length || + (metrics && metrics.length) || + (signal && signal.match !== 'gap') + ) + + return ( + + ) +} + +/** Compact USD, e.g. $15k, $1.2M. */ +function formatUSD(n: number): string { + if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M` + if (n >= 1_000) return `$${Math.round(n / 1_000)}k` + return `$${Math.round(n)}` +} + +/** Crowd-forecast row for the modal's horizontal Live-signal band (with market link). */ +function CrowdForecast({ signal, divider = false }: { signal: MarketSignal; divider?: boolean }) { + const pct = signal.prob != null ? Math.round(signal.prob * 100) : null + return ( +
+
+ + Crowd forecast + + {signal.platform && ( + + {PLATFORM_LABEL[signal.platform]} + {signal.viaFallback ? ' (fallback)' : ''} + + )} + {signal.volume != null && ( + + {formatUSD(signal.volume)} at stake + + )} + + {signal.readout ?? (pct != null ? `${pct}%` : '—')} + +
+ {signal.url && ( + + {signal.question} + + )} +

+ {signal.note} Independent estimate on the field axis — not PL contribution, not a settled outcome. +

+
+ ) +} + +function InflectionModal({ + point, + metrics, + signal, + onClose, +}: { + point: InflectionPoint + metrics?: LiveMetric[] + signal?: MarketSignal + onClose: () => void +}) { + const fa = FOCUS_AREAS.find((f) => f.key === point.area)! + + // Close on Escape, and lock body scroll while open. + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + const prev = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { + document.removeEventListener('keydown', onKey) + document.body.style.overflow = prev + } + }, [onClose]) + + return ( +
+
e.stopPropagation()} + > + {/* Close */} + + +
+ {/* Header */} +
+ + + + {fa.label} + · + {point.opportunitySpace} +
+ {/* THE FIELD — title, outcome, impact, then progress (grey card) */} +
+
+ The field +
+

+ {point.title} +

+
+
+
Outcome
+

{point.signal}

+
+
+
Impact
+

{point.cascade}

+
+
+
+
+ Progress against Inflection Point +
+ +
+
+ + {/* OUR HAND — grey card: PL intervention pills (definition on hover) + real examples */} +
+
+ Our hand +
+
How PL is making a difference
+ +
+
In practice
+

+

+
+
+ + {/* LIVE SIGNAL — grey card: title, PL-backed live outputs (Q3), crowd forecast, latest insights */} +
+
+ + + + + Live signal +
+ {(point.liveEvidence?.length || (metrics && metrics.length) || (signal && signal.match !== 'gap')) && ( +
+ {point.liveEvidence?.map((ev, i) => { + const external = /^https?:\/\//.test(ev.href) + return ( + 0 ? 'mt-3 border-t border-gray-100 pt-3' : ''}`} + > +
+ {ev.label} + + + +
+ {i === 0 && metrics && metrics.length > 0 && ( +
+ {metrics.map((m) => ( + + {m.value} + {m.label} + + ))} +
+ )} +

{ev.note}

+
+ ) + })} + + {!point.liveEvidence?.length && metrics && metrics.length > 0 && ( +
+ {metrics.map((m) => ( + + {m.value} + {m.label} + + ))} +
+ )} + + {signal && signal.match !== 'gap' && ( + + )} +
+ )} + + {signal && signal.match === 'gap' && ( +
+
+ Crowd forecast +
+

{signal.note}

+
+ )} + + + See the latest {fa.label} insights + + + + +
+
+
+
+ ) +} + +function EmptyState({ filter }: { filter: Filter }) { + const fa = FOCUS_AREAS.find((f) => f.key === filter) + return ( +
+ + Coming Soon + +

+ Inflection points for {fa?.label ?? 'this focus area'} are being defined. +

+

+ Not zero — just not yet. This focus area is still finalizing its plan of attack, and its + inflection points will appear here once they are set. +

+
+ ) +} + +/** “How to read this” — the one-line contract plus the two-axis legend. */ +function HowToReadModal({ onClose }: { onClose: () => void }) { + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + const prev = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { + document.removeEventListener('keydown', onKey) + document.body.style.overflow = prev + } + }, [onClose]) + + return ( +
+
e.stopPropagation()}> + + +
How to read this
+

{HOW_TO_READ}

+ +
+ + The change in the world — did it happen (outcome) and did it matter (impact). Moves with or without us. + + + Our contribution — the PL instruments on the critical path. The axis we control with our partners. + +
+ + + + + + +

+ Live signal — real-world evidence for and against, refreshed from the field. Never a settled outcome. +

+
+
+
+
+ ) +} + +function LegendRow({ + color, + ink, + label, + children, +}: { + color: string + ink: string + label: string + children: React.ReactNode +}) { + return ( +
+ +

+ {label} — {children} +

+
+ ) +} diff --git a/src/components/ImpactDashboardV2.tsx b/src/components/ImpactDashboardV2.tsx new file mode 100644 index 00000000..a74aa956 --- /dev/null +++ b/src/components/ImpactDashboardV2.tsx @@ -0,0 +1,705 @@ +'use client' + +// Impact Dashboard v2 — acceleration-centered. Separate component tree from the +// v1 ImpactDashboard so the two can be compared side by side. Shared primitives +// (colors, market-signals, focus-area metadata) are imported, never forked. + +import { useEffect, useMemo, useState } from 'react' +import { + FOCUS_AREAS, + INFLECTION_POINTS, + FIELD_COLOR, + FIELD_INK, + HAND_COLOR, + LIVE_COLOR, + FIELD_VELOCITY, + VELOCITY_KIND_META, + DIRECTION_META, + CONTRIBUTIONS_V2, + CONDITION_LABEL, + TOOLKIT_V2, + SENSOR_SEMANTICS, + SENSOR_STATUS_META, + SENSOR_STATUS_MICROCOPY, + MISSED_FIELD_MOVED_MICROCOPY, + HOW_TO_READ_V2, + sensorFor, + type FocusAreaKey, + type InflectionPoint, + type VelocitySignal, + type VelocityDirection, + type ContributionV2, + type ToolId, +} from '@/lib/field-velocity' +import { TEAM_LINKS } from '@/lib/inflection-points' +import { AreaIcon, type AreaIconType } from '@/components/AreaIcons' +import type { MarketSignal } from '@/lib/market-signals' + +export type MarketSignals = Record + +const PLATFORM_LABEL: Record<'polymarket' | 'kalshi' | 'metaculus', string> = { + polymarket: 'Polymarket', + kalshi: 'Kalshi', + metaculus: 'Metaculus', +} + +const FA_ICON: Record = { + 'digital-human-rights': 'shield', + 'economies-governance': 'hexagon', + 'ai-robotics': 'neural', + neurotech: 'brain', +} + +const TOOL_TITLE: Record = Object.fromEntries( + TOOLKIT_V2.map((t) => [t.id, t.title]), +) as Record +const TOOL_PROPOSED: Record = Object.fromEntries( + TOOLKIT_V2.map((t) => [t.id, !!t.proposed]), +) as Record + +export default function ImpactDashboardV2({ + marketSignals = {}, +}: { + marketSignals?: MarketSignals +}) { + const [filter, setFilter] = useState('digital-human-rights') + const [active, setActive] = useState(null) + const [howToOpen, setHowToOpen] = useState(false) + + const sensors = useMemo( + () => INFLECTION_POINTS.filter((p) => p.area === filter), + [filter], + ) + const velocity = FIELD_VELOCITY[filter] ?? [] + const contributions = CONTRIBUTIONS_V2[filter] ?? [] + + return ( + <> + {/* How to read this — persistent affordance above the grid. */} +
+ +
+ +
+ {/* Vertical tabs */} +
+ {FOCUS_AREAS.map((fa) => ( + p.area === fa.key).length} + forthcoming={fa.forthcoming} + icon={FA_ICON[fa.key]} + active={filter === fa.key} + onClick={() => setFilter(fa.key)} + /> + ))} +
+ + {/* Content: field (Y) + our hand (X) */} +
+ setActive(p)} + /> + +
+
+ + {active && ( + setActive(null)} + /> + )} + {howToOpen && setHowToOpen(false)} />} + + ) +} + +// ── Focus-area tab (mirrors v1 styling) ─────────────────────────────────────── +function Tab({ + label, + count, + forthcoming = false, + active, + icon, + onClick, +}: { + label: string + count: number + forthcoming?: boolean + active: boolean + icon?: AreaIconType + onClick: () => void +}) { + return ( + + ) +} + +// ── Y — the field card ──────────────────────────────────────────────────────── +function directionColor(dir: VelocityDirection): string { + switch (DIRECTION_META[dir].tone) { + case 'up': + return LIVE_COLOR + case 'down': + return '#d0894b' + case 'flat': + return '#6b7280' + default: + return '#9ca3af' + } +} + +/** Minimal three-bar trend glyph; unknown renders as an honest flat grey. */ +function TrendGlyph({ dir }: { dir: VelocityDirection }) { + const color = directionColor(dir) + const heights = + dir === 'accelerating' + ? [5, 9, 13] + : dir === 'decelerating' + ? [13, 9, 5] + : dir === 'flat' + ? [9, 9, 9] + : [4, 4, 4] + return ( + + {heights.map((h, i) => ( + + ))} + + {DIRECTION_META[dir].glyph} + + + ) +} + +function VelocityRow({ signal }: { signal: VelocitySignal }) { + const unknown = signal.direction === 'unknown' + return ( +
+
+ + {VELOCITY_KIND_META[signal.kind].label} + + + + + {DIRECTION_META[signal.direction].label} + + +
+

{signal.label}

+

+ +

+
+ ) +} + +function StatusChip({ point }: { point: InflectionPoint }) { + const sensor = sensorFor(point) + const meta = SENSOR_STATUS_META[sensor.status] + const fieldMoved = sensor.status === 'missed' && sensor.fieldMovedAnyway + const label = fieldMoved ? 'Missed · field moved' : meta.label + return ( + + {label} + + ) +} + +function FieldCard({ + velocity, + sensors, + onOpenSensor, +}: { + velocity: VelocitySignal[] + sensors: InflectionPoint[] + onOpenSensor: (p: InflectionPoint) => void +}) { + return ( +
+
+ The field +
+

Is the field accelerating?

+ + {/* Velocity basket — leads the card. */} +
+ Velocity basket +
+ {velocity.length > 0 ? ( +
+ {velocity.map((s) => ( + + ))} +
+ ) : ( +

Curating — velocity signals for this focus area are being defined.

+ )} + + {/* Sensors — inflection points demoted to a sensors list. */} +
+
Our sensors: inflection points
+

+ Dated, falsifiable markers we expect acceleration to produce. They grade our model of the + field — not the field.{' '} + Illustrative draft ledger — statuses are proposed, not ratified. +

+ {sensors.length > 0 ? ( +
    + {sensors.map((p) => { + const sensor = sensorFor(p) + const fieldMoved = sensor.status === 'missed' && sensor.fieldMovedAnyway + return ( +
  • + +
  • + ) + })} +
+ ) : ( +

+ Sensors for this focus area are being defined — not zero, just not yet. +

+ )} +
+
+ ) +} + +// ── X — our hand card ───────────────────────────────────────────────────────── +function ToolBadge({ tool }: { tool: ToolId }) { + return ( + + + {TOOL_TITLE[tool]} + + {TOOL_PROPOSED[tool] && ( + + Proposed tool + + )} + + ) +} + +function HandCard({ contributions }: { contributions: ContributionV2[] }) { + return ( +
+
+ Our hand +
+

The conditions we’re pushing on

+ + {contributions.length > 0 ? ( +
+ {contributions.map((c, i) => ( +
+
+ + + + + {CONDITION_LABEL[c.targetedCondition]} +
+
+
What we did
+

+
+
+
+ Observed movement +
+

{c.observedMovement}

+
+
+ ))} +
+ ) : ( +

+ The conditions this focus area is pushing on will appear once its bets are set. +

+ )} +
+ ) +} + +// ── Team-name linkify (reuses the shared TEAM_LINKS map, read-only) ─────────── +const TEAM_LINK_PATTERN = new RegExp( + '(' + + Object.keys(TEAM_LINKS) + .sort((a, b) => b.length - a.length) + .map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + .join('|') + + ')', + 'g', +) + +function Linkify({ text }: { text: string }) { + const parts = text.split(TEAM_LINK_PATTERN) + return ( + <> + {parts.map((part, i) => + TEAM_LINKS[part] ? ( + + {part} + + ) : ( + {part} + ), + )} + + ) +} + +// ── Crowd forecast (reuses market-signals on the field axis) ────────────────── +function formatUSD(n: number): string { + if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M` + if (n >= 1_000) return `$${Math.round(n / 1_000)}k` + return `$${Math.round(n)}` +} + +function CrowdForecast({ signal }: { signal: MarketSignal }) { + const pct = signal.prob != null ? Math.round(signal.prob * 100) : null + return ( +
+
+ Crowd read + {signal.platform && ( + + {PLATFORM_LABEL[signal.platform]} + {signal.viaFallback ? ' (fallback)' : ''} + + )} + {signal.volume != null && ( + {formatUSD(signal.volume)} at stake + )} + + {signal.readout ?? (pct != null ? `${pct}%` : '—')} + +
+ {signal.url && ( + + {signal.question} + + )} +

+ {signal.note} A Y-sensor: an independent read on whether the field is moving — not our contribution, not a settled outcome. +

+
+ ) +} + +// ── Sensor modal ────────────────────────────────────────────────────────────── +function useModalChrome(onClose: () => void) { + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + const prev = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { + document.removeEventListener('keydown', onKey) + document.body.style.overflow = prev + } + }, [onClose]) +} + +function SensorModal({ + point, + signal, + onClose, +}: { + point: InflectionPoint + signal?: MarketSignal + onClose: () => void +}) { + useModalChrome(onClose) + const fa = FOCUS_AREAS.find((f) => f.key === point.area)! + const sensor = sensorFor(point) + const meta = SENSOR_STATUS_META[sensor.status] + const fieldMoved = sensor.status === 'missed' && sensor.fieldMovedAnyway + + return ( +
+
e.stopPropagation()}> + + +
+
+ + + + {fa.label} + · + {point.opportunitySpace} +
+ + {/* Sensor card */} +
+
+ + Sensor · inflection point + + + {fieldMoved ? 'Missed · field moved' : meta.label} + +
+

{point.title}

+ +
+
+
The marker (what we predicted)
+

{point.signal}

+
+
+
+
Predicted by
+

{sensor.predictedBy}

+
+
+
Status
+

+ {fieldMoved ? MISSED_FIELD_MOVED_MICROCOPY : SENSOR_STATUS_MICROCOPY[sensor.status]} +

+
+
+
+
Falsification condition
+

{sensor.falsificationCondition}

+
+
+
If it matters (cascade)
+

{point.cascade}

+
+ {sensor.postMortem && ( +
+
+ {fieldMoved ? 'Post-mortem — the field accelerated via another path' : 'Post-mortem'} +
+

{sensor.postMortem}

+
+ )} +
+
+ + {/* Crowd read — reused market signal on the field axis */} + {signal && signal.match !== 'gap' && ( +
+ +
+ )} + {signal && signal.match === 'gap' && ( +
+
Crowd read
+

{signal.note}

+
+ )} + + + See the latest {fa.label} insights + + + + +
+
+
+ ) +} + +// ── How to read this (v2) ───────────────────────────────────────────────────── +function HowToReadModalV2({ onClose }: { onClose: () => void }) { + useModalChrome(onClose) + const statuses: { key: keyof typeof SENSOR_STATUS_META | 'field-moved'; label: string; micro: string }[] = [ + { key: 'pending', label: SENSOR_STATUS_META.pending.label, micro: SENSOR_STATUS_MICROCOPY.pending }, + { key: 'hit', label: SENSOR_STATUS_META.hit.label, micro: SENSOR_STATUS_MICROCOPY.hit }, + { key: 'missed', label: SENSOR_STATUS_META.missed.label, micro: SENSOR_STATUS_MICROCOPY.missed }, + { key: 'field-moved', label: 'Missed · field moved', micro: MISSED_FIELD_MOVED_MICROCOPY }, + { key: 'retired', label: SENSOR_STATUS_META.retired.label, micro: SENSOR_STATUS_MICROCOPY.retired }, + ] + return ( +
+
e.stopPropagation()}> + + +
How to read this
+

{HOW_TO_READ_V2}

+ +
+ + Is the field accelerating — read from the velocity basket. Moves with or without us. + + + The conditions we push on with the toolkit, judged by whether the condition actually moved. + +
+ +
+
Sensor statuses
+
    + {statuses.map((s) => ( +
  • + {s.label} + — {s.micro} +
  • + ))} +
+
+
+
+ ) +} + +function LegendRow({ + color, + ink, + label, + children, +}: { + color: string + ink: string + label: string + children: React.ReactNode +}) { + return ( +
+ +

+ {label} — {children} +

+
+ ) +} diff --git a/src/components/InsightsExplorer.tsx b/src/components/InsightsExplorer.tsx index 656eaaf8..3c308067 100644 --- a/src/components/InsightsExplorer.tsx +++ b/src/components/InsightsExplorer.tsx @@ -49,12 +49,16 @@ const DISPLAY_LIMIT = 12 export default function InsightsExplorer({ sections, areas, + initialArea, }: { sections: InsightSection[] areas: AreaDef[] + initialArea?: string }) { const [type, setType] = useState(ALL) - const [area, setArea] = useState(ALL) + const [area, setArea] = useState( + initialArea && areas.some((a) => a.slug === initialArea) ? initialArea : ALL, + ) const matchArea = (tile: InsightTile, a: string) => a === ALL || tile.areas.includes(a) diff --git a/src/components/MeasuringQuestions.tsx b/src/components/MeasuringQuestions.tsx new file mode 100644 index 00000000..eb83d721 --- /dev/null +++ b/src/components/MeasuringQuestions.tsx @@ -0,0 +1,171 @@ +'use client' + +import { useEffect, useState } from 'react' +import { FIELD_COLOR, FIELD_INK, HAND_COLOR, ROLE_META, PL_ROLE_ORDER } from '@/lib/inflection-points' + +function Badge({ letter, color, ink = '#ffffff' }: { letter: string; color: string; ink?: string }) { + return ( + + {letter} + + ) +} + +export default function MeasuringQuestions() { + const [rolesOpen, setRolesOpen] = useState(false) + + return ( + <> +
+ {/* A — our hand (whole card opens the roles modal) */} + + + {/* B — the field */} + + + {/* C — the field */} + +
+ + {rolesOpen && setRolesOpen(false)} />} + + ) +} + +function FieldCard({ + letter, + title, + eyebrow, + body, + footRest, +}: { + letter: string + title: string + eyebrow: string + body: string + footRest: string +}) { + return ( +
+
+ +

{title}

+
+
+ {eyebrow} +
+

{body}

+

+ The field. {footRest} +

+
+ ) +} + +function RolesModal({ onClose }: { onClose: () => void }) { + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + const prev = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { + document.removeEventListener('keydown', onKey) + document.body.style.overflow = prev + } + }, [onClose]) + + return ( +
+
e.stopPropagation()}> + + +
+ Question A · Our hand +
+

+ Six roles, matched to the bottleneck each one releases +

+

+ Every card tags the roles PL played. Match the instrument to the bottleneck — a point can + be reached with little PL involvement, which is still a win for the field, and we record + our role honestly. +

+ +
+ {PL_ROLE_ORDER.map((r) => ( +
+
+ + {ROLE_META[r].label} +
+

{ROLE_META[r].description}

+
+ ))} +
+ +

+ Each role answers a different bottleneck. On a card, the tags under “our hand” + tell you which ones PL brought to that specific inflection point. +

+
+
+ ) +} diff --git a/src/components/MeasuringQuestionsV2.tsx b/src/components/MeasuringQuestionsV2.tsx new file mode 100644 index 00000000..37ed96f6 --- /dev/null +++ b/src/components/MeasuringQuestionsV2.tsx @@ -0,0 +1,282 @@ +'use client' + +// v2 methodology section — the counterpart to v1's MeasuringQuestions, expanded +// into three compact blocks: (1) the X→Y logic model, (2) the three questions, +// (3) the toolkit checked against history. Separate component tree from v1. + +import { useEffect, useState } from 'react' +import { + FIELD_COLOR, + FIELD_INK, + HAND_COLOR, + METHODOLOGY_PARAGRAPH, + THREE_QUESTIONS, + TOOLKIT_FRAMING, + TOOLKIT_V2, + CO_FUNDER_GAPS, + type ToolkitEntry, +} from '@/lib/field-velocity' + +function Badge({ letter, color, ink = '#ffffff' }: { letter: string; color: string; ink?: string }) { + return ( + + {letter} + + ) +} + +export default function MeasuringQuestionsV2() { + const [tool, setTool] = useState(null) + + return ( +
+ {/* Block 1 — the X→Y logic model */} +
+
+
+

The logic model

+

+ We push on conditions (X) so the{' '} + field accelerates (Y). Inflection points + are the sensors in between. +

+
+

{METHODOLOGY_PARAGRAPH}

+
+
+ + {/* Block 2 — the three questions */} +
+
+
+

Three questions

+

+ Different jobs that should not be collapsed into a single score. +

+
+
+ {THREE_QUESTIONS.map((item, i) => { + const isField = item.axis === 'field' + const color = isField ? FIELD_COLOR : HAND_COLOR + const ink = isField ? FIELD_INK : '#ffffff' + return ( +
+
+ + + {item.axis === 'field' ? 'The field' : item.axis === 'hand' ? 'Our hand' : 'Attribution'} + +
+

{item.q}

+

{item.gloss}

+
+ ) + })} +
+
+
+ + {/* Block 3 — the toolkit, checked against history */} +
+
+
+

The toolkit, checked against history

+

{TOOLKIT_FRAMING}

+

+ Draft — the sharpened toolkit and co-funder plan are pending Molly + FA-lead review. +

+
+ +
+
+ {TOOLKIT_V2.map((t) => ( + + ))} +
+ + {/* Co-funder gaps — the honesty / work-plan artifact */} +
+

Where we need co-funders

+

+ Four insertions are only honest if partners carry part of the load. Each row is a named + bottleneck, why PL alone falls short, and the orgs to approach first (✳ = current PL + co-funder). +

+
    + {CO_FUNDER_GAPS.map((g) => ( +
  • +
    + {g.gap} + + {g.tool} + +
    +

    {g.why}

    +

    + {g.partners.map((p, i) => ( + + {i > 0 ? ' · ' : ''} + {p.name} + {p.current ? ' ✳' : ''} + + ))} +

    +
  • + ))} +
+
+
+
+
+ + {tool && setTool(null)} />} +
+ ) +} + +function ToolkitModal({ tool, onClose }: { tool: ToolkitEntry; onClose: () => void }) { + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + const prev = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { + document.removeEventListener('keydown', onKey) + document.body.style.overflow = prev + } + }, [onClose]) + + return ( +
+
e.stopPropagation()}> + + +
+
+ + Toolkit + + {tool.proposed && ( + + Proposed — our wildcard + + )} +
+

{tool.title}

+
{tool.subtitle}
+ + {/* Full description */} +

{tool.description}

+ + {/* Releases */} +
+
Releases
+

+ {tool.releases.map((r, i) => ( + + {i > 0 ? '; ' : ''} + {r.label} + {r.conditions.length > 0 ? ` (${r.conditions.join(', ')})` : ''} + + ))} +

+
+ + {/* What changed */} + {tool.whatChanged && ( +
+
+ What changed & which condition it closes +
+

+ Insertion: {tool.whatChanged.insertion} +

+

+ Condition closed: {tool.whatChanged.conditionClosed} +

+
+ )} + + {/* Co-funder gap */} + {tool.coFunderGap && ( +
+
+ We can’t close this alone +
+

{tool.coFunderGap.gap}

+

{tool.coFunderGap.why}

+
+ Candidate partners +

+ {tool.coFunderGap.partners.map((p, i) => ( + + {i > 0 ? ' · ' : ''} + {p.name} + {p.current ? ' ✳' : ''} + + ))} +

+

✳ = current PL co-funder

+
+
+ )} +
+
+
+ ) +} diff --git a/src/components/PLRadar.tsx b/src/components/PLRadar.tsx index adf8cf02..1354045a 100644 --- a/src/components/PLRadar.tsx +++ b/src/components/PLRadar.tsx @@ -34,6 +34,9 @@ export type RadarItem = { const STORAGE_KEY = 'plrd-radar-collapsed' +/** How long each story slide dwells before auto-advancing (ms). */ +const SLIDE_MS = 7000 + const AREA_GRADIENT: Record = { 'digital-human-rights': { from: '#0b1f4d', via: '#1e3a8a', to: '#3966FE', dot: '#3966FE' }, 'economies-governance': { from: '#0a3b2e', via: '#0f6b4c', to: '#12bfdf', dot: '#12bfdf' }, @@ -82,6 +85,8 @@ function CoverImage({ src, alt }: { src: string; alt: string }) { export default function PLRadar({ edition, items }: { edition: string; items: RadarItem[] }) { const [open, setOpen] = useState(true) const [i, setI] = useState(0) + const [progress, setProgress] = useState(0) + const [paused, setPaused] = useState(false) const sectionRef = useRef(null) const touchX = useRef(null) @@ -100,10 +105,40 @@ export default function PLRadar({ edition, items }: { edition: string; items: Ra const N = items.length + 1 // + "all caught up" slide const go = useCallback((n: number) => setI((prev) => Math.max(0, Math.min(N - 1, n))), [N]) + const atEnd = i === items.length + + // Story-style auto-advance: fill the current slide's bar over SLIDE_MS, then + // step to the next slide. Pauses while collapsed, held/hovered, or when the + // tab is hidden, and halts on the final "all caught up" slide. + useEffect(() => { + setProgress(0) + if (!open || paused || atEnd) return + let raf = 0 + let start: number | null = null + const tick = (now: number) => { + if (start === null) start = now + const p = Math.min(1, (now - start) / SLIDE_MS) + setProgress(p) + if (p >= 1) go(i + 1) + else raf = requestAnimationFrame(tick) + } + raf = requestAnimationFrame(tick) + return () => cancelAnimationFrame(raf) + }, [i, open, paused, atEnd, go]) + + // Pause auto-advance when the tab loses focus (browsers throttle rAF anyway). + useEffect(() => { + const onVis = () => setPaused(document.hidden) + document.addEventListener('visibilitychange', onVis) + return () => document.removeEventListener('visibilitychange', onVis) + }, []) + const onTouchStart = (e: React.TouchEvent) => { touchX.current = e.touches[0].clientX + setPaused(true) // press-and-hold pauses, like stories } const onTouchEnd = (e: React.TouchEvent) => { + setPaused(false) if (touchX.current === null) return const dx = e.changedTouches[0].clientX - touchX.current if (dx < -40) go(i + 1) @@ -130,8 +165,6 @@ export default function PLRadar({ edition, items }: { edition: string; items: Ra window.open(intent, '_blank', 'noopener,noreferrer') } - const atEnd = i === items.length - return (
{/* progress segments — own band above the content so they never overlap it */}
- {Array.from({ length: N }).map((_, idx) => ( -
-
-
- ))} + {Array.from({ length: N }).map((_, idx) => { + // Past slides are full; the current slide fills as it plays; future slides are empty. + const fill = idx < i ? 1 : idx === i ? (atEnd ? 1 : progress) : 0 + return ( +
+
+
+ ) + })}
setPaused(true)} + onMouseLeave={() => setPaused(false)} > {/* content slides */} {items.map((item, idx) => { diff --git a/src/lib/field-velocity.ts b/src/lib/field-velocity.ts new file mode 100644 index 00000000..7a799878 --- /dev/null +++ b/src/lib/field-velocity.ts @@ -0,0 +1,743 @@ +// ── Impact Dashboard v2 — the acceleration-centered data model ──────────────── +// +// This module is v2-only. It imports inflection points from +// `@/lib/inflection-points` as the single source of truth (titles, areas, +// opportunity spaces) and layers the v2 reframe on top WITHOUT editing any v1 +// file. Nothing here changes v1 behaviour; /impact keeps rendering from the v1 +// modules exactly as in PR #29. +// +// The reframe, in one line: +// Y (the target) = field acceleration itself — is the field moving faster? +// measured by a per-focus-area basket of velocity signals. +// Inflection pts = pre-registered SENSORS — dated, falsifiable markers we +// expect acceleration to produce. They grade our MODEL of +// the field, not the field and not us. Misses are kept. +// X (our hand) = pushing on named conditions/bottlenecks via the toolkit, +// with observable condition-movement (not a list of tasks). +// +// This is DRAFT-FOR-REVIEW copy and a DRAFT ledger — statuses, dates, and +// velocity readings below are proposed, not ratified. Real numbers we do not +// yet have are left as explicit `// TODO(lukas)` placeholders and render as an +// honest "curating" state — we never invent values. + +import { + INFLECTION_POINTS, + FOCUS_AREAS, + FIELD_COLOR, + FIELD_INK, + FIELD_TRACK, + HAND_COLOR, + LIVE_COLOR, + type FocusAreaKey, + type InflectionPoint, +} from '@/lib/inflection-points' + +// Re-export the shared color + two-axis primitives so v2 components import from +// one place and the theme-aware dark-mode handling is reused verbatim. +export { FIELD_COLOR, FIELD_INK, FIELD_TRACK, HAND_COLOR, LIVE_COLOR } +export { FOCUS_AREAS, INFLECTION_POINTS } +export type { FocusAreaKey, InflectionPoint } + +// ── Y — the field-velocity basket ──────────────────────────────────────────── +// A field is "accelerating" when the things that make fields go faster are +// themselves speeding up. We track five kinds per focus area. + +export type VelocityKind = 'talent' | 'capital' | 'tools' | 'cadence' | 'crowd' +export type VelocityDirection = 'accelerating' | 'flat' | 'decelerating' | 'unknown' + +export type VelocitySignal = { + id: string + label: string + kind: VelocityKind + direction: VelocityDirection + evidence: string + source?: string + asOf: string +} + +export const VELOCITY_KIND_META: Record = { + talent: { label: 'Talent inflow', blurb: 'New teams and researchers entering the field.' }, + capital: { label: 'External capital', blurb: 'Non-PL money forming around the field.' }, + tools: { label: 'Tool-cost trend', blurb: "The field's rate-limiting instrument — is it getting cheaper?" }, + cadence: { label: 'Output cadence', blurb: 'Demos, deployments, and the preprint-to-product lag.' }, + crowd: { label: 'Crowd read', blurb: 'Movement in the mapped forecast markets.' }, +} + +export const DIRECTION_META: Record< + VelocityDirection, + { label: string; glyph: string; tone: 'up' | 'flat' | 'down' | 'unknown' } +> = { + accelerating: { label: 'Accelerating', glyph: '↗', tone: 'up' }, + flat: { label: 'Flat', glyph: '→', tone: 'flat' }, + decelerating: { label: 'Decelerating', glyph: '↘', tone: 'down' }, + unknown: { label: 'Curating', glyph: '•', tone: 'unknown' }, +} + +// asOf date for the curated seed. Bump when a signal is re-read. +const ASOF = '2026-07' + +export const FIELD_VELOCITY: Record = { + 'digital-human-rights': [ + { + id: 'dhr-talent', + label: 'Teams entering resilient comms, identity & provenance', + kind: 'talent', + direction: 'unknown', + evidence: + 'PL-backed teams are active across the axis (Fluence, Huddle01, SpruceID, Privy, EQTY Lab), but we have not yet counted net new non-PL entrants per year. // TODO(lukas): new-team / new-researcher inflow rate.', + asOf: ASOF, + }, + { + id: 'dhr-capital', + label: 'External capital into decentralized identity & provenance', + kind: 'capital', + direction: 'unknown', + evidence: + 'Identity/personhood raised at scale (World, Privy), but we have not isolated non-PL capital formation for this field. // TODO(lukas): annual external $ into the field.', + asOf: ASOF, + }, + { + id: 'dhr-tools', + label: 'Cost of running open, permissionless comms & storage', + kind: 'tools', + direction: 'unknown', + evidence: + 'IPFS/Filecoin and libp2p lower the cost of content-addressed distribution, but we lack a single tracked $/unit for the rate-limiting instrument here. // TODO(lukas): pick and track the cost metric.', + asOf: ASOF, + }, + { + id: 'dhr-cadence', + label: 'Real-world provenance & censorship-resistant deployments', + kind: 'cadence', + direction: 'accelerating', + evidence: + 'Content-authenticity displays now ship in newsrooms (Reuters, AP) backed by content-addressed archives, and Wikipedia stayed reachable over IPFS through a state block — the cadence of shipped deployments is rising even if unmeasured. // TODO(lukas): deployments-per-quarter count.', + source: 'https://dispatch.starlinglab.org/p/verify-authenticity-displays', + asOf: ASOF, + }, + { + id: 'dhr-crowd', + label: 'Movement in the mapped identity / AI-regulation markets', + kind: 'crowd', + direction: 'unknown', + evidence: + 'Reuses market-signals.ts (World Network verification; AI-regulation-into-law). The live probability renders on each sensor; the quarter-over-quarter delta is not yet stored. // TODO(lukas): persist market history to compute the delta.', + asOf: ASOF, + }, + ], + 'economies-governance': [ + { + id: 'eg-talent', + label: 'Teams entering programmable capital & DPI', + kind: 'talent', + direction: 'unknown', + evidence: + 'A visible lineage exists (Hypercerts, Funding the Commons, GainForest, Glow, WeatherXM, Molecule), but net new entrants per year are not yet tracked. // TODO(lukas): entrant inflow rate.', + asOf: ASOF, + }, + { + id: 'eg-capital', + label: 'External capital into public-goods funding & ReFi', + kind: 'capital', + direction: 'unknown', + evidence: + 'The category is not yet a durable capital market — the inflection point itself. We have not isolated non-PL inflows. // TODO(lukas): annual external $ into programmable allocation.', + asOf: ASOF, + }, + { + id: 'eg-tools', + label: 'Cost of deploying programmable allocation rails', + kind: 'tools', + direction: 'unknown', + evidence: + 'Open allocation primitives lower the cost of running a mechanism, but no single tracked $/deployment exists. // TODO(lukas): choose the rate-limiting cost metric.', + asOf: ASOF, + }, + { + id: 'eg-cadence', + label: 'Live mechanism & verification activity', + kind: 'cadence', + direction: 'accelerating', + evidence: + 'PL-backed mechanisms report live output — Simocracy participation, GainForest species observations, Glow active solar farms — surfaced on the FA2 live dashboard. Cadence is real and rising; the trend line is not yet a single figure. // TODO(lukas): deployments-per-quarter.', + source: '/areas/economies-governance/impact/live-dashboard/', + asOf: ASOF, + }, + { + id: 'eg-crowd', + label: 'Movement in the mapped allocation / DPI markets', + kind: 'crowd', + direction: 'unknown', + evidence: + 'Reuses market-signals.ts — mostly white space here (no liquid market prices $1B/yr through programmable allocation), which is itself a Y-signal that the bet is early. // TODO(lukas): revisit as markets appear.', + asOf: ASOF, + }, + ], + neurotech: [ + { + id: 'nt-talent', + label: 'NeuroAI & comp-neuro talent flow', + kind: 'talent', + direction: 'accelerating', + evidence: + 'NeuroAI is pulling ML talent toward neuroscience and BCI programs are expanding — the flow the "neural distillation" sensor predicts. The magnitude is not yet counted. // TODO(lukas): net talent flow into the field.', + asOf: ASOF, + }, + { + id: 'nt-capital', + label: 'External capital into BCI & neurotech', + kind: 'capital', + direction: 'accelerating', + evidence: + 'Large private rounds and new entrants across invasive and non-invasive BCI signal a warming market. We have not isolated the annual non-PL figure. // TODO(lukas): external $ into the field per year.', + asOf: ASOF, + }, + { + id: 'nt-tools', + label: 'Neural recording cost — $ per channel', + kind: 'tools', + direction: 'accelerating', + evidence: + 'Cost per recording channel is the field\u2019s rate-limiting instrument and has trended down for years; a falling curve is acceleration. The current figure and slope are not pinned here. // TODO(lukas): $/channel and trend.', + asOf: ASOF, + }, + { + id: 'nt-cadence', + label: 'Implants, connectome throughput & demos', + kind: 'cadence', + direction: 'accelerating', + evidence: + 'Human implant counts, connectome data volume, and NeuroAI demos are all rising year over year. Exact throughput toward the 2030 milestones is tracked in PL Neuro plans, not yet here. // TODO(lukas): implants / connectome throughput.', + asOf: ASOF, + }, + { + id: 'nt-crowd', + label: 'Movement in the mapped BCI / WBE forecasts', + kind: 'crowd', + direction: 'unknown', + evidence: + 'Reuses market-signals.ts — Neuralink implant-count (Kalshi), brain-emulation-first-AI and WBE-date (Metaculus). Live readings render per sensor; the delta over time is not yet stored. // TODO(lukas): persist forecast history.', + asOf: ASOF, + }, + ], + 'ai-robotics': [ + // FA3 is deliberately forthcoming — its bets are still being defined, so its + // velocity basket is honestly empty rather than fabricated. + { + id: 'air-forthcoming', + label: 'Velocity basket forthcoming', + kind: 'talent', + direction: 'unknown', + evidence: + 'AI & Robotics is still finalizing its inflection points; its velocity basket will be seeded once the bets are set. // TODO(lukas): define the basket with the FA lead.', + asOf: ASOF, + }, + ], +} + +// ── Inflection points as pre-registered SENSORS ─────────────────────────────── +// Sensor semantics are keyed by the point's title (the single source of truth +// stays in inflection-points.ts). Being wrong is a logged learning event, not a +// failure verdict — a missed marker stays on the board with a post-mortem. + +export type SensorStatus = 'pending' | 'hit' | 'missed' | 'retired' + +export type SensorSemantics = { + /** The date by which we expect the marker to resolve. */ + predictedBy: string + /** 0..1 subjective confidence at time of pre-registration. Optional. */ + confidence?: number + /** What observation would falsify the marker — states the yes/no crisply. */ + falsificationCondition: string + status: SensorStatus + /** Logged reasoning for a hit / miss / retirement. Surfaces in the modal. */ + postMortem?: string + /** The honesty case: we were wrong about the marker, the field accelerated anyway. */ + fieldMovedAnyway?: boolean +} + +export const SENSOR_STATUS_MICROCOPY: Record = { + pending: 'not yet resolved', + hit: 'resolved as predicted', + missed: 'did not resolve as predicted', + retired: 'superseded; rationale logged', +} +/** Distinct microcopy for a missed marker where the field accelerated anyway. */ +export const MISSED_FIELD_MOVED_MICROCOPY = 'missed — the field accelerated via another path' + +export const SENSOR_STATUS_META: Record< + SensorStatus, + { label: string; color: string; bg: string } +> = { + pending: { label: 'Pending', color: '#6b6d79', bg: 'color-mix(in srgb, #6b6d79 12%, transparent)' }, + hit: { label: 'Hit', color: '#12bfdf', bg: 'color-mix(in srgb, #12bfdf 14%, transparent)' }, + missed: { label: 'Missed', color: '#d0894b', bg: 'color-mix(in srgb, #d0894b 14%, transparent)' }, + retired: { label: 'Retired', color: '#9ca3af', bg: 'color-mix(in srgb, #9ca3af 14%, transparent)' }, +} + +// DRAFT ledger. All real markers were pre-registered "as of 2026" and none have +// resolved true, so their honest baseline is `pending`. To let the team review +// the full ledger mechanic side by side, a small number of markers below are +// seeded with the other states as clearly-labelled DRAFT assessments (see the +// "Illustrative draft ledger" caption in the UI). Confidence numbers we have not +// set are left as TODO(lukas) and simply omitted rather than invented. +export const SENSOR_SEMANTICS: Record = { + 'Communication that cannot be switched off': { + predictedBy: '2030', + // confidence: // TODO(lukas) + falsificationCondition: + 'By 2030, no consumer-scale connectivity provider operates without state licensing/identity gating AND no metadata-resistant messenger crosses tens of millions of users.', + status: 'pending', + }, + 'Personhood without the state in the loop': { + predictedBy: '2028', + falsificationCondition: + 'By 2028, no service verifies >100M unique humans for everyday use without a nation-state ID or KYC.', + status: 'pending', + }, + 'Provenance becomes the default for truth': { + predictedBy: '2028', + falsificationCondition: + 'Two consecutive frontier-model generations ship WITHOUT attested provenance by default, and no major platform/archive adopts content-addressed provenance as default.', + status: 'pending', + }, + 'Agents run on open rails': { + predictedBy: '2030', + falsificationCondition: + 'By 2030, no frontier-scale model is trained across independent decentralized hardware AND agent-to-agent economic activity does not meaningfully settle on open permissionless rails.', + status: 'pending', + }, + 'Programmable government in production': { + predictedBy: '2029', + falsificationCondition: + 'By 2029, no sovereign moves >$1B/yr of public funds through programmable, real-time-auditable rails.', + status: 'pending', + }, + 'A binding decision at scale': { + predictedBy: '2028', + falsificationCondition: + 'By 2028, no city/government makes a consequential binding decision through a computational mechanism with tens of thousands participating.', + // Illustrative DRAFT status to exercise the "retired" mechanic for review. + status: 'retired', + postMortem: + 'DRAFT (illustrative): candidate for retirement — "binding decision" conflates two markers (a real government adopting a mechanism vs. turnout at scale). Proposed split into an adoption marker and a scale marker; rationale logged rather than the marker silently deleted.', + }, + 'Public goods become a financeable category': { + predictedBy: '2029', + falsificationCondition: + 'By 2029, <$1B/yr flows through programmable allocation and no mainstream allocator treats it as infrastructure.', + status: 'pending', + }, + 'Capital that pays on verified outcomes': { + predictedBy: '2029', + falsificationCondition: + 'By 2029, no $1B+ climate/public-goods fund disburses against independently verified real-world outcomes faster and cheaper than a legacy audit.', + status: 'pending', + }, + 'The BCI app store': { + predictedBy: '2027', + falsificationCondition: + 'By 2027, no regulator clears a standardized software/API layer for a commercial BCI, or no third-party app launches and is adopted.', + // Illustrative DRAFT status to exercise the "missed + field moved anyway" mechanic. + status: 'missed', + fieldMovedAnyway: true, + postMortem: + 'DRAFT (illustrative): the standardized third-party app layer did not clear on our timeline — so the marker, as written, is a miss. But the field accelerated via another path: first-party BCI capability and implant counts advanced faster than expected. The marker stays on the board; the lesson is that ecosystem-opening may trail single-vendor scaling, so we are re-dating rather than deleting.', + }, + 'Neural distillation': { + predictedBy: '2030', + falsificationCondition: + 'By 2030, no major lab matches frontier reasoning by training on human neural data at a fraction of the parameters/energy, and no venture surge into consumer EEG for cognitive data occurs.', + status: 'pending', + }, + 'The neuromorphic energy pivot': { + predictedBy: '2031', + falsificationCondition: + 'By 2031, no brain-inspired model matches SOTA at 3+ orders of magnitude better energy efficiency.', + status: 'pending', + }, + 'Memory retrieval in simulation': { + predictedBy: '2032', + falsificationCondition: + 'By 2032, no reconstructed mouse connectome simulated in silico reproduces a specific learned behavior of the biological mouse.', + status: 'pending', + }, +} + +/** Convenience: sensor semantics for one point, defaulting to a pending stub. */ +export function sensorFor(point: InflectionPoint): SensorSemantics { + return ( + SENSOR_SEMANTICS[point.title] ?? { + predictedBy: 'TODO(lukas)', + falsificationCondition: point.signal, + status: 'pending', + } + ) +} + +// ── X — our hand: conditions we push on via the toolkit ─────────────────────── +// Restructured from v1's inputs/activities/outputs into tool → targeted +// condition → what we did → observed movement (the X-side outcome, distinct +// from the Y-side field acceleration). + +export type ToolId = + | 'legibility' + | 'connection' + | 'funding' + | 'policy' + | 'infrastructure' + | 'translation' + | 'culture' + +/** The conditions checklist (1–7) plus the legacy bottleneck types. */ +export type TargetedCondition = + | 'evangelizing-culture' // 1 + | 'savant-fabricant-bridges' // 2 + | 'knowledge-diffusion' // 3 + | 'cheap-tools' // 4 + | 'priority-attribution' // 5 + | 'amateur-experimentation' // 6 + | 'hands-on-formation' // 7 + | 'coordination' + | 'capital' + | 'legibility' + +export const CONDITION_LABEL: Record = { + 'evangelizing-culture': 'Evangelizing improving culture (1)', + 'savant-fabricant-bridges': 'Savant–fabricant bridges (2)', + 'knowledge-diffusion': 'Knowledge diffusion to builders (3)', + 'cheap-tools': 'Cheap, accessible tools (4)', + 'priority-attribution': 'Priority & attribution norms (5)', + 'amateur-experimentation': 'Permissive amateur experimentation (6)', + 'hands-on-formation': 'Hands-on formation (7)', + coordination: 'Coordination bottleneck', + capital: 'Capital bottleneck', + legibility: 'Legibility bottleneck', +} + +export type ContributionV2 = { + tool: ToolId + targetedCondition: TargetedCondition + whatWeDid: string + observedMovement: string + evidence?: string +} + +export const CONTRIBUTIONS_V2: Record = { + 'digital-human-rights': [ + { + tool: 'infrastructure', + targetedCondition: 'cheap-tools', + whatWeDid: + 'Built and maintained the open libp2p / IPFS / Filecoin substrate that resilient-comms and provenance teams build on.', + observedMovement: + 'Content-addressed distribution is now cheap enough that newsrooms and archives adopt it — the condition moved. Attribution rails (who published what, when) came with it.', + evidence: 'https://dispatch.starlinglab.org/p/verify-authenticity-displays', + }, + { + tool: 'funding', + targetedCondition: 'capital', + whatWeDid: + 'Seeded and funded comms, messaging, identity and provenance teams (Fluence, Huddle01, SpruceID, Privy, EQTY Lab).', + observedMovement: + 'Pre-commercial teams reached shipping products; whether this closed the field-level capital gap is // TODO(lukas): track follow-on funding.', + }, + { + tool: 'connection', + targetedCondition: 'savant-fabricant-bridges', + whatWeDid: + 'Convened identity-protocol teams with builders working on portable credentials.', + observedMovement: + 'Cross-team collaboration formed around shared credential formats. // TODO(lukas): name the concrete collaborations that resulted.', + }, + ], + 'economies-governance': [ + { + tool: 'infrastructure', + targetedCondition: 'priority-attribution', + whatWeDid: + 'Originated Hypercerts — a content-addressed primitive that timestamps who produced which public-goods outcome.', + observedMovement: + 'Allocation experiments can now cite verifiable outcome records; the priority/attribution condition moved from absent to usable.', + }, + { + tool: 'translation', + targetedCondition: 'coordination', + whatWeDid: + 'Routed validated allocation and MRV work toward pilots and ventures (Funding the Commons lineage, Molecule).', + observedMovement: + 'Deployment paths opened for several teams. // TODO(lukas): track pilots converted to production.', + }, + { + tool: 'legibility', + targetedCondition: 'knowledge-diffusion', + whatWeDid: + 'Built evidence, benchmarks and standards for outcome verification (GainForest, Glow, WeatherXM).', + observedMovement: + 'A shared vocabulary for verified-outcome funding is emerging; whether it reached working allocators is // TODO(lukas).', + evidence: '/areas/economies-governance/impact/live-dashboard/', + }, + ], + neurotech: [ + { + tool: 'legibility', + targetedCondition: 'legibility', + whatWeDid: + 'Defined benchmarks and milestones for NeuroAI, connectomics throughput, and a WBE reference demo.', + observedMovement: + 'The efficiency / WBE targets give the field a shared scoreboard where none existed. Adoption by outside labs is // TODO(lukas).', + }, + { + tool: 'connection', + targetedCondition: 'savant-fabricant-bridges', + whatWeDid: + 'Bridged AI-lab and comp-neuro talent through the PL Neuro network.', + observedMovement: + 'Talent is flowing toward NeuroAI — the field-side signal is accelerating; our attributable share is // TODO(lukas).', + }, + { + tool: 'policy', + targetedCondition: 'amateur-experimentation', + whatWeDid: + 'Engaged regulators, device makers and developers toward a standardized BCI software/API layer.', + observedMovement: + 'DRAFT: the standardized app layer has not cleared (see the "BCI app store" sensor). The condition has not yet moved on our timeline; re-dating rather than dropping.', + }, + ], + 'ai-robotics': [], +} + +// ── The toolkit, checked against history ────────────────────────────────────── +// Seven tools designed to cover the seven conditions historians of invention +// (Howes, Mokyr, Jacob) identify in every sustained acceleration. The short +// entry (oneLiner + releases tags) renders in the methodology section; the rich +// description / what-changed / co-funder gap render in the toolkit modal. + +export type ReleaseTag = { label: string; conditions: number[] } + +export type CoFunderGap = { + gap: string + why: string + /** name + whether they are already a PL co-funder (marked ✳ in the UI). */ + partners: { name: string; current?: boolean }[] +} + +export type ToolkitEntry = { + id: ToolId + title: string + subtitle: string + proposed?: boolean + /** One-line description used in the scannable methodology list (verbatim). */ + oneLiner: string + releases: ReleaseTag[] + /** Fuller, published-style description (Lukas's revised bullets). */ + description: string + whatChanged?: { insertion: string; conditionClosed: string } + coFunderGap?: CoFunderGap +} + +/** Framing sentence for the toolkit block (verbatim). */ +export const TOOLKIT_FRAMING = + 'Our toolkit is checked against the seven conditions that historians of invention — Anton Howes, Joel Mokyr, Margaret Jacob — identify in every sustained acceleration: an evangelizing improving culture, bridges between theorists and builders, knowledge diffusion that reaches working people, cheap accessible tools, priority and attribution norms, permissive room for amateur experimentation, and hands-on formation. We designed the toolkit to cover all seven, so a field diagnosis can never land on a bottleneck we have no way to address.' + +export const TOOLKIT_V2: ToolkitEntry[] = [ + { + id: 'legibility', + title: 'Legibility', + subtitle: 'field-building communications', + oneLiner: + 'Maps, roadmaps, benchmarks, and builder-grade tutorials that make a field navigable for researchers and working builders.', + releases: [{ label: 'knowledge diffusion', conditions: [3] }], + description: + 'Many early fields are blocked by illegibility, to both internal and external audiences. The progress is real, but the map is missing. Researchers cannot easily find adjacent teams. Founders cannot tell which problems are ready for company formation. Funders cannot distinguish noise from signal. Policymakers cannot see which interventions would help rather than slow progress. Written artifacts like a field map, taxonomy, roadmap, whitepaper, benchmark, policy brief, or public research agenda can turn a loose collection of teams, researchers, and funders into a field with sustained momentum. Legibility also has a second audience: working builders. A field is fully legible only when a competent outsider can build in it, so tutorials, reference builds, teardowns, and partnerships with hands-on educators count as field-building communications alongside the papers.', + whatChanged: { + insertion: 'builder-grade artifacts, hands-on educator partnerships', + conditionClosed: + '3 — knowledge diffusion to working people (Jacob\u2019s lecturers taught mechanics, not readers)', + }, + }, + { + id: 'connection', + title: 'Connection', + subtitle: 'convenings & sustained cohorts', + oneLiner: + 'Rooms that mix theorists with builders and keep them in contact over time — retreats, residencies, advisory networks, apprenticeship-style pairings.', + releases: [ + { label: 'theorist–builder bridges', conditions: [2] }, + { label: 'sustained contact that spreads the improving mentality', conditions: [1] }, + ], + description: + 'Some bottlenecks are social instead of technical. The right talent may already exist to unlock a breakthrough, but the field has not yet connected them. PL R&D runs and supports convenings, retreats, workshops, residencies, hackathons, and advisory networks that bring high-context people together around specific bottlenecks. Two design rules do most of the work. Mix kinds, not just seniority: the productive rooms pair theorists with builders and operators, because that pairing is where research turns into working systems. And sustain the contact: innovation spreads person to person, so recurring cohorts, alumni networks, and apprenticeship-style pairings matter more than one-off events. The goal is not to host events, but to forge new collaborations, companies, grants, pilots, and enable shared roadmaps to emerge.', + whatChanged: { + insertion: 'mix kinds not seniority; sustained cohorts, apprenticeship-style pairings', + conditionClosed: + '2 — savant–fabricant bridges (Mokyr); 1 partially — Howes: 80% of innovators knew an innovator personally first', + }, + }, + { + id: 'funding', + title: 'Funding', + subtitle: 'grants, prizes & fellowships', + oneLiner: + 'Early money where small amounts unlock disproportionate downstream effort; prizes that define what "first" means for a field; fellowships and residencies that give outsiders hands-on entry.', + releases: [ + { label: 'capital gaps', conditions: [] }, + { label: 'priority norms', conditions: [5] }, + { label: 'hands-on formation', conditions: [7] }, + ], + description: + 'Many important ideas are too early for venture capital and too applied for traditional academic funding. Some need small grants to test whether a primitive works. Others need prizes, RFPs, fellowships, or retroactive funding to pull effort into an underbuilt area. A well-designed prize does more than pay out: it defines what counts as "first" in a field and sets the ambition bar for everyone watching. Where breakthrough variance is the goal, we prefer funding people over projects, with longer horizons and tolerance for early failure. Some of the highest-return grants buy entry rather than output: fellowships, residencies with equipment access, and support for the educators who give newcomers their first hands-on contact with a field. PL R&D deploys and helps coordinate early funding where a field-level bottleneck is clear and where small early funding can unlock much larger downstream effort.', + whatChanged: { + insertion: 'prizes define "first"; people over projects; entry-buying grants (fellowships, residencies, educator support)', + conditionClosed: '5 — priority norms; 7 — hands-on formation', + }, + coFunderGap: { + gap: 'School-age hands-on formation (deep end of 7)', + why: 'Education-system scale; PL can fund fellowships and kits, not curricula reform.', + partners: [ + { name: 'Fab Foundation (Gershenfeld)' }, + { name: 'Raspberry Pi Foundation' }, + { name: 'FIRST' }, + { name: 'Siegel Family Endowment' }, + { name: 'Schmidt Futures', current: true }, + { name: 'Gates', current: true }, + ], + }, + }, + { + id: 'policy', + title: 'Policy', + subtitle: 'standards & experimentation rights', + oneLiner: + 'Clearing adoption paths for working technology, and legal room for garage-scale experimentation.', + releases: [{ label: 'permissive amateur experimentation', conditions: [6] }], + description: + 'Frontier technologies often reach a point where the rules do not yet recognize what is possible. That can block adoption even when the technology works. In those cases, progress may require standards bodies, regulatory sandboxes, procurement pathways, model frameworks, or partnerships with public institutions. Adoption is half the job. Fields also need legal room to experiment at small scale: sandbox access for individuals and not only companies, hardware that may be repaired and modified, and workable pathways for citizen science in regulated domains. Many of the people a field will depend on in 10 years are amateurs today, and rules written only for incumbents quietly price them out. PL R&D engages in research, thought leadership, and participation in policy and standards-setting discussions with the goal of creating better rules, which, in turn, help responsible technologies reach users faster.', + whatChanged: { + insertion: 'experimentation rights for individuals, repairable hardware, citizen-science pathways', + conditionClosed: '6 — permissive amateur experimentation', + }, + coFunderGap: { + gap: 'Amateur experimentation rights (6)', + why: 'Agency-scale regulatory fights (FDA, FAA, spectrum, repair) are outside our policy weight class.', + partners: [ + { name: 'Institute for Progress' }, + { name: 'EFF (adjacent via Digital Human Rights)' }, + { name: 'Repair Association / iFixit' }, + { name: 'Mercatus (permissionless-innovation lineage)' }, + { name: 'ARIA (sandbox design)', current: true }, + ], + }, + }, + { + id: 'infrastructure', + title: 'Infrastructure', + subtitle: 'primitives, rails & cheap tools', + oneLiner: + 'Open protocols, shared facilities, and reference designs that collapse the cost of trying things; content-addressed registries that timestamp who built what, when.', + releases: [ + { label: 'cheap accessible tools', conditions: [4] }, + { label: 'priority & attribution norms', conditions: [5] }, + ], + description: + 'Sometimes the missing piece is open protocols, public-good infrastructure, or neutral rails that no single company has sufficient incentive to build alone. When a missing primitive or shared infrastructure layer is blocking a field, PL R&D may help build it directly, support the teams building it, or coordinate an ecosystem capable of maintaining it over time. Two kinds of rails get special attention. Tool-cost infrastructure: open reference designs, shared facilities, and instrument cost-down programs that collapse the price of trying things, especially in hardware fields where a single experiment can cost a career. And attribution infrastructure: content-addressed, timestamped records of who built what, and when, so that priority and credit work for protocols, designs, datasets, and negative results the way they already work for papers.', + whatChanged: { + insertion: 'tool-cost rails (atoms); content-addressed attribution rails', + conditionClosed: '4 — cheap accessible tools; 5 — priority & attribution', + }, + coFunderGap: { + gap: 'Atoms tool-cost collapse (4)', + why: 'Instrument programs are capex- and engineering-heavy; FRO territory.', + partners: [ + { name: 'Convergent Research' }, + { name: 'Astera', current: true }, + { name: 'ARIA', current: true }, + { name: 'CZI (imaging & bio tools)' }, + { name: 'Amaranth Foundation (neuro)' }, + { name: 'Wellcome', current: true }, + ], + }, + }, + { + id: 'translation', + title: 'Translation', + subtitle: 'venture conversion & visibility', + oneLiner: + 'Routing validated work to pilots, production, and capital — including surfacing independent inventors the world cannot yet see.', + releases: [ + { label: 'deployment gaps', conditions: [] }, + { label: 'inventor visibility', conditions: [] }, + ], + description: + 'Some work reaches the point where the bottleneck is no longer research but translation into pilot, production, and scale. PL R&D connects projects within the broader Protocol Labs network, including venture, capital, platform, and ecosystem support, to help promising work find the right deployment path, talent, and resources to succeed. Translation also has a scouting side: much of the most promising invention happens outside institutions and stays invisible until someone surfaces it. Part of this tool is finding independent builders before the market can see them and lending them the network\u2019s visibility, alongside converting the projects we already know.', + whatChanged: { + insertion: 'scouting invisible independent builders', + conditionClosed: + 'Hintz\u2019s visibility failure (independents didn\u2019t vanish, they stopped being seen)', + }, + }, + { + id: 'culture', + title: 'Culture', + subtitle: 'celebrating the improving mentality', + proposed: true, + oneLiner: + '(Proposed — our wildcard.) Deliberately open-ended creative bets: a film, a book, a photography prize that rewards human ingenuity, worldbuilding competitions — whatever recruits people into the conviction that things can be improved, and that they can be the ones to improve them.', + releases: [ + { + label: 'the evangelizing improving culture', + conditions: [1], + }, + ], + description: + 'Every sustained acceleration of invention has run on a culture that treats improvement as a personal calling, spread from person to person. That culture is the hardest condition to engineer, so we treat this tool as our wildcard: deliberately open-ended creative bets that recruit people into building. That might mean backing a film or a book, a photography prize that rewards human ingenuity, or a worldbuilding competition that makes a field\u2019s future concrete enough to want. Ask inventors where they started and they often name an artifact: a book, a film, a kit. Most of these bets will miss. The hits compound for decades, and we will track them on the same scoreboard as everything else we fund.', + whatChanged: { + insertion: 'the wildcard: creative bets on the improving mentality', + conditionClosed: '1 — evangelizing improving culture, the condition Howes ranks upstream of everything', + }, + coFunderGap: { + gap: 'Culture at scale (1)', + why: 'Media production and distribution is its own industry.', + partners: [ + { name: 'Sloan (science-film program is the proven model)' }, + { name: 'Foresight Institute (worldbuilding, prizes)' }, + { name: 'XPRIZE (prize mechanics)' }, + { name: 'Emergent Ventures (talent bets)' }, + ], + }, + }, +] + +/** The four co-funder gaps, for the "Where we need co-funders" summary panel. */ +export const CO_FUNDER_GAPS: (CoFunderGap & { tool: ToolId })[] = TOOLKIT_V2.filter( + (t) => t.coFunderGap, +).map((t) => ({ tool: t.id, ...(t.coFunderGap as CoFunderGap) })) + +// ── Verbatim copy blocks ────────────────────────────────────────────────────── + +/** The three success questions (verbatim). */ +export const THREE_QUESTIONS: { q: string; gloss: string; axis: 'field' | 'hand' | 'attribution' }[] = [ + { + q: 'Is the field accelerating?', + gloss: 'about the world — measured by the velocity basket', + axis: 'field', + }, + { + q: 'Did we move the conditions we targeted?', + gloss: 'about our hand — the X side', + axis: 'hand', + }, + { + q: "Did our push contribute to the field's acceleration?", + gloss: 'attribution — the hard one', + axis: 'attribution', + }, +] + +/** The methodology paragraph (verbatim). */ +export const METHODOLOGY_PARAGRAPH = + 'Inflection points are our pre-registered sensors: specific, dated, falsifiable markers we expect field acceleration to produce. They grade our model of the field — not the field, and not us. The field is graded by its velocity: talent entering, capital forming, tool costs falling, output cadence rising. We are graded by attribution: whether the conditions we pushed on moved, and whether that push traceably contributed to the field\u2019s acceleration. We can be wrong about a marker and still be right about the field; when that happens, the marker stays on the board with a post-mortem. Every prediction we make is dated, every miss is kept, and every retired marker carries its reasons.' + +/** The one-line v2 contract, written to the X→Y logic model. */ +export const HOW_TO_READ_V2 = + 'The target (Y) is field acceleration itself — is the field moving faster — read from a basket of velocity signals. Inflection points are demoted to pre-registered sensors: dated, falsifiable markers that grade our model of the field, not the field and not us. Our hand (X) is the conditions we push on with the toolkit, judged by whether those conditions actually moved. We can be wrong about a marker and still be right about the field — misses stay on the board with a post-mortem.' diff --git a/src/lib/inflection-points.ts b/src/lib/inflection-points.ts new file mode 100644 index 00000000..0fcbb353 --- /dev/null +++ b/src/lib/inflection-points.ts @@ -0,0 +1,449 @@ +// Cross-focus-area inflection points and how we measure them. +// +// Each focus area names a small set of inflection points: specific, observable, +// falsifiable shifts we believe would be catalytic and that have NOT yet happened +// as of 2026. They are hypotheses, not descriptions of the present. We judge our +// work against three questions, mapped to the fields on every point: +// +// Q1 (cascade) Did it matter? The second-order effects it should unlock. +// Q2 (signal) Did it happen? A pre-registered threshold — yes/no + date. +// Q3 (contribution) Did our work make it happen? PL's inputs -> activities -> outputs on the +// critical path (the planned-work side of the +// logic model), traced honestly. +// +// IMPORTANT — these are distinct jobs and are NOT collapsed into one score. A point +// can be reached (Q2) and matter (Q1) with low PL contribution (Q3); that is still a +// win for the field. So we track field progress (Q1 & Q2) on one axis and PL's +// contribution (Q3) on a separate, independent axis. +// +// Source: "Inflection points across PL R&D, and how we will measure them." + +export type InflectionStatus = 'watching' | 'early-signal' | 'tripped' + +/** PL's pre-registered role(s) on the critical path — claims to be evidenced, not a credit score. */ +export type PLRole = 'infrastructure' | 'legibility' | 'connection' | 'capital' | 'translation' | 'permission' + +/** Canonical display order for role pills, so cards read consistently. */ +export const PL_ROLE_ORDER: PLRole[] = ['infrastructure', 'legibility', 'connection', 'capital', 'translation', 'permission'] + +// Two-axis color system used across the impact dashboard: +// FIELD = the change in the world (outcomes + impact) — moves with or without us (black) +// HAND = our planned work / the PL toolkit — the axis we control with partners (PL blue) +// These resolve to theme-aware CSS variables (see globals.css) so the black +// “field” ink flips to a light tone in dark mode instead of vanishing. +export const FIELD_COLOR = 'var(--impact-field)' +export const HAND_COLOR = 'var(--impact-hand)' +/** Text/ink color to place on a FIELD_COLOR fill (white in light, dark in dark). */ +export const FIELD_INK = 'var(--impact-field-ink)' +/** Empty segment color for the field-progress meter. */ +export const FIELD_TRACK = 'var(--impact-field-track)' +/** Live-signal accent — the pulsing dot on points with live outputs (green). */ +export const LIVE_COLOR = '#22c55e' + +/** + * The one-line contract for reading the whole dashboard. Shared by the + * "How to read this" overlay (near the cards) and the methodology callout so + * the framing lives in exactly one place. + */ +export const HOW_TO_READ = + 'We don’t claim credit for outcomes. We name a falsifiable threshold, state what we contributed, and show live signal for and against. Judge us on whether our thresholds prove right and our contributions prove load-bearing.' + +/** + * Q3 contribution, structured along the planned-work side of the logic model. + * Inputs (resources committed) -> Activities (what we do) -> Outputs (what gets produced). + * The outcomes and impact these aim at are tracked on the field axis (Q2, Q1). + */ +export type Contribution = { + inputs: string + activities: string + outputs: string +} + +/** A pointer to live activity that is contribution evidence (Q3) — NOT a Q2 threshold reading. */ +export type LiveEvidence = { + label: string + href: string + note: string +} + +export type InflectionPoint = { + /** Focus-area slug, matches /areas/. */ + area: FocusAreaKey + /** The opportunity space this bet lives in. */ + opportunitySpace: string + /** The inflection point, stated as a hypothesis. */ + title: string + /** Q2 — observable threshold that says it happened (not yet true). */ + signal: string + /** Q1 — why it matters / the cascade to watch. */ + cascade: string + /** Q3 — PL contribution to trace, as inputs -> activities -> outputs. */ + contribution: Contribution + /** Q3, summarized as the PL role(s) on the critical path — the instruments we bring. */ + roles: PLRole[] + /** Field-progress lifecycle state. All start 'watching' — none reached as of 2026. */ + status: InflectionStatus + /** Optional live activity / real-world signals — strictly Q3 evidence, never Q2 progress. */ + liveEvidence?: LiveEvidence[] +} + +export type FocusAreaKey = + | 'digital-human-rights' + | 'economies-governance' + | 'ai-robotics' + | 'neurotech' + +export type FocusAreaMeta = { + key: FocusAreaKey + label: string + /** Short legacy code used in the strategy docs. */ + code: string + href: string + /** Accent color (hex from the theme) for badges and the field-progress meter. */ + accent: string + /** True when the focus area's inflection points are deliberately not-yet-defined (not empty). */ + forthcoming?: boolean +} + +export const FOCUS_AREAS: FocusAreaMeta[] = [ + { key: 'digital-human-rights', label: 'Digital Human Rights', code: 'FA1', href: '/areas/digital-human-rights/', accent: '#1982F4' }, + { key: 'economies-governance', label: 'Economies & Governance', code: 'FA2', href: '/areas/economies-governance/', accent: '#12bfdf' }, + { key: 'ai-robotics', label: 'AI & Robotics', code: 'FA3', href: '/areas/ai-robotics/', accent: '#3966FE', forthcoming: true }, + { key: 'neurotech', label: 'Neurotech', code: 'FA4', href: '/areas/neurotech/', accent: '#E51A66' }, +] + +// The logic-model chain: planned work (inputs/activities/outputs) → intended +// results (outcomes/impact). Shared by the Measuring-impact section, the three- +// questions explainer, and the per-point detail modal so the vocabulary lives +// in one place. +export const LOGIC_MODEL = [ + { key: 'inputs', label: 'Inputs', body: 'Funding, teams, convenings, standards we commit.' }, + { key: 'activities', label: 'Activities', body: 'Seeding teams, building primitives, setting standards.' }, + { key: 'outputs', label: 'Outputs', body: 'Teams funded, deployments, papers, ventures.' }, + { key: 'outcomes', label: 'Outcomes', body: 'Adoption, capital inflows, new entrants.' }, + { key: 'impact', label: 'Impact', body: 'The lasting shift: an inflection point that holds.' }, +] as const +export type LogicStageKey = (typeof LOGIC_MODEL)[number]['key'] + +// ── Field-progress lifecycle (Q1 & Q2). Deliberately separate from PL contribution. ── +export const FIELD_STAGES = ['Defined', 'Emerging', 'Reached', 'Scaling'] as const +export type FieldStage = (typeof FIELD_STAGES)[number] + +/** How far along the field axis a point sits, given its status. */ +export function stageIndexForStatus(status: InflectionStatus): number { + switch (status) { + case 'watching': + return 0 // threshold defined, waiting at it + case 'early-signal': + return 1 + case 'tripped': + return 2 + } +} + +export const STATUS_META: Record = { + watching: { + label: 'Defined', + description: 'The threshold is defined and pre-registered. No movement toward it yet.', + color: '#6b6d79', + }, + 'early-signal': { + label: 'Emerging', + description: 'Leading indicators are moving toward the threshold.', + color: '#1982F4', + }, + tripped: { + label: 'Reached', + description: 'The defined threshold has been crossed.', + color: '#12bfdf', + }, +} + +/** + * Team / venture display name -> canonical website. Used to linkify the named + * examples that appear in contribution copy (activities / outputs) so each + * mentioned team points to its own site. Keep names exactly as they are written + * in the copy (minus trailing qualifiers like "(World)"). + */ +export const TEAM_LINKS: Record = { + 'Funding the Commons': 'https://fundingthecommons.io/', + 'Tools for Humanity': 'https://www.toolsforhumanity.com/', + 'Impossible Cloud': 'https://www.impossiblecloud.com/', + Hypercerts: 'https://hypercerts.org/', + GainForest: 'https://gainforest.earth/', + WeatherXM: 'https://weatherxm.com/', + Molecule: 'https://www.molecule.xyz/', + 'EQTY Lab': 'https://eqtylab.io/', + SpruceID: 'https://spruceid.com/', + Huddle01: 'https://huddle01.com/', + Spheron: 'https://spheron.network/', + Filecoin: 'https://filecoin.io/', + Fluence: 'https://fluence.network/', + Expanso: 'https://www.expanso.io/', + libp2p: 'https://libp2p.io/', + Privy: 'https://privy.io/', + Fleek: 'https://fleek.co/', + Glow: 'https://glowlabs.org/', + Lava: 'https://www.lavanet.xyz/', + IPFS: 'https://ipfs.io/', +} + +export const ROLE_META: Record = { + infrastructure: { + label: 'Infrastructure', + description: 'The open, neutral rail this runs on did not exist. PL builds and maintains it (libp2p / IPFS / Filecoin lineage).', + }, + legibility: { + label: 'Legibility', + description: 'The field lacked a shared map. PL adds roadmaps, taxonomies, benchmarks, and written artifacts.', + }, + connection: { + label: 'Connection', + description: 'Too few connections blocked progress. PL convenes the people who need to collide: dinners, retreats, residencies, hackathons.', + }, + capital: { + label: 'Funding', + description: 'Pre-commercial work needed patient funding. PL runs grants and prizes and helps peer funders deploy theirs.', + }, + translation: { + label: 'Translation', + description: 'The work was ready to leave the lab. PL helps convert it into ventures, pilots, and deployments.', + }, + permission: { + label: 'Policy', + description: 'The rules did not yet recognize the technology. PL engages standards, policy, and regulatory pathways.', + }, +} + +export const INFLECTION_POINTS: InflectionPoint[] = [ + // ── FA1 · Digital Human Rights ─────────────────────────────────────────── + { + area: 'digital-human-rights', + opportunitySpace: 'Censorship-Resistant Communication', + title: 'Communication that cannot be switched off', + signal: + 'A consumer-scale connectivity provider operates without state licensing or identity gating, and a metadata-resistant messenger crosses tens of millions of users; a population stays connected through a deliberate shutdown.', + cascade: + 'Censoring speech and assembly becomes impractical, not just illegal; organizing survives adversarial conditions.', + contribution: { + inputs: 'The libp2p / IPFS open-source networking stack; funding for resilient-comms and private-messaging teams.', + activities: 'Maintaining the substrate, funding teams, and contributing to interoperability standards.', + outputs: 'libp2p / IPFS deployments and the funded comms / messaging teams building on them, such as Fluence and Huddle01.', + }, + roles: ['infrastructure', 'capital'], + status: 'early-signal', + liveEvidence: [ + { + label: 'Wikipedia kept online via IPFS during Turkey’s block', + href: 'https://observer.com/2017/05/turkey-wikipedia-ipfs/', + note: 'A content-addressed (IPFS) mirror kept Wikipedia reachable under Turkey’s state block — early evidence that information can route around a shutdown, not the population-scale-through-a-shutdown threshold (Q2).', + }, + ], + }, + { + area: 'digital-human-rights', + opportunitySpace: 'Portable Identity, Credentials & Trust', + title: 'Personhood without the state in the loop', + signal: + 'A service at >100M people verifies unique humans for everyday use without a nation-state ID or KYC.', + cascade: + 'Recognition stops being rented from platforms and states; privacy-preserving personhood becomes safe to build on.', + contribution: { + inputs: 'Convening capacity across identity protocols; seed funding for portable-credential initiatives.', + activities: 'Convening identity-protocol teams and seeding / funding portable-credential work.', + outputs: 'The identity and credential initiatives PL has seeded or funded, such as Tools for Humanity (World), SpruceID, and Privy.', + }, + roles: ['connection', 'capital'], + status: 'watching', + }, + { + area: 'digital-human-rights', + opportunitySpace: 'Verifiable Public Knowledge & Provenance', + title: 'Provenance becomes the default for truth', + signal: + 'Two consecutive frontier-model generations ship attested provenance by default, and a major platform or archive adopts content-addressed provenance as default.', + cascade: + 'A public record that can prove its own integrity becomes the precondition for trustworthy information in the AI era.', + contribution: { + inputs: 'Content addressing (IPFS) as the provenance substrate; funding for provenance and verifiable-compute teams.', + activities: 'Providing the content-addressing substrate and backing provenance / verifiable-compute teams.', + outputs: 'Content-addressed provenance tooling and the PL-backed teams building it, such as EQTY Lab.', + }, + roles: ['infrastructure', 'capital'], + status: 'early-signal', + liveEvidence: [ + { + label: 'Starling Lab — content-authenticity displays in newsrooms', + href: 'https://dispatch.starlinglab.org/p/verify-authenticity-displays', + note: 'Reuters, AP, Rolling Stone and others ship C2PA content credentials backed by content-addressed archives (IPFS / Filecoin) — early real-world provenance, not the default-everywhere threshold (Q2).', + }, + ], + }, + { + area: 'digital-human-rights', + opportunitySpace: 'Sovereign Infrastructure for AI & Agents', + title: 'Agents run on open rails', + signal: + 'A frontier-scale model is trained across independent decentralized hardware, or a meaningful share of agent-to-agent economic activity settles on open permissionless compute / storage / identity.', + cascade: + 'The rights architecture of the agent economy is set in the open rather than by whoever owns the cluster.', + contribution: { + inputs: 'The Filecoin / open-compute portfolio; PL capital and coordination across storage, compute, and identity.', + activities: 'Building open compute and storage rails and bridging them with identity for agents.', + outputs: 'Filecoin and the open-compute portfolio — Fluence, Spheron, Expanso, Impossible Cloud, Lava, Fleek — with integrations across storage, compute, and identity.', + }, + roles: ['infrastructure', 'connection', 'capital'], + status: 'watching', + }, + + // ── FA2 · Economies & Governance ───────────────────────────────────────── + { + area: 'economies-governance', + opportunitySpace: 'Sovereign Digital Public Infrastructure', + title: 'Programmable government in production', + signal: + 'At least one sovereign moves >$1B/yr of real public funds through programmable, real-time-auditable rails; 3+ jurisdictions use selective-disclosure credentials for a high-stakes function (election, passport, census).', + cascade: + 'Digital government crosses from digitization to transformation; a reference deployment others can copy.', + contribution: { + inputs: 'Standards and procurement playbooks; convening capacity with sovereigns and builders; funding for DPI primitives.', + activities: 'Writing standards and playbooks, convening sovereigns with builders, and funding DPI primitives.', + outputs: 'Published playbooks, convened sovereign–builder cohorts, and funded DPI primitives.', + }, + roles: ['connection', 'capital', 'permission'], + status: 'watching', + }, + { + area: 'economies-governance', + opportunitySpace: 'Computational Coordination & Governance', + title: 'A binding decision at scale', + signal: + 'A city or government makes a consequential, binding decision through a computational mechanism with tens of thousands participating, beating the legacy process on cost, turnout, or trust.', + cascade: + 'Deliberation tools move from advisory to part of real decision-making infrastructure.', + contribution: { + inputs: 'Support for computational-deliberation mechanisms (e.g. Simocracy, broad-listening tools); convening capacity.', + activities: 'Supporting mechanism teams and convening government teams with tool teams.', + outputs: 'Simocracy and broad-listening tools, and the government–tool convenings around them.', + }, + roles: ['connection', 'capital'], + status: 'watching', + liveEvidence: [ + { + label: 'Simocracy governance simulation — live participation', + href: '/areas/economies-governance/impact/live-dashboard/', + note: 'Live activity from a PL-supported mechanism. This is contribution evidence (Q3) from a simulation — not the binding-decision-in-government threshold (Q2).', + }, + ], + }, + { + area: 'economies-governance', + opportunitySpace: 'Programmable Capital Allocation', + title: 'Public goods become a financeable category', + signal: + '>$1B/yr flows through programmable allocation, a mainstream allocator (DFI, pension, sovereign fund) treats it as infrastructure, and a material real-world outcome is documented.', + cascade: + 'Funding public goods becomes a durable capital market rather than a niche.', + contribution: { + inputs: 'Hypercerts (PL origin) and the Gitcoin / Funding-the-Commons lineage; PL evidence and standards work.', + activities: 'Originating allocation mechanisms, building evidence and standards, and converting work into ventures.', + outputs: 'Hypercerts, Funding the Commons, and ventures spun out of / funded across this lineage such as Molecule.', + }, + roles: ['infrastructure', 'capital', 'translation'], + status: 'early-signal', + }, + { + area: 'economies-governance', + opportunitySpace: 'Verifiable Real-World Infrastructure & Systems', + title: 'Capital that pays on verified outcomes', + signal: + 'A $1B+ climate or public-goods fund disburses against independently verified real-world outcomes, faster and cheaper than a legacy audit.', + cascade: + 'Verification becomes the rail capital runs on; the loop back to better decisions closes.', + contribution: { + inputs: 'Funding for MRV / outcome-verification teams (e.g. GainForest, Glow); benchmark and standards convening.', + activities: 'Backing MRV teams and convening benchmark and standards work for outcome verification.', + outputs: 'GainForest, Glow, WeatherXM, and the verification benchmarks and standards they inform.', + }, + roles: ['legibility', 'connection', 'capital'], + status: 'early-signal', + liveEvidence: [ + { + label: 'GainForest & Glow — live verification activity', + href: '/areas/economies-governance/impact/live-dashboard/', + note: 'Live output from PL-backed MRV teams on this critical path. Contribution evidence (Q3) — not the $1B-disbursed-against-verified-outcomes threshold (Q2).', + }, + ], + }, + + // ── FA4 · Neurotech ────────────────────────────────────────────────────── + { + area: 'neurotech', + opportunitySpace: 'Neural Augmentation (BCI)', + title: 'The BCI app store', + signal: + 'A regulator clears a standardized software / API layer for a commercial BCI (with medical guardrails), and the first third-party app launches and is adopted.', + cascade: + 'BCI value compounds after surgery; a developer ecosystem forms; demand shifts toward elective use.', + contribution: { + inputs: 'PL Neuro standards work for a BCI component ecosystem; convening capacity across regulators, device makers, and developers.', + activities: 'Defining the BCI component-ecosystem standard and convening regulators, makers, and developers.', + outputs: 'A draft BCI component / API standard and the regulator–maker–developer convenings around it.', + }, + roles: ['connection', 'permission'], + status: 'watching', + }, + { + area: 'neurotech', + opportunitySpace: 'Biologically Inspired Intelligence (NeuroAI)', + title: 'Neural distillation', + signal: + 'A major lab matches frontier reasoning by training on human neural data at a fraction of the parameters / energy, or venture funding surges into consumer EEG to harvest cognitive data for AI.', + cascade: + 'High-fidelity neural data becomes as valuable to AI as text; comp-neuro talent flow reverses.', + contribution: { + inputs: 'The PL Neuro talent network bridging AI labs and comp neuro; investment in neural-data infrastructure and norms.', + activities: 'Bridging AI-lab and comp-neuro talent and building neural-data infrastructure and norms.', + outputs: 'The PL Neuro talent network and the neural-data infrastructure and norms it seeds.', + }, + roles: ['infrastructure', 'connection', 'capital'], + status: 'watching', + }, + { + area: 'neurotech', + opportunitySpace: 'Biologically Inspired Intelligence (NeuroAI)', + title: 'The neuromorphic energy pivot', + signal: + 'A brain-inspired model matches state-of-the-art performance at 3+ orders of magnitude better energy efficiency, and frontier labs begin acquiring neuromorphic startups.', + cascade: + 'Neurally derived design becomes foundational to commercial AI; AI is tethered to neuroscience.', + contribution: { + inputs: 'Funding for NeuroAI / neuromorphic research and demos; benchmark design defining the efficiency target.', + activities: 'Funding neuromorphic research and demos and defining the efficiency benchmark.', + outputs: 'PL-funded NeuroAI demos and the energy-efficiency benchmarks that frame the target.', + }, + roles: ['legibility', 'capital'], + status: 'watching', + }, + { + area: 'neurotech', + opportunitySpace: 'Whole Organism Emulation (WBE)', + title: 'Memory retrieval in simulation', + signal: + 'A reconstructed mouse connectome simulated in silico reproduces a specific behavior the biological mouse learned before its connectome was harvested.', + cascade: + 'WBE turns from speculation into a benchmarked engineering discipline; serious policy engagement begins.', + contribution: { + inputs: 'PL Neuro benchmark definition; connectomics workshops and throughput targets; engineering capacity for a demo.', + activities: 'Defining the WBE benchmark, running connectomics workshops, and engineering a reference demo.', + outputs: 'The WBE benchmark, connectomics throughput targets, and a PL-engineered demo.', + }, + roles: ['legibility', 'connection'], + status: 'watching', + }, +] + +export function pointsForArea(area: FocusAreaKey | 'all'): InflectionPoint[] { + if (area === 'all') return INFLECTION_POINTS + return INFLECTION_POINTS.filter((p) => p.area === area) +} diff --git a/src/lib/market-signals.ts b/src/lib/market-signals.ts new file mode 100644 index 00000000..5338f177 --- /dev/null +++ b/src/lib/market-signals.ts @@ -0,0 +1,361 @@ +// Prediction-market "field" signal for each inflection point. +// +// Each inflection point can be paired with the prediction market that most +// closely matches its pre-registered threshold (Q2). The platform is chosen per +// point on match quality, NOT convenience: +// - Polymarket — deepest liquidity for crypto/AI/identity/BCI-valuation themes. +// - Kalshi — regulated venue; better for US-regulatory / adoption-count themes. +// - Metaculus — reputation-weighted community forecasts for long-horizon, +// research-heavy bets no money market prices (e.g. WBE). Its API +// requires a token (METACULUS_API_TOKEN); without one we still +// link the question but show no live number. +// +// A market read is an *external, independent* estimate on the FIELD axis +// ("will it happen?") — it is never PL contribution (Q3) and never a settled Q2. +// Where no market exists, that gap is itself informative: the bet is early or +// contrarian enough that no one is pricing it yet. + +export type Platform = 'polymarket' | 'kalshi' | 'metaculus' +export type SignalMatch = 'direct' | 'proxy' | 'gap' + +type MarketRef = + | { platform: 'polymarket'; slug: string; hint?: string; question: string; url: string } + | { platform: 'kalshi'; ticker: string; question: string; url: string } + | { platform: 'metaculus'; id: number; kind?: 'binary' | 'date'; question: string; url: string } + +export type MarketMapping = { + /** Must match InflectionPoint.title exactly. */ + title: string + match: SignalMatch + /** Best-match market (drives the platform label). Omitted for gaps. */ + primary?: MarketRef + /** Liquidity fallback used only if the primary returns no live price. */ + fallback?: MarketRef + note: string +} + +export type MarketSignal = { + match: SignalMatch + platform: Platform | null + /** 0..1 crowd "yes" probability, or null when unavailable. */ + prob: number | null + /** Total money traded through the market, in USD, or null when unavailable. */ + volume: number | null + question: string | null + url: string | null + /** Pre-formatted headline reading for non-probability markets (e.g. a Metaculus date estimate, “~2068”). */ + readout?: string + /** True when prob came from the fallback rather than the best-match market. */ + viaFallback: boolean + note: string +} + +// ── Mapping: inflection point → closest market ──────────────────────────────── +export const MARKET_MAPPINGS: MarketMapping[] = [ + { + title: 'Communication that cannot be switched off', + match: 'gap', + note: 'No liquid market prices a metadata-resistant messenger at scale or surviving a shutdown — a genuine white space.', + }, + { + title: 'Personhood without the state in the loop', + match: 'proxy', + primary: { + platform: 'polymarket', + slug: 'over-30m-humans-verified-on-world-network-by-december-31', + question: 'Over 30M humans verified on World Network by Dec 31?', + url: 'https://polymarket.com/event/over-30m-humans-verified-on-world-network-by-december-31', + }, + note: 'Proof-of-human adoption at scale is the closest live analogue; Polymarket has the deepest World/identity liquidity.', + }, + { + title: 'Provenance becomes the default for truth', + match: 'proxy', + primary: { + platform: 'kalshi', + ticker: 'KXAILEGISLATION-27-JAN01', + question: 'Will AI regulation become US law by 2027?', + url: 'https://kalshi.com/markets/kxailegislation/ai-regulation', + }, + note: 'Provenance mandates ride on AI regulation; Kalshi (regulated, US-policy focus) is the better-matched venue than any crypto market.', + }, + { + title: 'Agents run on open rails', + match: 'proxy', + primary: { + platform: 'polymarket', + slug: 'us-government-removes-public-access-to-another-major-ai-model-in-2026-20260703202936862', + question: 'US government removes public access to another major AI model in 2026?', + url: 'https://polymarket.com/event/us-government-removes-public-access-to-another-major-ai-model-in-2026-20260703202936862', + }, + note: 'A contrarian, inverse read: if Washington moves to cut off public access to a major (open, largely Chinese) AI model, that is a headwind for open, permissionless rails — high odds here cut against this inflection point, not for it.', + }, + { + title: 'Programmable government in production', + match: 'gap', + note: 'No liquid market prices $1B/yr of public funds moving through programmable, real-time-auditable rails, or selective-disclosure credentials at national scale — a genuine white space.', + }, + { + title: 'A binding decision at scale', + match: 'gap', + note: 'No market prices a binding, at-scale computational decision. We already surface a live Simocracy signal here (Q3 contribution, not Q2).', + }, + { + title: 'Public goods become a financeable category', + match: 'gap', + note: 'No liquid market prices $1B/yr flowing through programmable allocation — long-horizon white space.', + }, + { + title: 'Capital that pays on verified outcomes', + match: 'gap', + note: 'No market prices a $1B+ outcomes-verified climate fund.', + }, + { + title: 'The BCI app store', + match: 'proxy', + primary: { + platform: 'kalshi', + ticker: 'KXNEURALINKCOUNT-26-40', + question: 'Will Neuralink implant ≥40 people in 2026?', + url: 'https://kalshi.com/markets/kxneuralinkcount/neuralink-count', + }, + fallback: { + platform: 'polymarket', + slug: 'will-neuralinks-valuation-hit-by-december-31', + hint: '$75B', + question: "Will Neuralink's valuation hit $75B by Dec 31?", + url: 'https://polymarket.com/event/will-neuralinks-valuation-hit-by-december-31', + }, + note: 'Implant-count (Kalshi) is the best adoption/scaling proxy for a commercial BCI ecosystem; Polymarket valuation is the liquidity fallback.', + }, + { + title: 'Neural distillation', + match: 'proxy', + primary: { + platform: 'metaculus', + id: 372, + question: 'Will brain emulation be the first successful route to human-level AI?', + url: 'https://www.metaculus.com/questions/372/brain-emulation-produces-first-human-ai/', + }, + note: 'The closest live forecast: Metaculus’ long-running question on whether brain emulation is the first route to human-level AI — a reputation-weighted proxy for how seriously the crowd takes neural-data-driven paths to frontier AI. No money at stake, and the crowd prices it in the low single digits.', + }, + { + title: 'The neuromorphic energy pivot', + match: 'gap', + note: 'No market prices a neuromorphic model matching SOTA at 1000× efficiency — pure white space.', + }, + { + title: 'Memory retrieval in simulation', + match: 'proxy', + primary: { + platform: 'metaculus', + id: 2813, + kind: 'date', + question: 'When will the first human whole-brain emulation happen?', + url: 'https://www.metaculus.com/questions/2813/date-of-first-human-whole-brain-emulation/', + }, + note: 'The closest live forecast: Metaculus’ community estimate for the date of the first human whole-brain emulation — the crowd currently places it decades out. A reputation-weighted forecast, no money at stake.', + }, +] + +export function mappingByTitle(): Record { + return Object.fromEntries(MARKET_MAPPINGS.map((m) => [m.title, m])) +} + +// ── Fetchers ───────────────────────────────────────────────────────────────── +const GAMMA = 'https://gamma-api.polymarket.com' +const KALSHI = 'https://api.elections.kalshi.com/trade-api/v2' +const METACULUS = 'https://www.metaculus.com/api' +const METACULUS_TOKEN = process.env.METACULUS_API_TOKEN +const REVALIDATE = 3600 + +function safeParse(s?: string): string[] | null { + if (!s) return null + try { + const v = JSON.parse(s) + return Array.isArray(v) ? v.map(String) : null + } catch { + return null + } +} + +function distinctiveToken(s?: string): string | null { + if (!s) return null + const money = s.match(/\$[0-9]+(?:\.[0-9]+)?[bmk]?/i) + if (money) return money[0].toLowerCase() + const num = s.match(/[0-9]+m\b|[0-9]{2,}/i) + return num ? num[0].toLowerCase() : null +} + +type Quote = { prob: number | null; volume: number | null; readout?: string } + +async function fetchPolymarket(slug: string, hint?: string): Promise { + try { + const res = await fetch(`${GAMMA}/events?slug=${encodeURIComponent(slug)}`, { + next: { revalidate: REVALIDATE }, + }) + if (!res.ok) return null + const events = (await res.json()) as Array<{ + title?: string + markets?: Array<{ question?: string; outcomes?: string; outcomePrices?: string; volumeNum?: number }> + }> + const markets = events?.[0]?.markets + if (!markets?.length) return null + const parsed = markets + .map((m) => { + const outcomes = safeParse(m.outcomes) + const prices = safeParse(m.outcomePrices) + if (!outcomes || !prices) return null + const yesIdx = outcomes.findIndex((o) => o.toLowerCase() === 'yes') + if (yesIdx === -1) return null + const yes = Number(prices[yesIdx]) + if (Number.isNaN(yes)) return null + const volume = typeof m.volumeNum === 'number' && m.volumeNum > 0 ? m.volumeNum : null + return { yes, volume, q: (m.question || '').toLowerCase() } + }) + .filter((x): x is { yes: number; volume: number | null; q: string } => x != null) + if (!parsed.length) return null + const token = distinctiveToken(hint) + const pick = (token && parsed.find((m) => m.q.includes(token))) || parsed[0] + return { prob: pick.yes, volume: pick.volume } + } catch { + return null + } +} + +async function fetchKalshi(ticker: string): Promise { + try { + const res = await fetch(`${KALSHI}/markets/${encodeURIComponent(ticker)}`, { + next: { revalidate: REVALIDATE }, + }) + if (!res.ok) return null + const m = ((await res.json()) as { market?: Record }).market + if (!m) return null + // dollar_volume is in USD; volume is in contracts (each settles $0-$1), so the + // contract count is a reasonable USD upper bound when no dollar figure is given. + const dollarVol = m.dollar_volume + const contractVol = m.volume + const volume = + typeof dollarVol === 'number' && dollarVol > 0 + ? dollarVol + : typeof contractVol === 'number' && contractVol > 0 + ? contractVol + : null + // Prices are in cents (0-100). Prefer last trade, else the bid/ask midpoint. + const last = m.last_price + if (typeof last === 'number' && last > 0) return { prob: last / 100, volume } + const bid = m.yes_bid + const ask = m.yes_ask + if (typeof bid === 'number' && typeof ask === 'number' && (bid > 0 || ask > 0)) { + return { prob: (bid + ask) / 2 / 100, volume } + } + return null + } catch { + return null + } +} + +// Metaculus gates its API behind a token. With METACULUS_API_TOKEN set we read +// the recency-weighted community prediction for a binary question; without it, +// or on any failure, we return null and the caller still links the question +// (prob shows as “—”). Metaculus has no money at stake, so volume is always null. +async function fetchMetaculus(id: number, kind: 'binary' | 'date' = 'binary'): Promise { + if (!METACULUS_TOKEN) return null + try { + const res = await fetch(`${METACULUS}/posts/${id}/`, { + headers: { Authorization: `Token ${METACULUS_TOKEN}` }, + next: { revalidate: REVALIDATE }, + }) + if (!res.ok) return null + const d = (await res.json()) as { + question?: { + scaling?: { range_min?: number; range_max?: number } + aggregations?: { recency_weighted?: { latest?: { centers?: number[]; means?: number[] } } } + } + } + const q = d.question + const latest = q?.aggregations?.recency_weighted?.latest + const center = latest?.centers?.[0] ?? latest?.means?.[0] + if (typeof center !== 'number') return null + if (kind === 'date') { + // Date questions report the community CDF location in 0..1; unscale it + // linearly across the question's range (unix seconds) to a median year. + const min = q?.scaling?.range_min + const max = q?.scaling?.range_max + if (typeof min !== 'number' || typeof max !== 'number') return null + const year = new Date((min + center * (max - min)) * 1000).getFullYear() + if (!Number.isFinite(year)) return null + return { prob: null, volume: null, readout: `~${year}` } + } + return { prob: center, volume: null } + } catch { + return null + } +} + +async function fetchRef(ref: MarketRef): Promise { + switch (ref.platform) { + case 'polymarket': + return fetchPolymarket(ref.slug, ref.hint) + case 'kalshi': + return fetchKalshi(ref.ticker) + case 'metaculus': + return fetchMetaculus(ref.id, ref.kind) + } +} + +/** Resolve one mapping into a display-ready signal (best-match, with fallback). */ +export async function resolveSignal(m: MarketMapping): Promise { + if (m.match === 'gap' || !m.primary) { + return { match: 'gap', platform: null, prob: null, volume: null, question: null, url: null, viaFallback: false, note: m.note } + } + const primary = await fetchRef(m.primary) + if (primary != null) { + return { + match: m.match, + platform: m.primary.platform, + prob: primary.prob, + volume: primary.volume, + readout: primary.readout, + question: m.primary.question, + url: m.primary.url, + viaFallback: false, + note: m.note, + } + } + if (m.fallback) { + const fb = await fetchRef(m.fallback) + if (fb != null) { + return { + match: m.match, + platform: m.fallback.platform, + prob: fb.prob, + volume: fb.volume, + readout: fb.readout, + question: m.fallback.question, + url: m.fallback.url, + viaFallback: true, + note: m.note, + } + } + } + // Market exists but no live price available — still link it. + return { + match: m.match, + platform: m.primary.platform, + prob: null, + volume: null, + question: m.primary.question, + url: m.primary.url, + viaFallback: false, + note: m.note, + } +} + +/** Resolve every mapping, keyed by inflection-point title. */ +export async function resolveAllSignals(): Promise> { + const entries = await Promise.all( + MARKET_MAPPINGS.map(async (m) => [m.title, await resolveSignal(m)] as const), + ) + return Object.fromEntries(entries) +} diff --git a/src/lib/site-config.ts b/src/lib/site-config.ts index 1078cd16..70e11ae0 100644 --- a/src/lib/site-config.ts +++ b/src/lib/site-config.ts @@ -35,6 +35,7 @@ export const mainNav: NavItem[] = [ { name: 'Neurotech', url: '/areas/neurotech/' }, ], }, + { name: 'Impact', url: '/impact/' }, { name: 'Insights', url: '/insights/' }, { name: 'Team', url: '/authors/' }, ]