The globe is the overview screen. Each city is at a different stage of development. The dashboard shows what exists — founders, permits, properties, services, stays, creators, businesses, opportunities — and what's missing. The gap is the opportunity.
A player zooms into any city and immediately comprehends its economy: what assets are there, what state they're in, and where the underserved markets are.
getMapCountryStats(cc) already returns counts per category per country. Extend to city-level granularity. The dashboard renders from a single JSON response.
{
"city": "new-york",
"country": "US",
"stage": "liquid",
"assets": {
"properties": { "count": 870241, "states": { "idle": 812000, "claimed": 41200, "for_rent": 12800, "for_sale": 4241 } },
"businesses": { "count": 186402 },
"permits": { "count": 24531, "last_24h": 47 },
"creators": { "count": 2847, "states": { "scraped": 2102, "claimed": 580, "verified": 165 } },
"services": { "count": 312, "states": { "active": 248, "booked": 64 } },
"stays": { "count": 89, "states": { "available": 61, "booked": 28 } },
"founders": { "count": 47 },
"opportunities": { "count": 48, "states": { "open": 31, "assigned": 17 } }
},
"transactions_30d": 284,
"gaps": ["none"]
}
EcosystemDashboard.tsx{
citySlug: string
isOpen: boolean
onClose: () => void
}
// Pattern: same as StaysFloatingCard
// Position: absolute right-2 top-2 bottom-2
// Width: 360px
// Background: bg-white/70 backdrop-blur-xl
// Triggered by: clicking city cluster on globe
// Data: single fetch to /api/v1/ecosystem/:slug
// Stage computed from asset counts + txn volume
// Route: /ecosystem (new route in MapShell) // Theme: dark basemap, zoom 2 (world view) // Layer: city-ecosystem-clusters // Source: /api/v1/ecosystem/clusters // Click handler: fly to city → open dashboard
function deriveStage(assets, txn30d) {
const typesPresent = Object.values(assets)
.filter(a => a.count > 0).length
if (typesPresent >= 8 && txn30d >= 500)
return 'liquid'
if (typesPresent >= 5 && txn30d >= 50)
return 'active'
if (typesPresent >= 3 && txn30d >= 1)
return 'growing'
return 'genesis'
}
// Gap detection
function detectGaps(assets) {
const ALL = ['properties','businesses','permits',
'creators','services','stays','founders',
'opportunities']
return ALL.filter(k => assets[k].count === 0)
}
src/map-layers/creators.ts dual-mode rendering. Zoom < 5: city ecosystem dots with glow. Zoom >= 5: transition to regular category layers.
// Source: GeoJSON from /api/v1/ecosystem/clusters // Each feature: { type: "Feature", geometry: { type: "Point", coordinates: [lng, lat] }, properties: { city: "new-york", label: "NYC", stage: "liquid", // → color total_assets: 1084380, // → dot size types_present: 8, // → glow intensity txn_30d: 284 // → pulse speed } } // Dot color by stage genesis: #6b7280 (gray-500, dim) growing: #14b8a6 (teal-500) active: #f59e0b (amber-500) liquid: #f59e0b (amber-500, bright glow) // Dot size by total_assets circle-radius: interpolate(total_assets) 0 → 4px 1000 → 6px 10000 → 8px 100000 → 12px 500000+→ 16px
/ecosystem
ecosystem: {
name: 'ecosystem',
applyBasemap: applyDarkTheme,
initialCamera: {
center: [20, 25], // centered on Atlantic
zoom: 2, // world view
pitch: 0,
bearing: 0
}
}
// Router — new route under MapShell
<Route path="ecosystem" element={<Ecosystem />} />
// Navigation — add to top nav bar
// Position: between "Digital City" and "Marketplace"
// Label: "Ecosystem"
// Interaction flow:
// 1. Land on /ecosystem → dark globe, city dots
// 2. Click city dot → flyTo city, open dashboard
// 3. Click asset row in dashboard → navigate to
// that category's existing route (/stays, etc.)
// 4. Click gap → shows CTA for that missing layer
The digital city spans every geography. Each city is at a different stage of development. The ecosystem dashboard makes the stage and the gaps legible at a glance. The gap is the opportunity — a city with properties but no founders, a city with creators but no services. Players see where the economy needs them and enter accordingly.
/ecosystem → new route under MapShellecosystem-clusters.ts → new map layerEcosystemDashboard.tsx → new floating cardgetMapCountryStats() → extend to city-level/api/v1/ecosystem/clusters — all cities with counts/api/v1/ecosystem/:city_slug — single city detailderiveStage() — computed, not configureddetectGaps() — names missing asset types