UX: country hover highlight + instant loading feedback on click

- Add transparent interactive fill layer for country hover detection
- Hover shows subtle highlight + pointer cursor so user knows it's clickable
- Show loading modal immediately on click (before geocode completes)
- Dismiss modal if geocode fails (ocean click etc)
This commit is contained in:
Elie Habib
2026-01-27 16:49:56 +04:00
parent 1292f0595f
commit b32674df10
3 changed files with 75 additions and 1 deletions

View File

@@ -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;

View File

@@ -89,6 +89,24 @@ export class CountryIntelModal {
`;
}
public showLoading(): void {
this.currentCode = '__loading__';
this.headerEl.innerHTML = `
<span class="country-flag">🌍</span>
<span class="country-name">Identifying country...</span>
`;
this.contentEl.innerHTML = `
<div class="intel-brief-section">
<div class="intel-brief-loading">
<div class="intel-skeleton"></div>
<div class="intel-skeleton short"></div>
<span class="intel-loading-text">Locating region...</span>
</div>
</div>
`;
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);

View File

@@ -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