Real World Bank indicators, live market news, and AI research notes — in one interactive surface. No database. No cloud account. One optional API key.
Economic data is scattered. The World Bank has the numbers, newswires have the context, and neither tells you where a country actually stands. Meridian pulls them into a single view: spin the globe, click a country, and get a full macro dashboard — GDP, growth, inflation, unemployment, trade balance, a ten-year trend, live business headlines, and an institutional-style research note.
It is built to survive its own dependencies. The World Bank API is free and keyless, and it is
also genuinely unreliable — during development it returned sticky 502s for specific
country/indicator pairs for minutes at a time. Meridian retries, caches, and falls back to a
bundled snapshot of 264 countries, then tells you in the UI which source each number came
from. Every AI surface degrades to a deterministic, data-derived report when no API key is
configured.
- Demo
- Key features
- Architecture
- Tech stack
- Prerequisites
- Getting started
- Environment variables
- Scripts
- Project structure
- Reliability
- Accessibility
- Performance
- Testing
- Deployment
- Known limitations
- Credits
- License
Opening the command palette, jumping to Brazil, switching on the GDP-per-capita heatmap and trade routes, then scrubbing the timeline from 2015 to 2025 and watching the globe recolour. Every figure on screen is live World Bank data.
| Feature | What it does | |
|---|---|---|
| 🌐 | Interactive 3D globe | Click any of 177 countries for a full economic dashboard. Radar-ring selection markers, city pulse rings, and auto-rotate that yields to interaction. |
| 📊 | Real World Bank data | GDP, growth, GDP per capita, CPI inflation, unemployment, life expectancy, exports/imports/balance — free API, no key required. |
| 🛡️ | Honest data provenance | Every reading is labelled Verified, Partly cached, or Cached snapshot, so you always know whether a number came from the live API. |
| 📰 | Live economic news | Google News, BBC, Reuters and country-specific RSS feeds, ranked by freshness × source quality and filtered to the last 72 hours. |
| 🧠 | AI research notes | A Managing-Director-style investment note with recommendation, sector signals and confidence score — grounded in the metrics on screen. |
| 🎙️ | AI news anchor | An on-air briefing read aloud through the Web Speech API. No external TTS service. |
| ⚖️ | Country comparison | Side-by-side macro dashboards with a composite investment verdict and point spread. |
| 🗺️ | Intelligence layers | GDP/capita percentile heatmap, animated bilateral trade routes, city corridors, and an investment-signal overlay. |
| ⏳ | Animated timeline | Scrub or press play to sweep 2015→2025. The globe recolours per year and the sidebar reloads that year's indicators. All eleven years are prefetched in the background so playback is smooth. |
| ⌘ | Command palette | ⌘K / Ctrl K to jump to any country, capital or city, or toggle any layer — fully keyboard-driven. |
| 🔗 | Shareable deep links | Selection, comparison, layers and timeline year all live in the URL: ?c=IN&vs=CN&layers=heatmap,news |
| 🎬 | Demo mode | An auto-cycling tour of six major economies, for when you want it to present itself. |
The interesting part is the middle band — how a single indicator is resolved:
- Live request. Seven indicators fire in parallel per country. Identical in-flight requests are deduped.
- Retry. Three attempts with backoff, each capped at 8s, the whole chain capped at 30s. A failed lookup is never written to the cache, so a blip cannot blank a country for 24 hours.
- Snapshot. Anything still missing is filled per-indicator from a bundled 264-country JSON.
- Labelled response. The sidebar badge reports which of those paths produced the numbers.
Successful lookups go through Next.js's data cache with a 24-hour revalidate, so caching survives serverless cold starts — an in-memory map would not.
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 16 (App Router) | Route handlers, streaming responses, and the fetch data cache in one runtime |
| UI | React 19 + TypeScript 5 | Strict mode throughout, no any in application code |
| Styling | Tailwind CSS v4 | Utility-first, over a small @layer components design system |
| 3D | react-globe.gl / three.js | WebGL globe with polygon, arc, point and ring layers |
| Motion | Framer Motion | Panel transitions and staged reveals |
| AI | Anthropic Claude | Schema-enforced structured outputs — no prompt-prefill JSON hacks |
| Testing | Vitest + Testing Library | A node project for logic, a jsdom project for components |
| Data | World Bank Open Data · Natural Earth · RSS | All free, all keyless |
- Node.js 22 or newer — check with
node --version - npm 10+ (ships with Node 22)
- A WebGL-capable browser. Without one the app detects the gap and shows a usable fallback rather than a blank screen.
- Optional: an Anthropic API key for the AI surfaces. Everything else works without one.
- Optional, for
npm run capture: Google Chrome andffmpegon yourPATH.
1. Clone and install
git clone https://github.com/lumixed/plateful.gitcd plateful && npm install2. Configure the environment (optional — the app runs without it)
cp .env.example .env.localLeave the file untouched for a fully working, keyless build. Add ANTHROPIC_API_KEY if you want
Claude-generated briefings instead of the deterministic fallbacks.
3. Start the dev server
npm run dev4. Open http://localhost:5320
Give the globe a few seconds to boot, then press ⌘K and type a country name.
5. Verify everything before you commit
npm run lint && npx tsc --noEmit && npm test && npm run build| Variable | Required | Default | Purpose |
|---|---|---|---|
ANTHROPIC_API_KEY |
No | — | Enables Claude briefings, research notes and the anchor script. Without it, every AI surface renders a deterministic report built from the same real data. |
CLAUDE_MODEL |
No | claude-opus-5 |
Set claude-haiku-4-5 for cheaper, faster briefings. |
NEXT_PUBLIC_SITE_URL |
No | http://localhost:5320 |
Canonical origin for Open Graph tags. Set this in production or link previews break. |
| Command | What it does |
|---|---|
npm run dev |
Dev server on port 5320 |
npm run build |
Production build |
npm start |
Serve the production build (honours $PORT, defaults to 3000) |
npm run lint |
ESLint — zero warnings enforced in CI |
npm test |
Vitest, both projects |
npm run snapshot |
Rebuild the offline indicator snapshot from the World Bank |
npm run trim-geo |
Re-trim countries.geojson after replacing the source file |
npm run capture |
Regenerate the demo GIF and screenshots in docs/ |
src/
app/
api/ Route handlers — country, news, trade, history, heatmap,
ticker, search, cities, insight (streaming), anchor, report
opengraph-image Social card, generated at build time
components/
Globe/ WebGL scene, capability guard, error boundary, arc data
Sidebar/ Country, city, comparison and investment-report panels
UI/ Top bar, ticker, layer rail, command palette, anchor, timeline
lib/
worldbank.ts Indicator client: retry, dedupe, deadline, snapshot fallback
country-meta.ts Country metadata and sovereign-code list
news-scoring.ts RSS parsing and freshness × source-quality ranking
rate-limit.ts Per-IP token buckets for the AI routes
scores.ts Risk and innovation composites
data/ Country list, city hubs, indicator snapshot, territory notes
hooks/ useUrlState (deep links), useDialog (focus trap), useCountUp
scripts/ Snapshot builder, geojson trimmer, demo capture
docs/ Architecture diagram, demo GIF, screenshots
The World Bank API is the one upstream that matters, and it fails in awkward ways — not outright
downtime, but sticky 502s scoped to a single country and a single indicator. Meridian handles
that in layers:
- Retry — 3 attempts, 8s each, with backoff
- Deadline — the whole chain is capped at 30s, so a hung socket can never pin a request forever
- Negative results are never cached — only successful lookups are memoised
- Per-indicator snapshot fallback — gaps are filled individually, not all-or-nothing
- Provenance in the UI — the badge tells the truth about where each number came from
Refresh the offline snapshot whenever you like:
npm run snapshotIt makes seven bulk calls — one per indicator, not one per country — so it finishes in seconds. A GitHub Action re-runs it monthly and opens a PR if the data has moved.
Lighthouse scores 100 for accessibility. Concretely:
- Every surface is keyboard-reachable;
⌘Kdrives navigation and layers without touching the globe - Dialogs trap focus, close on
Escape, and restore focus to their trigger - Streaming AI text and the news feed are
aria-liveregions - Informational text sits at or above 4.96:1 contrast against the page background; only non-informational dividers fall below
prefers-reduced-motiondisables the ticker marquee and blinking cursors
Lighthouse, desktop preset, against a production build:
| Category | Score |
|---|---|
| Performance | 76 |
| Accessibility | 100 |
| Best Practices | 100 |
| SEO | 100 |
| Web vital | Value |
|---|---|
| First Contentful Paint | 0.2 s |
| Largest Contentful Paint | 1.3 s |
| Cumulative Layout Shift | 0 |
| Speed Index | 1.6 s |
| Total Blocking Time | 400 ms |
Static assets were cut from ~2.6 MB to 376 KB: textures re-encoded to WebP at half resolution,
and countries.geojson reduced to two-decimal precision with unused properties stripped — 588 KB
down to 172 KB, and 56 KB over the wire.
The geometry itself is deliberately not simplified. Running it through a simplifier rewinds polygon rings to RFC 7946 counter-clockwise order, which inverts every country fill on a sphere: the whole globe renders solid, with a country-shaped hole in it.
Performance sits at 76 because Total Blocking Time is 400 ms. three.js and react-globe.gl are a 514 KB chunk that must parse and execute before the globe can draw. That is the honest cost of a WebGL globe, and it is the main thing left to optimise.
npm test86 tests across two Vitest projects:
lib(node) — scoring composites, city pulse maths, RSS parsing and freshness ranking, rate-limit buckets, and the full indicator-resolution path withfetchmocked to fail, hang, or partially succeedui(jsdom) —CountryViewloading / loaded / territory / retry states and provenance badges, the command palette's search and keyboard navigation, and the timeline's playback, wrap-around and manual-override behaviour
Any Node host works. On Vercel:
- Import the repository
- Add
NEXT_PUBLIC_SITE_URL(your production origin) and, optionally,ANTHROPIC_API_KEY - Deploy — no build configuration needed
Long-running route handlers declare maxDuration = 60, because a cold World Bank
year-scoped query can take 10–20s. On a host that caps function duration lower than that,
the timeline prefetch and first country load will fail — raise the cap or shorten the
upstream timeouts in src/lib/worldbank.ts and src/app/api/heatmap/route.ts.
Any other Node host works too — npm run build && npm start, with $PORT respected.
Being straight about what is not solved:
- Rate limiting is per-instance. The AI routes use in-memory token buckets, so on serverless each instance keeps its own count and the effective ceiling is higher than configured. It stops casual abuse, not a determined attacker. A shared store (Vercel KV / Upstash) would fix it, at the cost of the "no external services" property.
- City metrics are estimates. The 29 hubs carry illustrative figures, labelled
(Est.)in the UI. National figures are the real data. - Risk and Innovation are invented composites, derived from World Bank indicators and labelled as Meridian composites — they are not standard indices.
- The bundled snapshot ages. The monthly workflow keeps it current; a fork without Actions enabled will drift.
- The first load of a timeline year is slow. The World Bank generates year-scoped bulk responses on demand and can take 10–20s cold. Meridian prefetches all eleven years the moment the timeline layer is enabled and caches them client- and server-side, so this is only felt once per year per deployment.
- Total Blocking Time is 400 ms, dominated by the WebGL bundle.
Concept inspired by the MIT-licensed PulseEarth hackathon project, rebuilt from scratch on a leaner stack.
- Economic indicators — World Bank Open Data
- Country borders — Natural Earth
- Globe textures — three-globe
- News — Google News, BBC Business, Reuters, and country-specific publications
MIT — see the file for third-party data and asset notices.




