Add anonymised world map of active eggs
Some checks failed
Build & publish Docker images / Server image (push) Failing after 15s
Build & publish Docker images / Egg image (push) Failing after 17s

Server geolocates each egg by IP on first contact (ip-api.com), snaps
coordinates to the nearest 1-degree grid (~111 km) and adds a small
deterministic per-egg jitter so eggs in the same cell don't overlap.
Only country + rounded lat/lon are stored; exact IPs are never saved.

Front-end uses Leaflet with CartoDB dark tiles. Markers update on every
poll cycle and are coloured to match the current coherence index.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hexadual
2026-04-30 01:32:19 -05:00
parent ee2249c5f8
commit 62b9995cd9
2 changed files with 190 additions and 3 deletions

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Global Consciousness Project — Live Dot</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
:root {
--dot-color: #00cc44;
@@ -107,6 +108,41 @@
margin: .5rem 0 2rem;
}
/* ── map ── */
#map-wrap {
width: min(480px, 96vw);
background: #0e0e1c;
border: 1px solid #18182a;
border-radius: 12px;
overflow: hidden;
margin-bottom: 1rem;
}
#map-title {
font-size: .65rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #303050;
padding: 1rem 1.6rem .6rem;
}
#map {
height: 240px;
width: 100%;
background: #080812;
}
/* Override Leaflet chrome for dark theme */
.leaflet-container { background: #080812; }
.leaflet-control-attribution { display: none; }
.leaflet-tooltip {
background: #10101e;
border: 1px solid #202038;
color: #9090b8;
font-size: .75rem;
padding: .3rem .6rem;
border-radius: 6px;
box-shadow: none;
}
.leaflet-tooltip-top::before { border-top-color: #202038; }
/* ── legend ── */
.card {
width: min(480px, 96vw);
@@ -228,6 +264,12 @@
<canvas id="chart"></canvas>
<!-- map -->
<div id="map-wrap">
<p id="map-title">Active eggs — anonymised to ~111 km</p>
<div id="map"></div>
</div>
<!-- legend -->
<div class="card">
<p class="card-title">What the color means</p>
@@ -278,6 +320,7 @@
<p id="tick"></p>
<div id="toast">Copied!</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// ── color mapping ──────────────────────────────────────────────────────
function palette(index) {
@@ -392,6 +435,7 @@
try {
const [status, history] = await Promise.all([fetchStatus(), fetchHistory()]);
currentIndex = status.index;
applyColor(status.index);
document.getElementById('status-label').innerHTML = label(status.index, status.n_eggs);
document.getElementById('s-index').textContent = Math.round(status.index) + '%';
@@ -399,11 +443,56 @@
document.getElementById('s-age').textContent = ageStr(status.timestamp);
drawChart(history.slice().reverse());
updateMap();
} catch (e) {
document.getElementById('status-label').textContent = 'Connection error — retrying…';
}
}
// ── map ───────────────────────────────────────────────────────────────
const leafletMap = L.map('map', {
center: [20, 10],
zoom: 1,
zoomControl: false,
scrollWheelZoom: false,
attributionControl: false,
});
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png', {
subdomains: 'abcd',
maxZoom: 6,
}).addTo(leafletMap);
let eggMarkers = [];
let currentIndex = 50;
async function updateMap() {
try {
const r = await fetch('/api/map');
const eggs = await r.json();
eggMarkers.forEach(m => m.remove());
eggMarkers = [];
const [color] = palette(currentIndex);
eggs.forEach(egg => {
const m = L.circleMarker([egg.lat, egg.lon], {
radius: 7,
fillColor: color,
color: color,
fillOpacity: 0.75,
weight: 0,
}).addTo(leafletMap);
m.bindTooltip(egg.country || 'Unknown', { direction: 'top', offset: [0, -4] });
eggMarkers.push(m);
});
// Update egg count in map title
document.getElementById('map-title').textContent =
`Active eggs (${eggs.length}) — anonymised to ~111 km`;
} catch (_) {}
}
// ── embed code ────────────────────────────────────────────────────────
const origin = window.location.origin;
document.querySelectorAll('.origin-span').forEach(el => el.textContent = origin);