All People Groups: the whole task around the DOXA assignment (new bundle)
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

Every people group on earth (peoplegroups.org, 12,379 group-country rows) in
four colors: BLACK doxa (live ROP3 match vs the prayer API), RED frontier
(GSEC 0-1), YELLOW unreached (GSEC 2-3), GREEN reached (GSEC 4-6). Clickable
legend filters a category and fits the view to all of it; popups carry GSEC,
language, religion; DOXA dots link to pray with utm attribution. Uses the
shared static dataset + the standard pray-tools pipeline; renders fully by
GSEC even if the API is down. Wired into the SPA nav (the 'soon' placeholder
is now a real map) + deep-link preload. Smoke: mounts with live canvas.
This commit is contained in:
doxa-maps 2026-09-16 07:34:08 +00:00
parent a5214fd48c
commit 22eb9205a0
21 changed files with 1048 additions and 14 deletions

View file

@ -0,0 +1,297 @@
<script setup>
/**
* all-people-groups.vue — every people group on earth, around the DOXA set.
*
* @description All 12,379 people groups (peoplegroups.org) in four colors — reached,
* unreached, frontier — with the DOXA groups in black at the center of the story.
* @profile all-people-groups
* @bundle all-people-groups
* @element all-people-groups
*
* THE PICTURE THIS MAP PAINTS: the DOXA assignment (black) sits inside the whole
* remaining task. Green shows where the church already is (the senders next
* door); red shows where nothing has started. A mobilizer reads it in one look:
* "who near this black dot could go?"
*
* CATEGORIES (from peoplegroups.org GSEC — Global Status of Evangelical Christianity):
* BLACK doxa — the DOXA people groups (live match vs the prayer API by ROP3)
* RED frontier — GSEC 0–1: no evangelicals, or <2% with NO church planting
* YELLOW unreached — GSEC 2–3: <2% evangelical, active church-planting work
* GREEN reached — GSEC 4–6: 2%+ evangelical
*
* DATA: the shared static dataset assets/people-groups/people-groups-slim.json
* (built from the public peoplegroups.org CSV — one row per group×country) + the
* live DOXA set via the standard pray-tools pipeline. If the API is unreachable
* the map still renders fully by GSEC — the black layer just stays empty.
*/
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useMapInstance } from '@map/composables/useMapInstance.js'
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
import { useMapData } from '@map/composables/useMapData.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { DataSourceManager } from '@map/utils/DataSourceManager.js'
useShadowStyles(`
.apg-root { position:absolute;inset:0;overflow:hidden;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif; }
.apg-canvas { position:absolute;inset:0; }
.apg-loading { position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
background:#0b1220;color:#9aa7b8;font-size:14px;z-index:5; }
.apg-error { position:absolute;top:50%;left:12px;right:12px;z-index:7;transform:translateY(-50%);
text-align:center;font-size:13px;line-height:1.5;color:#7f1d1d;background:rgba(255,255,255,.94);
border:1px solid rgba(127,29,29,.25);border-radius:10px;padding:12px 14px; }
.apg-legend { position:absolute;left:10px;top:10px;z-index:6;background:rgba(255,255,255,.95);
border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 3px 12px rgba(0,0,0,.15);
padding:12px 14px;min-width:230px; }
.apg-legend-title { font-size:12px;font-weight:800;color:#111827;margin-bottom:2px;
text-transform:uppercase;letter-spacing:.05em; }
.apg-legend-sub { font-size:11px;color:#6b7280;margin-bottom:8px; }
.apg-row { display:flex;align-items:center;gap:9px;font-size:12.5px;color:#374151;
line-height:2;cursor:pointer;border-radius:8px;padding:0 8px;margin:0 -8px;
border:1px solid transparent; }
.apg-row:hover { background:#f3f4f6; }
.apg-row.active { background:#eef2ff;border-color:#c7d2fe;font-weight:700; }
.apg-dot { flex:none;width:11px;height:11px;border-radius:50%;border:1.5px solid #fff;
box-shadow:0 0 0 1px rgba(0,0,0,.35); }
.apg-count { margin-left:auto;font-variant-numeric:tabular-nums;color:#6b7280;font-size:11.5px; }
.apg-hint { font-size:10.5px;color:#9ca3af;margin-top:7px; }
.apg-pop { font:12.5px/1.55 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; }
.apg-pop b { font-size:13.5px; }
.apg-pop .cat { display:inline-block;padding:1px 8px;border-radius:999px;color:#fff;
font-size:10.5px;font-weight:700;letter-spacing:.03em;vertical-align:1px; }
.apg-pop .meta { color:#4b5563;margin-top:4px; }
.apg-pop a { display:inline-block;margin-top:7px;font-weight:700;color:#b45309;text-decoration:none; }
.apg-pop a:hover { text-decoration:underline; }
`, 'all-people-groups')
// ─── Config from ProfileLoader ───────────────────────────────────────────────
const profileConfig = inject('profileConfig')
const mapboxToken = inject('mapboxToken')
const dataSource = inject('dataSource')
const dataStore = inject('dataStore')
const mapStore = inject('mapStore')
const mapProvider = resolveMapProvider(profileConfig)
const assetsBase = computed(() =>
String(profileConfig?.value?.assetsBase || '/assets').replace(/\/+$/, ''))
// ─── The four categories (the Driver's palette) ──────────────────────────────
const CATS = [
{ id: 'doxa', label: 'DOXA people groups', color: '#111827' },
{ id: 'frontier', label: 'Frontier — nothing started', color: '#e74c3c' },
{ id: 'unreached', label: 'Unreached — work beginning', color: '#f1c40f' },
{ id: 'reached', label: 'Reached — church is there', color: '#2ecc71' },
]
const CAT_COLOR = Object.fromEntries(CATS.map(c => [c.id, c.color]))
const counts = ref({ doxa: 0, frontier: 0, unreached: 0, reached: 0 })
function categorize(row, doxaSet) {
if (doxaSet.has(String(row.ROP3Code || ''))) return 'doxa'
const g = Number(row.GSEC)
if (!Number.isFinite(g) || g <= 1) return 'frontier'
if (g <= 3) return 'unreached'
return 'reached'
}
// ─── Outbound attribution (same ladder as PeopleGroupDetail) ─────────────────
function utmSource() {
let fromUrl = ''
try { fromUrl = new URLSearchParams(window.location.search).get('utm_source') || '' } catch (_) {}
const fromConfig = profileConfig?.value?.utmSource
return encodeURIComponent(String(fromUrl || fromConfig || 'agwm'))
}
// ─── Map boot (shared composable — provider-aware, token-aware) ──────────────
const mapContainer = ref(null)
const appReady = ref(false)
const mapError = ref(null)
const { map, initializeMap, destroy } = useMapInstance({
containerRef: mapContainer,
accessToken: mapboxToken.value,
provider: mapProvider,
style: profileConfig?.value?.style ?? 'ink', // named style: near-black, max pin contrast
center: [20, 12],
zoom: 1.6,
})
// DOXA set via the standard pipeline (rop3 = peoplegroups.org ROP3Code)
const dsm = new DataSourceManager()
const mapData = useMapData({
mapId: 'all-people-groups',
dataSourceId: dataSource.value,
dataSourceManager: dsm,
dataStore,
markRaw: (v) => v,
})
let featuresAll = [] // kept for legend clicks (filter + fitBounds)
let doxaBySlugRop = new Map() // rop3 → { slug, name } for the pray link
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]))
}
async function buildFeatures() {
const [slim, doxa] = await Promise.all([
fetch(`${assetsBase.value}/people-groups/people-groups-slim.json`).then(r => {
if (!r.ok) throw new Error(`people-groups-slim.json ${r.status}`)
return r.json()
}),
mapData.loadData().then(() => mapData.normalizedPeopleGroups.value || []).catch((e) => {
console.warn('[all-people-groups] DOXA overlay unavailable (map still renders by GSEC):', e)
return []
}),
])
const doxaSet = new Set()
for (const pg of doxa) {
const rop = String(pg.peopleGroupId || pg.rop3 || '')
if (!rop) continue
doxaSet.add(rop)
if (!doxaBySlugRop.has(rop)) doxaBySlugRop.set(rop, { slug: pg.slug || '', name: pg.name || '' })
}
const feats = []
const c = { doxa: 0, frontier: 0, unreached: 0, reached: 0 }
for (const row of (slim.rows || [])) {
const lat = Number(row.Lat), lng = Number(row.Long)
if (!Number.isFinite(lat) || !Number.isFinite(lng) || (lat === 0 && lng === 0)) continue
const cat = categorize(row, doxaSet)
c[cat]++
feats.push({
type: 'Feature',
geometry: { type: 'Point', coordinates: [lng, lat] },
properties: {
name: row.Name || '', country: row.CountryName || '',
population: Number(row.Population) || 0,
gsec: row.GSEC, evangelical: row.EvangelicalLevel || '',
language: row.LanguageName || '', religion: row.ReligionName || '',
rop3: String(row.ROP3Code || ''), category: cat,
},
})
}
counts.value = c
featuresAll = feats
return { type: 'FeatureCollection', features: feats }
}
function addLayer(fc) {
const m = map.value
m.addSource('apg', { type: 'geojson', data: fc })
m.addLayer({
id: 'apg-pins', type: 'circle', source: 'apg',
paint: {
'circle-color': ['match', ['get', 'category'],
'doxa', CAT_COLOR.doxa, 'frontier', CAT_COLOR.frontier,
'unreached', CAT_COLOR.unreached, CAT_COLOR.reached],
// Thin high-contrast ring — visible, never clunky (the pin-visibility spec).
'circle-stroke-color': '#ffffff',
'circle-stroke-width': ['interpolate', ['linear'], ['zoom'], 0, 0.4, 6, 0.8, 12, 1.1],
'circle-stroke-opacity': 0.85,
'circle-radius': ['interpolate', ['linear'], ['zoom'], 0, 2.2, 4, 3.4, 8, 6, 12, 9],
'circle-opacity': 0.92,
// DOXA above frontier above unreached above reached — the assignment stays visible.
'circle-sort-key': ['match', ['get', 'category'], 'doxa', 4, 'frontier', 3, 'unreached', 2, 1],
},
})
m.on('mouseenter', 'apg-pins', () => { m.getCanvas().style.cursor = 'pointer' })
m.on('mouseleave', 'apg-pins', () => { m.getCanvas().style.cursor = '' })
m.on('click', 'apg-pins', (e) => {
const f = e.features && e.features[0]
if (!f) return
const p = f.properties
const doxaRec = p.category === 'doxa' ? doxaBySlugRop.get(p.rop3) : null
const cat = CATS.find(x => x.id === p.category)
const pray = doxaRec && doxaRec.slug
? `<a href="https://pray.doxa.life/${encodeURIComponent(doxaRec.slug)}?source=doxalife&utm_source=${utmSource()}" target="_blank" rel="noopener">Pray for them →</a>`
: ''
const NS = mapProvider === 'maplibre' ? window.maplibregl : window.mapboxgl
new NS.Popup({ closeButton: true, maxWidth: '290px' })
.setLngLat(e.lngLat)
.setHTML(`<div class="apg-pop">
<b>${esc(p.name)}</b> <span class="cat" style="background:${cat.color}">${esc(cat.label.split(' — ')[0])}</span>
<div class="meta">${esc(p.country)} · pop ${Number(p.population).toLocaleString()}</div>
<div class="meta">${esc(p.evangelical || ('GSEC ' + p.gsec))}</div>
<div class="meta">${esc(p.language)}${p.religion ? ' · ' + esc(p.religion) : ''}</div>
${pray}
</div>`)
.addTo(m)
})
}
// ─── Legend interactions: click a category → filter + fit to ALL of it ───────
const activeCat = ref(null)
function selectCat(id) {
const m = map.value
if (!m || !m.getLayer('apg-pins')) return
if (activeCat.value === id) { // click again = show everything
activeCat.value = null
m.setFilter('apg-pins', null)
m.fitBounds([[-170, -55], [179, 72]], { padding: 20, duration: 900 })
return
}
activeCat.value = id
m.setFilter('apg-pins', ['==', ['get', 'category'], id])
// Zoom OUT to see every dot of the chosen category (the visibility spec).
let minX = 180, minY = 85, maxX = -180, maxY = -85, n = 0
for (const f of featuresAll) {
if (f.properties.category !== id) continue
const [x, y] = f.geometry.coordinates
if (x < minX) minX = x; if (x > maxX) maxX = x
if (y < minY) minY = y; if (y > maxY) maxY = y
n++
}
if (n) m.fitBounds([[minX, minY], [maxX, maxY]], { padding: 60, duration: 900, maxZoom: 6 })
}
onMounted(async () => {
const dataPromise = buildFeatures().catch((err) => {
mapError.value = String(err?.message || err)
return null
})
try { await initializeMap() } catch (err) {
mapError.value = String(err?.message || err); return
}
if (!map.value) { mapError.value = 'Map failed to initialize — check token and container'; return }
map.value.once('style.load', async () => {
try {
const fc = await dataPromise
if (!fc) return
try { map.value.setProjection('mercator') } catch (_) {}
addLayer(fc)
mapStore?.registerMap?.('all-people-groups', map.value)
appReady.value = true
} catch (err) { mapError.value = String(err?.message || err) }
})
})
onBeforeUnmount(() => {
mapStore?.unregisterMap?.('all-people-groups')
destroy()
})
const totalShown = computed(() =>
counts.value.doxa + counts.value.frontier + counts.value.unreached + counts.value.reached)
</script>
<template>
<div class="apg-root">
<div v-if="!appReady && !mapError" class="apg-loading">Loading every people group…</div>
<div v-if="mapError" class="apg-error">The map could not be loaded. {{ mapError }}</div>
<div ref="mapContainer" class="apg-canvas" />
<div v-if="appReady" class="apg-legend">
<div class="apg-legend-title">All People Groups</div>
<div class="apg-legend-sub">{{ totalShown.toLocaleString() }} group–country locations</div>
<div
v-for="c in CATS" :key="c.id"
class="apg-row" :class="{ active: activeCat === c.id }"
@click="selectCat(c.id)"
>
<span class="apg-dot" :style="{ background: c.color }"></span>
{{ c.label }}
<span class="apg-count">{{ (counts[c.id] || 0).toLocaleString() }}</span>
</div>
<div class="apg-hint">Click a row to see all of them · click again for everything</div>
</div>
</div>
</template>

