diff --git a/library/components/info/MapInfo.vue b/library/components/info/MapInfo.vue index 06cd3ec56..81dd7f8af 100644 --- a/library/components/info/MapInfo.vue +++ b/library/components/info/MapInfo.vue @@ -26,7 +26,9 @@ * Relative srcs resolve against `assetsBase`, so the same list works in any tree. * * ★ THE POINTER GOES FOR GOOD, per map, once its popup has been opened AND closed (opening alone - * could be a mis-tap). The checkbox in the popup turns the pointer off on EVERY map. Both live in + * could be a mis-tap). The checkbox in the popup turns the pointer off on EVERY map. A map given + * `pointer-keep` offers "Keep tooltip activated" instead: opening alone retires its pointer, and the box + * keeps it showing on every load (see the prop). All of it lives in * localStorage, read through a guard: private browsing and sandboxed frames throw on access, and a * hint must never be the thing that breaks a page. The button itself always stays. * @@ -75,6 +77,12 @@ const props = defineProps({ * actually on screen (see "control mode" below). */ trigger: { type: String, default: 'floating', validator: (v) => ['floating', 'control'].includes(v) }, + /** + * Offer "Keep tooltip activated" in the popup, IN PLACE OF the every-map "don't point again" box. Off by + * default: opening the popup once retires this map's pointer, as before. Checked: the pointer keeps + * showing on every load. Per map (its storageKey), so it answers only for the map that offers it. + */ + pointerKeep: { type: Boolean, default: false }, }) // ── locale and text ────────────────────────────────────────────────────────────────────────────── @@ -93,19 +101,24 @@ const isRtl = () => lang() === 'ar' // ── storage, guarded ───────────────────────────────────────────────────────────────────────────── const SEEN = 'dxmaps.mapInfo.seen.' const OFF = 'dxmaps.mapInfo.pointer' +const KEEP = 'dxmaps.mapInfo.keep.' const OFF_EVENT = 'dxmaps:mapinfo-pointer-off' function readStore(k) { try { return window.localStorage.getItem(k) } catch { return null } } function writeStore(k, v) { try { window.localStorage.setItem(k, v) } catch { /* private mode: in-memory only */ } } const pointing = ref(false) const pointerOff = ref(false) +const keep = ref(false) +// A kept pointer outranks both "seen" and the every-map off switch: it is this map's own, later answer. +const wantPointer = () => keep.value || (!pointerOff.value && readStore(SEEN + props.storageKey) !== '1') function refreshPointer() { pointerOff.value = readStore(OFF) === 'off' - pointing.value = !pointerOff.value && readStore(SEEN + props.storageKey) !== '1' + keep.value = props.pointerKeep && readStore(KEEP + props.storageKey) === '1' + pointing.value = wantPointer() } // Another map on the page (same document) or another tab (storage event) turned the pointer off. function onPointerOff() { refreshPointer() } -function onStorage(e) { if (!e || e.key === OFF || e.key === SEEN + props.storageKey) refreshPointer() } +function onStorage(e) { if (!e || e.key === OFF || e.key === SEEN + props.storageKey || e.key === KEEP + props.storageKey) refreshPointer() } onMounted(() => { refreshPointer() window.addEventListener(OFF_EVENT, onPointerOff) @@ -118,9 +131,13 @@ onBeforeUnmount(() => { function setPointerOff(off) { pointerOff.value = off writeStore(OFF, off ? 'off' : 'on') - pointing.value = !off && readStore(SEEN + props.storageKey) !== '1' + pointing.value = wantPointer() try { window.dispatchEvent(new CustomEvent(OFF_EVENT)) } catch { /* no window */ } } +function setKeep(on) { + keep.value = on + writeStore(KEEP + props.storageKey, on ? '1' : '0') +} // ── open / close ───────────────────────────────────────────────────────────────────────────────── const open = ref(false) @@ -131,13 +148,16 @@ const failed = reactive({}) async function show() { opens.value++ open.value = true + // With the keep box on offer, OPENING the popup is what retires the pointer (the box is right there + // to say otherwise), so a reader who opens it and reloads without closing is not pointed at again. + if (props.pointerKeep) writeStore(SEEN + props.storageKey, '1') await nextTick() closeEl.value?.focus() } async function hide() { open.value = false writeStore(SEEN + props.storageKey, '1') - pointing.value = false + pointing.value = keep.value await nextTick() // Back to whichever button the reader can see: the map's own control when it is on screen, else ours. const back = props.trigger === 'control' && controlShown.value && control.value ? control.value : btnEl.value @@ -169,9 +189,35 @@ function measureControl() { if (c && c.isConnected && !inert(c)) { const r = c.getBoundingClientRect() shown = r.width > 0 && r.height > 0 && r.bottom > 0 && r.right > 0 && r.top < window.innerHeight && r.left < window.innerWidth + if (shown) { + const n = placeBeside(r), o = ctlAt.value + if (!n || !o || n.side !== o.side || n.top !== o.top || n.left !== o.left || n.right !== o.right) ctlAt.value = n + } } controlShown.value = shown } +// ★ THE POINTER FOLLOWS THE CONTROL, WHEREVER THE MAP PUT IT. The floating pointer is anchored to our +// own button by CSS; the control lives in the map's shadow root, so its place is MEASURED, against the +// box our absolutely-placed pointer is laid out in (our offsetParent, the pane, which scrolls: hence +// scrollTop). It goes on the control's physical inner side: a right-to-left map mirrors its column to +// the left edge, and that is read from where the control IS, never assumed from the page's direction. +const GAP = 10 +function placeBeside(r) { + const cb = rootEl.value && rootEl.value.offsetParent + if (!cb) return null + const b = cb.getBoundingClientRect() + const x0 = b.left + cb.clientLeft, y0 = b.top + cb.clientTop + const top = Math.round(r.top - y0 + cb.scrollTop + r.height / 2) + return (r.left + r.width / 2) > (x0 + cb.clientWidth / 2) + ? { side: 'right', top, right: Math.round(x0 + cb.clientWidth - r.left + GAP) } + : { side: 'left', top, left: Math.round(r.right - x0 + GAP) } +} +const ctlAt = ref(null) +const ctlStyle = () => { + const p = ctlAt.value + return p.side === 'right' ? { top: p.top + 'px', right: p.right + 'px' } : { top: p.top + 'px', left: p.left + 'px' } +} +const onResize = () => measureControl() function adopt(el) { if (!el || el === control.value) { measureControl(); return } try { io?.disconnect(); ro?.disconnect() } catch { /* already gone */ } @@ -202,6 +248,7 @@ onMounted(() => { pane.addEventListener('dxmaps:mapinfo-control', onControl) pane.addEventListener('dxmaps:mapinfo-open', onOpenRequest) timer = window.setInterval(measureControl, 1500) + window.addEventListener('resize', onResize) // A control that mounted before we did asked into the void; tell it to ask again. try { window.dispatchEvent(new CustomEvent('dxmaps:mapinfo-host')) } catch { /* no CustomEvent */ } }) @@ -212,6 +259,7 @@ onBeforeUnmount(() => { } try { io?.disconnect(); ro?.disconnect() } catch { /* already gone */ } if (timer) window.clearInterval(timer) + window.removeEventListener('resize', onResize) }) watch(open, (v) => { control.value?.setAttribute('aria-expanded', String(v)) }) const floatingShown = () => props.trigger !== 'control' || !controlShown.value @@ -281,6 +329,16 @@ useShadowStyles(` .mi-pointer svg{ width:16px; height:16px; flex:none; } @keyframes mi-nudge{ 0%,100%{ transform:translateX(0) } 50%{ transform:translateX(6px) } } @keyframes mi-nudge-left{ 0%,100%{ transform:translateX(0) } 50%{ transform:translateX(-6px) } } + /* Control mode: top/left/right come from placeBeside (inline), centred on the control by translate, + which composes with the nudge's transform. Laid out left-to-right on purpose: the arrow's side is the + control's PHYSICAL side, measured, so the page direction must not flip the row, the glyph or the + nudge (the span keeps the text's own direction). (0,3,0) so the 1200px rule below does not hide it: + that rule measured the floating pointer crossing each tab's top strip, and this one is placed and + measured beside one map's own control instead. */ + .mi .mi-pointer.mi-ctl{ display:flex; direction:ltr; translate:0 -50%; } + .mi .mi-pointer.mi-ctl-left{ flex-direction:row-reverse; animation-name:mi-nudge-left; } + .mi .mi-pointer.mi-ctl-right{ animation-name:mi-nudge; } + .mi[dir="rtl"] .mi-pointer.mi-ctl svg{ transform:none; } /* z-index above every map's own top layer: PinHint's first-visit pill sits at 2147482000, so at 1000 it floated bright and clickable over the dimmed backdrop (gospel). The dialog is a child of this fixed, @@ -382,7 +440,7 @@ useShadowStyles(` /* This component animates (the pointer's nudge), so it carries its own reduced-motion rule where its styles are injected: a page-level rule cannot reach into a shadow root. */ @media (prefers-reduced-motion: reduce){ - .mi-pointer{ animation:none; } + .mi-pointer, .mi .mi-pointer.mi-ctl{ animation:none; } } `, 'map-info') @@ -406,6 +464,14 @@ useShadowStyles(` + +
-
- `}function u(b){const y=b.iso_3166_1_alpha_3||"",L=b["name_"+((s==null?void 0:s.value)||"en")]||b.name_en||b.name||y,P=a(),F=y.toUpperCase(),w=P.filter(J=>{const T=(J.countryIso||J.country||"").toUpperCase();return T&&T===F}),G=w.length,U=w[0],Q=(U==null?void 0:U.wagfRegionLabel)||(U==null?void 0:U.doxaRegionLabel)||(U==null?void 0:U.doxaRegion)||(U==null?void 0:U.wagfRegion)||"—",D=(U==null?void 0:U.wagfBlockLabel)||(U==null?void 0:U.wagfBlock)||"—";return` + `}function u(b){const y=b.iso_3166_1_alpha_3||"",L=b["name_"+((s==null?void 0:s.value)||"en")]||b.name_en||b.name||y,P=a(),F=y.toUpperCase(),w=P.filter(J=>{const T=(J.countryIso||J.country||"").toUpperCase();return T&&T===F}),j=w.length,U=w[0],Q=(U==null?void 0:U.wagfRegionLabel)||(U==null?void 0:U.doxaRegionLabel)||(U==null?void 0:U.doxaRegion)||(U==null?void 0:U.wagfRegion)||"—",D=(U==null?void 0:U.wagfBlockLabel)||(U==null?void 0:U.wagfBlock)||"—";return`

