---
// The localness device (design system: "one map, framed by country"). It renders in a same-origin
// iframe because it needs d3 + topojson at runtime; keeping it out of the main bundle means the
// heavy map libs load only when the map scrolls into view (loading="lazy"). All deps are
// self-hosted under /public/map/vendor — no third-party runtime requests.
//
// Build-time values (cc, accent, member dots, and on .eu the active communities) go in the src
// query string; the iframe resolves light/dark itself so the map follows the page theme.
import { getScope, statsCc } from '../config/scopes';
import { EU27, ALSO_IN_EUROPE } from '../data/europe-regions';
interface Props {
/** Member dots as "lon:lat:n,lon:lat:n". Empty renders the country outline with no members yet. */
dots?: string;
class?: string;
title?: string;
/** Internal frame size. Larger + landscape = a denser character grid, for the background use. */
w?: number;
h?: number;
/** 'cover' fills the frame (no letterbox) for the full-bleed background; default keeps the whole map. */
fit?: 'cover' | 'meet';
}
const { dots = '', class: cls, title, w, h, fit } = Astro.props;
const scope = getScope(import.meta.env.PUBLIC_SITE_SCOPE);
// Cache-bust: /public map assets are served without a content hash, so browsers keep the old
// locality-map.js after a deploy. Bump this when the map renderer changes; it versions both the
// iframe URL (fresh HTML) and the script it loads (fresh JS).
const MAP_VERSION = '11';
// A city scope colours the land by its home country (statsCc: Barcelona -> es) and frames the map to
// its region via mapBox (Barcelona -> Catalunya). National scopes: statsCc === cc, no mapBox.
const params = new URLSearchParams({ cc: statsCc(scope), accent: scope.accent });
if (dots) params.set('dots', dots);
if (w) params.set('w', String(w));
if (h) params.set('h', String(h));
if (fit) params.set('fit', fit);
if (scope.mapBox) params.set('box', scope.mapBox.join(','));
// A city scope marks its own city in red on the region frame (Barcelona highlighted on Catalunya).
if (scope.tier === 'city') {
const city = Object.values(scope.cities)[0];
if (city) {
params.set('hi', city.join(','));
params.set('hicolor', scope.accent2);
}
}
params.set('v', MAP_VERSION);
// On the aggregate, colour by region — the SAME three tiers as the /stats table (shared lists in
// data/europe-regions.ts, so map and stats never disagree): EU members are the bright #, the non-EU
// "also in Europe" set (gb/no/ch/is/li) are the dim #, and any other land in the frame is dots.
// The renderer reads `active` as the bright set and `lit` as the dim set.
if (scope.aggregate) {
params.set('active', EU27.join(','));
params.set('lit', ALSO_IN_EUROPE.join(','));
}
const src = `/map/locality-map.html?${params.toString()}`;
---