Partitioned location data for country-adaptive form fields. Base path at runtime: /assets/data/geo/.
Drill-down: country → admin1 (region/state/județ) → [admin2 (county), only where it exists] → city.
Partitioning keeps the Cloudflare Pages deployment under its 20,000-file limit: cities are one file per admin1, not per county.
index.json → { countries: [{ cc, name, hasAdmin1, hasAdmin2 }], labels: { <CC>: { admin1, admin2 } } }
labels[cc].admin1 = display label for level 1 (e.g. "State", "Județ", "Region"). admin2 = label for county tier, or null when the country has no county tier.<CC>/admin1.json → [{ code, name }] sorted by name.<CC>/<A1>/admin2.json → [{ code, name }] counties for that admin1. Only present when hasAdmin2 is true.<CC>/<A1>/cities.json — cities for the whole admin1 (ONE file per admin1):
hasAdmin2:true): array of objects [{ n: cityName, a2: admin2Code }], sorted by population desc then name. Client filters by the selected county's code (a2).[ "City", ... ].<CC>/admin1.json; label region from labels[cc].admin1.hasAdmin2: fetch <CC>/<A1>/admin2.json (counties) AND <CC>/<A1>/cities.json (objects); show all region cities until a county is picked. Else: fetch <CC>/<A1>/cities.json (strings).a2 equals the selected county code.country = country name, region = admin1 name, county = admin2 name (empty when no county tier), city = city name.
_ ("Other").scripts/geo/build-geo.mjs from GeoNames cities500 (population ≥ 500). Raw dumps live in gitignored temp/.