Changelog f6d4710tree, with Claude Opus 5.5
Country profile: neighbours by score under the score over time
The country among its land neighbours (borders.js), ranked by score, each linked, with a link to compare it with the closest of them. A long list keeps the rows around the country; an island is shown among the countries of its region with the most similar scores.
2 files of code, design or docs +40 −0 lines
Code, design and documents 2 files
src/components/CountryArticle.astro +27 −0
@@ -25,4 +25,5 @@ import { Image } from "astro:assets"; import { cultureOf, cultureSection } from "../lib/culture.js"; import { factsOf } from "../lib/facts.js"; +import { neighboursOf } from "../lib/borders.js"; import FactText from "./FactText.astro"; @@ -119,4 +120,15 @@ const sections = [ ].filter(Boolean); +// Beside the score: the country among its land neighbours, by score. A country with none (an island) +// is shown among those nearest to it in rank in its region. A long list keeps the rows around the +// country itself. Not on a state's page. +const landNeighbours = sub ? [] : (neighboursOf.get(c.id) ?? []).map((id) => countries.find((x) => x.id === id)).filter(Boolean); +const around = (list, size) => { const at = list.indexOf(c), from = Math.max(0, Math.min(at - Math.floor(size / 2), list.length - size)); return list.slice(from, from + size); }; +const nearby = sub || c.territory ? [] : landNeighbours.length + ? around([c, ...landNeighbours].sort((a, b) => b.raw - a.raw || a.name.localeCompare(b.name)), 9) + : around(countries.filter((x) => x.region === c.region), 5); +// the compare page takes up to six countries: this one and the nearest in score +const compareIds = [c, ...nearby.filter((x) => x !== c).sort((a, b) => Math.abs(a.raw - c.raw) - Math.abs(b.raw - c.raw))].slice(0, 4).map((x) => x.slug); + // The score's shape: the six segments as a radar, over the world average (and, for a state, its country). const radarPt = (i, val, r = 40) => { const a = -Math.PI / 2 + (i * 2 * Math.PI) / SEGMENTS.length; return [50 + Math.cos(a) * r * (val / 10), 50 + Math.sin(a) * r * (val / 10)]; }; @@ -309,4 +321,19 @@ const uncited = srcList.filter((x) => !citedSet.has(refKey(x))); </figure> {scored.length > 0 && <CountryChart history={scored} context={history.filter((ch) => !ch.score)} />} + {nearby.length > 1 && ( + <div class="c-near"> + <h2>{landNeighbours.length ? `Neighbours${landNeighbours.length + 1 > nearby.length ? ` · ${nearby.length} of ${landNeighbours.length + 1}` : ""}` : `Similar scores in ${c.region}`}</h2> + <ol> + {nearby.map((x) => ( + <li class={x === c ? "on" : undefined}> + {x === c ? <span>{x.flag} {x.name}</span> : <a href={`${base}country/${x.slug}/`}>{x.flag} {x.name}</a>} + <i style={`--w:${x.raw}%;--c:${tierColor(x.raw)}`}></i> + <b>{x.score}</b> + </li> + ))} + </ol> + <p><a href={`${base}compare/?c=${compareIds.join(",")}`}>Compare {landNeighbours.length ? "with neighbours" : "them"} →</a></p> + </div> + )} </div> <div class="score-rows">
src/styles/global.css +13 −0
@@ -1393,4 +1393,17 @@ details.segrow .segrow-top > span, .segrow:not(details) .segrow-top > span { fon .c-radar:has(.key-avg:hover, .key-avg:focus-visible) .c-radar-label .avg { display: inline; } .score-side .c-chart { margin: 0; } +/* the country among its land neighbours, by score, under its score over time */ +.c-near h2 { margin: 0 0 10px; color: var(--muted); font: 400 10.5px var(--mono); text-transform: uppercase; letter-spacing: .08em; } +.c-near ol { margin: 0; padding: 0; list-style: none; } +.c-near li { display: grid; grid-template-columns: minmax(0, 1fr) 34% 28px; gap: 12px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 13.5px; color: var(--text-2); } +.c-near li > span, .c-near li > a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: inherit; } +.c-near li > a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; } +.c-near li i { display: block; height: 4px; width: var(--w); min-width: 2px; background: var(--c); opacity: .55; } +.c-near li b { text-align: right; font: 400 12.5px var(--mono); } +.c-near li.on { color: var(--text); font-weight: 600; } +.c-near li.on i { opacity: 1; } +.c-near p { margin: 10px 0 0; font-size: 12.5px; } +.c-near p a { color: var(--text-2); text-decoration: underline; text-decoration-color: var(--border); text-underline-offset: 3px; } +.c-near p a:hover { color: var(--text); text-decoration-color: currentColor; } /* a segment read in the text lights its corner of the shape */ .score-grid:has(.segrow[data-seg="possession"]:hover) [data-seg-dot="possession"],