generate-tree decided a single leaf's map-ness as "its own .vue
references an engine OR anything under the BUNDLE's src/ does", so a
map-less profile inherited it from a sibling. regions-dashboard/
regions-list shows a list and no map, but src/RegionCountryMap.vue made
it isMap=true. Its leaf loaded the Mapbox engine for nothing, and since
4a6886d2 its embed snippet also preloaded it.
A leaf's map-ness now follows the RELATIVE imports reachable from its
own .vue, bounded to its bundle, so a map several imports deep
(research-dashboard's DashboardMapPanel) still counts. An explicit
boolean isMap in <profile>.meta.json wins. Composites keep their
separate rule, since they do mount their siblings.
Measured: across all 34 profiles exactly one changed (regions-list,
true -> false). 18 map profiles keep isMap and their preload; 0 non-map
snippets carry one. On a served built tree regions-list makes 0 engine
requests and 0 api.mapbox.com requests and renders its list (673
elements, no errors), where before it downloaded the 1.75 MB engine.
regions-mfe still loads the engine and draws its map. The regenerated
files are only the regions-list leaf, the two manifest copies (still
byte-identical), the staging page and the gallery; check:sri passes.
414 lines
72 KiB
HTML
414 lines
72 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>
|
|
<!-- data-k EXPOSES THE ROW IDENTITY TO THE DOM. A Vue :key is a RECONCILIATION HINT and is NOT
|
|
rendered as an attribute — a distinction that had smoke:gallery failing on a working page.
|
|
That gate reads row.dataset.k to confirm a deep link selected the row it asked for; with no
|
|
such attribute it read undefined and reported "deep link resolved to undefined".
|
|
MEASURED 2026-09-22: data-k appeared ZERO times in this generator and zero times in the
|
|
built page, while the gallery itself mounts its elements correctly — a false RED, which is
|
|
as corrosive as a false green because it gets the gate switched off.
|
|
Asserting on the rendered LABEL instead would be worse: it couples a machine check to
|
|
display text, so renaming a profile would break the gate. Identity belongs in the DOM.
|
|
NOTE: this markup lives inside a TEMPLATE LITERAL — no backticks anywhere in this comment,
|
|
or the literal ends here and the rest of the file is parsed as code. -->
|
|
<button v-for="r in grp.rows" :key="r.key" :data-k="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-y1vbqdZBzO1YR4TEieGRDPE3lzO9f4mBqgVJCH1KICQRomjsGIu/pRk9uVGi0n86","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-y1vbqdZBzO1YR4TEieGRDPE3lzO9f4mBqgVJCH1KICQRomjsGIu/pRk9uVGi0n86\" 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-AGFs5KcwxgY8QHHMGDyC9H3eZXCrL32t2mMrBXkTV7IdCqdcY9vHOZymJ7oUUfq2","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-AGFs5KcwxgY8QHHMGDyC9H3eZXCrL32t2mMrBXkTV7IdCqdcY9vHOZymJ7oUUfq2\" 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-tWFKP5lpuEIney1vkcEFXPPhlr/fgu3siY7p7J25oMxBOedxS/MJjwKD+FMKnsA4","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-tWFKP5lpuEIney1vkcEFXPPhlr/fgu3siY7p7J25oMxBOedxS/MJjwKD+FMKnsA4\" 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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-tWFKP5lpuEIney1vkcEFXPPhlr/fgu3siY7p7J25oMxBOedxS/MJjwKD+FMKnsA4\" 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-tWFKP5lpuEIney1vkcEFXPPhlr/fgu3siY7p7J25oMxBOedxS/MJjwKD+FMKnsA4\" 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-Q8/xltlJzL+Ils4lU+8lOsk9QUZQNVGyLQCeIokoWyaQQJyD66uYDHKBnblVaixo","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-Q8/xltlJzL+Ils4lU+8lOsk9QUZQNVGyLQCeIokoWyaQQJyD66uYDHKBnblVaixo\" 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-Q8/xltlJzL+Ils4lU+8lOsk9QUZQNVGyLQCeIokoWyaQQJyD66uYDHKBnblVaixo\" 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>"}},{"p":"country-posters-mfe","e":"country-posters-mfe","u":"./country-posters/country-posters-mfe/index.html","d":"Country Posters as ONE two-tab element — the countries map and the printable","c":4,"em":{"iframe":"\u003ciframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-Q8/xltlJzL+Ils4lU+8lOsk9QUZQNVGyLQCeIokoWyaQQJyD66uYDHKBnblVaixo\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"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-+qkzAU3t2PkgAUw2zihEdB39N5CSzR81Ak7O8wELgkxC/bGiQSXL8IjVUWoAAaT9","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-+qkzAU3t2PkgAUw2zihEdB39N5CSzR81Ak7O8wELgkxC/bGiQSXL8IjVUWoAAaT9\" 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-lOu5irBnYEEu0wzHP5/F6Xdw90SQFOStrK8TosOU+7h36kCOv1jlLC4K+ZknVri1","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./demo-map/demo-map.js\" integrity=\"sha384-lOu5irBnYEEu0wzHP5/F6Xdw90SQFOStrK8TosOU+7h36kCOv1jlLC4K+ZknVri1\" 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-SSR99fy7d4D0pg07vxksTDEXhf7Saky5hhVJCkPlQ1O45Il0/VkW4cj/yFar6QaP","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-SSR99fy7d4D0pg07vxksTDEXhf7Saky5hhVJCkPlQ1O45Il0/VkW4cj/yFar6QaP\" 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":"Research Gallery — Big Picture + every research map (app-shell)","a":"","pr":{"profile":"doxa-maps-spa","provider":"maplibre","instanceId":"doxa-maps-spa-app","title":"Research Gallery"},"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-jfyq06QMD5wn7PNdNkn96eZgO2SlRjqD5uzcgA3hJ9dBgHDHXbhLy8XR+FQAp9zM","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-jfyq06QMD5wn7PNdNkn96eZgO2SlRjqD5uzcgA3hJ9dBgHDHXbhLy8XR+FQAp9zM\" 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>"},"in":[{"id":"maplibre","t":"Research map — MapLibre (token-free, no search bar)","a":"","pr":{"profile":"research-map","provider":"maplibre","instanceId":"research-map-maplibre"},"e":"doxa-research-map","u":"./doxa-research-map/research-map/maplibre/index.html"}]}]},{"b":"doxa-simple-map","s":"./doxa-simple-map/doxa-simple-map.js","i":"sha384-EfPGYd5J6I7s63wk0UDKzcR4cDdzlidQGYZ4+z20DTEExkCUMyUjTOxSY35eTLus","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-EfPGYd5J6I7s63wk0UDKzcR4cDdzlidQGYZ4+z20DTEExkCUMyUjTOxSY35eTLus\" 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-/oOxfonW4sXJgaXVJgZOZb1R+3gbePfTP1Wh2irBX2KnDCsKjv1YUnzbyheKJzZ9","e":["doxa-vision-card"],"p":[{"p":"doxa-vision-card","e":"doxa-vision-card","u":"./doxa-vision-card/doxa-vision-card/index.html","d":"A true funnel of the remaining task, every number computed from data: all people","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-/oOxfonW4sXJgaXVJgZOZb1R+3gbePfTP1Wh2irBX2KnDCsKjv1YUnzbyheKJzZ9\" 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: fewer than one person in a thousand is an evangelical believer."},"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-PEvIo9qJtlQUr7v+NdWX8BDcKxd5n6HrtzM7961RqfzHThKAs3GQ7xxUI5zGtY3L","e":["gospel-resources-map"],"p":[{"p":"charts","e":"gospel-resources-map","u":"./gospel-resources/charts/index.html","d":"The gap measured by LANGUAGE (ISO 639-3) rather than by people group, because one translation serves every group that speaks it, so counting by group overstates how much work remains.","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-PEvIo9qJtlQUr7v+NdWX8BDcKxd5n6HrtzM7961RqfzHThKAs3GQ7xxUI5zGtY3L\" 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":"Eleven tables covering both halves of one question: every gospel resource with its real URL and how much Scripture it is, and the 1,613 people groups where nothing was found with the pools searched recorded per row, plus translation priority, bridge languages, city bridges, the deaf gap and access against population growth.","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-PEvIo9qJtlQUr7v+NdWX8BDcKxd5n6HrtzM7961RqfzHThKAs3GQ7xxUI5zGtY3L\" 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":"gospel-dashboard","e":"gospel-resources-map","u":"./gospel-resources/gospel-dashboard/index.html","d":"The gospel map and its tables as one scrolling element: 12,379 people-group pins with the real Scripture, film and Bible-story links for each group's own language, above every resource with its URL and the 1,613 groups where nothing was found, exportable as CSV.","c":4,"em":{"iframe":"\u003ciframe src=\"./gospel-resources/gospel-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-PEvIo9qJtlQUr7v+NdWX8BDcKxd5n6HrtzM7961RqfzHThKAs3GQ7xxUI5zGtY3L\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"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 people groups have Scripture in their own language and which have none, zero-resource languages first, with every link the verification pass proved and Open Bible Stories labelled as stories rather than as Scripture.","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-PEvIo9qJtlQUr7v+NdWX8BDcKxd5n6HrtzM7961RqfzHThKAs3GQ7xxUI5zGtY3L\" 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":"12,379 people-group pins over a country choropleth: tap a pin for the Scripture, audio, film, Bible-story and training links that exist in that group's own language with real URLs, tap a country for every group in it, search by group, language or ISO 639-3 code, and filter to the groups where nothing was found.","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-PEvIo9qJtlQUr7v+NdWX8BDcKxd5n6HrtzM7961RqfzHThKAs3GQ7xxUI5zGtY3L\" 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-vqUN7kfMcv1vUvjQIzNxapDlwPSdtR8atXoIETM0hxoqOCKG+7FAPPltRsA4jukY","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\"","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-vqUN7kfMcv1vUvjQIzNxapDlwPSdtR8atXoIETM0hxoqOCKG+7FAPPltRsA4jukY\" 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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\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-vqUN7kfMcv1vUvjQIzNxapDlwPSdtR8atXoIETM0hxoqOCKG+7FAPPltRsA4jukY\" 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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\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-vqUN7kfMcv1vUvjQIzNxapDlwPSdtR8atXoIETM0hxoqOCKG+7FAPPltRsA4jukY\" 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-ziaGuqTpW4HvR1P76Nzu6OByBHUzs1E1C4l4m+bC0I8C7KivdbTLEFN/36ZCT/mF","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-ziaGuqTpW4HvR1P76Nzu6OByBHUzs1E1C4l4m+bC0I8C7KivdbTLEFN/36ZCT/mF\" 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-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM","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-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM\" 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":"pplr-data-maps","s":"./pplr-data-maps/pplr-data-maps.js","i":"sha384-+EOcMT3P42D39HGIjZBbJ6waYI2yJDhNDwNf+8wFruaHqEhdYYa36M+iLIT5fRiH","e":["pplr-data-maps"],"p":[{"p":"pplr-data-maps","e":"pplr-data-maps","u":"./pplr-data-maps/pplr-data-maps/index.html","d":"Ten views that let a researcher check the PPLR people-group data by eye","c":3,"em":{"iframe":"\u003ciframe src=\"./pplr-data-maps/pplr-data-maps/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-+EOcMT3P42D39HGIjZBbJ6waYI2yJDhNDwNf+8wFruaHqEhdYYa36M+iLIT5fRiH\" crossorigin=\"anonymous\">\u003c/script>\n\u003cpplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/pplr-data-maps>"}}]},{"b":"prayer-walk-planner","s":"./prayer-walk-planner/prayer-walk-planner.js","i":"sha384-MfnmQILsSWd3itg/eN9+UVcZZJnQGVMdX0X3XI9BbKlbaTTwY/IfQ5MlVPAv5MKj","e":["prayer-walk-planner"],"p":[{"p":"prayer-walk-planner","e":"prayer-walk-planner","u":"./prayer-walk-planner/prayer-walk-planner/index.html","d":"A simple polygon-drawing tool for planning prayer-walking routes.","c":1,"em":{"iframe":"\u003ciframe src=\"./prayer-walk-planner/prayer-walk-planner/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-MfnmQILsSWd3itg/eN9+UVcZZJnQGVMdX0X3XI9BbKlbaTTwY/IfQ5MlVPAv5MKj\" crossorigin=\"anonymous\">\u003c/script>\n\u003cprayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/prayer-walk-planner>"}}]},{"b":"regions-dashboard","s":"./regions-dashboard/regions-dashboard.js","i":"sha384-bt2/psRmAJTmO9mm3KULoLN7GSzcxwIeOTbVwIvSkMZOqyzaU5pOt7G0r2w8ir9r","e":["regions-dashboard"],"p":[{"p":"region-dashboards","e":"regions-dashboard","u":"./regions-dashboard/region-dashboards/index.html","d":"The dashboard half of the Driver's 2-tab Regions element: given a region (or a","c":1,"em":{"iframe":"\u003ciframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-bt2/psRmAJTmO9mm3KULoLN7GSzcxwIeOTbVwIvSkMZOqyzaU5pOt7G0r2w8ir9r\" crossorigin=\"anonymous\">\u003c/script>\n\u003cregions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/regions-dashboard>"}},{"p":"regions-list","e":"regions-dashboard","u":"./regions-dashboard/regions-list/index.html","d":"A refactor of DX-life's /regions index page (app/pages/regions/index.vue) into","c":1,"em":{"iframe":"\u003ciframe src=\"./regions-dashboard/regions-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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-bt2/psRmAJTmO9mm3KULoLN7GSzcxwIeOTbVwIvSkMZOqyzaU5pOt7G0r2w8ir9r\" crossorigin=\"anonymous\">\u003c/script>\n\u003cregions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/regions-dashboard>"}},{"p":"regions-mfe","e":"regions-dashboard","u":"./regions-dashboard/regions-mfe/index.html","d":"The composite the Driver asked for: ONE element carrying the SELECT VIEW switcher","c":4,"em":{"iframe":"\u003ciframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-bt2/psRmAJTmO9mm3KULoLN7GSzcxwIeOTbVwIvSkMZOqyzaU5pOt7G0r2w8ir9r\" crossorigin=\"anonymous\">\u003c/script>\n\u003cregions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/regions-dashboard>"}}]},{"b":"research-dashboard","s":"./research-dashboard/research-dashboard.js","i":"sha384-mYz0wRhq1Kel13s+l46d5eObb7fHRFJ9tpmSj7qDnvoGOY6f1lgYH1UYQqDBPwKI","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":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-mYz0wRhq1Kel13s+l46d5eObb7fHRFJ9tpmSj7qDnvoGOY6f1lgYH1UYQqDBPwKI\" 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-hETnSGVenHXQXaI+EUClxIoaB0xyA9jHa3AaZE6grq4UHxTuX0QNZj93mAUi3H7b","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-hETnSGVenHXQXaI+EUClxIoaB0xyA9jHa3AaZE6grq4UHxTuX0QNZj93mAUi3H7b\" 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-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G","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-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G\" 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":"upg-neighbor","s":"./upg-neighbor/upg-neighbor.js","i":"sha384-7MsLJUu/zKAjJKFXL9WssodTCfVjVDwIBT7aGrXqfkvlIjavDDzsthDzHkUeiAR5","e":["upg-neighbor"],"p":[{"p":"upg-neighbor","e":"upg-neighbor","u":"./upg-neighbor/upg-neighbor/index.html","d":"Who goes to whom: reached neighbors matched to unreached people groups, five lenses","c":3,"em":{"iframe":"\u003ciframe src=\"./upg-neighbor/upg-neighbor/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003c!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n\u003clink rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n\u003cscript>window.MAP_APP_API_URL=\"https://pray.doxa.life\";\u003c/script>\n\u003cscript src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-7MsLJUu/zKAjJKFXL9WssodTCfVjVDwIBT7aGrXqfkvlIjavDDzsthDzHkUeiAR5\" crossorigin=\"anonymous\">\u003c/script>\n\u003cupg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'>\u003c/upg-neighbor>"},"in":[{"id":"send-map","t":"Send Map","a":"","pr":{"profile":"upg-neighbor","view":"send-map"},"e":"upg-neighbor","u":"./upg-neighbor/upg-neighbor/send-map/index.html"},{"id":"upg-neighbors","t":"UPG Neighbors","a":"","pr":{"profile":"upg-neighbor","view":"cockpit"},"e":"upg-neighbor","u":"./upg-neighbor/upg-neighbor/upg-neighbors/index.html"},{"id":"lang-cluster","t":"Language Clusters","a":"","pr":{"profile":"upg-neighbor","view":"lang-cluster"},"e":"upg-neighbor","u":"./upg-neighbor/upg-neighbor/lang-cluster/index.html"},{"id":"bloom-cockpit","t":"Bloom Cockpit","a":"","pr":{"profile":"upg-neighbor","view":"cockpit"},"e":"upg-neighbor","u":"./upg-neighbor/upg-neighbor/bloom-cockpit/index.html"},{"id":"flower-mesh","t":"Flower Mesh","a":"","pr":{"profile":"upg-neighbor","view":"flower-mesh"},"e":"upg-neighbor","u":"./upg-neighbor/upg-neighbor/flower-mesh/index.html"}]}]}];
|
|
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","country-posters/country-posters-mfe":"nested-elements-mfe","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/gospel-dashboard":"nested-elements-mfe","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","pplr-data-maps/pplr-data-maps":"monolithic","prayer-walk-planner/prayer-walk-planner":"single","regions-dashboard/region-dashboards":"single","regions-dashboard/regions-list":"single","regions-dashboard/regions-mfe":"nested-elements-mfe","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","upg-neighbor/upg-neighbor":"monolithic"}; // "<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(/\/*$/, '') + '/';
|
|
})();
|
|
// ★ THE TREE ROOT AS ONE ABSOLUTE URL — derive it ONCE, never re-join TREE by hand.
|
|
// TREE is PAGE-RELATIVE and its value differs per surface ('../doxa-maps' in the built tree,
|
|
// '__plugin-gallery' in dev), so a bare TREE + '/x' join is only accidentally right. It was right
|
|
// in the built tree (where the '../' climbs out of /__gallery/) and WRONG in dev, where it asked
|
|
// for /__gallery/__plugin-gallery/manifest.json. MEASURED 2026-09-22, both servers running:
|
|
// dev /__gallery/__plugin-gallery/manifest.json ... 404 (what the page asked for)
|
|
// dev /__plugin-gallery/manifest.json ............. 200 (what it meant)
|
|
// built /doxa-maps/manifest.json .................... 200
|
|
// rel() never had the bug because it always anchored to BASE. Resolving through the URL parser
|
|
// (rather than string-joining) also collapses the '../' properly for BOTH surfaces at once.
|
|
// Resolve TREE *against* the page's own directory — do NOT string-join it onto BASE. TREE may be
|
|
// ROOTED ('/__plugin-gallery' in dev), and 'BASE + TREE' would glue it on to yield
|
|
// /__gallery//__plugin-gallery, which 404s just as surely as the original bug. Passing BASE as
|
|
// the base URL lets the URL parser apply the right rule per shape: a rooted TREE replaces the
|
|
// path, a '../' TREE climbs out of /__gallery/, and a bare name resolves beside it.
|
|
var TREE_URL = new URL(TREE, new URL(BASE, location.href)).href.replace(/\/+$/, '');
|
|
// ASSETS live INSIDE the tree (public/<tree>/assets), which is what every leaf page points at:
|
|
// a leaf at /<tree>/<bundle>/<profile>/ bakes assetsBase "../../assets" -> /<tree>/assets. So
|
|
// anchor to TREE_URL, not to this PAGE's directory.
|
|
// This was BASE + 'assets' — correct only while the gallery WAS the root page, where the two
|
|
// coincide. Since the gallery moved to /__gallery it asked for /__gallery/assets/... MEASURED
|
|
// on the built tree with the page at /__gallery/:
|
|
// /__gallery/assets/vendor/vue/vue.global.prod.js ... 404 (what the page asked for)
|
|
// /doxa-maps/assets/vendor/vue/vue.global.prod.js ... 200 (where it lives)
|
|
// It degraded quietly rather than blanking — loadVue() falls back to the jsDelivr CDN — so the
|
|
// page still worked while silently losing the portable/offline/no-CDN property the vendored
|
|
// copy exists to provide. The dev server passes assetsBaseUrl explicitly, so this is the
|
|
// built-tree half of the same fix its comment already documents.
|
|
var ASSETS = ASSETS_BASE || TREE_URL + '/assets';
|
|
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;
|
|
// ★ HAND THE TREE ROOT DOWN — an element that fetches the manifest cannot guess it from here.
|
|
// A cap-5 shell derives its tree root from location.pathname when nothing is passed, which is
|
|
// right on its own <tree>/<bundle>/<profile>/ leaf page and WRONG in this gallery, where the
|
|
// path belongs to the PAGE, not the element. doxa-maps-spa's relative fallback is '../', so
|
|
// from /__gallery/ it asked for /manifest.json. MEASURED by the smoke:gallery gate:
|
|
// ✗ doxa-maps-spa/doxa-maps-spa data: 404 http://localhost:5173/manifest.json
|
|
// The SPA's OWN nested-mount path already sets both of these for exactly this reason; this
|
|
// gallery was the one mount path that never learned the lesson. Set both, because not every
|
|
// element reads profile-config (an element mounting its own SFC has no ProfileLoader above it
|
|
// and takes tree-base as a prop); an element that declares neither ignores both.
|
|
cfg.treeBase = TREE_URL;
|
|
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));
|
|
el.setAttribute('tree-base', TREE_URL); // see cfg.treeBase above — both, for both kinds of element
|
|
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_URL + '/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_URL + '/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>
|