A flag appears — click the country you think it belongs to. Get it right and your score climbs; skip if you're stumped. The map only includes countries large enough to click comfortably (filtered by the areakm2 property), and the flags come straight from flagpedia.net by ISO2 code.
No country names on hover — that would give it away. Guess from the shape and location, or skip to set a flag aside — skipped flags come back around later.
https://api.mapjson.com/v1/geo?layer=countries&detail=medium&properties=name,iso2,areakm2&format=geojson
Flags: https://flagpedia.net/data/flags/h240/{iso2}.png — e.g. .../ua.png for Ukraine.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Flag Guess Game</title>
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<style>
body { font-family: sans-serif; margin: 2rem; }
.game-bar { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
.flag { height: 74px; border: 1px solid #ddd; background: #fff; }
#map-wrap { position: relative; background: #eaf0f2; max-width: 960px; }
#map-wrap svg { width: 100%; display: block; }
.score { position: absolute; top: 10px; right: 10px; background: #1a1a1a; color: #fff;
font: 13px monospace; padding: 6px 12px; border-radius: 20px; display: flex; gap: 12px; }
.s-ok { color: #7fdca9; } .s-no { color: #f2a0a0; }
</style>
</head>
<body>
<div class="game-bar">
<img class="flag" id="flag" alt="Flag to guess">
<div id="prompt-msg">Loading…</div>
<button id="skip" type="button">Skip →</button>
</div>
<div id="map-wrap"><div class="score" id="score"></div></div>
<script>
const OCEAN = "#eaf0f2", LAND = "#c9c3b6", STROKE = "#ffffff",
CORRECT = "#4caf7d", WRONG = "#d6534e", HILITE = "#555";
const MIN_AREA_KM2 = 25000; // drop countries too small to click comfortably
const flagURL = (iso2) => `https://flagpedia.net/data/flags/h240/${iso2.toLowerCase()}.png`;
const width = 960, height = 500;
const wrap = document.getElementById("map-wrap");
const flag = document.getElementById("flag");
const promptEl = document.getElementById("prompt-msg");
const scoreEl = document.getElementById("score");
const svg = d3.select(wrap).append("svg")
.attr("viewBox", `0 0 ${width} ${height}`).style("width", "100%").style("height", "auto");
svg.append("rect").attr("width", width).attr("height", height).attr("fill", OCEAN);
const g = svg.append("g");
const projection = d3.geoNaturalEarth1();
const path = d3.geoPath(projection);
let paths, queue = [], qi = 0, target = null, locked = false, correct = 0, wrong = 0;
d3.json("https://api.mapjson.com/v1/geo?layer=countries&detail=medium&properties=name,iso2,areakm2&format=geojson")
.then((geo) => {
const eligible = geo.features.filter((d) => {
const p = d.properties;
return p.iso2 && p.name && p.name !== "Antarctica" && (p.areakm2 || 0) >= MIN_AREA_KM2;
});
projection.fitExtent([[6, 6], [width - 6, height - 6]], { type: "FeatureCollection", features: eligible });
paths = g.selectAll("path").data(eligible).join("path")
.attr("d", path).attr("fill", LAND).attr("stroke", STROKE).attr("stroke-width", 0.6)
.style("cursor", "pointer")
.on("mouseenter", function () { if (!locked) d3.select(this).attr("stroke", HILITE).attr("stroke-width", 1).raise(); })
.on("mouseleave", function () { d3.select(this).attr("stroke", STROKE).attr("stroke-width", 0.6); })
.on("click", (event, d) => guess(d));
queue = shuffle(eligible.slice());
updateScore();
next();
});
function shuffle(a) { for (let i = a.length - 1; i > 0; i--) { const j = (Math.random() * (i + 1)) | 0; const t = a[i]; a[i] = a[j]; a[j] = t; } return a; }
function paint(d, color) { paths.filter((x) => x === d).attr("fill", color); }
function updateScore() { scoreEl.innerHTML = `<span class="s-ok">✓ ${correct}</span><span class="s-no">✗ ${wrong}</span>`; }
function next() {
if (qi >= queue.length) { shuffle(queue); qi = 0; }
target = queue[qi++]; locked = false;
paths.attr("fill", LAND);
promptEl.textContent = "Which country flies this flag?"; promptEl.className = "";
flag.src = flagURL(target.properties.iso2);
}
function guess(d) {
if (locked) return;
locked = true;
if (d.properties.iso2 === target.properties.iso2) {
correct++; paint(d, CORRECT);
promptEl.textContent = `✓ Correct — ${target.properties.name}`; promptEl.className = "ok";
updateScore(); setTimeout(next, 900);
} else {
wrong++; paint(d, WRONG); paint(target, CORRECT);
promptEl.textContent = `✗ You picked ${d.properties.name} — it was ${target.properties.name}`; promptEl.className = "no";
updateScore(); setTimeout(next, 1600);
}
}
document.getElementById("skip").addEventListener("click", () => {
if (locked) return;
queue.push(target); // "skip for now" — re-ask this flag later, no reveal
next();
});
// A handful of territories have no flag on flagpedia — skip if the image fails.
flag.addEventListener("error", () => { if (!locked) next(); });
</script>
</body>
</html>