Skip to content
This repository was archived by the owner on Jun 16, 2026. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 147 additions & 38 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
#title { height: 36px; display: flex; align-items: center; padding: 0 14px; background: #1a1a2e; color: #fff; font-size: 14px; font-weight: 600; letter-spacing: 0.3px; }
#title span { opacity: 0.6; margin-left: 8px; font-weight: 400; }
#title a { margin-left: auto; display: flex; align-items: center; color: #fff; opacity: 0.6; transition: opacity 0.15s; }
#title a:hover { opacity: 1; }
#map { height: calc(100vh - 36px); width: 100%; }
.legend { background: white; padding: 8px 12px; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,0.3); line-height: 1.6; font-size: 13px; }
.legend i { width: 12px; height: 12px; display: inline-block; margin-right: 6px; border-radius: 50%; vertical-align: middle; }
.legend .line { width: 20px; height: 0; border-top: 2px dashed #888; display: inline-block; margin-right: 6px; vertical-align: middle; }
.playback-control a { background: white; width: 34px; height: 34px; line-height: 34px; text-align: center; font-size: 18px; display: block; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,0.3); text-decoration: none; color: #333; }
.playback-control a:hover { background: #f4f4f4; }
</style>
</head>
<body>
<div id="title">Tezos Protocol Cities</div>
<div id="title"><span id="title-text">Tezos Protocol Cities</span><a href="https://github.com/cotezos/tezosprotocolmap" target="_blank" rel="noopener" aria-label="GitHub"><svg height="20" width="20" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.64 7.64 0 0 1 2-.27c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg></a></div>
<div id="map"></div>
<script>
(function () {
Expand All @@ -39,13 +43,134 @@
return el.innerHTML;
}

// Shared state.
var entries = [];
var markers = [];
var trail = null;
var trailCoords = [];
var playbackTimer = null;
var playbackIndex = 0;
var isPlaying = false;

function buildPopup(d) {
var hashTrunc = d.hash ? d.hash.substring(0, 12) + "..." : "N/A";
var date = d.activationDate ? d.activationDate.split("T")[0] : "N/A";
var network = d.mainnet ? "Mainnet" : "Testnet only";

var html =
"<strong>" + esc(String(d.number).padStart(3, "0") + " " + d.name) + "</strong><br>" +
"Hash: <code>" + esc(hashTrunc) + "</code><br>" +
"Activated: " + esc(date) + "<br>" +
"Network: " + esc(network);

if (d.voting) {
html += "<br>Voted: Epoch " + esc(String(d.voting.epoch)) +
" \u2014 " + esc(d.voting.result) +
"<br>Ballots: " + d.voting.yayBallots + " yay, " +
d.voting.nayBallots + " nay, " + d.voting.passBallots + " pass";
}
Comment on lines +66 to +71

return html;
}

function addMarker(index) {
var d = entries[index];
var color = d.mainnet ? COLOR_MAINNET : COLOR_TESTNET;
var marker = L.circleMarker([d.lat, d.lon], {
radius: 7,
fillColor: color,
color: "#fff",
weight: 1.5,
fillOpacity: 0.9
}).addTo(map);
marker.bindPopup(buildPopup(d));
markers.push(marker);

trailCoords.push([d.lat, d.lon]);
if (trail) {
map.removeLayer(trail);
}
if (trailCoords.length > 1) {
trail = L.polyline(trailCoords, {
color: "#888888",
weight: 1.5,
dashArray: "6 4",
opacity: 0.5
}).addTo(map);
}
}

function clearAll() {
markers.forEach(function (m) { map.removeLayer(m); });
markers = [];
if (trail) {
map.removeLayer(trail);
trail = null;
}
trailCoords = [];
}

function updateTitle(count) {
var text = "Tezos Protocol Cities";
if (typeof count === "number") {
text += " \u2014 " + count + " protocol" + (count !== 1 ? "s" : "");
}
document.getElementById("title-text").textContent = text;
}
Comment on lines +113 to +119

function showAll() {
clearAll();
for (var i = 0; i < entries.length; i++) {
addMarker(i);
}
updateTitle(entries.length);
}

// Playback logic.
function stopPlayback() {
if (playbackTimer) {
clearTimeout(playbackTimer);
playbackTimer = null;
}
isPlaying = false;
playbackIndex = 0;
if (playBtn) playBtn.textContent = "\u25B6";
}

function playStep() {
if (playbackIndex >= entries.length) {
stopPlayback();
return;
}
addMarker(playbackIndex);
updateTitle(playbackIndex + 1);
playbackIndex++;
playbackTimer = setTimeout(playStep, 800);
}

function togglePlayback() {
if (isPlaying) {
stopPlayback();
// Leave current markers visible, show all.
showAll();
} else {
clearAll();
isPlaying = true;
playbackIndex = 0;
if (playBtn) playBtn.textContent = "\u23F8";
playStep();
}
}

var playBtn = null;

fetch(PROTOCOLS_URL)
.then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then(function (data) {
var entries = Object.keys(data).map(function (name) {
entries = Object.keys(data).map(function (name) {
var d = data[name];
d.name = name;
return d;
Expand All @@ -55,43 +180,27 @@
return a.number - b.number;
});

document.getElementById("title").innerHTML =
"Tezos Protocol Cities" + "<span>\u2014 " + entries.length + " protocols</span>";

var trailCoords = [];

entries.forEach(function (d) {
var color = d.mainnet ? COLOR_MAINNET : COLOR_TESTNET;
var marker = L.circleMarker([d.lat, d.lon], {
radius: 7,
fillColor: color,
color: "#fff",
weight: 1.5,
fillOpacity: 0.9
}).addTo(map);

var hashTrunc = d.hash ? d.hash.substring(0, 12) + "..." : "N/A";
var date = d.activationDate ? d.activationDate.split("T")[0] : "N/A";
var network = d.mainnet ? "Mainnet" : "Testnet only";

marker.bindPopup(
"<strong>" + esc(String(d.number).padStart(3, "0") + " " + d.name) + "</strong><br>" +
"Hash: <code>" + esc(hashTrunc) + "</code><br>" +
"Activated: " + esc(date) + "<br>" +
"Network: " + esc(network)
);

trailCoords.push([d.lat, d.lon]);
});
showAll();

if (trailCoords.length > 1) {
L.polyline(trailCoords, {
color: "#888888",
weight: 1.5,
dashArray: "6 4",
opacity: 0.5
}).addTo(map);
}
// Playback control.
var PlaybackControl = L.Control.extend({
options: { position: "topleft" },
onAdd: function () {
var container = L.DomUtil.create("div", "playback-control leaflet-bar");
playBtn = L.DomUtil.create("a", "", container);
playBtn.href = "#";
playBtn.title = "Play/Pause timeline";
playBtn.textContent = "\u25B6";
playBtn.setAttribute("role", "button");
L.DomEvent.disableClickPropagation(container);
Comment on lines +192 to +195
L.DomEvent.on(playBtn, "click", function (e) {
L.DomEvent.preventDefault(e);
togglePlayback();
});
return container;
}
});
new PlaybackControl().addTo(map);

// Legend control.
var legend = L.control({ position: "bottomright" });
Expand Down