View file

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>doxa-maps · all-people-groups staging</title>
<link rel="icon" href="data:,">
<!-- Mapbox peer (loaded once per page, before the bundle) -->
<link href="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css" rel="stylesheet" />
<script src="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { background: #0b0f14; color: #e5e7eb; min-height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; }
.wrap { max-width: 1100px; margin: 0 auto; padding: 32px 20px; }
h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
.sub { color: #9ca3af; font-size: 13px; margin: 6px 0 24px; }
.slot { border: 1px solid #1f2937; border-radius: 12px; overflow: hidden; }
.slot .label { font: 12px/1 ui-monospace, monospace; color: #9ca3af; padding: 8px 12px;
background: #111827; border-bottom: 1px solid #1f2937; }
.frame { position: relative; height: 74vh; min-height: 460px; }
all-people-groups { display: block; position: absolute; inset: 0; width: 100%; height: 100%; }
</style>
</head>
<body>
<div class="wrap">
<h1>all-people-groups · staging</h1>
<p class="sub">Every people group on earth around the DOXA set —
<strong>black</strong> DOXA · <strong style="color:#e74c3c">red</strong> frontier ·
<strong style="color:#f1c40f">yellow</strong> unreached ·
<strong style="color:#2ecc71">green</strong> reached. Click a legend row to see all of a kind.</p>
<div class="slot">
<div class="label">profile-config.profile = "all-people-groups"</div>
<div class="frame">
<all-people-groups id="apg-slot"></all-people-groups>
</div>
</div>
</div>
<script>
// Pulled from .env at dev-server start (gitignored).
window.DOXA_TOKEN = '%VITE_MAPBOX_TOKEN%';
document.getElementById('apg-slot').setAttribute('profile-config',
JSON.stringify({ profile: 'all-people-groups', tk: window.DOXA_TOKEN,
instanceId: 'dev-all-people-groups', assetsBase: '/assets' }));
</script>
<script>window.MAP_APP_BASE_URL = '/';</script>
<script type="module" src="./index.js"></script>
</body>
</html>

View file

@ -0,0 +1,33 @@
/**
* app-profiles/all-people-groups/index.js
*
* Bundle entry for the ALL PEOPLE GROUPS map — every people group on earth
* (peoplegroups.org, 12,379 group×country rows) around the DOXA set.
*
* Usage from a host page:
* <script src="/doxa-maps/all-people-groups/all-people-groups.js"></script>
* <all-people-groups profile-config='{"profile":"all-people-groups"}'></all-people-groups>
*/
import { defineCustomElement } from 'vue'
import { createPinia } from 'pinia'
import ProfileLoader from '@map/ProfileLoader.vue'
import { createAppI18n } from '@map/i18n/index.js'
const profileModules = import.meta.glob('./*.vue')
const AllPeopleGroupsElement = defineCustomElement(ProfileLoader, {
configureApp(app) {
app.use(createPinia())
const i18n = createAppI18n()
app.use(i18n)
app.provide('appI18n', i18n)
app.provide('profileModules', profileModules)
}
})
if (typeof customElements !== 'undefined' && !customElements.get('all-people-groups')) {
customElements.define('all-people-groups', AllPeopleGroupsElement)
}
export default AllPeopleGroupsElement

View file

@ -23,6 +23,7 @@
'upg-100-list':'./doxa-maps/my-upg-100-list/my-upg-100-list.js',
'countries-map':'./doxa-maps/country-posters/country-posters.js',
'key-city-posters':'./doxa-maps/key-cities/key-cities.js',
'all-people-groups':'./doxa-maps/all-people-groups/all-people-groups.js',
'cities-with-internet':'./doxa-maps/cities-internet/cities-internet.js',
'gospel-resources':'./doxa-maps/gospel-resources/gospel-resources.js',
'research-map':'./doxa-maps/doxa-research-map/doxa-research-map.js',
@ -877,6 +878,15 @@ const PROFILES = {
// The nav has carried this id as a placeholder since 2026-07-29; this entry is
// what turns it into a real app. `profile` is the .vue FILENAME, not the folder.
// Ships WITH `panes` (map/list/operators below) — the pane grid is live.
// ALL PEOPLE GROUPS — every group on earth (peoplegroups.org) around the DOXA
// set: black doxa · red frontier · yellow unreached · green reached. Replaces
// the long-standing placeholder.
'all-people-groups': {
tag: 'all-people-groups',
src: './doxa-maps/all-people-groups/all-people-groups.js',
config: { profile:'all-people-groups', instanceId:'wrapper-all-people-groups' },
},
'cities-with-internet': {
tag: 'cities-internet-map',
src: './doxa-maps/cities-internet/cities-internet.js',

View file

@ -54,7 +54,7 @@ export const NAV = [
// keep them straight. (The "15k" suffix was dropped from the label at the
// maintainer's request 2026-07-29; the distinction is recorded here instead.)
// ⚠ NO BUNDLE YET — renders the placeholder card.
{ kind: 'item', id: 'all-people-groups', label: 'All People Groups', badge: 'soon', icon: 'globe', target: 'both' },
{ kind: 'item', id: 'all-people-groups', label: 'All People Groups', icon: 'globe', target: 'both' },
// REMOVED 'Reached People Groups' (2026-07-29) — added and withdrawn the
// same round; do not re-add without an explicit ask.
// REMOVED 'Simple Map' (2026-07-29). REASON WORTH KEEPING: the

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>all-people-groups · all-people-groups</title>
<link rel="icon" href="data:,">
<!-- Mapbox peer loaded ONCE here; the map custom element mounts in this page and
uses window.mapboxgl directly (same realm → no cross-realm container errors). -->
<link rel="preconnect" href="https://api.mapbox.com" crossorigin />
<link href="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css" rel="stylesheet" />
<link href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.css" rel="stylesheet" />
<script src="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.min.js"></script>
<style>
/* BARE full-page map — no chrome. The custom element IS the page, so the
browser URL shares the map itself (design note: "Share shares THE MAP"). */
html, body { height: 100%; margin: 0; background: #0a0e15; }
#slot, all-people-groups { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
padding: 24px; text-align: center; color: #9aa7b8;
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
#status.err { color: #f87171; }
</style>
</head>
<body>
<div id="slot"></div>
<div id="status">loading token…</div>
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <all-people-groups> custom element. -->
<script src="../all-people-groups.js" integrity="sha384-jD1/0tFzjeM4sSBNhuPeWdbQFIok3ATZ5it0KkbDTDcsozWGOReP4YQLB4S12/0Z" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
// 2. ?tk= URL param — token carried in the shared link
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
// *endpoint* is resolved and fetched:
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
// means "read that global"; anything else is used as a URL — so both a global
// name and a plain URL are safe (no SyntaxError footgun).
(function () {
var slot = document.getElementById('slot');
var status = document.getElementById('status');
var BASE_CONFIG = {"profile":"all-people-groups","dataSource":"pray-tools","instanceId":"all-people-groups-all-people-groups","assetsBase":"../../../assets"};
// MapLibre pages need NO token: the ladder below is skipped entirely.
var TOKEN_FREE = false;
var TOKEN_SRC = "/api/maps/token.json";
var BAKED_TOKEN = "";
var DEFAULT_TOKEN_URL = '/api/maps/token';
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
function askToken() {
if (!status) return;
status.className = 'err';
status.innerHTML = 'map needs a Mapbox token — ' +
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
var go = document.getElementById('tkGo');
if (go) go.addEventListener('click', function () {
var v = (document.getElementById('tkIn').value || '').trim();
if (!v) return;
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
mount(v);
});
}
function done() { if (status && status.parentNode) { status.parentNode.removeChild(status); } }
function mount(token) {
if (!token && !TOKEN_FREE) { return askToken(); }
try {
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
var el = document.createElement("all-people-groups");
el.setAttribute('profile-config', JSON.stringify(cfg));
slot.innerHTML = '';
slot.appendChild(el);
done();
} catch (e) { fail(e && e.message ? e.message : String(e)); }
}
function qp(name) {
try { return new URLSearchParams(location.search).get(name) || ''; }
catch (e) { return ''; }
}
function extractToken(data) {
if (typeof data === 'string') return data.trim();
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
return '';
}
// Rungs 1-3b: an actual token → mount directly, no fetch.
// 2b = localStorage: the paste-once store shared with the staging pages
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
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 = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
if (TOKEN_FREE) { return mount(''); }
if (directToken) { return mount(directToken); }
// Rungs 4-6: resolve a token endpoint and fetch it.
var tokenUrl =
(window.MAP_TOKEN_URL) ||
(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '') ||
DEFAULT_TOKEN_URL;
fetch(tokenUrl)
.then(function (r) {
var ct = r.headers.get('content-type') || '';
return ct.indexOf('json') !== -1 ? r.json() : r.text();
})
.then(function (data) { mount(extractToken(data)); })
.catch(function (e) {
fail('Token fetch failed from ' + tokenUrl + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + (e && e.message ? e.message : e) + ')');
});
})();
</script>
</body>
</html>

View file

@ -0,0 +1,371 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>doxa-maps</title>
<link rel="icon" href="data:,">
<link rel="preconnect" href="https://api.mapbox.com" crossorigin />
<style>/* staging-forge.css — the ONE-PAGE semantic staging theme (root + bundle pages).
design brief 2026-07-11: tree sidebar left on desktop, tree-top + scroll-to-map
on mobile, GEM FRAMES as the hero (each map in its colored jewel frame), quiet
architectural dark, zero folder-hopping. doxa-maps design language. */
:root {
--bg:#0a0e15; --surf:#121a27; --surf2:#0e1623; --line:#223047;
--ink:#eef2f7; --mut:#9aa7b8; --dim:#6b7787;
--cy:#46d4ff; --vi:#b9a6f5; --gold:#ffc24b; --grn:#5ee0a0;
--sans:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
--mono:ui-monospace,SFMono-Regular,Menlo,monospace;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
background:
radial-gradient(1100px 480px at 82% -12%, rgba(70,212,255,.10), transparent 60%),
radial-gradient(820px 460px at -8% 112%, rgba(185,166,245,.10), transparent 60%),
var(--bg);
color: var(--ink); font-family: var(--sans); -webkit-font-smoothing: antialiased;
}
code { font-family: var(--mono); }
.hidden { display: none !important; }
.empty { color: var(--dim); font: 12px/1.5 var(--mono); padding: 18px; }
/* ── Shell: header strip, then sidebar|stage grid (desktop) ── */
header.top { padding: 14px 18px 10px; border-bottom: 1px solid var(--surf2); }
header.top h1 { font: 800 15px/1.2 var(--sans); letter-spacing: -.01em; }
header.top h1 .ac { color: var(--cy); font-weight: 600; }
header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut); }
.strip { padding: 7px 18px; font: 11px/1.4 var(--mono); color: var(--mut);
border-bottom: 1px solid var(--surf2); background: rgba(255,255,255,.012); }
.strip .pat { color: var(--cy); } .strip .err { color: #f87171; } .strip strong { color: var(--ink); }
/* token bar — visible only while no token resolves */
#tokenBar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
padding: 9px 18px; font: 12px/1.4 var(--sans); color: var(--gold);
border-bottom: 1px solid var(--surf2); background: rgba(255,194,75,.06); }
#tokenBar input { flex: 1 1 220px; max-width: 420px; font: 12px/1 var(--mono);
color: var(--ink); background: var(--surf2); border: 1px solid var(--line);
border-radius: 8px; padding: 8px 10px; }
#tokenBar button { font: 700 11px/1 var(--mono); color: #0a0e15; cursor: pointer;
background: linear-gradient(90deg, var(--cy), var(--vi)); border: 0;
border-radius: 999px; padding: 8px 14px; }
/* ── The one-page layout ── */
#shell { display: grid; grid-template-columns: minmax(280px, 340px) 1fr;
height: calc(100vh - 118px); min-height: 420px; }
/* sidebar: THE semantic tree — navigation and architecture in one */
#treeNav { overflow-y: auto; border-right: 1px solid var(--surf2);
padding: 12px 10px 28px; background: rgba(255,255,255,.008); }
.bgroup { margin-bottom: 8px; border: 1px solid var(--line); border-radius: 10px;
background: var(--surf); overflow: hidden; }
.bgroup summary { list-style: none; cursor: pointer; display: flex; align-items: baseline;
gap: 8px; padding: 10px 12px; font: 700 13px/1.2 var(--sans); }
.bgroup summary::-webkit-details-marker { display: none; }
.bgroup summary .bdot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
align-self: center; background: var(--gem, var(--cy)); box-shadow: 0 0 8px var(--gem, var(--cy)); }
.bgroup summary .bmeta { margin-left: auto; font: 10px/1.3 var(--mono); color: var(--dim); font-weight: 400; }
.bgroup[open] summary { border-bottom: 1px solid var(--surf2); }
.node { display: block; width: 100%; text-align: left; cursor: pointer; color: inherit;
background: transparent; border: 0; border-left: 2px solid transparent;
padding: 8px 12px 8px 22px; transition: background .12s; }
.node:hover { background: rgba(255,255,255,.03); }
.node.active { background: color-mix(in srgb, var(--gem, var(--cy)) 10%, transparent);
border-left-color: var(--gem, var(--cy)); }
.node .nlabel { display: block; font: 600 12.5px/1.3 var(--sans); }
.node .nsub { display: block; font: 10.5px/1.4 var(--mono); color: var(--mut); margin-top: 1px; }
.node .nedit { display: block; font: 9.5px/1.4 var(--mono); color: var(--dim); margin-top: 2px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* kind glyphs */
.node.k-instance .nlabel::before { content: "◇ "; color: var(--grn); }
.node.k-profile .nlabel::before { content: "● "; color: var(--vi); }
.node.k-nested .nlabel::before { content: "◉ "; color: var(--gold); }
/* ── Stage: the ONE live map inside its GEM FRAME (the recurring hero theme) ── */
#stage { display: flex; flex-direction: column; min-width: 0; padding: 12px 16px 16px; }
.stage-head { display: flex; align-items: center; gap: 10px; padding: 0 2px 9px; }
.stage-head .sctag { font: 800 13px/1 var(--mono); color: var(--gem, var(--cy)); }
.stage-head .sckind { font: 8.5px/1 var(--mono); color: var(--dim); border: 1px solid var(--line);
border-radius: 999px; padding: 3px 8px; text-transform: uppercase; letter-spacing: .08em; }
.stage-head .popout { margin-left: auto; font: 600 11px/1 var(--mono); color: var(--mut);
text-decoration: none; border: 1px solid var(--line); border-radius: 999px; padding: 7px 12px; }
.stage-head .popout:hover { color: var(--ink); border-color: #3a4a63; }
/* the gem frame: colored jewel border + inner ring + soft glow in the node's gem color */
.map-slot { flex: 1 1 0; min-height: 0; position: relative; border-radius: 12px;
border: 1px solid var(--gem, var(--cy));
box-shadow: 0 0 0 1px color-mix(in srgb, var(--gem, var(--cy)) 45%, transparent) inset,
0 0 26px rgba(var(--gem-rgb, 70,212,255), .16),
0 8px 30px rgba(0,0,0,.35);
background: #0d1622; overflow: hidden; padding: 8px; }
.map-slot iframe { position: absolute; inset: 8px; width: calc(100% - 16px);
height: calc(100% - 16px); border: 0; border-radius: 8px; }
/* ── Mobile: single column — tree on top, the map takes the FULL SCREEN below ──
(2026-07-11: mobile map = pretty much full screen; scroll must reach it.
100dvh = dynamic viewport height, tracks the mobile URL bar; 100vh = fallback.) */
@media (max-width: 899px) {
html, body { height: auto; } /* let the page actually scroll */
#shell { display: block; height: auto; }
#treeNav { border-right: 0; border-bottom: 1px solid var(--surf2); max-height: 44vh; }
#stage { height: 100vh; height: 100dvh; padding: 8px; } /* full-screen map pane */
.stage-head { padding-bottom: 6px; }
.map-slot { padding: 4px; border-radius: 10px; } /* slimmer gem frame = max map */
.map-slot iframe { inset: 4px; width: calc(100% - 8px); height: calc(100% - 8px); }
.node .nedit { display: none; } /* edit paths are a desktop affordance */
}
</style>
</head>
<body>
<header class="top">
<h1>doxa-maps<span class="ac"> / all-people-groups</span></h1>
<div class="sub">all-people-groups — one bundle, its profiles as paste-anywhere maps</div>
</header>
<div class="strip" id="frameInfo">loading…</div>
<div id="tokenBar" class="hidden"></div>
<div id="shell">
<nav id="treeNav" aria-label="semantic tree"></nav>
<main id="stage"></main>
</div>
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"all-people-groups","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"all-people-groups","scriptUrl":"./all-people-groups/all-people-groups.js","output":"doxa-maps/all-people-groups/all-people-groups.js","integrity":"sha384-jD1/0tFzjeM4sSBNhuPeWdbQFIok3ATZ5it0KkbDTDcsozWGOReP4YQLB4S12/0Z","elements":["all-people-groups"],"profiles":[{"profile":"all-people-groups","file":"all-people-groups.vue","description":"All 12,379 people groups (peoplegroups.org) in four colors — reached,","element":"all-people-groups","iframeUrl":"./all-people-groups/all-people-groups/index.html","scriptUrl":"./all-people-groups/all-people-groups.js","embed":{"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 src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-jD1/0tFzjeM4sSBNhuPeWdbQFIok3ATZ5it0KkbDTDcsozWGOReP4YQLB4S12/0Z\" crossorigin=\"anonymous\">\u003c/script>\n\u003call-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/all-people-groups>"},"bundle":"all-people-groups"}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*
* design brief (2026-07-11, the merger of all prior staging files):
* - ONE page. No folder-hopping: the semantic tree IS the navigation.
* - Desktop: hierarchical tree SIDEBAR on the left (bundle → profile → instance,
* kind-badged, "where do I edit" hints) · selected map fills the stage on the right.
* - Mobile: tree on top, map below — scroll down to the map. Native responsive,
* no simulator toggle.
* - Token UX: resolve ?tk= → localStorage → none. If none, show a token bar
* (paste once, stored locally in the browser, never in files/links unless you
* use ?tk= on purpose). Maps must Just Work after one paste.
* - Auto-generated: whatever app-profiles exist, the tree detects and shows them.
*
* file:// contract: classic script, manifest inlined at build (window.FORGE.manifest),
* fetch only as dev fallback. No import/export. The DEV-HMR block at the bottom is
* stripped by generate-tree.mjs for built pages.
*/
/* global window, document */
const FORGE = window.FORGE || {}
const SCOPE = FORGE.scope || 'root'
const MODE = FORGE.mode || 'built'
const MANIFEST_URL = FORGE.manifestUrl || './manifest.json'
const HREF_BASE = FORGE.hrefBase || './'
const GEMS = ['#46d4ff', '#b9a6f5', '#ffc24b', '#5ee0a0', '#f97fb5', '#8ef0e4']
const $ = (sel) => document.querySelector(sel)
let tree = null
let flat = [] // flat list of selectable map nodes (profiles + instances)
let activeKey = null // selected node key
// ── Token resolution (maintainer ladder: ?tk= → localStorage → ask once) ─────────
const TK_STORE = 'MAPS_STAGING_TK'
function resolveToken() {
const qp = new URLSearchParams(location.search).get('tk')
if (qp) { try { localStorage.setItem(TK_STORE, qp) } catch (e) {} return qp }
try { return localStorage.getItem(TK_STORE) || '' } catch (e) { return '' }
}
let TK = resolveToken()
function withTk(href) {
if (!TK) return href
return href + (href.includes('?') ? '&' : '?') + 'tk=' + encodeURIComponent(TK)
}
// Carry the page's ?provider= into the embedded map iframe so the engine choice propagates.
function withProvider(href) {
const p = typeof location !== 'undefined' ? new URLSearchParams(location.search).get('provider') : null
return p ? href + (href.includes('?') ? '&' : '?') + 'provider=' + encodeURIComponent(p) : href
}
// ── Load the semantic tree (inline-first; fetch = dev fallback) ──────────────
async function loadTree() {
if (FORGE.manifest) return FORGE.manifest
const res = await fetch(MANIFEST_URL, { cache: 'no-cache' })
if (!res.ok) throw new Error('manifest ' + res.status + ' from ' + MANIFEST_URL)
return res.json()
}
const treeHref = (rel) => HREF_BASE + rel
const hexRgb = (hex) => {
const h = hex.replace('#', '')
return parseInt(h.slice(0,2),16) + ',' + parseInt(h.slice(2,4),16) + ',' + parseInt(h.slice(4,6),16)
}
function profileKind(p) {
if (Array.isArray(p.instances) && p.instances.length) return 'parameterized'
if (p.nested || /nested|tabs|web components/i.test(p.description || '')) return 'nested'
return 'individual'
}
// ── Build the flat selectable list + the sidebar tree ────────────────────────
// Selectable = anything that renders as ONE map page: individual/nested profiles
// and each instance of a parameterized profile.
function buildFlat() {
flat = []
;(tree.bundles || []).forEach((b, bi) => { b.__gem = GEMS[bi % GEMS.length] })
for (const b of tree.bundles || []) {
for (const p of b.profiles || []) {
const kind = profileKind(p)
if (kind === 'parameterized') {
for (const inst of p.instances) {
flat.push({
key: b.bundle + '/' + p.profile + '/' + inst.id,
kind: 'instance', label: inst.title || inst.id,
sub: p.profile + ' · preset "' + inst.id + '"',
edit: b.bundle + '/' + p.profile + '.instances.json',
href: treeHref(b.bundle + '/' + p.profile + '/' + inst.id + '/index.html'),
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
})
}
} else {
flat.push({
key: b.bundle + '/' + p.profile,
kind: kind === 'nested' ? 'nested' : 'profile',
label: p.profile,
sub: p.description || (kind === 'nested' ? 'embeds child web components' : 'one web component = one map'),
edit: 'app-profiles/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
href: treeHref(b.bundle + '/' + p.profile + '/index.html'),
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
})
}
}
}
}
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]))
}
// Sidebar: bundles are <details> groups (open by default), each map node a row.
function renderSidebar() {
const el = $('#treeNav')
if (!el) return
const parts = []
const bundles = tree.bundles || []
bundles.forEach((b, bi) => {
const gem = b.__gem || GEMS[bi % GEMS.length]
const rows = flat.filter((n) => n.bundle === b.bundle).map((n) => `
<button class="node k-${n.kind}${n.key === activeKey ? ' active' : ''}" data-key="${esc(n.key)}" style="--gem:${gem}">
<span class="nlabel">${esc(n.label)}</span>
<span class="nsub">${esc(n.sub)}</span>
<code class="nedit">${esc(n.edit)}</code>
</button>`).join('')
parts.push(`
<details class="bgroup" open style="--gem:${gem}">
<summary><span class="bdot"></span>${esc(b.bundle)}
<span class="bmeta">${(b.profiles || []).length} profile${(b.profiles || []).length === 1 ? '' : 's'} · one .js</span>
</summary>
${rows}
</details>`)
})
el.innerHTML = parts.join('') || '<p class="empty">no bundles built yet</p>'
el.querySelectorAll('.node').forEach((btn) =>
btn.addEventListener('click', () => select(btn.dataset.key)))
}
// ── Stage: the ONE live map (iframe of the node's self-contained page) ───────
function renderStage() {
const st = $('#stage')
if (!st) return
const nd = flat.find((n) => n.key === activeKey) || flat[0]
if (!nd) { st.innerHTML = '<p class="empty">no maps in this tree</p>'; return }
activeKey = nd.key
const gem = nd.gem || '#46d4ff'
st.style.setProperty('--gem', gem)
st.style.setProperty('--gem-rgb', hexRgb(gem))
st.innerHTML = `
<div class="stage-head">
<span class="sctag">&lt;${esc(nd.element || nd.label)}&gt;</span>
<span class="sckind">${esc(nd.kind)}</span>
<a class="popout" href="${esc(withProvider(withTk(nd.href)))}" target="_blank" rel="noopener">open ↗</a>
<button class="popout" id="copyEmbed" type="button" title="Copy an iframe snippet with this host's full URL">copy embed</button>
</div>
<div class="map-slot"><iframe allowfullscreen src="${esc(withProvider(withTk(nd.href)))}"></iframe></div>`
// COPY EMBED — the partner handoff. The snippet is built AT CLICK TIME from
// location.href, so it always carries the FULL URL of whatever host serves
// this tree (localhost, doxa.life/js/doxa-maps-build/…, any CDN) — embeds
// work no matter where the build folder lives. utm_source placeholder =
// per-partner traffic attribution (see the manifest snippets).
const copyBtn = $('#copyEmbed')
if (copyBtn) copyBtn.addEventListener('click', async () => {
const abs = new URL(nd.href, location.href)
abs.searchParams.set('utm_source', 'YOUR_SOURCE')
const snip = '<iframe src="' + abs.href + '" width="100%" height="600" '
+ 'style="border:none;border-radius:12px;" allowfullscreen></iframe>'
let ok = false
try { await navigator.clipboard.writeText(snip); ok = true } catch (_) {
try {
const ta = document.createElement('textarea')
ta.value = snip; document.body.appendChild(ta); ta.select()
ok = document.execCommand('copy'); ta.remove()
} catch (_) {}
}
copyBtn.textContent = ok ? 'copied ✓' : 'copy failed'
setTimeout(() => { copyBtn.textContent = 'copy embed' }, 1800)
})
// keep the sidebar highlight in sync
document.querySelectorAll('#treeNav .node').forEach((b) =>
b.classList.toggle('active', b.dataset.key === activeKey))
}
function select(key) {
activeKey = key
renderStage()
// Mobile: after picking from the tree, bring the map into view (scroll down to it).
if (window.matchMedia('(max-width: 899px)').matches) {
$('#stage')?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
// ── Token bar: shown only while no token resolves; paste once, stored locally ─
function renderTokenBar() {
const bar = $('#tokenBar')
if (!bar) return
if (TK) { bar.classList.add('hidden'); return }
bar.classList.remove('hidden')
bar.innerHTML = `
<span>maps need a Mapbox token —</span>
<input id="tkInput" type="password" placeholder="paste pk.… (stays in this browser only)" autocomplete="off" />
<button id="tkSave">use it</button>`
$('#tkSave').addEventListener('click', () => {
const v = $('#tkInput').value.trim()
if (!v) return
TK = v
try { localStorage.setItem(TK_STORE, v) } catch (e) {}
bar.classList.add('hidden')
renderStage()
})
}
// ── Boot ─────────────────────────────────────────────────────────────────────
async function boot() {
const info = $('#frameInfo')
try {
tree = await loadTree()
buildFlat()
if (info) {
info.innerHTML = '<span class="pat">semantic tree</span> — '
+ (tree.bundles || []).length + ' bundle(s) · ' + flat.length + ' map(s) · '
+ '<strong>' + esc(MODE) + '</strong>'
}
renderSidebar()
renderTokenBar()
if (flat.length) { activeKey = flat[0].key; renderStage() }
} catch (e) {
if (info) info.innerHTML = '<span class="err">failed to load semantic tree: ' + esc(e.message) + '</span>'
}
}
boot()
/* dev-hmr stripped for built pages */
</script>
</body>
</html>

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"cities-internet","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","integrity":"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD","elements":["cities-internet-map"],"profiles":[{"profile":"connectivity-list","file":"connectivity-list.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-list/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"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 src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"connectivity-map","file":"connectivity-map.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-map/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"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 src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/data-tables/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"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 src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","integrity":"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD","elements":["cities-internet-map"],"profiles":[{"profile":"connectivity-list","file":"connectivity-list.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-list/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"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 src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"connectivity-map","file":"connectivity-map.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-map/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"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 src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/data-tables/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"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 src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-KjP9vcYgXqhfXYxNS9D2sy5IlDpI18BdqjdBqr0nJ/qGWxb/KkP25DjbWbZfcZpD\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"country-posters","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","integrity":"sha384-uzVZRaD6wmMfQuDoWGCWc4a4vsTqzUw410fysaPsUTDDYPmg9Opa5/WTEXR1oju/","elements":["country-posters-map"],"profiles":[{"profile":"countries-map","file":"countries-map.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/countries-map/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"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 src=\"./country-posters/country-posters.js\" integrity=\"sha384-uzVZRaD6wmMfQuDoWGCWc4a4vsTqzUw410fysaPsUTDDYPmg9Opa5/WTEXR1oju/\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}},{"profile":"country-poster-wall","file":"country-poster-wall.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/country-poster-wall/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"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 src=\"./country-posters/country-posters.js\" integrity=\"sha384-uzVZRaD6wmMfQuDoWGCWc4a4vsTqzUw410fysaPsUTDDYPmg9Opa5/WTEXR1oju/\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","integrity":"sha384-uzVZRaD6wmMfQuDoWGCWc4a4vsTqzUw410fysaPsUTDDYPmg9Opa5/WTEXR1oju/","elements":["country-posters-map"],"profiles":[{"profile":"countries-map","file":"countries-map.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/countries-map/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"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 src=\"./country-posters/country-posters.js\" integrity=\"sha384-uzVZRaD6wmMfQuDoWGCWc4a4vsTqzUw410fysaPsUTDDYPmg9Opa5/WTEXR1oju/\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}},{"profile":"country-poster-wall","file":"country-poster-wall.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/country-poster-wall/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"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 src=\"./country-posters/country-posters.js\" integrity=\"sha384-uzVZRaD6wmMfQuDoWGCWc4a4vsTqzUw410fysaPsUTDDYPmg9Opa5/WTEXR1oju/\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"demo-map","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"demo-map","scriptUrl":"./demo-map/demo-map.js","output":"doxa-maps/demo-map/demo-map.js","integrity":"sha384-3K61vrZb7gtpShtG7dujt+zboQee344tTZT2e55FPLS2tKyjWSbOT/dA70FxQi4Y","elements":["demo-map"],"profiles":[{"profile":"demo-map","file":"demo-map.vue","description":"Simple two-tab demo map. Tab 1 colors pins by religion (shared","element":"demo-map","iframeUrl":"./demo-map/demo-map/index.html","scriptUrl":"./demo-map/demo-map.js","embed":{"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 src=\"./demo-map/demo-map.js\" integrity=\"sha384-3K61vrZb7gtpShtG7dujt+zboQee344tTZT2e55FPLS2tKyjWSbOT/dA70FxQi4Y\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdemo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/demo-map>"},"instances":[{"id":"maplibre","title":"Demo map — MapLibre (token-free)","apiBaseUrl":"","props":{"profile":"demo-map","provider":"maplibre","instanceId":"demo-maplibre"},"element":"demo-map","iframeUrl":"./demo-map/demo-map/maplibre/index.html","embed":{"iframe":"\u003ciframe src=\"./demo-map/demo-map/maplibre/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}],"bundle":"demo-map"}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"demo-map","scriptUrl":"./demo-map/demo-map.js","output":"doxa-maps/demo-map/demo-map.js","integrity":"sha384-3K61vrZb7gtpShtG7dujt+zboQee344tTZT2e55FPLS2tKyjWSbOT/dA70FxQi4Y","elements":["demo-map"],"profiles":[{"profile":"demo-map","file":"demo-map.vue","description":"Simple two-tab demo map. Tab 1 colors pins by religion (shared","element":"demo-map","iframeUrl":"./demo-map/demo-map/index.html","scriptUrl":"./demo-map/demo-map.js","embed":{"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 src=\"./demo-map/demo-map.js\" integrity=\"sha384-3K61vrZb7gtpShtG7dujt+zboQee344tTZT2e55FPLS2tKyjWSbOT/dA70FxQi4Y\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdemo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/demo-map>"},"instances":[{"id":"maplibre","title":"Demo map — MapLibre (token-free)","apiBaseUrl":"","props":{"profile":"demo-map","provider":"maplibre","instanceId":"demo-maplibre"},"element":"demo-map","iframeUrl":"./demo-map/demo-map/maplibre/index.html","embed":{"iframe":"\u003ciframe src=\"./demo-map/demo-map/maplibre/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}],"bundle":"demo-map"}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"doxa-research-map","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"doxa-research-map","scriptUrl":"./doxa-research-map/doxa-research-map.js","output":"doxa-maps/doxa-research-map/doxa-research-map.js","integrity":"sha384-9OB3pemF6VMyXIIIr8ttC3fIroDvfpvDoSrxnK48sxq7Y/zkXnqiFBwTg7PEoY7D","elements":["doxa-research-map"],"profiles":[{"profile":"research-map","file":"research-map.vue","description":"","element":"doxa-research-map","iframeUrl":"./doxa-research-map/research-map/index.html","scriptUrl":"./doxa-research-map/doxa-research-map.js","embed":{"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 src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-9OB3pemF6VMyXIIIr8ttC3fIroDvfpvDoSrxnK48sxq7Y/zkXnqiFBwTg7PEoY7D\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/doxa-research-map>"}}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"doxa-research-map","scriptUrl":"./doxa-research-map/doxa-research-map.js","output":"doxa-maps/doxa-research-map/doxa-research-map.js","integrity":"sha384-9OB3pemF6VMyXIIIr8ttC3fIroDvfpvDoSrxnK48sxq7Y/zkXnqiFBwTg7PEoY7D","elements":["doxa-research-map"],"profiles":[{"profile":"research-map","file":"research-map.vue","description":"","element":"doxa-research-map","iframeUrl":"./doxa-research-map/research-map/index.html","scriptUrl":"./doxa-research-map/doxa-research-map.js","embed":{"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 src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-9OB3pemF6VMyXIIIr8ttC3fIroDvfpvDoSrxnK48sxq7Y/zkXnqiFBwTg7PEoY7D\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/doxa-research-map>"}}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"doxa-simple-map","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"doxa-simple-map","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","output":"doxa-maps/doxa-simple-map/doxa-simple-map.js","integrity":"sha384-vvuDU7leqbJ6T8jJkoAX9A7tHfZmRwiDdja6fthbXB3zxtA9XEZGQrvu12io7KbB","elements":["doxa-map","doxa-simple-map"],"profiles":[{"profile":"doxa-simple-map","file":"doxa-simple-map.vue","description":"","element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/index.html","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","embed":{"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 src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-vvuDU7leqbJ6T8jJkoAX9A7tHfZmRwiDdja6fthbXB3zxtA9XEZGQrvu12io7KbB\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/doxa-map>"},"instances":[{"id":"prayer","title":"Prayer Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-prayer","dataSource":"pray-tools","tabs":[{"id":"prayer","colorStrategy":"prayer","legend":"prayer","popup":"prayer"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/prayer/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/prayer/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"engagement","title":"Engagement Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-engagement","dataSource":"pray-tools","tabs":[{"id":"engagement","colorStrategy":"engagement","legend":"engagement","popup":"engagement"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/engagement/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/engagement/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"adoption","title":"Adoption Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-adoption","dataSource":"pray-tools","tabs":[{"id":"adoption","colorStrategy":"adoption","legend":"adoption","popup":"adoption"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/adoption/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/adoption/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}]}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"doxa-simple-map","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","output":"doxa-maps/doxa-simple-map/doxa-simple-map.js","integrity":"sha384-vvuDU7leqbJ6T8jJkoAX9A7tHfZmRwiDdja6fthbXB3zxtA9XEZGQrvu12io7KbB","elements":["doxa-map","doxa-simple-map"],"profiles":[{"profile":"doxa-simple-map","file":"doxa-simple-map.vue","description":"","element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/index.html","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","embed":{"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 src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-vvuDU7leqbJ6T8jJkoAX9A7tHfZmRwiDdja6fthbXB3zxtA9XEZGQrvu12io7KbB\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/doxa-map>"},"instances":[{"id":"prayer","title":"Prayer Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-prayer","dataSource":"pray-tools","tabs":[{"id":"prayer","colorStrategy":"prayer","legend":"prayer","popup":"prayer"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/prayer/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/prayer/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"engagement","title":"Engagement Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-engagement","dataSource":"pray-tools","tabs":[{"id":"engagement","colorStrategy":"engagement","legend":"engagement","popup":"engagement"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/engagement/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/engagement/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"adoption","title":"Adoption Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-adoption","dataSource":"pray-tools","tabs":[{"id":"adoption","colorStrategy":"adoption","legend":"adoption","popup":"adoption"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/adoption/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/adoption/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}]}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"gospel-resources","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"gospel-resources","scriptUrl":"./gospel-resources/gospel-resources.js","output":"doxa-maps/gospel-resources/gospel-resources.js","integrity":"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc","elements":["gospel-resources-map"],"profiles":[{"profile":"charts","file":"charts.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/charts/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/data-tables/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-list","file":"resources-list.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-list/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-map","file":"resources-map.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-map/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"gospel-resources","scriptUrl":"./gospel-resources/gospel-resources.js","output":"doxa-maps/gospel-resources/gospel-resources.js","integrity":"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc","elements":["gospel-resources-map"],"profiles":[{"profile":"charts","file":"charts.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/charts/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/data-tables/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-list","file":"resources-list.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-list/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-map","file":"resources-map.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-map/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"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 src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KIl3/mrv+4VV6c+jt1JMlwOZ6hNTaVYrahzVEnDNS23NG9lHAbuyoxdX4U8PLIEc\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

File diff suppressed because one or more lines are too long

View file

@ -2,9 +2,33 @@
"schema": "doxa-maps-manifest@1",
"generatedFrom": "app-profiles/*/*.vue",
"embedBase": ".",
"bundleCount": 9,
"profileCount": 17,
"bundleCount": 10,
"profileCount": 18,
"bundles": [
{
"bundle": "all-people-groups",
"scriptUrl": "./all-people-groups/all-people-groups.js",
"output": "doxa-maps/all-people-groups/all-people-groups.js",
"integrity": "sha384-jD1/0tFzjeM4sSBNhuPeWdbQFIok3ATZ5it0KkbDTDcsozWGOReP4YQLB4S12/0Z",
"elements": [
"all-people-groups"
],
"profiles": [
{
"profile": "all-people-groups",
"file": "all-people-groups.vue",
"description": "All 12,379 people groups (peoplegroups.org) in four colors — reached,",
"element": "all-people-groups",
"iframeUrl": "./all-people-groups/all-people-groups/index.html",
"scriptUrl": "./all-people-groups/all-people-groups.js",
"embed": {
"iframe": "<iframe src=\"./all-people-groups/all-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-jD1/0tFzjeM4sSBNhuPeWdbQFIok3ATZ5it0KkbDTDcsozWGOReP4YQLB4S12/0Z\" crossorigin=\"anonymous\"></script>\n<all-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></all-people-groups>"
},
"bundle": "all-people-groups"
}
]
},
{
"bundle": "cities-internet",
"scriptUrl": "./cities-internet/cities-internet.js",

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"my-upg-100-list","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"my-upg-100-list","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","output":"doxa-maps/my-upg-100-list/my-upg-100-list.js","integrity":"sha384-2glb9WoqMp/D9ngfM8IPoKE8ysdaR0oTiHKtaNoi2bYVtDjJpO4rwKfVv3hYwfmj","elements":["my-upg-100-list-map"],"profiles":[{"profile":"poster-wall","file":"poster-wall.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/poster-wall/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"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 src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-2glb9WoqMp/D9ngfM8IPoKE8ysdaR0oTiHKtaNoi2bYVtDjJpO4rwKfVv3hYwfmj\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}},{"profile":"upg-100-list","file":"upg-100-list.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/upg-100-list/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"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 src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-2glb9WoqMp/D9ngfM8IPoKE8ysdaR0oTiHKtaNoi2bYVtDjJpO4rwKfVv3hYwfmj\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"my-upg-100-list","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","output":"doxa-maps/my-upg-100-list/my-upg-100-list.js","integrity":"sha384-2glb9WoqMp/D9ngfM8IPoKE8ysdaR0oTiHKtaNoi2bYVtDjJpO4rwKfVv3hYwfmj","elements":["my-upg-100-list-map"],"profiles":[{"profile":"poster-wall","file":"poster-wall.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/poster-wall/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"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 src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-2glb9WoqMp/D9ngfM8IPoKE8ysdaR0oTiHKtaNoi2bYVtDjJpO4rwKfVv3hYwfmj\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}},{"profile":"upg-100-list","file":"upg-100-list.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/upg-100-list/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"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 src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-2glb9WoqMp/D9ngfM8IPoKE8ysdaR0oTiHKtaNoi2bYVtDjJpO4rwKfVv3hYwfmj\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"research-dashboard","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"research-dashboard","scriptUrl":"./research-dashboard/research-dashboard.js","output":"doxa-maps/research-dashboard/research-dashboard.js","integrity":"sha384-9+QpRNZ+XKx6COLMQmi5v/hYHpQnAEDHqn28eVwnTJperBNgTLoDM6EmOMNgXfSQ","elements":["research-dashboard-map"],"profiles":[{"profile":"research-dashboard","file":"research-dashboard.vue","description":"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.","element":"research-dashboard-map","iframeUrl":"./research-dashboard/research-dashboard/index.html","scriptUrl":"./research-dashboard/research-dashboard.js","embed":{"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 src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-9+QpRNZ+XKx6COLMQmi5v/hYHpQnAEDHqn28eVwnTJperBNgTLoDM6EmOMNgXfSQ\" crossorigin=\"anonymous\">\u003c/script>\n\u003cresearch-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/research-dashboard-map>"},"bundle":"research-dashboard"}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"research-dashboard","scriptUrl":"./research-dashboard/research-dashboard.js","output":"doxa-maps/research-dashboard/research-dashboard.js","integrity":"sha384-9+QpRNZ+XKx6COLMQmi5v/hYHpQnAEDHqn28eVwnTJperBNgTLoDM6EmOMNgXfSQ","elements":["research-dashboard-map"],"profiles":[{"profile":"research-dashboard","file":"research-dashboard.vue","description":"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.","element":"research-dashboard-map","iframeUrl":"./research-dashboard/research-dashboard/index.html","scriptUrl":"./research-dashboard/research-dashboard.js","embed":{"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 src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-9+QpRNZ+XKx6COLMQmi5v/hYHpQnAEDHqn28eVwnTJperBNgTLoDM6EmOMNgXfSQ\" crossorigin=\"anonymous\">\u003c/script>\n\u003cresearch-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/research-dashboard-map>"},"bundle":"research-dashboard"}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -130,7 +130,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"template-bundle","hrefBase":"../"};
/* file:// contract: manifest inlined at build — no fetch needed to render. */
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":17,"bundles":[{"bundle":"template-bundle","scriptUrl":"./template-bundle/template-bundle.js","output":"doxa-maps/template-bundle/template-bundle.js","integrity":"sha384-7oKMpdhxEwHZk1lLpNS5+8FNfK7CEv2BAlgmw65N5F6CkNhduVcSVgIshYLDKYhJ","elements":["template-bundle"],"profiles":[{"profile":"template-map-a","file":"template-map-a.vue","description":"Minimal example profile A — a colored placeholder, no Mapbox or data.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-a/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"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 src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-7oKMpdhxEwHZk1lLpNS5+8FNfK7CEv2BAlgmw65N5F6CkNhduVcSVgIshYLDKYhJ\" crossorigin=\"anonymous\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-a\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"},{"profile":"template-map-b","file":"template-map-b.vue","description":"Minimal example profile B — a second profile in the same bundle.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-b/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"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 src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-7oKMpdhxEwHZk1lLpNS5+8FNfK7CEv2BAlgmw65N5F6CkNhduVcSVgIshYLDKYhJ\" crossorigin=\"anonymous\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-b\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"}]}]};</script>
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":18,"bundles":[{"bundle":"template-bundle","scriptUrl":"./template-bundle/template-bundle.js","output":"doxa-maps/template-bundle/template-bundle.js","integrity":"sha384-7oKMpdhxEwHZk1lLpNS5+8FNfK7CEv2BAlgmw65N5F6CkNhduVcSVgIshYLDKYhJ","elements":["template-bundle"],"profiles":[{"profile":"template-map-a","file":"template-map-a.vue","description":"Minimal example profile A — a colored placeholder, no Mapbox or data.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-a/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"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 src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-7oKMpdhxEwHZk1lLpNS5+8FNfK7CEv2BAlgmw65N5F6CkNhduVcSVgIshYLDKYhJ\" crossorigin=\"anonymous\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-a\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"},{"profile":"template-map-b","file":"template-map-b.vue","description":"Minimal example profile B — a second profile in the same bundle.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-b/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"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 src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-7oKMpdhxEwHZk1lLpNS5+8FNfK7CEv2BAlgmw65N5F6CkNhduVcSVgIshYLDKYhJ\" crossorigin=\"anonymous\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-b\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"}]}]};</script>
<script>/**
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
*

View file

@ -23,6 +23,7 @@
'upg-100-list':'./doxa-maps/my-upg-100-list/my-upg-100-list.js',
'countries-map':'./doxa-maps/country-posters/country-posters.js',
'key-city-posters':'./doxa-maps/key-cities/key-cities.js',
'all-people-groups':'./doxa-maps/all-people-groups/all-people-groups.js',
'cities-with-internet':'./doxa-maps/cities-internet/cities-internet.js',
'gospel-resources':'./doxa-maps/gospel-resources/gospel-resources.js',
'research-map':'./doxa-maps/doxa-research-map/doxa-research-map.js',
@ -877,6 +878,15 @@ const PROFILES = {
// The nav has carried this id as a placeholder since 2026-07-29; this entry is
// what turns it into a real app. `profile` is the .vue FILENAME, not the folder.
// Ships WITH `panes` (map/list/operators below) — the pane grid is live.
// ALL PEOPLE GROUPS — every group on earth (peoplegroups.org) around the DOXA
// set: black doxa · red frontier · yellow unreached · green reached. Replaces
// the long-standing placeholder.
'all-people-groups': {
tag: 'all-people-groups',
src: './doxa-maps/all-people-groups/all-people-groups.js',
config: { profile:'all-people-groups', instanceId:'wrapper-all-people-groups' },
},
'cities-with-internet': {
tag: 'cities-internet-map',
src: './doxa-maps/cities-internet/cities-internet.js',

View file

@ -54,7 +54,7 @@ export const NAV = [
// keep them straight. (The "15k" suffix was dropped from the label at the
// maintainer's request 2026-07-29; the distinction is recorded here instead.)
// ⚠ NO BUNDLE YET — renders the placeholder card.
{ kind: 'item', id: 'all-people-groups', label: 'All People Groups', badge: 'soon', icon: 'globe', target: 'both' },
{ kind: 'item', id: 'all-people-groups', label: 'All People Groups', icon: 'globe', target: 'both' },
// REMOVED 'Reached People Groups' (2026-07-29) — added and withdrawn the
// same round; do not re-add without an explicit ask.
// REMOVED 'Simple Map' (2026-07-29). REASON WORTH KEEPING: the