Some checks are pending
build-gate / build (push) Waiting to run
public/ is committed so a developer can run the dev server immediately without a four-minute build first, which means stale build output ships to every clone. This rebuild carries the heading-prop rename in both capability-5 shells and relocates the archived gallery to its own path.
363 lines
56 KiB
HTML
363 lines
56 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>doxa-maps</title>
|
|
<link rel="icon" href="data:,">
|
|
<style>
|
|
:root{--bg:#fff;--bg2:#f6f7f9;--bg3:#eceef2;--bd:#e2e5ea;--fg:#15181d;--mut:#667085;--err:#d13c3c}
|
|
@media(prefers-color-scheme:dark){:root{--bg:#111318;--bg2:#181b21;--bg3:#232730;--bd:#2b3038;--fg:#eef0f3;--mut:#9aa3b2;--err:#ff7b7b}}
|
|
*{box-sizing:border-box}html,body{margin:0;height:100%}
|
|
body{background:var(--bg);color:var(--fg);font:14px/1.4 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;display:flex;flex-direction:column;height:100vh}
|
|
header{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:8px 14px;border-bottom:1px solid var(--bd)}
|
|
header b{font-size:15px}.n{color:var(--mut);font-size:12px}
|
|
#menu{display:none;margin-left:auto}
|
|
button,.btn{font:inherit;font-size:12px;padding:5px 10px;border:1px solid var(--bd);border-radius:8px;background:var(--bg2);color:var(--fg);cursor:pointer;text-decoration:none;white-space:nowrap}
|
|
button:hover,.btn:hover{background:var(--bg3)}
|
|
.body{flex:1 1 auto;display:flex;min-height:0}
|
|
nav{flex:0 0 270px;overflow:auto;border-right:1px solid var(--bd);padding:6px 0 20px}
|
|
.gh{padding:12px 14px 4px;font-weight:600;font-size:13px;display:flex;justify-content:space-between;gap:8px}.gh span{color:var(--mut);font-weight:400}
|
|
.row{display:block;width:100%;text-align:left;border:0;border-radius:0;background:none;padding:6px 14px;font-size:13px;line-height:1.3;white-space:normal}
|
|
.row.in{padding-left:28px}.row code{display:block;font-size:11px;color:var(--mut)}
|
|
.row:hover{background:var(--bg2)}.row[aria-current=true]{background:var(--bg3);font-weight:600}
|
|
main{flex:1 1 auto;display:flex;flex-direction:column;min-width:0;padding:10px 12px 12px;gap:8px}
|
|
.bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
.bar .t{flex:1 1 auto;min-width:0}.bar b{font-size:15px}.s{display:block;font-size:12px;color:var(--mut)}
|
|
code{font-family:ui-monospace,Menlo,monospace}
|
|
#embed{display:flex;flex-direction:column;gap:6px;padding:8px;border:1px solid var(--bd);border-radius:8px;background:var(--bg2)}
|
|
#embed label{font-size:12px;color:var(--mut);display:flex;justify-content:space-between;align-items:center}
|
|
#embed textarea{width:100%;height:58px;font:11px/1.35 ui-monospace,Menlo,monospace;border:1px solid var(--bd);border-radius:6px;background:var(--bg);color:var(--fg);padding:6px;resize:vertical}
|
|
#slot{flex:1 1 auto;position:relative;min-height:240px;border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg2)}
|
|
#slot>*{position:absolute;inset:0;width:100%;height:100%;display:block;border:0}
|
|
.hint{display:grid;place-items:center;padding:20px;text-align:center;color:var(--mut);font-size:13px}
|
|
.hint.err{color:var(--err)}
|
|
.hint input{font:12px ui-monospace,Menlo,monospace;padding:6px 8px;border-radius:6px;border:1px solid var(--bd);background:var(--bg);color:var(--fg);width:min(320px,70vw)}
|
|
/* Capability tiers (5 → 1): the vertical-tab sections of the nested-mfe-spa sidebar. */
|
|
.tier{padding:12px 14px 2px;display:flex;align-items:baseline;gap:7px}
|
|
.tier .cap{font:700 10px/1 system-ui,sans-serif;letter-spacing:.5px;color:var(--bg);background:var(--mut);border-radius:999px;padding:3px 7px}
|
|
.tier .tl{font-weight:600;font-size:12px}.tier .td{color:var(--mut);font-size:11px;margin-left:auto}
|
|
#app{flex:1 1 auto;display:flex;flex-direction:column;min-height:0}
|
|
[hidden]{display:none!important}
|
|
@media(max-width:700px){.body{flex-direction:column}nav{flex:0 0 auto;max-height:45vh;border-right:0;border-bottom:1px solid var(--bd)}#menu{display:inline-block}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- The doxa-maps nested-mfe-spa (capability 5, the reserved app-shell): a Vue single-page app
|
|
whose sidebar is the composability ladder rendered as vertical tabs (capability 5 → 1),
|
|
each row mounting that plugin's custom element in the one slot. This SAME page is the
|
|
served studio root (public/index.html) AND the Capacitor mobile-app entry (webDir: public).
|
|
Vue drives the shell; the map elements mount imperatively into #slot (they own their DOM). -->
|
|
<div id="app">
|
|
<header>
|
|
<b>doxa-maps</b><span class="n">{{ countLabel }}</span>
|
|
<button id="menu" type="button" aria-controls="nav" aria-expanded="true" @click="navOpen = !navOpen">Maps</button>
|
|
</header>
|
|
<div class="body">
|
|
<nav id="nav" aria-label="Maps · sorted by capability 5 → 1" v-show="navOpen">
|
|
<div class="grp" v-for="grp in groups" :key="grp.cap">
|
|
<div class="tier"><span class="cap">CAP {{ grp.cap }}</span><span class="tl">{{ grp.label }}</span><span class="td">{{ grp.rows.length }}</span></div>
|
|
<button v-for="r in grp.rows" :key="r.key" type="button" class="row" :class="{ in: r.kind === 'instance' }"
|
|
:aria-current="String(activeKey === r.key)" @click="go(r.key)">
|
|
<span>{{ r.label }}</span><code><{{ r.tag }}></code>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
<main>
|
|
<div class="bar">
|
|
<div class="t"><b>{{ ttl }}</b><span class="s">{{ sub }}</span></div>
|
|
<a class="btn" :href="openHref" target="_blank" rel="noopener" v-show="openHref">Open page</a>
|
|
<button type="button" v-show="hasSel" :aria-expanded="String(embedOpen)" @click="embedOpen = !embedOpen">Copy embed</button>
|
|
</div>
|
|
<div id="embed" v-show="embedOpen && hasSel">
|
|
<label>iframe embed <button type="button" @click="copyText(embedIframe, $event.target)">Copy</button></label>
|
|
<textarea readonly :value="embedIframe"></textarea>
|
|
<div v-show="embedScript"><label>script embed <button type="button" @click="copyText(embedScript, $event.target)">Copy</button></label>
|
|
<textarea readonly :value="embedScript"></textarea></div>
|
|
</div>
|
|
<div id="slot" ref="slot"><div class="hint">Choose a map from the list.</div></div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
(function () {
|
|
var TREE = "../doxa-maps", TOKEN_SRC = "window.MAP_TOKEN_URL", BAKED_TOKEN = "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ", API_BASE = "https://pray.doxa.life", ASSETS_BASE = "", QA = false;
|
|
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
|
var BAKED = [{"b":"all-people-groups","s":"./all-people-groups/all-people-groups.js","i":"sha384-ow6UD//vvtt8jD+CSsKeKm4oLZF6l0lYw94ro7it8ugZTrTudkDYjq/bjwOKK3Ku","e":["all-people-groups"],"p":[{"p":"all-people-groups","e":"all-people-groups","u":"./all-people-groups/all-people-groups/index.html","d":"All 12,379 people groups (peoplegroups.org) in four colors — reached,","c":1,"em":{"iframe":"\u003ciframe src=\"./all-people-groups/all-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-ow6UD//vvtt8jD+CSsKeKm4oLZF6l0lYw94ro7it8ugZTrTudkDYjq/bjwOKK3Ku\" crossorigin=\"anonymous\">\u003c/script>\n\u003call-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/all-people-groups>"}}]},{"b":"big-picture","s":"./big-picture/big-picture.js","i":"sha384-7X3BmNd7ppXsO/aL4Dpjt8/j1vrjXEuZ3JMOodzDyGZIevb9/IounnGQjDut1RCd","e":["bp-big-picture","bp-research-header","people-groups-grid"],"p":[{"p":"big-picture","e":"bp-big-picture","u":"./big-picture/big-picture/index.html","d":"The Cat-4 composite that PAIRS the two real capability elements of the","c":4,"em":{"iframe":"\u003ciframe src=\"./big-picture/big-picture/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./big-picture/big-picture.js\" integrity=\"sha384-7X3BmNd7ppXsO/aL4Dpjt8/j1vrjXEuZ3JMOodzDyGZIevb9/IounnGQjDut1RCd\" crossorigin=\"anonymous\">\u003c/script>\n\u003cbp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/bp-big-picture>"},"in":[{"id":"big-picture","t":"Big Picture — research header ↔ people-groups grid (composite)","a":"","pr":{"profile":"big-picture","provider":"maplibre","instanceId":"bp-big-picture"},"e":"bp-big-picture","u":"./big-picture/big-picture/big-picture/index.html"},{"id":"frontier","t":"Big Picture — frontier accent","a":"","pr":{"profile":"big-picture","provider":"maplibre","instanceId":"bp-big-picture-frontier","accent":"#22d3ee","title":"Big Picture — Frontier Focus"},"e":"bp-big-picture","u":"./big-picture/big-picture/frontier/index.html"}]}]},{"b":"cities-internet","s":"./cities-internet/cities-internet.js","i":"sha384-TxqECrIH9paOxCxEJOSIPPJ0iOGlOh/Dp01NQ9WcJQ6Gu5K2pB7IP2NXw6tqOTbS","e":["cities-internet-map"],"p":[{"p":"connectivity-list","e":"cities-internet-map","u":"./cities-internet/connectivity-list/index.html","d":"Countries where DX people groups live, grouped by region, showing measured internet penetration","c":1,"em":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-TxqECrIH9paOxCxEJOSIPPJ0iOGlOh/Dp01NQ9WcJQ6Gu5K2pB7IP2NXw6tqOTbS\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/cities-internet-map>"}},{"p":"connectivity-map","e":"cities-internet-map","u":"./cities-internet/connectivity-map/index.html","d":"Every country shaded by measured internet penetration, with its named carriers and dominant","c":1,"em":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-TxqECrIH9paOxCxEJOSIPPJ0iOGlOh/Dp01NQ9WcJQ6Gu5K2pB7IP2NXw6tqOTbS\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/cities-internet-map>"}},{"p":"data-tables","e":"cities-internet-map","u":"./cities-internet/data-tables/index.html","d":"Four validated research tables, one tab each, with sortable and filterable columns, CSV and JSON","c":1,"em":{"iframe":"\u003ciframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-TxqECrIH9paOxCxEJOSIPPJ0iOGlOh/Dp01NQ9WcJQ6Gu5K2pB7IP2NXw6tqOTbS\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/cities-internet-map>"}}]},{"b":"country-posters","s":"./country-posters/country-posters.js","i":"sha384-W7C5dqO9Otokvs5+g+2BO/FIY0e//dqfciPCAZUkDRhF2iGY1+HhccCUFkSeavUU","e":["country-posters-map"],"p":[{"p":"countries-map","e":"country-posters-map","u":"./country-posters/countries-map/index.html","d":"The researcher's workbench retargeted from people groups to COUNTRIES — region","c":1,"em":{"iframe":"\u003ciframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-W7C5dqO9Otokvs5+g+2BO/FIY0e//dqfciPCAZUkDRhF2iGY1+HhccCUFkSeavUU\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/country-posters-map>"}},{"p":"country-poster-wall","e":"country-posters-map","u":"./country-posters/country-poster-wall/index.html","d":"One printable card per country, grouped into COLLAPSIBLE regional sections so the","c":1,"em":{"iframe":"\u003ciframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-W7C5dqO9Otokvs5+g+2BO/FIY0e//dqfciPCAZUkDRhF2iGY1+HhccCUFkSeavUU\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/country-posters-map>"}}]},{"b":"dashboard-plus-grid","s":"./dashboard-plus-grid/dashboard-plus-grid.js","i":"sha384-ZTBT33h/wSugbZO41VmjJTYKdWeRVwuSD7mP5uSMGSpYkjBZkFed1AaAzabxAxRV","e":["dashboard-plus-grid"],"p":[{"p":"dashboard-plus-grid","e":"dashboard-plus-grid","u":"./dashboard-plus-grid/dashboard-plus-grid/index.html","d":"Both children are IMPORTED DIRECTLY (compiled into this ONE bundle — no runtime bundle","c":4,"em":{"iframe":"\u003ciframe src=\"./dashboard-plus-grid/dashboard-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-ZTBT33h/wSugbZO41VmjJTYKdWeRVwuSD7mP5uSMGSpYkjBZkFed1AaAzabxAxRV\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/dashboard-plus-grid>"},"in":[{"id":"default","t":"default","a":"","pr":{"profile":"dashboard-plus-grid"},"e":"dashboard-plus-grid","u":"./dashboard-plus-grid/dashboard-plus-grid/default/index.html"}]}]},{"b":"demo-map","s":"./demo-map/demo-map.js","i":"sha384-sNkrKqoXD+BS4MABLGB8THNMi3BRl+Wcazb7xoMqaowlGknoCQHF8pU1q7CtsfpT","e":["demo-map"],"p":[{"p":"demo-map","e":"demo-map","u":"./demo-map/demo-map/index.html","d":"Simple two-tab demo map. Tab 1 colors pins by religion (shared","c":2,"em":{"iframe":"\u003ciframe src=\"./demo-map/demo-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./demo-map/demo-map.js\" integrity=\"sha384-sNkrKqoXD+BS4MABLGB8THNMi3BRl+Wcazb7xoMqaowlGknoCQHF8pU1q7CtsfpT\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdemo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/demo-map>"},"in":[{"id":"maplibre","t":"Demo map — MapLibre (token-free)","a":"","pr":{"profile":"demo-map","provider":"maplibre","instanceId":"demo-maplibre"},"e":"demo-map","u":"./demo-map/demo-map/maplibre/index.html"}]}]},{"b":"doxa-maps-spa","s":"./doxa-maps-spa/doxa-maps-spa.js","i":"sha384-B4pav5Auf7CroDqfDfc+lKMOsRiLGmktb3q9sU7VFs7OOQZQcMfi2x/8clPoHMjr","e":["doxa-maps-spa"],"p":[{"p":"doxa-maps-spa","e":"doxa-maps-spa","u":"./doxa-maps-spa/doxa-maps-spa/index.html","d":"The DX-maps app itself: one element whose vertical-tab rail nests every other","c":5,"em":{"iframe":"\u003ciframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-B4pav5Auf7CroDqfDfc+lKMOsRiLGmktb3q9sU7VFs7OOQZQcMfi2x/8clPoHMjr\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/doxa-maps-spa>"},"in":[{"id":"app","t":"DX-Maps SPA — Big Picture + every research map (app-shell)","a":"","pr":{"profile":"doxa-maps-spa","provider":"maplibre","instanceId":"doxa-maps-spa-app","title":"DX-Maps"},"e":"doxa-maps-spa","u":"./doxa-maps-spa/doxa-maps-spa/app/index.html"}]}]},{"b":"doxa-research-map","s":"./doxa-research-map/doxa-research-map.js","i":"sha384-5I+d9O+m1RpzzM6S93o6lKRRtURUbFV+Pw4liNp0ontv/2mwm8yqD6UT2vBytNFY","e":["doxa-research-map"],"p":[{"p":"research-map","e":"doxa-research-map","u":"./doxa-research-map/research-map/index.html","d":"The researcher's workbench: every piece a missiologist, demographer or","c":3,"em":{"iframe":"\u003ciframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-5I+d9O+m1RpzzM6S93o6lKRRtURUbFV+Pw4liNp0ontv/2mwm8yqD6UT2vBytNFY\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/doxa-research-map>"}}]},{"b":"doxa-simple-map","s":"./doxa-simple-map/doxa-simple-map.js","i":"sha384-qTaIz+Irs4qPFjmJqkNCYlI2/tiLO9f/9uDnpLbOPkXkCEbja6VxfyspfuuXh8Qy","e":["doxa-map","doxa-simple-map"],"p":[{"p":"doxa-simple-map","e":"doxa-map","u":"./doxa-simple-map/doxa-simple-map/index.html","d":"The simple world map, shipped as three presets off ONE prop table —","c":2,"em":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-qTaIz+Irs4qPFjmJqkNCYlI2/tiLO9f/9uDnpLbOPkXkCEbja6VxfyspfuuXh8Qy\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/doxa-map>"},"in":[{"id":"prayer","t":"Prayer Map","a":"https://pray.doxa.life","pr":{"profile":"doxa-simple-map","instanceId":"embed-prayer","dataSource":"pray-tools","tabs":[{"id":"prayer","colorStrategy":"prayer","legend":"prayer","popup":"prayer"}]},"e":"doxa-map","u":"./doxa-simple-map/doxa-simple-map/prayer/index.html"},{"id":"engagement","t":"Engagement Map","a":"https://pray.doxa.life","pr":{"profile":"doxa-simple-map","instanceId":"embed-engagement","dataSource":"pray-tools","tabs":[{"id":"engagement","colorStrategy":"engagement","legend":"engagement","popup":"engagement"}]},"e":"doxa-map","u":"./doxa-simple-map/doxa-simple-map/engagement/index.html"},{"id":"adoption","t":"Adoption Map","a":"https://pray.doxa.life","pr":{"profile":"doxa-simple-map","instanceId":"embed-adoption","dataSource":"pray-tools","tabs":[{"id":"adoption","colorStrategy":"adoption","legend":"adoption","popup":"adoption"}]},"e":"doxa-map","u":"./doxa-simple-map/doxa-simple-map/adoption/index.html"}]}]},{"b":"doxa-vision-card","s":"./doxa-vision-card/doxa-vision-card.js","i":"sha384-2mza3cltN5LQQIrlENG/IVL6Icd9zzcFve3E8R8VWuCdYOGW9uGKFvxcbu6eFfwn","e":["doxa-vision-card"],"p":[{"p":"doxa-vision-card","e":"doxa-vision-card","u":"./doxa-vision-card/doxa-vision-card/index.html","d":"The Capability-1 example: ONE folder -> ONE bundle -> ONE element.","c":2,"em":{"iframe":"\u003ciframe src=\"./doxa-vision-card/doxa-vision-card/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-vision-card/doxa-vision-card.js\" integrity=\"sha384-2mza3cltN5LQQIrlENG/IVL6Icd9zzcFve3E8R8VWuCdYOGW9uGKFvxcbu6eFfwn\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-vision-card profile-config='{\"profile\":\"doxa-vision-card\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/doxa-vision-card>"},"in":[{"id":"default","t":"Vision card — reach at a glance","a":"","pr":{"profile":"doxa-vision-card","provider":"maplibre","instanceId":"vision-card-default"},"e":"doxa-vision-card","u":"./doxa-vision-card/doxa-vision-card/default/index.html"},{"id":"frontier","t":"Vision card — frontier accent","a":"","pr":{"profile":"doxa-vision-card","provider":"maplibre","instanceId":"vision-card-frontier","accent":"#22d3ee","title":"The last frontier of the gospel.","tagline":"Frontier people groups — where fewer than one in a thousand follow Jesus.","stats":[{"value":"1,910","label":"Frontier groups","note":"under 0.1% believers"},{"value":"2.1B","label":"People","note":"living in frontier settings"},{"value":"0.1%","label":"Christian","note":"or fewer, in each group"}]},"e":"doxa-vision-card","u":"./doxa-vision-card/doxa-vision-card/frontier/index.html"}]}]},{"b":"golden-path-check","s":"./golden-path-check/golden-path-check.js","i":"sha384-1V/sT5SkV0U9yTCK9ELIOFfU1ZIJkInIF6nG3UllJ4dxax3RsEAupSGLJEFK6e14","e":["golden-path-check"],"p":[{"p":"golden-path-check","e":"golden-path-check","u":"./golden-path-check/golden-path-check/index.html","d":"A live self-test of the develop-a-plugin-and-see-it-live path: it reads the built","c":1,"em":{"iframe":"\u003ciframe src=\"./golden-path-check/golden-path-check/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./golden-path-check/golden-path-check.js\" integrity=\"sha384-1V/sT5SkV0U9yTCK9ELIOFfU1ZIJkInIF6nG3UllJ4dxax3RsEAupSGLJEFK6e14\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgolden-path-check profile-config='{\"profile\":\"golden-path-check\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/golden-path-check>"}}]},{"b":"gospel-resources","s":"./gospel-resources/gospel-resources.js","i":"sha384-AyMRDqYNDLShkHxHQAWRlGIPRlnfmG1S565v8hz+mCbGxQuc6WqRDiweBUTk86VI","e":["gospel-resources-map"],"p":[{"p":"charts","e":"gospel-resources-map","u":"./gospel-resources/charts/index.html","d":"The gap by LANGUAGE (ISO 639-3) rather than by people group — because one translation serves every","c":1,"em":{"iframe":"\u003ciframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-AyMRDqYNDLShkHxHQAWRlGIPRlnfmG1S565v8hz+mCbGxQuc6WqRDiweBUTk86VI\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/gospel-resources-map>"}},{"p":"data-tables","e":"gospel-resources-map","u":"./gospel-resources/data-tables/index.html","d":"Two validated tables: what resources exist per people group, and whether that access is keeping","c":1,"em":{"iframe":"\u003ciframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-AyMRDqYNDLShkHxHQAWRlGIPRlnfmG1S565v8hz+mCbGxQuc6WqRDiweBUTk86VI\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/gospel-resources-map>"}},{"p":"resources-list","e":"gospel-resources-map","u":"./gospel-resources/resources-list/index.html","d":"Which DX people groups have scripture in their own language, and which have none. The zero-resource","c":1,"em":{"iframe":"\u003ciframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-AyMRDqYNDLShkHxHQAWRlGIPRlnfmG1S565v8hz+mCbGxQuc6WqRDiweBUTk86VI\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/gospel-resources-map>"}},{"p":"resources-map","e":"gospel-resources-map","u":"./gospel-resources/resources-map/index.html","d":"Every country shaded by how much of its unengaged population has NO gospel resource in their own","c":1,"em":{"iframe":"\u003ciframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-AyMRDqYNDLShkHxHQAWRlGIPRlnfmG1S565v8hz+mCbGxQuc6WqRDiweBUTk86VI\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/gospel-resources-map>"}}]},{"b":"my-upg-100-list","s":"./my-upg-100-list/my-upg-100-list.js","i":"sha384-Juq7s0kvFMgANiysoEh2N3MFkIZU16TZCmRdTKJ6nI8MWOWn4BfpaT8Syy9NJlJk","e":["my-upg-100-list-map"],"p":[{"p":"poster-wall","e":"my-upg-100-list-map","u":"./my-upg-100-list/poster-wall/index.html","d":"A wall of printable poster cards, one per 100-list entry, each with a \"Show on map\" action that","c":1,"em":{"iframe":"\u003ciframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Juq7s0kvFMgANiysoEh2N3MFkIZU16TZCmRdTKJ6nI8MWOWn4BfpaT8Syy9NJlJk\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/my-upg-100-list-map>"}},{"p":"upg-100-list","e":"my-upg-100-list-map","u":"./my-upg-100-list/upg-100-list/index.html","d":"A personal unreached-people-groups list painted as true boundary polygons —","c":1,"em":{"iframe":"\u003ciframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Juq7s0kvFMgANiysoEh2N3MFkIZU16TZCmRdTKJ6nI8MWOWn4BfpaT8Syy9NJlJk\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/my-upg-100-list-map>"}},{"p":"upg-100-mfe","e":"my-upg-100-list-map","u":"./my-upg-100-list/upg-100-mfe/index.html","d":"The standalone, shareable and embeddable composite of the my-upg-100-list family — it nests the map","c":4,"em":{"iframe":"\u003ciframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Juq7s0kvFMgANiysoEh2N3MFkIZU16TZCmRdTKJ6nI8MWOWn4BfpaT8Syy9NJlJk\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/my-upg-100-list-map>"}}]},{"b":"people-groups-grid","s":"./people-groups-grid/people-groups-grid.js","i":"sha384-0cN3wEexnRF5JYOWTVJojVHBjB4zJOmF5JTsGlbF2Z++9HXr80xEm8QNgYf0emZl","e":["people-groups-grid"],"p":[{"p":"people-groups-grid","e":"people-groups-grid","u":"./people-groups-grid/people-groups-grid/index.html","d":"The people-groups grid as an owned custom element: card list, search and four","c":2,"em":{"iframe":"\u003ciframe src=\"./people-groups-grid/people-groups-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./people-groups-grid/people-groups-grid.js\" integrity=\"sha384-0cN3wEexnRF5JYOWTVJojVHBjB4zJOmF5JTsGlbF2Z++9HXr80xEm8QNgYf0emZl\" crossorigin=\"anonymous\">\u003c/script>\n\u003cpeople-groups-grid profile-config='{\"profile\":\"people-groups-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/people-groups-grid>"},"in":[{"id":"people-groups-grid","t":"People Groups Grid — standalone (own search + 4 dropdowns)","a":"","pr":{"profile":"people-groups-grid","provider":"maplibre","instanceId":"people-groups-grid","pray-base-url":"https://pray.doxa.life"},"e":"people-groups-grid","u":"./people-groups-grid/people-groups-grid/people-groups-grid/index.html"},{"id":"driven","t":"People Groups Grid — externally driven (controls off, props/events)","a":"","pr":{"profile":"people-groups-grid","provider":"maplibre","instanceId":"people-groups-grid-driven","pray-base-url":"https://pray.doxa.life","show-search":"false","show-filters":"false"},"e":"people-groups-grid","u":"./people-groups-grid/people-groups-grid/driven/index.html"}]}]},{"b":"plugin-studio","s":"./plugin-studio/plugin-studio.js","i":"sha384-7kLgDpGt6tGiS6b7It8xEQne6llXA/JwlLosJlS7DuWrLQzk7aHJt0jMsPNN0e1H","e":["plugin-studio"],"p":[{"p":"plugin-studio","e":"plugin-studio","u":"./plugin-studio/plugin-studio/index.html","d":"The developer's viewing surface: a sidebar that is the composability ladder","c":5,"em":{"iframe":"\u003ciframe src=\"./plugin-studio/plugin-studio/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-7kLgDpGt6tGiS6b7It8xEQne6llXA/JwlLosJlS7DuWrLQzk7aHJt0jMsPNN0e1H\" crossorigin=\"anonymous\">\u003c/script>\n\u003cplugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/plugin-studio>"},"in":[{"id":"studio","t":"Plugin Studio — every element by capability 5 -> 1 (developer studio)","a":"","pr":{"profile":"plugin-studio","provider":"maplibre","instanceId":"plugin-studio","title":"Plugin Studio"},"e":"plugin-studio","u":"./plugin-studio/plugin-studio/studio/index.html"}]}]},{"b":"research-dashboard","s":"./research-dashboard/research-dashboard.js","i":"sha384-9TEWL+obO+3sksIr7XxE9w0XHvQ8EjxlzQ3OoVWWfFsc+IKoxwj5npHIStzBGgH/","e":["research-dashboard-map"],"p":[{"p":"research-dashboard","e":"research-dashboard-map","u":"./research-dashboard/research-dashboard/index.html","d":"Coordinated-Multiple-Views research dashboard for 10/40-window UPGs (live pray.doxa.life): docked facet rail + map-as-hero + linked table/charts, brushing-and-linking over one in-memory store.","c":3,"em":{"iframe":"\u003ciframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-9TEWL+obO+3sksIr7XxE9w0XHvQ8EjxlzQ3OoVWWfFsc+IKoxwj5npHIStzBGgH/\" crossorigin=\"anonymous\">\u003c/script>\n\u003cresearch-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/research-dashboard-map>"}}]},{"b":"research-map-plus-grid","s":"./research-map-plus-grid/research-map-plus-grid.js","i":"sha384-kFphDHIpEeUHa9M6viOQW9dLfeg/Yzrd7WFcIoZVAKhvbarfXlYJ00efPxO9AKXy","e":["research-map-plus-grid"],"p":[{"p":"research-map-plus-grid","e":"research-map-plus-grid","u":"./research-map-plus-grid/research-map-plus-grid/index.html","d":"The monolithic 7-tab research map (cap-3) on top, the People Groups grid beneath,","c":4,"em":{"iframe":"\u003ciframe src=\"./research-map-plus-grid/research-map-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./research-map-plus-grid/research-map-plus-grid.js\" integrity=\"sha384-kFphDHIpEeUHa9M6viOQW9dLfeg/Yzrd7WFcIoZVAKhvbarfXlYJ00efPxO9AKXy\" crossorigin=\"anonymous\">\u003c/script>\n\u003cresearch-map-plus-grid profile-config='{\"profile\":\"research-map-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/research-map-plus-grid>"},"in":[{"id":"default","t":"default","a":"","pr":{"profile":"research-map-plus-grid"},"e":"research-map-plus-grid","u":"./research-map-plus-grid/research-map-plus-grid/default/index.html"}]}]},{"b":"stack-demo","s":"./stack-demo/stack-demo.js","i":"sha384-+yA8BJlj7zWj0MB/w3yjs/DlM6wpCqlHg1/8UTd9Q/xNcwk0G5j9c2aQGiMzbBTu","e":["stack-demo"],"p":[{"p":"stack-demo","e":"stack-demo","u":"./stack-demo/stack-demo/index.html","d":"Groups TWO independent elements TOP-TO-BOTTOM so you can embed the pair on a page and","c":4,"em":{"iframe":"\u003ciframe src=\"./stack-demo/stack-demo/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./stack-demo/stack-demo.js\" integrity=\"sha384-+yA8BJlj7zWj0MB/w3yjs/DlM6wpCqlHg1/8UTd9Q/xNcwk0G5j9c2aQGiMzbBTu\" crossorigin=\"anonymous\">\u003c/script>\n\u003cstack-demo profile-config='{\"profile\":\"stack-demo\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/stack-demo>"},"in":[{"id":"pair","t":"pair","a":"","pr":{"profile":"stack-demo"},"e":"stack-demo","u":"./stack-demo/stack-demo/pair/index.html"}]}]},{"b":"template-bundle","s":"./template-bundle/template-bundle.js","i":"sha384-aIcrWHM2nmCSPqnMA+9xMC8jfc5ynJ+/eFCrR+WwZcQViaXErkZEjzV2fw/p8IoH","e":["template-bundle"],"p":[{"p":"template-map-a","e":"template-bundle","u":"./template-bundle/template-map-a/index.html","d":"Minimal example profile A — a colored placeholder, no Mapbox or data.","c":1,"em":{"iframe":"\u003ciframe src=\"./template-bundle/template-map-a/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-aIcrWHM2nmCSPqnMA+9xMC8jfc5ynJ+/eFCrR+WwZcQViaXErkZEjzV2fw/p8IoH\" crossorigin=\"anonymous\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-a\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/template-bundle>"}},{"p":"template-map-b","e":"template-bundle","u":"./template-bundle/template-map-b/index.html","d":"Minimal example profile B — a second profile in the same bundle.","c":1,"em":{"iframe":"\u003ciframe src=\"./template-bundle/template-map-b/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-aIcrWHM2nmCSPqnMA+9xMC8jfc5ynJ+/eFCrR+WwZcQViaXErkZEjzV2fw/p8IoH\" crossorigin=\"anonymous\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-b\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/template-bundle>"}}]}];
|
|
var snip = function snip(b, p, x) {
|
|
var u = (x || p).u, tag = (x && x.e) || p.e || b.e[0] || b.b
|
|
var r = { iframe: '<iframe src="' + u + '?utm_source=YOUR_SOURCE" width="100%" height="600" style="border:none;border-radius:12px;" allowfullscreen></iframe>' }
|
|
if (!x) r.script = '<script src="' + b.s + '"' + (b.i ? ' integrity="' + b.i + '" crossorigin="anonymous"' : '') + '><\/script>\n<' + tag + " profile-config='" + JSON.stringify({ profile: p.p, tk: 'YOUR_MAPBOX_TOKEN', utmSource: 'YOUR_SOURCE' }) + "'></" + tag + '>'
|
|
return r
|
|
};
|
|
var compact = function compact(m, lean, tierMap) {
|
|
tierMap = tierMap || {}
|
|
// Composition tier → capability number, self-contained so the browser copy of this function
|
|
// (shipped via src(compact) and re-run on the live manifest) needs no import. 5 = app-shell
|
|
// (nested-mfe-spa) … 1 = single. The model may bake `sp.type`; else tierMap resolves it.
|
|
var PT = { 'nested-mfe-spa': 0, 'nested-elements-mfe': 1, 'monolithic': 2, 'parameterized': 3, 'single': 4 }
|
|
var capFor = function (t) { return PT[t] != null ? 5 - PT[t] : 1 }
|
|
return (m.bundles || []).map(function (src) {
|
|
var b = { b: src.bundle, s: src.scriptUrl || '', i: src.integrity || '', e: src.elements || [], p: [] }
|
|
b.p = (src.profiles || []).map(function (sp) {
|
|
var type = sp.type || tierMap[src.bundle + '/' + sp.profile] || 'single'
|
|
var c = capFor(type)
|
|
var p = { p: sp.profile, e: sp.element || '', u: sp.iframeUrl || '', d: sp.description || '', c: c }
|
|
var em = sp.embed || {}, d = snip(b, p, null)
|
|
if (!lean || em.iframe !== d.iframe || em.script !== d.script) p.em = em
|
|
var ins = (sp.instances || []).map(function (sx) {
|
|
var x = { id: sx.id, t: sx.title || sx.id, a: sx.apiBaseUrl || '', pr: sx.props || {}, e: sx.element || '', u: sx.iframeUrl || '' }
|
|
var xe = sx.embed || {}, xd = snip(b, p, x)
|
|
if (!lean || xe.iframe !== xd.iframe) x.em = xe
|
|
return x
|
|
})
|
|
if (ins.length) p.in = ins
|
|
return p
|
|
})
|
|
return b
|
|
})
|
|
};
|
|
// Peer engines, the same tags every leaf page carries; loaded once, before the bundle.
|
|
var PEERS = {
|
|
mapbox: { css: ['https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css', 'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.css'],
|
|
js: ['https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js', 'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.min.js'] },
|
|
maplibre: { css: ['https://cdn.jsdelivr.net/npm/maplibre-gl@5/dist/maplibre-gl.css'], js: ['https://cdn.jsdelivr.net/npm/maplibre-gl@5/dist/maplibre-gl.js'] }
|
|
};
|
|
var $ = function (id) { return document.getElementById(id) };
|
|
// The map custom elements mount imperatively into this slot (they own their own DOM and must
|
|
// not be re-rendered by Vue); the Vue app writes its ref here after mount. Falls back to the
|
|
// element id so the loader works even before the app boots.
|
|
var slotEl = function () { return (window.__spa && window.__spa.slot) || $('slot') };
|
|
var TIER_MAP = {"all-people-groups/all-people-groups":"single","big-picture/big-picture":"nested-elements-mfe","cities-internet/connectivity-list":"single","cities-internet/connectivity-map":"single","cities-internet/data-tables":"single","country-posters/countries-map":"single","country-posters/country-poster-wall":"single","dashboard-plus-grid/dashboard-plus-grid":"nested-elements-mfe","demo-map/demo-map":"parameterized","doxa-maps-spa/doxa-maps-spa":"nested-mfe-spa","doxa-research-map/research-map":"monolithic","doxa-simple-map/doxa-simple-map":"parameterized","doxa-vision-card/doxa-vision-card":"parameterized","golden-path-check/golden-path-check":"single","gospel-resources/charts":"single","gospel-resources/data-tables":"single","gospel-resources/resources-list":"single","gospel-resources/resources-map":"single","key-cities/cities-list":"single","key-cities/cities-map":"single","key-cities/city-poster-wall":"single","key-cities/data-tables":"single","my-upg-100-list/poster-wall":"single","my-upg-100-list/upg-100-list":"single","my-upg-100-list/upg-100-mfe":"nested-elements-mfe","people-groups-grid/people-groups-grid":"parameterized","plugin-studio/plugin-studio":"nested-mfe-spa","research-dashboard/research-dashboard":"monolithic","research-map-plus-grid/research-map-plus-grid":"nested-elements-mfe","stack-demo/stack-demo":"nested-elements-mfe","template-bundle/template-map-a":"single","template-bundle/template-map-b":"single"}; // "<bundle>/<profile>" → composition tier, for live re-compact
|
|
// THIS PAGE'S OWN DIRECTORY. A host may serve the folder at /…/doxa-maps-build (no trailing
|
|
// slash); the browser would then resolve every relative ref against the PARENT directory and
|
|
// drop the folder name, so bundles 404. Derive the directory explicitly and build refs from it.
|
|
var BASE = (function () {
|
|
var p = location.pathname || '/';
|
|
return /\.[a-z0-9]+$/i.test(p) ? p.replace(/[^/]*$/, '') : p.replace(/\/*$/, '') + '/';
|
|
})();
|
|
var ASSETS = ASSETS_BASE || new URL(BASE + 'assets', location.origin).href.replace(/\/+$/, '');
|
|
var REG = BAKED, active = null, seq = 0, loaded = {}, fetchedToken = '';
|
|
var narrow = function () { return window.matchMedia && window.matchMedia('(max-width:700px)').matches };
|
|
|
|
// Manifest refs are relative to the tree root; this page sits one level above it. Refs are
|
|
// anchored to BASE (above) so they resolve the same with or without a trailing slash.
|
|
function rel(u) { return /^([a-z]+:|\/)/i.test(u) ? u : BASE + TREE + '/' + u.replace(/^\.\//, '') }
|
|
function qp(name) { try { return new URLSearchParams(location.search).get(name) || '' } catch (e) { return '' } }
|
|
function hint(msg, err) { var slot = slotEl(); var d = document.createElement('div'); d.className = 'hint' + (err ? ' err' : ''); d.textContent = msg; slot.innerHTML = ''; slot.appendChild(d); return d }
|
|
function assign(t) { for (var i = 1; i < arguments.length; i++) { var s = arguments[i]; for (var k in s) if (Object.prototype.hasOwnProperty.call(s, k)) t[k] = s[k] } return t }
|
|
|
|
function once(url, kind, attrs) {
|
|
if (loaded[url]) return loaded[url];
|
|
loaded[url] = new Promise(function (ok, fail) {
|
|
var el = document.createElement(kind === 'css' ? 'link' : 'script');
|
|
if (kind === 'css') { el.rel = 'stylesheet'; el.href = url } else { el.src = url; assign(el, attrs || {}) }
|
|
el.onload = function () { ok() }; el.onerror = function () { delete loaded[url]; fail(new Error('failed to load ' + url)) };
|
|
document.head.appendChild(el);
|
|
});
|
|
return loaded[url];
|
|
}
|
|
function peer(provider) {
|
|
var p = PEERS[provider] || PEERS.mapbox, chain = Promise.resolve();
|
|
p.css.forEach(function (u) { once(u, 'css') });
|
|
p.js.forEach(function (u) { chain = chain.then(function () { return once(u, 'js') }) });
|
|
return chain;
|
|
}
|
|
function bundle(b) { return once(rel(b.s), 'js', b.i ? { integrity: b.i, crossOrigin: 'anonymous' } : {}) }
|
|
|
|
// Resolve a deep-link key ("<bundle>/<profile>[/<instance>]") against the live registry.
|
|
function find(parts) {
|
|
for (var i = 0; i < REG.length; i++) if (REG[i].b === parts[0]) {
|
|
var b = REG[i];
|
|
for (var j = 0; j < b.p.length; j++) if (b.p[j].p === parts[1]) {
|
|
var p = b.p[j];
|
|
if (parts.length < 3) return { b: b, p: p, x: null };
|
|
for (var k = 0; k < (p.in || []).length; k++) if (p.in[k].id === parts[2]) return { b: b, p: p, x: p.in[k] };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Same profile-config the leaf page bakes (profile page: profile + dataSource + instanceId;
|
|
// instance page: its props), plus the host-supplied share URL of that leaf page.
|
|
function mount(b, p, x, tag) {
|
|
var cfg = x ? assign({ assetsBase: ASSETS }, x.pr) : { profile: p.p, dataSource: 'pray-tools', instanceId: b.b + '-' + p.p, assetsBase: ASSETS };
|
|
if (QA) cfg.qa = true;
|
|
cfg.shareUrl = new URL(rel((x || p).u), location.href).href;
|
|
window.MAP_APP_API_URL = (x && x.a) || API_BASE;
|
|
var provider = cfg.provider === 'maplibre' ? 'maplibre' : 'mapbox', my = ++seq;
|
|
hint(provider === 'maplibre' ? 'loading map…' : 'loading token…');
|
|
peer(provider).then(function () { return bundle(b) }).then(function () {
|
|
if (my !== seq) return;
|
|
resolveToken(cfg, provider === 'maplibre', function (token) {
|
|
if (my !== seq) return;
|
|
try {
|
|
var c = token ? assign({}, cfg, { tk: token }) : cfg;
|
|
var el = document.createElement(tag); el.setAttribute('profile-config', JSON.stringify(c));
|
|
var slot = slotEl(); slot.innerHTML = ''; slot.appendChild(el);
|
|
} catch (e) { hint(e && e.message ? e.message : String(e), true) }
|
|
});
|
|
}).catch(function (e) { if (my === seq) hint('Failed to load ' + b.b + ': ' + (e && e.message ? e.message : e), true) });
|
|
}
|
|
|
|
// Mapbox token resolution — the leaf pages' ladder, in precedence order:
|
|
// 1. prop (config.tk) 2. ?tk= on this page's URL (stored once for every page of the tree)
|
|
// 3. baked token — then, none found, a token ENDPOINT is fetched, first answer wins:
|
|
// 4. window.MAP_TOKEN_URL 5. configured TOKEN_SRC 6. same-origin /api/maps/token — else ask once.
|
|
function resolveToken(cfg, tokenFree, done) {
|
|
if (tokenFree) return done('');
|
|
var stored = ''; try { stored = localStorage.getItem('MAPS_STAGING_TK') || '' } catch (e) {}
|
|
var urlTk = qp('tk'); if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk) } catch (e) {} }
|
|
var directToken = cfg.tk || urlTk || stored || BAKED_TOKEN || fetchedToken;
|
|
if (directToken) return done(directToken);
|
|
function askToken(prefix) {
|
|
var d = hint('', true);
|
|
d.innerHTML = (prefix ? '<div></div>' : '') + '<div>map needs a Mapbox token — <input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)"> <button id="tkGo" type="button">use it</button></div>';
|
|
if (prefix) d.firstChild.textContent = prefix;
|
|
$('tkGo').onclick = function () { var v = ($('tkIn').value || '').trim(); if (!v) return; try { localStorage.setItem('MAPS_STAGING_TK', v) } catch (e) {} done(v) };
|
|
}
|
|
var candidates = [];
|
|
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u) }
|
|
addCandidate(window.MAP_TOKEN_URL);
|
|
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
|
addCandidate(DEFAULT_TOKEN_URL);
|
|
function extractToken(data) { if (typeof data === 'string') return data.trim(); if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || ''; return '' }
|
|
function fetchToken(i, errors) {
|
|
if (i >= candidates.length) return askToken('Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')');
|
|
var url = candidates[i];
|
|
return fetch(url).then(function (r) {
|
|
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
|
return (r.headers.get('content-type') || '').indexOf('json') !== -1 ? r.json() : r.text();
|
|
}).then(function (data) {
|
|
var tk = extractToken(data); if (!tk) throw new Error(url + ' → no token in response');
|
|
fetchedToken = tk; done(tk);
|
|
}).catch(function (e) { errors.push(e && e.message ? e.message : String(e)); return fetchToken(i + 1, errors) });
|
|
}
|
|
fetchToken(0, []);
|
|
}
|
|
|
|
function copyText(text, btn) {
|
|
var ok = function () { var t = btn.textContent; btn.textContent = 'Copied'; setTimeout(function () { btn.textContent = t }, 1200) };
|
|
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(ok, function () {}); else {
|
|
try { var ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); ok() } catch (e) {}
|
|
}
|
|
}
|
|
|
|
// ── The capability ladder for the sidebar headings (5 → 1), reader-facing names ──
|
|
var CAP_LABEL = { 5: 'App shell · nested-mfe-spa', 4: 'Composite element', 3: 'Multi-view element', 2: 'Preset element', 1: 'Single element' };
|
|
// REG → tab groups keyed by capability, most-composed first (5 → 1). Each group lists every
|
|
// profile (and its preset instances) as one row; a bundle contributes rows to the tier of each
|
|
// profile it holds, so one bundle can appear under several capability headings.
|
|
function toGroups() {
|
|
var byCap = {};
|
|
REG.forEach(function (b) {
|
|
(b.p || []).forEach(function (p) {
|
|
var cap = p.c || 1, tag = p.e || b.e[0] || b.b;
|
|
(byCap[cap] = byCap[cap] || []).push({ key: b.b + '/' + p.p, kind: 'profile', label: p.p, tag: tag, bundle: b.b });
|
|
(p.in || []).forEach(function (x) {
|
|
byCap[cap].push({ key: b.b + '/' + p.p + '/' + x.id, kind: 'instance', label: x.t, tag: x.e || tag, bundle: b.b });
|
|
});
|
|
});
|
|
});
|
|
return [5, 4, 3, 2, 1].filter(function (c) { return byCap[c] && byCap[c].length }).map(function (c) {
|
|
return { cap: c, label: CAP_LABEL[c] || ('capability ' + c), rows: byCap[c] };
|
|
});
|
|
}
|
|
|
|
function bootVue(Vue) {
|
|
var app = Vue.createApp({
|
|
data: function () {
|
|
return {
|
|
groups: toGroups(), activeKey: '', navOpen: true, embedOpen: false,
|
|
ttl: 'Choose a map', sub: '', openHref: '', hasSel: false, embedIframe: '', embedScript: ''
|
|
};
|
|
},
|
|
computed: {
|
|
countLabel: function () {
|
|
var nb = REG.length, np = 0, ni = 0;
|
|
REG.forEach(function (b) { np += (b.p || []).length; (b.p || []).forEach(function (p) { ni += (p.in || []).length }) });
|
|
return nb + ' bundles · ' + np + ' profiles · ' + ni + ' instances';
|
|
}
|
|
},
|
|
methods: {
|
|
go: function (key) { location.hash = '#/' + key.split('/').map(encodeURIComponent).join('/') },
|
|
copyText: copyText,
|
|
rebuild: function (m) { if (m) { try { REG = compact(m, false, TIER_MAP) } catch (e) { REG = BAKED } } this.groups = toGroups(); this.route(); },
|
|
route: function () { var h = (location.hash || '').replace(/^#\/?/, ''); if (h) this.select(h); },
|
|
select: function (key) {
|
|
var parts = key.split('/').map(function (s) { try { return decodeURIComponent(s) } catch (e) { return s } });
|
|
var hit = find(parts); this.activeKey = parts.join('/');
|
|
if (!hit) { this.ttl = 'No such map'; this.sub = key; this.openHref = ''; this.hasSel = false; this.embedOpen = false; hint('No such map: ' + key, true); return; }
|
|
var b = hit.b, p = hit.p, x = hit.x, tag = (x && x.e) || p.e || b.e[0] || b.b;
|
|
this.ttl = x ? x.t : p.p; this.sub = b.b + ' · <' + tag + '>' + (p.d ? ' · ' + p.d : '');
|
|
this.openHref = rel((x || p).u); this.hasSel = true; this.embedOpen = false;
|
|
var em = x ? (x.em || snip(b, p, x)) : (p.em || snip(b, p, null));
|
|
this.embedIframe = em.iframe || ''; this.embedScript = em.script || '';
|
|
if (narrow()) this.navOpen = false;
|
|
mount(b, p, x, tag);
|
|
}
|
|
},
|
|
mounted: function () {
|
|
var self = this;
|
|
window.__spa = { slot: this.$refs.slot, app: this };
|
|
window.addEventListener('hashchange', function () { self.route() });
|
|
try { window.matchMedia('(max-width:700px)').addEventListener('change', function (e) { if (!e.matches) self.navOpen = true }) } catch (e) {}
|
|
// Live manifest first (the page stays right when bundles change); baked copy is the fallback.
|
|
if (typeof fetch === 'function' && location.protocol !== 'file:') {
|
|
fetch(TREE + '/manifest.json', { cache: 'no-cache' }).then(function (r) { return r.ok ? r.json() : null })
|
|
.then(function (m) { self.rebuild(m) }, function () { self.route() });
|
|
} else { self.route(); }
|
|
}
|
|
});
|
|
app.config.compilerOptions.isCustomElement = function () { return true }; // map tags are custom elements
|
|
app.mount('#app');
|
|
}
|
|
|
|
// Vue drives the shell (the nested-mfe-spa). Self-hosted first (portable / offline / no-CDN),
|
|
// then a CDN fallback; if neither loads, degrade to a plain rendered list so the page never
|
|
// blanks. once() resolves the tree-relative ASSETS path the same way every leaf page does.
|
|
function loadVue() {
|
|
if (window.Vue) return Promise.resolve(window.Vue);
|
|
var local = ASSETS + '/vendor/vue/vue.global.prod.js';
|
|
var cdn = 'https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js';
|
|
return once(local, 'js').then(function () { return window.Vue }, function () {
|
|
return once(cdn, 'js').then(function () { return window.Vue });
|
|
});
|
|
}
|
|
loadVue().then(function (Vue) {
|
|
if (Vue) bootVue(Vue);
|
|
else fallbackRender();
|
|
}).catch(function () { fallbackRender(); });
|
|
|
|
// No-Vue degrade: render the same capability-sorted list with plain DOM so a map is still
|
|
// reachable (parse-safe, dependency-free). Rare — Vue is vendored in the tree.
|
|
function fallbackRender() {
|
|
var host = $('app'); if (!host) return;
|
|
function paint() {
|
|
var nav = host.querySelector('#nav'); if (!nav) return;
|
|
nav.innerHTML = '';
|
|
toGroups().forEach(function (g) {
|
|
var h = document.createElement('div'); h.className = 'tier'; h.textContent = 'CAP ' + g.cap + ' · ' + g.label; nav.appendChild(h);
|
|
g.rows.forEach(function (r) {
|
|
var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'row' + (r.kind === 'instance' ? ' in' : '');
|
|
btn.textContent = r.label; btn.onclick = function () { location.hash = '#/' + r.key.split('/').map(encodeURIComponent).join('/') };
|
|
nav.appendChild(btn);
|
|
});
|
|
});
|
|
}
|
|
function route() {
|
|
var h = (location.hash || '').replace(/^#\/?/, ''); if (!h) return;
|
|
var parts = h.split('/').map(function (s) { try { return decodeURIComponent(s) } catch (e) { return s } });
|
|
var hit = find(parts); if (hit) mount(hit.b, hit.p, hit.x, (hit.x && hit.x.e) || hit.p.e || hit.b.e[0] || hit.b.b); else hint('No such map: ' + h, true);
|
|
}
|
|
window.__spa = { slot: $('slot') };
|
|
if (typeof fetch === 'function' && location.protocol !== 'file:') {
|
|
fetch(TREE + '/manifest.json', { cache: 'no-cache' }).then(function (r) { return r.ok ? r.json() : null })
|
|
.then(function (m) { if (m) { try { REG = compact(m, false, TIER_MAP) } catch (e) { REG = BAKED } } paint(); route(); }, function () { paint(); route(); });
|
|
} else { paint(); route(); }
|
|
window.addEventListener('hashchange', route);
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|