UPG Neighbor Campaigns: hovering a dot names its group

After a language was picked, its believer (green) and unreached (red) dots were
anonymous until clicked, and a green dot could not be clicked at all. Hovering either
now shows the group and its country, as Mobilize does.
This commit is contained in:
Doxa Maps 2026-09-28 10:08:03 +00:00
parent 77c7b16fef
commit beb82f2e2e

View file

@ -82,6 +82,9 @@ function exportAll() {
// ─── Map: believers (green) and the unreached they would reach (red) ─────────
const SRC_R = 'cp-r', SRC_U = 'cp-u'
const fc = (features) => ({ type: 'FeatureCollection', features })
// Hover label: after a language is picked, its believers (green) and the unreached they reach (red)
// were anonymous until clicked, and a green dot could not be clicked at all. Same label as Mobilize.
const hover = ref(null) // { x, y, text }
const pt = (g) => ({ type: 'Feature', geometry: { type: 'Point', coordinates: [g.lng, g.lat] }, properties: { id: g.pgid, pop: g.pop, b: g.believers } })
function setSrc(m, id, d) { const s = m.getSource(id); if (s) s.setData(d); else m.addSource(id, { type: 'geojson', data: d }) }
let repaintPending = false
@ -131,6 +134,14 @@ onMounted(async () => {
})
m.on('mouseenter', 'cp-u', () => { m.getCanvas().style.cursor = 'pointer' })
m.on('mouseleave', 'cp-u', () => { m.getCanvas().style.cursor = '' })
const byId = (id) => active.value && [...active.value.reached, ...active.value.unreached].find((g) => g.pgid === id)
for (const id of ['cp-u', 'cp-r']) {
m.on('mousemove', id, (ev) => {
const g = byId(ev.features?.[0]?.properties?.id)
hover.value = g ? { x: ev.point.x, y: ev.point.y, text: `${g.name} · ${g.country}` } : null
})
m.on('mouseleave', id, () => { hover.value = null })
}
})
m.on('style.load', repaint)
})
@ -169,6 +180,7 @@ useShadowStyles(`
.cp-line { display:flex; justify-content:space-between; gap:8px; font-size:12.5px; padding:5px 0; border-bottom:1px solid #f1f3f6; }
.cp-line button { border:0; background:none; padding:0; font:inherit; color:${NAVY}; cursor:pointer; text-align:left; }
.cp-note { padding:14px; color:#6b7280; font-size:13px; line-height:1.45; }
.cp-hover { position:absolute; z-index:25; pointer-events:none; background:rgba(0,27,68,.92); color:#fff; font-size:12px; line-height:1.3; padding:5px 8px; border-radius:6px; max-width:260px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cp-maperr { position:absolute; right:12px; top:12px; z-index:15; background:#fff; border:1px solid #d9dee6; border-radius:8px; padding:8px 12px; font-size:13px; color:#6b7280; }
@media (max-width:760px) { .cp-panel { top:auto; left:6px; right:6px; bottom:6px; width:auto; height:55%; } }
`, 'upgn-campaigns')
@ -178,6 +190,7 @@ useShadowStyles(`
<div class="cp-root">
<div ref="mapContainer" class="cp-map"></div>
<div v-if="mapError" class="cp-maperr" role="status">{{ mapError }}</div>
<div v-if="hover" class="cp-hover" :style="{ left: hover.x + 12 + 'px', top: hover.y + 12 + 'px' }">{{ hover.text }}</div>
<aside class="cp-panel">
<template v-if="!active">