${Qe(L)} @@ -181,10 +181,10 @@ ${Qe(r("popup.country.country"))}: ${Qe(L)} (${Qe(y)})

- ${Qe(r("popup.country.uupgCount"))}: ${Qe(G)} + ${Qe(r("popup.country.uupgCount"))}: ${Qe(j)}

- `}function d(b){const y=t();if(!y)return;const k=b||"language-family-pins";if(!y.getLayer(k))return;const L=P=>{try{const F=P.features[0],w=F.properties,G=F.geometry.coordinates.slice();for(;Math.abs(P.lngLat.lng-G[0])>180;)G[0]+=P.lngLat.lng>G[0]?360:-360;if(i&&i.legendMode!==void 0){i.selectPeopleGroup(F),i.isMobile&&i.legendState==="collapsed"&&i.setLegendState("open"),i.isMobile||window.dispatchEvent(new CustomEvent("dxm:openDesktopLegend",{detail:{mapId:o}}));return}new(Es(y)).Popup({maxWidth:"320px",className:"people-group-popup"}).setLngLat(G).setHTML(c(w)).addTo(y)}catch(F){console.error(` [${o}] Pin click handler error:`,F)}};y.on("click",k,L),l.value.push({event:"click",layer:k,handler:L})}function p(){const b=t();if(!b)return;const y="regions-fill";if(!b.getLayer(y))return;const k=L=>{const P=["language-family-pins","language-family-pins-hitbox","people-groups-pins"];for(const F of P)if(b.getLayer(F)){const w=b.queryRenderedFeatures(L.point,{layers:[F]});if(w&&w.length>0)return}if(L.features&&L.features.length>0){const F=L.features[0].properties;new(Es(b)).Popup().setLngLat(L.lngLat).setHTML(u(F)).addTo(b)}};b.on("click",y,k),l.value.push({event:"click",layer:y,handler:k})}function A(){const b=t();if(!b)return;const y="clusters";if(!b.getLayer(y))return;const k=L=>{const P=b.queryRenderedFeatures(L.point,{layers:[y]});if(!P.length)return;const F=P[0].properties.cluster_id,w=P[0].source;b.getSource(w).getClusterExpansionZoom(F,(G,U)=>{G||b.easeTo({center:P[0].geometry.coordinates,zoom:U})})};b.on("click",y,k),l.value.push({event:"click",layer:y,handler:k})}function x(b){const y=t();if(!y||!y.getLayer(b))return;const k=()=>{y.getCanvas().style.cursor="pointer"},L=()=>{y.getCanvas().style.cursor=""};y.on("mouseenter",b,k),y.on("mouseleave",b,L),l.value.push({event:"mouseenter",layer:b,handler:k}),l.value.push({event:"mouseleave",layer:b,handler:L})}function h(){d("language-family-pins-hitbox"),x("language-family-pins"),x("language-family-pins-hitbox"),A(),x("clusters")}function v(){p(),x("regions-fill")}function $(){const b=t();b&&(b.getLayer("language-family-pins")&&h(),b.getLayer("regions-fill")&&v(),b.getLayer("people-groups-pins")&&(d("people-groups-pins"),x("people-groups-pins")))}function m(){const b=t();if(b){for(const{event:y,layer:k,handler:L}of l.value)try{b.off(y,k,L)}catch{}l.value=[]}}return{attachAllEventHandlers:$,attachLanguageFamilyEvents:h,attachRegionEvents:v,attachPinClickHandler:d,attachRegionClickHandler:p,attachClusterClickHandler:A,attachCursorHandlers:x,detachAllEventHandlers:m,generatePinPopupContent:c,generateRegionPopupContent:u,registeredHandlers:l}}const Os=[">",["to-number",["get","peoplePraying"],0],0],zs={0:.25,8:.4,14:.5},Pn={0:.1,8:.18,14:.22},Tn={0:1.2,8:1.6,14:2},Np=["interpolate",["linear"],["zoom"],0,["case",Os,zs[0],Pn[0]],8,["case",Os,zs[8],Pn[8]],14,["case",Os,zs[14],Pn[14]]],o0=["interpolate",["linear"],["zoom"],0,Tn[0],8,Tn[8],14,Tn[14]],Rp=["==",["to-number",["get","peoplePraying"],0],0],n0={prayer:[Rp],prayerProgress:[Rp],engagement:[["all",["!=",["get","engagementStatus"],!0],["!=",["get","engagementStatus"],1]]],adoption:[["all",["!=",["get","adoptionStatus"],!0],["!=",["get","adoptionStatus"],1]]]};function a0(e){const t=n0[e]||[];if(!t.length)return o0;const o=t.length===1?t[0]:["any",...t];return["interpolate",["linear"],["zoom"],0,["case",o,Pn[0],Tn[0]],8,["case",o,Pn[8],Tn[8]],14,["case",o,Pn[14],Tn[14]]]}const i0=[[/ sign language$/i,"Sign Language"]];function r0(e){if(!e||typeof e!="string")return null;const t=e.trim(),o=t.split(",").map(a=>a.trim()).filter(Boolean);if(o.length>=2){const a=[...o].reverse().join(" ");if(Je[a])return Je[a];if(o.length>=3){const i=[o[1],o[0]].join(" ");if(Je[i])return Je[i]}}if(Je[t])return Je[t];const n=t.replace(/\s*\(.*?\)\s*$/,"").trim();if(n!==t&&Je[n])return Je[n];for(const[a,i]of i0)if(a.test(t))return i;return null}function s0(e={}){const{getMap:t=()=>null,mapId:o="unknown",getLanguageFamilyColor:n=()=>"#999999",getNormalizedPeopleGroups:a=()=>[]}=e,i=()=>{const ee=t();return ee?De(ee):null},r=t0({getMap:i,mapId:o,getLanguageFamilyColor:n,getNormalizedPeopleGroups:a}),s=ge({languageFamilyPins:!1,regions:!1,peopleGroups:!1,familyConnections:!1}),l=ge({});function c(ee,ce="languageFamily"){const se=i();if(!se||!(ee!=null&&ee.length))return;const he=Cs(ce),_=ee.map(M=>{var ue,oe,g,f,B,K,fe,te,R,H,be,ke,Ne,nt,Ye,St,It,at,st;return{type:"Feature",properties:{uniqueId:M.id||M.uniqueId,slug:M.slug||((ue=M._normalized)==null?void 0:ue.slug)||"",rop3:M.rop3||M.peopleGroupId,peopleId3:M.peopleId3||"",name:M.name,language:M.language,languageFamily:M.languageFamily||((oe=M._normalized)==null?void 0:oe.languageFamily)||r0(M.language)||"Unknown",affinityBlock:M.affinityBlock||M.affbloc||M.Affbloc||((g=M._normalized)==null?void 0:g.affinityBlock)||"Unknown",affinityBlockLabel:M.affinityBlockLabel||((f=M._normalized)==null?void 0:f.affinityBlockLabel)||"",cluster:M.cluster||((B=M._normalized)==null?void 0:B.cluster)||"",clusterLabel:M.clusterLabel||((K=M._normalized)==null?void 0:K.clusterLabel)||"",peopleGroup:M.peopleGroup||((fe=M._normalized)==null?void 0:fe.peopleGroup)||"",peopleGroupLabel:M.peopleGroupLabel||((te=M._normalized)==null?void 0:te.peopleGroupLabel)||"",doxaRegion:M.doxaRegion||((R=M._normalized)==null?void 0:R.doxaRegion)||"Unknown",wagfBlock:M.wagfBlock||((H=M._normalized)==null?void 0:H.wagfBlock)||"",wagfBlockLabel:M.wagfBlockLabel||((be=M._normalized)==null?void 0:be.wagfBlockLabel)||"",wagfRegionLabel:M.wagfRegionLabel||((ke=M._normalized)==null?void 0:ke.wagfRegionLabel)||"",country:M.country,countryIso:M.countryIso,countryIsoLabel:M.countryIsoLabel,countryName:M.countryName,religion:M.religion,religionCode:M.religionCode,religionLabel:M.religionLabel,religionName:M.religionName,population:M.population,status:M.status,description:M.description||"",imageUrl:M.imageUrl||"",peoplePraying:Number(M.peoplePraying??((Ne=M._normalized)==null?void 0:Ne.peoplePraying)??0),engagementStatus:M.engagementStatus===!0||M.engagementStatus===1||!1,adoptionStatus:M.adoptionStatus===!0||M.adoptionStatus===1||!1,bible:M.bible||((nt=M._normalized)==null?void 0:nt.bible)||"Not Available",jesusFilm:M.jesusFilm||((Ye=M._normalized)==null?void 0:Ye.jesusFilm)||"Not Available",radio:M.radio||((St=M._normalized)==null?void 0:St.radio)||"Not Available",gospel:M.gospel||((It=M._normalized)==null?void 0:It.gospel)||"Not Available",audio:M.audio||((at=M._normalized)==null?void 0:at.audio)||"Not Available",stories:M.stories||((st=M._normalized)==null?void 0:st.stories)||"Not Available",lng:parseFloat(M.longitude||M.lng)||0},geometry:{type:"Point",coordinates:[parseFloat(M.longitude||M.lng),parseFloat(M.latitude||M.lat)]}}}).filter(M=>!isNaN(M.geometry.coordinates[0])&&!isNaN(M.geometry.coordinates[1])),E=.04,C=M=>`${M.geometry.coordinates[0]},${M.geometry.coordinates[1]}`,j=new Map;for(const M of _){const ue=C(M);j.has(ue)||j.set(ue,[]),j.get(ue).push(M)}for(const M of j.values()){if(M.length<2)continue;const[ue,oe]=M[0].geometry.coordinates,g=M.length;for(let f=0;f",["to-number",["get","peoplePraying"],0],0],"#ffffff","#1f2937"],"circle-opacity":1,"circle-stroke-opacity":1,"circle-opacity-transition":{duration:250}}}),se.addLayer({id:"language-family-pins-hitbox",type:"circle",source:"language-families",paint:{"circle-radius":Ds("touchTarget"),"circle-color":"rgba(0,0,0,0)","circle-stroke-width":0,"circle-opacity":0}}),s.value.languageFamilyPins=!0,r.attachLanguageFamilyEvents()}function u(ee,ce="doxa-regions"){const se=i();if(!se)return;se.getLayer("regions-fill")&&se.removeLayer("regions-fill"),se.getLayer("regions-border")&&se.removeLayer("regions-border"),se.getSource("regions")&&se.removeSource("regions");const he={africa:"#ff2e2e",asia:"#1a8cff",europe:"#1ee05f","latin_america_&_caribbean":"#FFEB3B",middle_east:"#a83dff","north_america_&_non-spanish_caribbean":"#00e0bd",oceania:"#ff7f11"},_="#cccccc",E=ee&&ee.isoToRegion?ee.isoToRegion:{},C=[],j={};for(const[oe,g]of Object.entries(E)){const f=he[String(g).toLowerCase()];f&&C.push(oe,f),j[g]||(j[g]=[]),j[g].push(oe)}l.value=j;const Y=C.length?["match",["get","iso_3166_1_alpha_3"],...C,_]:_,M=se.getLayer("language-family-pins-shadow")?"language-family-pins-shadow":se.getLayer("language-family-pins")?"language-family-pins":void 0,ue=(()=>{var oe,g,f;try{return!!((f=(g=(oe=se.getContainer)==null?void 0:oe.call(se))==null?void 0:g.classList)!=null&&f.contains("maplibregl-map"))}catch{return!1}})();ue?(se.addSource("regions",{type:"geojson",data:{type:"FeatureCollection",features:[]}}),$n(`${Is(null)}/boundaries/world-adm0-web.geojson`).then(oe=>oe.json()).then(oe=>{for(const f of oe.features||[])f.properties&&f.properties.iso3&&(f.properties.iso_3166_1_alpha_3=f.properties.iso3);const g=se.getSource("regions");g&&g.setData(oe)}).catch(oe=>console.warn("[map-layers] region boundaries failed to load",oe))):se.addSource("regions",{type:"vector",url:"mapbox://mapbox.country-boundaries-v1"}),se.addLayer({id:"regions-fill",type:"fill",source:"regions",...ue?{}:{"source-layer":"country_boundaries"},paint:{"fill-color":Y,"fill-opacity":ce==="none"?.1:.7,"fill-antialias":!0,...jo(se,"fill"),"fill-opacity-transition":{duration:300}}},M),se.addLayer({id:"regions-border",type:"line",source:"regions",...ue?{}:{"source-layer":"country_boundaries"},paint:{"line-color":Y,"line-width":1,"line-opacity":.9,...jo(se,"line")}},M),s.value.regions=!0,r.attachRegionEvents()}function d(ee,ce){const se=i();if(!se||!ee||!(ce!=null&&ce.length))return;p();const he=ce.filter(Y=>{var ue;return(Y.languageFamily||((ue=Y._normalized)==null?void 0:ue.languageFamily)||"Unknown")===ee});if(he.length<2)return;const _=n(ee),E=[],C=15,j=[...he].sort((Y,M)=>(Y.longitude||Y.lng)-(M.longitude||M.lng));for(let Y=0;Y=",["get","peoplePraying"],zo],"#15803d",[">",["get","peoplePraying"],0],"#d97706","rgba(0,0,0,0)"]}const Z=[0,5,2,6,4,7,5,9,6,12,7,14,8,18,10,25,12,32,14,40];function ne(ee,ce){const se=["interpolate",["linear"],["zoom"]];for(let he=0;he",["get","peoplePraying"],0],se=V();if(!F.every(_=>ee.getLayer(_))){for(const _ of F)if(ee.getLayer(_))try{ee.removeLayer(_)}catch{}ee.addLayer({id:k,type:"circle",source:"language-families",filter:ce,paint:{"circle-radius":ne(Z,1),"circle-color":se,"circle-blur":.3,"circle-opacity":.45,"circle-stroke-width":0,...jo(ee)}},"language-family-pins");for(const _ of P)ee.addLayer({id:_,type:"circle",source:"language-families",filter:ce,paint:{"circle-radius":ne(Z,1),"circle-radius-transition":J,"circle-color":"rgba(0,0,0,0)","circle-blur":0,"circle-opacity":1,"circle-stroke-width":2.5,"circle-stroke-color":se,"circle-stroke-opacity":0,"circle-stroke-opacity-transition":J,...jo(ee)}},"language-family-pins")}}function ie(){const ee=i();if(ee)for(const ce of F)ee.getLayer(ce)&&ee.removeLayer(ce)}function pe(ee){const ce=i();if(!ce)return;const se=[">",["get","peoplePraying"],0],he=ee?["all",se,ee]:se;for(const _ of F)if(ce.getLayer(_))try{ce.setFilter(_,he)}catch{}}function _e(){const ee=i();if(ee){if(O(),m&&(cancelAnimationFrame(m),m=null),b=!0,ee.getLayer("language-family-pins"))try{ee.setLayoutProperty("language-family-pins","circle-sort-key",["case",[">=",["get","peoplePraying"],zo],1,[">",["get","peoplePraying"],0],1,3])}catch{}xe(ee),we()}}function we(){m&&(cancelAnimationFrame(m),m=null);const ee=++y;let ce=performance.now(),se=-1/0;function he(_){if(!b||ee!==y)return;const E=i();if(!E||!F.some(j=>E.getLayer(j))){m=null;return}if(_-se"u")return;const ce=typeof ee.getContainer=="function"?ee.getContainer():null;if(ce)try{N=new IntersectionObserver(se=>{for(const he of se)T=he.isIntersecting},{threshold:0}),N.observe(ce)}catch{N=null,T=!0}}function ve(){if(N){try{N.disconnect()}catch{}N=null}T=!0}function Te(){b=!1,m&&(cancelAnimationFrame(m),m=null),ve();try{ie();const ee=i();ee&&ee.getLayer("language-family-pins")&&ee.setLayoutProperty("language-family-pins","circle-sort-key",void 0)}catch{}}return{layersAdded:s,regionIsoMap:l,addLanguageFamilyLayer:c,addRegionsLayer:u,addFamilyConnectionLines:d,removeFamilyConnectionLines:p,updateLayerSource:A,removeLayer:x,hasLayer:h,setPaintProperty:v,mapEvents:r,startPrayerGlow:_e,stopPrayerGlow:Te,syncGlowFilter:pe,cleanup:$}}const ki="#1a1a1a",So="selected-pin-highlight";function l0(e=ki){const t=document.createElement("div");t.className="map-go-pin-wrapper";const o=`pinGrad-${Math.random().toString(36).slice(2,8)}`;return t.innerHTML=` + `}function d(b){const y=t();if(!y)return;const k=b||"language-family-pins";if(!y.getLayer(k))return;const L=P=>{try{const F=P.features[0],w=F.properties,j=F.geometry.coordinates.slice();for(;Math.abs(P.lngLat.lng-j[0])>180;)j[0]+=P.lngLat.lng>j[0]?360:-360;if(i&&i.legendMode!==void 0){i.selectPeopleGroup(F),i.isMobile&&i.legendState==="collapsed"&&i.setLegendState("open"),i.isMobile||window.dispatchEvent(new CustomEvent("dxm:openDesktopLegend",{detail:{mapId:o}}));return}new(Es(y)).Popup({maxWidth:"320px",className:"people-group-popup"}).setLngLat(j).setHTML(c(w)).addTo(y)}catch(F){console.error(` [${o}] Pin click handler error:`,F)}};y.on("click",k,L),l.value.push({event:"click",layer:k,handler:L})}function p(){const b=t();if(!b)return;const y="regions-fill";if(!b.getLayer(y))return;const k=L=>{const P=["language-family-pins","language-family-pins-hitbox","people-groups-pins"];for(const F of P)if(b.getLayer(F)){const w=b.queryRenderedFeatures(L.point,{layers:[F]});if(w&&w.length>0)return}if(L.features&&L.features.length>0){const F=L.features[0].properties;new(Es(b)).Popup().setLngLat(L.lngLat).setHTML(u(F)).addTo(b)}};b.on("click",y,k),l.value.push({event:"click",layer:y,handler:k})}function x(){const b=t();if(!b)return;const y="clusters";if(!b.getLayer(y))return;const k=L=>{const P=b.queryRenderedFeatures(L.point,{layers:[y]});if(!P.length)return;const F=P[0].properties.cluster_id,w=P[0].source;b.getSource(w).getClusterExpansionZoom(F,(j,U)=>{j||b.easeTo({center:P[0].geometry.coordinates,zoom:U})})};b.on("click",y,k),l.value.push({event:"click",layer:y,handler:k})}function A(b){const y=t();if(!y||!y.getLayer(b))return;const k=()=>{y.getCanvas().style.cursor="pointer"},L=()=>{y.getCanvas().style.cursor=""};y.on("mouseenter",b,k),y.on("mouseleave",b,L),l.value.push({event:"mouseenter",layer:b,handler:k}),l.value.push({event:"mouseleave",layer:b,handler:L})}function m(){d("language-family-pins-hitbox"),A("language-family-pins"),A("language-family-pins-hitbox"),x(),A("clusters")}function v(){p(),A("regions-fill")}function _(){const b=t();b&&(b.getLayer("language-family-pins")&&m(),b.getLayer("regions-fill")&&v(),b.getLayer("people-groups-pins")&&(d("people-groups-pins"),A("people-groups-pins")))}function h(){const b=t();if(b){for(const{event:y,layer:k,handler:L}of l.value)try{b.off(y,k,L)}catch{}l.value=[]}}return{attachAllEventHandlers:_,attachLanguageFamilyEvents:m,attachRegionEvents:v,attachPinClickHandler:d,attachRegionClickHandler:p,attachClusterClickHandler:x,attachCursorHandlers:A,detachAllEventHandlers:h,generatePinPopupContent:c,generateRegionPopupContent:u,registeredHandlers:l}}const Os=[">",["to-number",["get","peoplePraying"],0],0],zs={0:.25,8:.4,14:.5},Pn={0:.1,8:.18,14:.22},Tn={0:1.2,8:1.6,14:2},Np=["interpolate",["linear"],["zoom"],0,["case",Os,zs[0],Pn[0]],8,["case",Os,zs[8],Pn[8]],14,["case",Os,zs[14],Pn[14]]],oy=["interpolate",["linear"],["zoom"],0,Tn[0],8,Tn[8],14,Tn[14]],Rp=["==",["to-number",["get","peoplePraying"],0],0],ny={prayer:[Rp],prayerProgress:[Rp],engagement:[["all",["!=",["get","engagementStatus"],!0],["!=",["get","engagementStatus"],1]]],adoption:[["all",["!=",["get","adoptionStatus"],!0],["!=",["get","adoptionStatus"],1]]]};function ay(e){const t=ny[e]||[];if(!t.length)return oy;const o=t.length===1?t[0]:["any",...t];return["interpolate",["linear"],["zoom"],0,["case",o,Pn[0],Tn[0]],8,["case",o,Pn[8],Tn[8]],14,["case",o,Pn[14],Tn[14]]]}const iy=[[/ sign language$/i,"Sign Language"]];function ry(e){if(!e||typeof e!="string")return null;const t=e.trim(),o=t.split(",").map(a=>a.trim()).filter(Boolean);if(o.length>=2){const a=[...o].reverse().join(" ");if(Je[a])return Je[a];if(o.length>=3){const i=[o[1],o[0]].join(" ");if(Je[i])return Je[i]}}if(Je[t])return Je[t];const n=t.replace(/\s*\(.*?\)\s*$/,"").trim();if(n!==t&&Je[n])return Je[n];for(const[a,i]of iy)if(a.test(t))return i;return null}function sy(e={}){const{getMap:t=()=>null,mapId:o="unknown",getLanguageFamilyColor:n=()=>"#999999",getNormalizedPeopleGroups:a=()=>[]}=e,i=()=>{const ee=t();return ee?De(ee):null},r=ty({getMap:i,mapId:o,getLanguageFamilyColor:n,getNormalizedPeopleGroups:a}),s=ge({languageFamilyPins:!1,regions:!1,peopleGroups:!1,familyConnections:!1}),l=ge({});function c(ee,ce="languageFamily"){const se=i();if(!se||!(ee!=null&&ee.length))return;const me=Cs(ce),$=ee.map(M=>{var ue,te,g,f,B,K,fe,oe,I,H,be,ke,Ne,nt,Ye,St,Dt,at,lt;return{type:"Feature",properties:{uniqueId:M.id||M.uniqueId,slug:M.slug||((ue=M._normalized)==null?void 0:ue.slug)||"",rop3:M.rop3||M.peopleGroupId,peopleId3:M.peopleId3||"",name:M.name,language:M.language,languageFamily:M.languageFamily||((te=M._normalized)==null?void 0:te.languageFamily)||ry(M.language)||"Unknown",affinityBlock:M.affinityBlock||M.affbloc||M.Affbloc||((g=M._normalized)==null?void 0:g.affinityBlock)||"Unknown",affinityBlockLabel:M.affinityBlockLabel||((f=M._normalized)==null?void 0:f.affinityBlockLabel)||"",cluster:M.cluster||((B=M._normalized)==null?void 0:B.cluster)||"",clusterLabel:M.clusterLabel||((K=M._normalized)==null?void 0:K.clusterLabel)||"",peopleGroup:M.peopleGroup||((fe=M._normalized)==null?void 0:fe.peopleGroup)||"",peopleGroupLabel:M.peopleGroupLabel||((oe=M._normalized)==null?void 0:oe.peopleGroupLabel)||"",doxaRegion:M.doxaRegion||((I=M._normalized)==null?void 0:I.doxaRegion)||"Unknown",wagfBlock:M.wagfBlock||((H=M._normalized)==null?void 0:H.wagfBlock)||"",wagfBlockLabel:M.wagfBlockLabel||((be=M._normalized)==null?void 0:be.wagfBlockLabel)||"",wagfRegionLabel:M.wagfRegionLabel||((ke=M._normalized)==null?void 0:ke.wagfRegionLabel)||"",country:M.country,countryIso:M.countryIso,countryIsoLabel:M.countryIsoLabel,countryName:M.countryName,religion:M.religion,religionCode:M.religionCode,religionLabel:M.religionLabel,religionName:M.religionName,population:M.population,status:M.status,description:M.description||"",imageUrl:M.imageUrl||"",peoplePraying:Number(M.peoplePraying??((Ne=M._normalized)==null?void 0:Ne.peoplePraying)??0),engagementStatus:M.engagementStatus===!0||M.engagementStatus===1||!1,adoptionStatus:M.adoptionStatus===!0||M.adoptionStatus===1||!1,bible:M.bible||((nt=M._normalized)==null?void 0:nt.bible)||"Not Available",jesusFilm:M.jesusFilm||((Ye=M._normalized)==null?void 0:Ye.jesusFilm)||"Not Available",radio:M.radio||((St=M._normalized)==null?void 0:St.radio)||"Not Available",gospel:M.gospel||((Dt=M._normalized)==null?void 0:Dt.gospel)||"Not Available",audio:M.audio||((at=M._normalized)==null?void 0:at.audio)||"Not Available",stories:M.stories||((lt=M._normalized)==null?void 0:lt.stories)||"Not Available",lng:parseFloat(M.longitude||M.lng)||0},geometry:{type:"Point",coordinates:[parseFloat(M.longitude||M.lng),parseFloat(M.latitude||M.lat)]}}}).filter(M=>!isNaN(M.geometry.coordinates[0])&&!isNaN(M.geometry.coordinates[1])),E=.04,C=M=>`${M.geometry.coordinates[0]},${M.geometry.coordinates[1]}`,G=new Map;for(const M of $){const ue=C(M);G.has(ue)||G.set(ue,[]),G.get(ue).push(M)}for(const M of G.values()){if(M.length<2)continue;const[ue,te]=M[0].geometry.coordinates,g=M.length;for(let f=0;f",["to-number",["get","peoplePraying"],0],0],"#ffffff","#1f2937"],"circle-opacity":1,"circle-stroke-opacity":1,"circle-opacity-transition":{duration:250}}}),se.addLayer({id:"language-family-pins-hitbox",type:"circle",source:"language-families",paint:{"circle-radius":Ds("touchTarget"),"circle-color":"rgba(0,0,0,0)","circle-stroke-width":0,"circle-opacity":0}}),s.value.languageFamilyPins=!0,r.attachLanguageFamilyEvents()}function u(ee,ce="doxa-regions"){const se=i();if(!se)return;se.getLayer("regions-fill")&&se.removeLayer("regions-fill"),se.getLayer("regions-border")&&se.removeLayer("regions-border"),se.getSource("regions")&&se.removeSource("regions");const me={africa:"#ff2e2e",asia:"#1a8cff",europe:"#1ee05f","latin_america_&_caribbean":"#FFEB3B",middle_east:"#a83dff","north_america_&_non-spanish_caribbean":"#00e0bd",oceania:"#ff7f11"},$="#cccccc",E=ee&&ee.isoToRegion?ee.isoToRegion:{},C=[],G={};for(const[te,g]of Object.entries(E)){const f=me[String(g).toLowerCase()];f&&C.push(te,f),G[g]||(G[g]=[]),G[g].push(te)}l.value=G;const Y=C.length?["match",["get","iso_3166_1_alpha_3"],...C,$]:$,M=se.getLayer("language-family-pins-shadow")?"language-family-pins-shadow":se.getLayer("language-family-pins")?"language-family-pins":void 0,ue=(()=>{var te,g,f;try{return!!((f=(g=(te=se.getContainer)==null?void 0:te.call(se))==null?void 0:g.classList)!=null&&f.contains("maplibregl-map"))}catch{return!1}})();ue?(se.addSource("regions",{type:"geojson",data:{type:"FeatureCollection",features:[]}}),_n(`${Is(null)}/boundaries/world-adm0-web.geojson`).then(te=>te.json()).then(te=>{for(const f of te.features||[])f.properties&&f.properties.iso3&&(f.properties.iso_3166_1_alpha_3=f.properties.iso3);const g=se.getSource("regions");g&&g.setData(te)}).catch(te=>console.warn("[map-layers] region boundaries failed to load",te))):se.addSource("regions",{type:"vector",url:"mapbox://mapbox.country-boundaries-v1"}),se.addLayer({id:"regions-fill",type:"fill",source:"regions",...ue?{}:{"source-layer":"country_boundaries"},paint:{"fill-color":Y,"fill-opacity":ce==="none"?.1:.7,"fill-antialias":!0,...Go(se,"fill"),"fill-opacity-transition":{duration:300}}},M),se.addLayer({id:"regions-border",type:"line",source:"regions",...ue?{}:{"source-layer":"country_boundaries"},paint:{"line-color":Y,"line-width":1,"line-opacity":.9,...Go(se,"line")}},M),s.value.regions=!0,r.attachRegionEvents()}function d(ee,ce){const se=i();if(!se||!ee||!(ce!=null&&ce.length))return;p();const me=ce.filter(Y=>{var ue;return(Y.languageFamily||((ue=Y._normalized)==null?void 0:ue.languageFamily)||"Unknown")===ee});if(me.length<2)return;const $=n(ee),E=[],C=15,G=[...me].sort((Y,M)=>(Y.longitude||Y.lng)-(M.longitude||M.lng));for(let Y=0;Y=",["get","peoplePraying"],zo],"#15803d",[">",["get","peoplePraying"],0],"#d97706","rgba(0,0,0,0)"]}const Z=[0,5,2,6,4,7,5,9,6,12,7,14,8,18,10,25,12,32,14,40];function ne(ee,ce){const se=["interpolate",["linear"],["zoom"]];for(let me=0;me",["get","peoplePraying"],0],se=V();if(!F.every($=>ee.getLayer($))){for(const $ of F)if(ee.getLayer($))try{ee.removeLayer($)}catch{}ee.addLayer({id:k,type:"circle",source:"language-families",filter:ce,paint:{"circle-radius":ne(Z,1),"circle-color":se,"circle-blur":.3,"circle-opacity":.45,"circle-stroke-width":0,...Go(ee)}},"language-family-pins");for(const $ of P)ee.addLayer({id:$,type:"circle",source:"language-families",filter:ce,paint:{"circle-radius":ne(Z,1),"circle-radius-transition":J,"circle-color":"rgba(0,0,0,0)","circle-blur":0,"circle-opacity":1,"circle-stroke-width":2.5,"circle-stroke-color":se,"circle-stroke-opacity":0,"circle-stroke-opacity-transition":J,...Go(ee)}},"language-family-pins")}}function re(){const ee=i();if(ee)for(const ce of F)ee.getLayer(ce)&&ee.removeLayer(ce)}function pe(ee){const ce=i();if(!ce)return;const se=[">",["get","peoplePraying"],0],me=ee?["all",se,ee]:se;for(const $ of F)if(ce.getLayer($))try{ce.setFilter($,me)}catch{}}function $e(){const ee=i();if(ee){if(O(),h&&(cancelAnimationFrame(h),h=null),b=!0,ee.getLayer("language-family-pins"))try{ee.setLayoutProperty("language-family-pins","circle-sort-key",["case",[">=",["get","peoplePraying"],zo],1,[">",["get","peoplePraying"],0],1,3])}catch{}xe(ee),we()}}function we(){h&&(cancelAnimationFrame(h),h=null);const ee=++y;let ce=performance.now(),se=-1/0;function me($){if(!b||ee!==y)return;const E=i();if(!E||!F.some(G=>E.getLayer(G))){h=null;return}if($-se"u")return;const ce=typeof ee.getContainer=="function"?ee.getContainer():null;if(ce)try{N=new IntersectionObserver(se=>{for(const me of se)T=me.isIntersecting},{threshold:0}),N.observe(ce)}catch{N=null,T=!0}}function ve(){if(N){try{N.disconnect()}catch{}N=null}T=!0}function Te(){b=!1,h&&(cancelAnimationFrame(h),h=null),ve();try{re();const ee=i();ee&&ee.getLayer("language-family-pins")&&ee.setLayoutProperty("language-family-pins","circle-sort-key",void 0)}catch{}}return{layersAdded:s,regionIsoMap:l,addLanguageFamilyLayer:c,addRegionsLayer:u,addFamilyConnectionLines:d,removeFamilyConnectionLines:p,updateLayerSource:x,removeLayer:A,hasLayer:m,setPaintProperty:v,mapEvents:r,startPrayerGlow:$e,stopPrayerGlow:Te,syncGlowFilter:pe,cleanup:_}}const ki="#1a1a1a",So="selected-pin-highlight";function ly(e=ki){const t=document.createElement("div");t.className="map-go-pin-wrapper";const o=`pinGrad-${Math.random().toString(36).slice(2,8)}`;return t.innerHTML=`