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