diff --git a/src/App.ts b/src/App.ts
index a81beea72..5560e71fd 100644
--- a/src/App.ts
+++ b/src/App.ts
@@ -404,8 +404,13 @@ export class App {
this.countryIntelModal = new CountryIntelModal();
this.map.onCountryClicked(async (lat, lon) => {
+ this.countryIntelModal!.showLoading();
+
const geo = await reverseGeocode(lat, lon);
- if (!geo) return;
+ if (!geo) {
+ this.countryIntelModal!.hide();
+ return;
+ }
const scores = calculateCII();
const score = scores.find((s) => s.code === geo.code) ?? null;
diff --git a/src/components/CountryIntelModal.ts b/src/components/CountryIntelModal.ts
index 143056320..fddd2dc24 100644
--- a/src/components/CountryIntelModal.ts
+++ b/src/components/CountryIntelModal.ts
@@ -89,6 +89,24 @@ export class CountryIntelModal {
`;
}
+ public showLoading(): void {
+ this.currentCode = '__loading__';
+ this.headerEl.innerHTML = `
+ 🌍
+ Identifying country...
+ `;
+ this.contentEl.innerHTML = `
+
+
+
+
+
Locating region...
+
+
+ `;
+ this.overlay.classList.add('active');
+ }
+
public show(country: string, code: string, score: CountryScore | null, signals?: ActiveSignals): void {
this.currentCode = code;
const flag = this.countryFlag(code);
diff --git a/src/components/DeckGLMap.ts b/src/components/DeckGLMap.ts
index 050915473..965db2517 100644
--- a/src/components/DeckGLMap.ts
+++ b/src/components/DeckGLMap.ts
@@ -2585,6 +2585,25 @@ export class DeckGLMap {
type: 'geojson',
data: geojson,
});
+ this.maplibreMap.addLayer({
+ id: 'country-interactive',
+ type: 'fill',
+ source: 'country-boundaries',
+ paint: {
+ 'fill-color': '#3b82f6',
+ 'fill-opacity': 0,
+ },
+ });
+ this.maplibreMap.addLayer({
+ id: 'country-hover-fill',
+ type: 'fill',
+ source: 'country-boundaries',
+ paint: {
+ 'fill-color': '#3b82f6',
+ 'fill-opacity': 0.06,
+ },
+ filter: ['==', ['get', 'ISO3166-1-Alpha-2'], ''],
+ });
this.maplibreMap.addLayer({
id: 'country-highlight-fill',
type: 'fill',
@@ -2606,11 +2625,43 @@ export class DeckGLMap {
},
filter: ['==', ['get', 'ISO3166-1-Alpha-2'], ''],
});
+
+ this.setupCountryHover();
console.log('[DeckGLMap] Country boundaries loaded');
})
.catch((err) => console.warn('[DeckGLMap] Failed to load country boundaries:', err));
}
+ private setupCountryHover(): void {
+ if (!this.maplibreMap) return;
+ const map = this.maplibreMap;
+ let hoveredCode: string | null = null;
+
+ map.on('mousemove', (e) => {
+ if (!this.onCountryClick) return;
+ const features = map.queryRenderedFeatures(e.point, { layers: ['country-interactive'] });
+ const code = features?.[0]?.properties?.['ISO3166-1-Alpha-2'] as string | undefined;
+
+ if (code && code !== hoveredCode) {
+ hoveredCode = code;
+ map.setFilter('country-hover-fill', ['==', ['get', 'ISO3166-1-Alpha-2'], code]);
+ map.getCanvas().style.cursor = 'pointer';
+ } else if (!code && hoveredCode) {
+ hoveredCode = null;
+ map.setFilter('country-hover-fill', ['==', ['get', 'ISO3166-1-Alpha-2'], '']);
+ map.getCanvas().style.cursor = '';
+ }
+ });
+
+ map.on('mouseout', () => {
+ if (hoveredCode) {
+ hoveredCode = null;
+ map.setFilter('country-hover-fill', ['==', ['get', 'ISO3166-1-Alpha-2'], '']);
+ map.getCanvas().style.cursor = '';
+ }
+ });
+ }
+
public highlightCountry(code: string): void {
if (!this.maplibreMap || !this.countryGeoJsonLoaded) return;
// Update MapLibre filter to highlight this country