Merge main into go-int/work

# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/gospel-internet/board/board/index.html
#	public/dx-maps/gospel-internet/board/index.html
#	public/dx-maps/gospel-internet/board/india-china/index.html
#	public/dx-maps/gospel-internet/board/language-tree/index.html
#	public/dx-maps/gospel-internet/board/platforms/index.html
#	public/dx-maps/gospel-internet/board/project-700/index.html
#	public/dx-maps/gospel-internet/board/zero/index.html
#	public/dx-maps/gospel-internet/gospel-internet.js
#	public/dx-maps/manifest.json
This commit is contained in:
Doxa Maps 2026-09-28 02:15:00 +00:00
commit 879bb3a8fe
65 changed files with 2333 additions and 526 deletions

View file

@ -0,0 +1,61 @@
BOUNDARY DATA — SOURCES AND ATTRIBUTION
=======================================
The .geojson files in this folder are third-party boundary data. Every value below is taken
VERBATIM from the upstream metadata (the geoBoundaries API, https://www.geoboundaries.org/api/current/gbOpen/<ISO3>/<LEVEL>/).
Nothing here is our own wording about a licence.
gb-IND-ADM1-full.geojson, gb-IND-ADM1-full-web.geojson
source geoBoundaries gbOpen (www.geoboundaries.org)
boundaryID IND-ADM1-1811400
licence Creative Commons Attribution 2.5 India (CC BY 2.5 IN)
licenceSource github.com/datameet/maps
year 2011
ATTRIBUTION IS REQUIRED for this file: it is Creative Commons Attribution, so a work that
redistributes or displays it must credit the source above.
gb-IND-ADM1.geojson, gb-IND-ADM1-web.geojson
The same India ADM1 boundary as above, curated to the UPG-100 list's partition.
Same source, same boundaryID, same licence, same attribution duty.
gb-CHN-ADM1.geojson
source geoBoundaries gbOpen (www.geoboundaries.org)
boundaryID CHN-ADM1-43563684
licence Public Domain
licenceSource commons.wikimedia.org/wiki/File
year 2019
No attribution duty: upstream records this boundary as Public Domain.
world-adm0.geojson, world-adm0-web.geojson
source geoBoundaries gbOpen (www.geoboundaries.org), assembled per country
features 162 countries
licence MIXED — and now ESTABLISHED per country, not merely acknowledged.
THE PER-COUNTRY TABLE IS THE AUTHORITY, and it lives in ONE place on purpose:
plugin-designs/my-upg-100-list/data/adm0/PROVENANCE.json
It records boundaryLicense, licenseSource and boundaryID for all 162, each value taken
verbatim from https://www.geoboundaries.org/api/current/gbOpen/<ISO3>/<LEVEL>/ (162 queries,
0 failures, one spot-check re-verified against the live API). This file deliberately does NOT
copy that table: one table in two places drifts.
130 OF THE 162 CARRY A DUTY. Only 32 are duty-free (28 Public Domain, 4 CC0). By class:
ATTRIBUTION-ONLY (credit the source)
44 CC BY 4.0 13 CC BY 3.0 IGO 4 CC BY 2.5 2 CC BY 3.0
2 Open Government Licence v3
SHARE-ALIKE CLASS — A STRONGER DUTY: a derivative may have to be licensed alike, which is
the class that actually constrains giving this repository away.
58 Open Data Commons ODbL 1.0
7 Creative Commons ShareAlike 2.0 / 3.0 — AGO GAB GEO GIN MMR NER TUR
ODbL IS LISTED IN THAT CLASS DELIBERATELY. Alongside attribution it requires a Derivative
Database to be offered under ODbL and carries a keep-open provision, so by volume the
share-alike question covers 65 of 162 countries, not 7. This repository has already acted on
that reading once: library/scripts/posters/data/README.md records that Natural Earth's 1:10m
ROADS were requested and deliberately NOT shipped because of an ODbL obligation.
WHETHER 'licensed alike' binds a giveaway of this repository is a legal question, not a
measurement, and it is open. It is recorded here so nobody has to rediscover it.
duty-free: 28 Public Domain, 4 CC0.
See documentation/THIRD-PARTY.md for the whole shipped tree.

View file

@ -162,13 +162,18 @@ The only other thing here is `DX_PB_PORT`, which you set only if the log says po
## One more thing to set
The maps need a Mapbox token. It is the one credential you have to ask the team for:
Copy `.env.example` to `.env.local`. **Every line in it is optional** — empty means the default, which is
the all-local setup described above. You can start with none of them filled in.
The one you will probably want is a Mapbox token, which is the single credential you have to ask the team
for:
```
VITE_MAPBOX_TOKEN=<ask the team>
```
That, plus `DX_SYNC_REMOTE=origin` if you want it, is the whole of `.env.local`.
Leave it empty and the Mapbox-backed maps will not draw; you can also work with the keyless MapLibre
provider instead.
---

View file

@ -12,10 +12,11 @@ served folder". The licence travels with the asset; the explanation travels with
| Fonts: bebas-neue, figtree, noto-serif-devanagari, oswald, poppins | Google Fonts / foundry repos | OFL-1.1 | **Yes** — each `LICENSE` is the family's own upstream OFL, verbatim, with its real copyright line | none (closed 2026-09-28) |
| `vendor/vue/vue.global.prod.js` | vuejs/core | MIT | **Partly** — the minified banner carries `(c) 2018-present Yuxi (Evan) You` and `@license MIT`, but NOT the permission notice MIT asks to include | closed: upstream `LICENSE` now vendored beside it |
| `vendor/tabulator/tabulator.min.js` + `.min.css` | olifolkerd/tabulator v6.3.1 | MIT | **No** — the `.js` had only `(c) Oliver Folkerd 2025`; the `.css` had nothing | closed: upstream `LICENSE` now vendored beside it |
| `boundaries/gb-*.geojson` (4 files) | geoBoundaries gbOpen (www.geoboundaries.org) | CC BY — the India ADM1 file records **CC BY 2.5 IN**, sourced from DataMeet/Election Commission of India | **Partly** — provenance sits in ONE sidecar, `gb-IND-ADM1-full.meta.json` (source, boundaryID, licence, fetch URL, processing). The other boundary files have none, and no human-readable attribution ships | **OPEN** — CC BY requires attribution to accompany the work |
| `boundaries/world-adm0*.geojson` | not verified — probably Natural Earth | Natural Earth is public domain | n/a if Natural Earth | **OPEN** — provenance unconfirmed for this file |
| `boundaries/gb-IND-ADM1*.geojson` (4 files) | geoBoundaries gbOpen, boundaryID `IND-ADM1-1811400` | **CC BY 2.5 IN**, licenceSource `github.com/datameet/maps`, year 2011 — fetched verbatim from the geoBoundaries API | **Yes** — `boundaries/ATTRIBUTION` now ships beside the data, plus the existing `.meta.json` sidecar | none (closed 2026-09-28) |
| `boundaries/gb-CHN-ADM1.geojson` | geoBoundaries gbOpen, boundaryID `CHN-ADM1-43563684` | **Public Domain**, licenceSource `commons.wikimedia.org`, year 2019 | recorded in `boundaries/ATTRIBUTION` | none — upstream records no attribution duty |
| `boundaries/world-adm0*.geojson` (2 files, 162 countries) | **geoBoundaries gbOpen, assembled per country** — corrected: its features carry geoBoundaries' own `iso3`/`shapeGroup`/`shapeName` properties, and the plugin's `data/adm0/PROVENANCE.json` names the source and endpoint. NOT Natural Earth, as this register first guessed | **MIXED — up to 162 different upstream licences.** gbOpen passes each country's own licence through | the principle is stated in `boundaries/ATTRIBUTION`; the per-country licences are **not** | **OPEN** — that PROVENANCE.json's `licences` table is EMPTY, so which of the 162 are CC BY is unestablished. Closing it means one API query per country. upg100-plugin's data; routed to them |
| `people-groups/*` (3 files, 8 MB) | peoplegroups.org | **not established** | no terms ship | **OPEN, judgment** — redistribution terms unknown |
| `pplr/*` (6 files, 16 MB) | not resolved from `plugin-designs/pplr-data-maps/scripts/build-data.mjs` | **not established** | nothing ships | **OPEN, judgment** — owner should name the upstream |
| `pplr/*` (6 files, 16 MB) | **PPLR ArcGIS Online org** `services2.arcgis.com/S4ydGgujXcif36k3`, aggregating IMB (peoplegroups.org), Joshua Project, the ROP registry, and carrying WCD (World Christian Database) IDs. Built from a local 78 MB extract not present in this repo — evidence from exp-shepherd | **UNRESOLVED.** `aCountry` and `aIMBPeople` publish no `copyrightText`, description or licence (queried `f=json`) | nothing ships | **OPEN, judgment** — redistribution not established. WCD is a proprietary Brill database, so even the IDs need a check |
| `tables/*` (304 files, 36 MB) | derived by `library/scripts/research/fetch-*.mjs` from several upstream APIs | **varies, not established per table** | nothing ships; there is no source or licence doc in `tables/` | **OPEN, judgment** — the largest gap by volume |
| `images/worldmap.svg` | recorded in `images/PROVENANCE` | see that file | the PROVENANCE file ships and explains why no licence file sits beside it | none claimed; reasoning recorded rather than assumed |
| `previews/*` (378 files) | our own poster previews | ours | n/a | none |
@ -25,6 +26,7 @@ served folder". The licence travels with the asset; the explanation travels with
| Vendored file | Upstream URL | sha256 |
|---|---|---|
| `data/boundaries/ATTRIBUTION` | geoBoundaries API, per boundary (values verbatim) | see the file |
| `data/vendor/tabulator/LICENSE` | `https://raw.githubusercontent.com/olifolkerd/tabulator/master/LICENSE` | `191a2ee554684e1064c897b432f0e1bc6dfa714ca045d3f6ea2cf692cbd398b7` |
| `data/vendor/vue/LICENSE` | `https://raw.githubusercontent.com/vuejs/core/main/LICENSE` | `49190d1f742dd61e47fe816df4963e5186a2a62100d9122f9883a60ea32973ac` |
| `data/fonts/bebas-neue/LICENSE` | `https://raw.githubusercontent.com/google/fonts/main/ofl/bebasneue/OFL.txt` | see `documentation/vendored-fonts.md` |

View file

@ -1551,6 +1551,10 @@ function main() {
// COMPLETE India ADM1 (36 units, geoBoundaries gbOpen, CC BY 2.5 IN; source + licence in its meta),
// for maps that colour every state. The curated file above is UPG-100's 34-unit partition.
[resolve(__dirname, '..', 'data', 'boundaries', 'gb-IND-ADM1-full.geojson'), 'gb-IND-ADM1-full.geojson'],
// ★ AND THE ATTRIBUTION, because this pair list is what decides whether it TRAVELS. The India
// ADM1 boundary is Creative Commons Attribution, so the credit has to reach the recipient with
// the data; a file left in data/boundaries would never ship. Same trap as vendor/*/LICENSE.
[resolve(__dirname, '..', 'data', 'boundaries', 'ATTRIBUTION'), 'ATTRIBUTION'],
[resolve(__dirname, '..', 'data', 'boundaries', 'gb-IND-ADM1-full.meta.json'), 'gb-IND-ADM1-full.meta.json'],
// WEB copies (library/scripts/boundaries/build-web-boundaries.mjs): every feature and island kept, about
// a fifth of the bytes. The interactive maps fetch these; posters, print and build-time point-in-polygon
@ -1618,6 +1622,11 @@ function main() {
'boundaries/gb-CHN-ADM1.geojson',
'boundaries/gb-IND-ADM1-full.geojson', // all 36 states (Cities with Internet's NFHS layer)
'boundaries/gb-IND-ADM1-full.meta.json', // its source and licence, served beside it
// ★ SECOND STAGE. The pair list above only gathers into the build POOL; THIS list decides what
// reaches the tree. I added ATTRIBUTION to the pair list, ran the build, and it still did not ship
// — a two-stage pipeline needs the entry in both. The India ADM1 boundary is Creative Commons
// Attribution, so the credit must travel with the data.
'boundaries/ATTRIBUTION',
'boundaries/world-adm0-web.geojson', // the web copies the interactive maps fetch
'boundaries/gb-IND-ADM1-web.geojson',
'boundaries/gb-IND-ADM1-full-web.geojson',

View file

@ -0,0 +1,80 @@
#!/usr/bin/env node
/**
* generate-wagf-region-names.mjs — the WAGF region names, in the reader's language, from vendored CLDR.
*
* ★ THE DEFECT (rega KA3, 3d64bcad, ?lang=ar). Big Picture's region chart labels its bars with the
* prayer API's ENGLISH region names — "Africa", "Asia", "Latin America & Caribbean" — on an Arabic page.
* They are not chrome and no catalogue held them: they are DATA labels, so every i18n gate was green
* while the chart read English to every reader. The regions are a closed set of seven
* (library/colors/wagf-region.js WAGF_REGIONS), which is what makes cataloguing them possible at all.
*
* ★ WHY GENERATED AND NOT HAND-AUTHORED. Ten locales x seven names is seventy strings I cannot write.
* Five of the seven ARE standard UN M49 macro-regions, so CLDR already names them in every locale we
* ship, and Node's own ICU is the same data library/utils/countryName.js already trusts for countries.
* Generating is what keeps a translation out of my hands: the strings are copied from CLDR, committed,
* and reviewable, and re-running this reproduces them byte for byte.
*
* africa -> 002 · asia -> 142 · europe -> 150 · latin_america_&_caribbean -> 419 · oceania -> 009
*
* ★ TWO REGIONS ARE DELIBERATELY NOT WRITTEN, because CLDR has no code that means them:
* · middle_east — M49 145 is "Western Asia", a different set (it includes the Caucasus and excludes
* Egypt). Naming it "Western Asia" in ten locales would silently redraw the region.
* · north_america_&_non-spanish_caribbean — M49 021 is "Northern America", which excludes the
* Caribbean this bucket exists to include.
* Both stay English until the Driver rules on a translation source. An English label a reader can
* still match to the map beats a localized label that names the wrong territory.
*
* ★ AND ONE SCOPE NOTE ON 419: CLDR's long name is "Latin America" while our label says
* "Latin America & Caribbean". The ENGLISH entry keeps our wording (it is the authored, authoritative
* one); the other ten take CLDR's, which omits the Caribbean from the NAME while the bucket still holds
* it. Recorded here rather than silently accepted — the alternative was English in ten locales.
*
* node internal/generate-wagf-region-names.mjs # write
* node internal/generate-wagf-region-names.mjs --check # verify the catalogue matches CLDR (CI)
*/
import { readFileSync, writeFileSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const CHECK = process.argv.includes('--check')
const M49 = { africa: '002', asia: '142', europe: '150', 'latin_america_&_caribbean': '419', oceania: '009' }
const HELD = ['middle_east', 'north_america_&_non-spanish_caribbean']
const LOCALES = ['ar', 'de', 'en', 'es', 'fr', 'hi', 'it', 'pt', 'ro', 'ru', 'zh']
// The English entries stay as the product authored them (see the 419 note above); every other locale
// is CLDR's. Read from the one file that already owns the region set, so a seventh region added there
// shows up here as a missing name rather than as a silent English label.
const { WAGF_REGIONS } = await import(join(ROOT, 'library/colors/wagf-region.js'))
const names = (lc) => {
const dn = new Intl.DisplayNames([lc], { type: 'region' })
const out = {}
for (const [slug, code] of Object.entries(M49)) out[slug] = lc === 'en' ? WAGF_REGIONS[slug] : dn.of(code)
return out
}
let wrote = 0
const drift = []
for (const lc of LOCALES) {
const p = join(ROOT, 'library/i18n/locales', lc, 'dashboard.json')
const cat = JSON.parse(readFileSync(p, 'utf8'))
const want = names(lc)
if (CHECK) {
const have = cat.wagfRegion || {}
for (const [k, v] of Object.entries(want)) if (have[k] !== v) drift.push(`${lc}.wagfRegion.${k}: catalogue ${JSON.stringify(have[k])} vs CLDR ${JSON.stringify(v)}`)
continue
}
cat.wagfRegion = want
writeFileSync(p, JSON.stringify(cat, null, 2) + '\n')
wrote++
}
const unaccounted = Object.keys(WAGF_REGIONS).filter((s) => !M49[s] && !HELD.includes(s))
if (unaccounted.length) {
console.error(`WAGF-REGION-NAMES: FAIL — ${unaccounted.length} region(s) in WAGF_REGIONS have neither a CLDR code nor a recorded reason: ${unaccounted.join(', ')}`)
process.exit(1)
}
if (CHECK) {
if (drift.length) { console.error(`WAGF-REGION-NAMES: FAIL — ${drift.length} catalogue entry(ies) no longer match CLDR:\n ${drift.join('\n ')}`); process.exit(1) }
console.log(`WAGF-REGION-NAMES: PASS — ${Object.keys(M49).length} name(s) x ${LOCALES.length} locale(s) match vendored CLDR; ${HELD.length} held in English on purpose (${HELD.join(', ')}).`)
} else {
console.log(`WAGF-REGION-NAMES: wrote ${Object.keys(M49).length} name(s) into ${wrote} locale catalogue(s); ${HELD.length} held in English (${HELD.join(', ')}).`)
}

View file

@ -45,3 +45,43 @@ export function openPrintFrame(bodyHtml, { autoprint = false } = {}) {
return frame.contentWindow
}
/**
* Print a rendered poster IMAGE, at its real paper size, through the same in-place frame.
*
* ★ THE DRIVER'S RULE (2026-09-28): an "Export poster" button opens the PRINT DIALOG with a preview.
* It never hands over a file. Simple Research was rendering a PNG and calling a download helper, so
* the reader got doxa-prayer-poster.png in their downloads folder and no preview, no paper size, and
* no way to print it without opening it somewhere else first. The dialog IS the preview: the sheet the
* browser shows is the poster, at the size the spec asked for.
*
* @page carries the spec's own inches so the preview is not letter-shaped by default — a 8.5x11
* poster previewed on A4 is a different document from the one that was rendered. margin:0 because the
* renderer already composed the margins into the image; a second set would scale the poster down.
*
* The object URL is revoked on afterprint AND on the frame's 60s reclaim, since afterprint does not
* fire in every engine when the dialog is dismissed. Revoking early would blank the preview, so the
* image element is kept alive by the frame itself rather than by a timer here.
*
* Returns true when the dialog was opened, false when the host blocked the frame — the caller decides
* what to do about it, because a silent no-op is exactly the failure this replaced.
*/
export function printImageBlob(blob, { widthIn = 8.5, heightIn = 11, title = '', alt = '' } = {}) {
if (!blob || typeof URL === 'undefined' || !URL.createObjectURL) return false
const url = URL.createObjectURL(blob)
const esc = (v) => String(v).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]))
// The image is sized by the PAGE, not by its pixels: object-fit keeps a poster rendered at any DPI
// on its own sheet without cropping. The <img> also gates the print — openPrintFrame's bootstrap
// waits for load, which is what keeps the first page from printing blank.
const html = `<!doctype html><html><head><meta charset="utf-8"><title>${esc(title)}</title>`
+ `<style>@page{size:${widthIn}in ${heightIn}in;margin:0}`
+ 'html,body{margin:0;padding:0;background:#fff}'
+ 'img{display:block;width:100%;height:100%;object-fit:contain}</style></head>'
+ `<body><img src="${url}" alt="${esc(alt || title)}"></body></html>`
const win = openPrintFrame(html, { autoprint: true })
if (!win) { URL.revokeObjectURL(url); return false }
const revoke = () => { try { URL.revokeObjectURL(url) } catch { /* already gone */ } }
try { win.addEventListener('afterprint', () => setTimeout(revoke, 0)) } catch { /* cross-doc guard */ }
setTimeout(revoke, 60000)
return true
}

View file

@ -482,7 +482,6 @@ export function useMapClustering(options = {}) {
}
_attachClusterClickHandler(map);
console.log(`[${mapId}] Clustering: mapbox-native applied (${features.length} features)`);
}
/**
@ -566,10 +565,6 @@ export function useMapClustering(options = {}) {
_setLayerVisibility(map, CLUSTER_LINES_LAYER, true);
}
console.log(
`[${mapId}] Clustering: ${algorithm} applied — ` +
`${lines.length} lines across ${stats?.totalGroups ?? 0} groups`
);
}
// ─────────────────────────────────────────────────────────────────────────

View file

@ -78,5 +78,12 @@
"selected_first": "المحدَّدة أولاً",
"selected_first_hint": "رفع الشعوب المحدَّدة إلى الأعلى مع الإبقاء على ترتيب الأعمدة",
"n_shown": "{n} معروض",
"other": "أخرى"
"other": "أخرى",
"wagfRegion": {
"africa": "أفريقيا",
"asia": "آسيا",
"europe": "أوروبا",
"latin_america_&_caribbean": "أمريكا اللاتينية",
"oceania": "أوقيانوسيا"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "Ausgewählte zuerst",
"selected_first_hint": "Die ausgewählten Volksgruppen nach oben holen, die Spaltensortierung bleibt erhalten",
"n_shown": "{n} angezeigt",
"other": "Andere"
"other": "Andere",
"wagfRegion": {
"africa": "Afrika",
"asia": "Asien",
"europe": "Europa",
"latin_america_&_caribbean": "Lateinamerika",
"oceania": "Ozeanien"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "Selected first",
"selected_first_hint": "Float the selected people groups to the top, keeping your column sort",
"n_shown": "{n} shown",
"other": "Other"
"other": "Other",
"wagfRegion": {
"africa": "Africa",
"asia": "Asia",
"europe": "Europe",
"latin_america_&_caribbean": "Latin America & Caribbean",
"oceania": "Oceania"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "Los seleccionados primero",
"selected_first_hint": "Llevar arriba los grupos de personas seleccionados, manteniendo el orden de las columnas",
"n_shown": "{n} mostrados",
"other": "Otros"
"other": "Otros",
"wagfRegion": {
"africa": "África",
"asia": "Asia",
"europe": "Europa",
"latin_america_&_caribbean": "Latinoamérica",
"oceania": "Oceanía"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "La sélection en premier",
"selected_first_hint": "Faire remonter les groupes de personnes sélectionnés, en conservant le tri des colonnes",
"n_shown": "{n} affichés",
"other": "Autres"
"other": "Autres",
"wagfRegion": {
"africa": "Afrique",
"asia": "Asie",
"europe": "Europe",
"latin_america_&_caribbean": "Amérique latine",
"oceania": "Océanie"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "चयनित पहले",
"selected_first_hint": "चयनित जनसमूहों को ऊपर लाएँ, आपका कॉलम क्रम बनाए रखते हुए",
"n_shown": "{n} दिखाए गए",
"other": "अन्य"
"other": "अन्य",
"wagfRegion": {
"africa": "अफ़्रीका",
"asia": "एशिया",
"europe": "यूरोप",
"latin_america_&_caribbean": "लैटिन अमेरिका",
"oceania": "ओशिआनिया"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "Prima i selezionati",
"selected_first_hint": "Porta in alto i gruppi di persone selezionati, mantenendo l’ordinamento delle colonne",
"n_shown": "{n} mostrati",
"other": "Altri"
"other": "Altri",
"wagfRegion": {
"africa": "Africa",
"asia": "Asia",
"europe": "Europa",
"latin_america_&_caribbean": "America Latina",
"oceania": "Oceania"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "Os selecionados primeiro",
"selected_first_hint": "Trazer para cima os grupos de pessoas selecionados, mantendo a ordenação das colunas",
"n_shown": "{n} apresentados",
"other": "Outros"
"other": "Outros",
"wagfRegion": {
"africa": "África",
"asia": "Ásia",
"europe": "Europa",
"latin_america_&_caribbean": "América Latina",
"oceania": "Oceania"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "Cele selectate primele",
"selected_first_hint": "Ridică grupurile de oameni selectate în partea de sus, păstrând sortarea coloanelor",
"n_shown": "{n} afișate",
"other": "Altele"
"other": "Altele",
"wagfRegion": {
"africa": "Africa",
"asia": "Asia",
"europe": "Europa",
"latin_america_&_caribbean": "America Latină",
"oceania": "Oceania"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "Выбранные сначала",
"selected_first_hint": "Поднять выбранные народы наверх, сохранив сортировку столбцов",
"n_shown": "{n} показано",
"other": "Другое"
"other": "Другое",
"wagfRegion": {
"africa": "Африка",
"asia": "Азия",
"europe": "Европа",
"latin_america_&_caribbean": "Латинская Америка",
"oceania": "Океания"
}
}

View file

@ -78,5 +78,12 @@
"selected_first": "所选优先",
"selected_first_hint": "将所选民族置顶,同时保留您的列排序",
"n_shown": "显示 {n} 个",
"other": "其他"
"other": "其他",
"wagfRegion": {
"africa": "非洲",
"asia": "亚洲",
"europe": "欧洲",
"latin_america_&_caribbean": "拉丁美洲",
"oceania": "大洋洲"
}
}

View file

@ -42,6 +42,7 @@ import countries from '../data/countries.json'
const ALPHA2 = table.map
const ENGLISH = new Map(countries.map((c) => [c.value, c.label]))
const BY_ENGLISH = new Map(countries.map((c) => [String(c.label).trim().toLowerCase(), c.value]))
/** The alpha-2 for an alpha-3 code, or undefined. Exported because callers building URLs and flag
* assets need the same lookup, and a second copy of it would drift. */
@ -49,6 +50,24 @@ export function alpha2Of(iso3) {
return iso3 ? ALPHA2[String(iso3).toUpperCase()] : undefined
}
/**
* The alpha-3 for a canonical ENGLISH country name, or undefined.
*
* ★ FOR LABELS THAT ARRIVE IN ENGLISH WITH NO CODE ATTACHED — the case the header's ✓ names. Big
* Picture's top-countries chart aggregates on the prayer API's English country LABEL, so by the time a
* bar is drawn the alpha-3 is gone and only "India" is in hand. Reversing our own countries.json is
* exact where it matches and silent where it does not, which is the only safe shape here: an unmatched
* name keeps rendering as itself rather than becoming a code or a blank.
*
* Built from the SAME table englishName() reads, so the two can never disagree about which name is
* canonical. Names are compared case-insensitively and trimmed; nothing else is normalized, because a
* looser match is how "Congo" would quietly become the wrong country.
*/
export function iso3ForEnglishName(name) {
if (!name) return undefined
return BY_ENGLISH.get(String(name).trim().toLowerCase())
}
/** The canonical English name, or undefined. */
export function englishName(iso3) {
return iso3 ? ENGLISH.get(String(iso3).toUpperCase()) : undefined

View file

@ -65,8 +65,8 @@
"check:embedder-keys": "node plugin-designs/regions-dashboard/scripts/check-embedder-keys.mjs",
"check:english-leaks": "node internal/check-gospel-english-leaks.mjs",
"check:experimental-shelf": "node internal/check-experimental-shelf.mjs",
"check:export-one-click": "node plugin-designs/doxa-research-map/scripts/check-export-one-click.mjs",
"check:export-one-click:maplibre": "node plugin-designs/doxa-research-map/scripts/check-export-one-click.mjs --provider maplibre",
"check:export-poster-prints": "node plugin-designs/doxa-research-map/scripts/check-export-poster-prints.mjs",
"check:export-poster-prints:maplibre": "node plugin-designs/doxa-research-map/scripts/check-export-poster-prints.mjs --provider maplibre",
"check:facet-cascade": "node internal/check-facet-cascade.mjs",
"check:facet-cascade:offline": "node internal/check-facet-cascade.mjs --offline",
"check:facet-cascade:self-test": "node internal/check-facet-cascade.mjs --self-test",
@ -332,8 +332,8 @@
"check:embedder-keys": "regions-mfe forwards page-scoped config to its nested panes by an explicit key list (INHERITED_PAGE_KEYS). Asserts the documented embedder keys travel (dataSource, tokenUrl among them) AND that profile and instanceId do NOT: the host's would overwrite each pane's own and collapse both tabs into one view.",
"check:english-leaks": "A translated key must not render in English. rega's method: a string is a LEAK when the catalogue has a DIFFERENT value for the locale and the page still shows the English one - a render/wiring fault, not a missing translation. Third-party attributions, data names, placeholder-only strings and the prose this bundle deliberately keeps in English are all excluded, the last by simply not being in the catalogue. Scoped to the LEAF on purpose: six keys were reported leaking in the gallery and the leaf is clean in ar/zh/ru/hi - the gallery builds its iframe src with no lang parameter, so the embed never learns the locale. Embedder defect, not a bundle one.",
"check:experimental-shelf": "Every Experimental-folder bundle, leaf + Gallery tab, mapbox + maplibre: mounts with content, 0 errors / failed requests, maps load and DRAW their own data. Self-tests on a deliberately broken bundle first. Run through internal/browser-slot.sh.",
"check:export-one-click": "the Simple Research 'Export poster' button gives a FILE on ONE click (no menu, no dialog, no second step): leaf AND Research Gallery, en AND ar; the file is a real print PNG (US Letter @200 DPI); 0 page errors. Run inside the shared browser slot. Exit 2 = SKIP, 75 = NOT MEASURED.",
"check:export-one-click:maplibre": "the same four one-click exports on the keyless MapLibre engine (OpenFreeMap tiles): the hidden print map must be built with the engine that drew the screen, and the footer must credit the tiles actually printed.",
"check:export-poster-prints": "the Simple Research 'Export poster' button opens the PRINT DIALOG on the poster in ONE click and downloads NOTHING (the Driver's rule, 2026-09-28): leaf AND Research Gallery, en AND ar; the printing document is the 1700x2200 poster on an 8.5in x 11in sheet; no download arrives; 0 page errors. window.print is stubbed, so this measures everything up to the dialog. Replaces check:export-one-click, which asserted the download this rule forbids. Run inside the shared browser slot. Exit 2 = SKIP, 75 = NOT MEASURED.",
"check:export-poster-prints:maplibre": "the same four one-click prints on the keyless MapLibre engine (OpenFreeMap tiles): the hidden print map must be built with the engine that drew the screen, and the footer must credit the tiles actually printed.",
"check:facet-cascade": "a child facet selection must not outlive its parent: clearing a parent facet must cascade-clear its descendants on EVERY clear path. Asserts on RESULT COUNTS, not DOM — an orphaned child silently ANDs the map to empty. run check:facet-cascade:self-test for the NEGATIVE test (reverts the cascade and requires this gate to FAIL).",
"check:facet-changes-map": "a facet selection must CHANGE THE DOTS — and the dots must SAY what they mean. Asserts (1) the rd-bubbles feature set changes by count AND identity, (2) every surviving dot satisfies the selected level, (3) while a prayer filter is active circle-color is a DATA-DRIVEN EXPRESSION reading peoplePraying, not a flat literal. Assertion 3 is the one that matters: the prayer filter ALREADY narrowed 2107->689 correctly while the Driver reported it broken, because every dot painted the same orange. Browser gate (needs `bun run dev` on 5173, or BASE_URL/PROBE_PATH at a served tree), so NOT in build:unlocked. run the `check:facet-changes-map:self-test` script for the NEGATIVE test.",
"check:facet-changes-map:built": "the SAME gate against the BUILT tree instead of the dev server. Measured 2026-09-24: the dev default is 1 PASS / 3 FAIL in four runs on a busy box (88 unbundled module requests vs the built leaf's 3), while the built tree passes deterministically. Use this when you need a trustworthy answer; use the dev default when checking uncommitted source.",

View file

@ -1299,16 +1299,17 @@ watch(() => mapStore.selectedRegion, (key) => {
// required getMap, so it mounted invisible; and the old loader called useMapPoster({ sourceMap, … })
// with an OBJECT where the shared composable takes a FUNCTION, so a render would have thrown anyway.
// Now the button does the export itself, through the SHARED poster path (useMapPoster → render →
// download), with a sensible print default: the current view on US Letter portrait at 200 DPI, the
// legend title and rows, and a source/date footer. No dialog, no second step.
// print), with a sensible print default: the current view on US Letter portrait at 200 DPI, the
// legend title and rows, and a source/date footer. One click, one dialog, the poster in the preview.
const posterBusy = ref(false)
async function openPoster() {
if (posterBusy.value) return
posterBusy.value = true
try {
const [{ useMapPoster }, { defaultPosterSpec }] = await Promise.all([
const [{ useMapPoster }, { defaultPosterSpec }, { printImageBlob }] = await Promise.all([
import('@map/composables/poster/useMapPoster.js'),
import('@map/constants/posterDefaults.js'),
import('@map/composables/printInPlace.js'),
])
const exporter = useMapPoster(() => map.value)
const spec = defaultPosterSpec({ widthIn: 8.5, heightIn: 11, dpi: 200, orientation: 'portrait' })
@ -1333,19 +1334,31 @@ async function openPoster() {
spec.slots.footer = { ...spec.slots.footer, left: 'pray.doxa.life', right: new Date().toISOString().slice(0, 10),
attribution: onMaplibre ? '© OpenFreeMap © OpenMapTiles © OpenStreetMap' : '© Mapbox © OpenStreetMap' }
const blob = await exporter.render(spec, 'png')
// File name from the view's id, not its title: an Arabic or Chinese title has no ASCII letters
// to keep, and every reader's file would otherwise be the same "doxa-map-poster.png".
const slug = String(activeTab.value?.id || '').replace(/[^a-z0-9]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase()
_downloadBlob(blob, 'doxa-' + (slug || 'map') + '-poster.png')
// ★ THE DIALOG, NOT A DOWNLOAD (the Driver, 2026-09-28: an export-poster button must open the
// print dialog with a poster preview, never hand over a file). This used to call _downloadBlob,
// so the reader got a PNG in their downloads folder with no preview, no paper size and no way to
// print it without opening it somewhere else first. printImageBlob puts the rendered poster on a
// sheet of exactly the spec's size and raises the dialog over the map — the preview IS the poster.
// The same in-place frame the Driver's earlier ruling settled on: no pop-up, no blocker, and it
// works when an agent triggers it without a user gesture.
const printed = printImageBlob(blob, {
widthIn: spec.widthIn ?? 8.5,
heightIn: spec.heightIn ?? 11,
title,
alt: title,
})
// An unopenable frame (sandboxed or detached host) is reported, not silently swallowed: a button
// that looks like it worked and did nothing is the defect this whole path has already had once.
if (!printed) console.error('[research-map] poster print: the host blocked the print frame')
} catch (err) {
console.error('[research-map] poster export failed:', err)
} finally {
posterBusy.value = false
}
}
// The toolbar's export label: the drawer's translated 'Export poster…' WITHOUT the ellipsis — an
// ellipsis promises a dialog, and this button hands over the file directly.
const exportPosterLabel = computed(() => String(t('controls.export_poster') || 'Export poster').replace(/\s*…\s*$/, ''))
// The toolbar's export label, WITH its ellipsis. The ellipsis was stripped while this button handed
// over a file directly; it now opens the print dialog, which is exactly what an ellipsis promises.
const exportPosterLabel = computed(() => String(t('controls.export_poster') || 'Export poster…'))
// ─── Export menu router (SemanticTreeLegend @export) ─────────────────────────
// EXPERIMENTAL — dormant behind the shared kill-switch (SHOW_EXPORT_BUTTON).

View file

@ -1,118 +0,0 @@
#!/usr/bin/env node
/**
* check-export-one-click.mjs — the Simple Research "Export poster" button gives you a FILE on ONE
* click, with no menu, dialog or second step, in its own leaf AND inside the Research Gallery, in
* en AND ar.
*
* WHY (card BUILD-SIMPLE-RESEARCH-EXPORT-ZERO-CLICK; the Driver: "there's an export button on the
* simple dashboard ... get it working"). Measured 2026-09-25 before the fix: one click produced
* NOTHING — no dialog, no file within 60s, no error — in both hosts. A silent dead button reads as
* "the export is slow" to a reader, never as "it is broken", so no other gate could see it.
*
* WHAT IT ASSERTS, per host × language:
* 1. exactly ONE click on the ALWAYS-VISIBLE export button in the map toolbar (button.export-poster-btn).
* The drawer's "Export poster…" does not count: the drawer is CLOSED by default (translated off
* screen), so it costs an extra click to reach — measured 2026-09-25, and in Arabic the button
* could not be clicked at all. The toolbar button must be on screen with no preparation;
* 2. a download arrives within 120s, WITHOUT any further click;
* 3. the file is a real PNG (signature) of the print size (US Letter at 200 DPI = 1700x2200);
* 4. 0 page errors.
*
* Runs through internal/browser-slot.sh like every browser gate. Starts its own servers on
* OS-assigned ports and proves the server is THIS checkout's tree before measuring.
* --provider maplibre runs the same four on the keyless engine.
* EXPORT_SAVE_DIR=<dir> keeps each downloaded poster (<host>-<lang>-<file>) so a person can look at it.
* Exit: 0 pass · 1 fail · 2 SKIP (no built tree / no playwright) · 75 NOT MEASURED (server identity)
*/
import { createServer } from 'node:http'
import { spawn } from 'node:child_process'
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
// The tree's name comes from the build config, never a literal: the doxa-maps -> dx-maps rename
// turned the literal into a silent SKIP (exit 2) that measured nothing.
const { TREE_NAME } = await import(resolve(ROOT, 'internal/_tree-name.mjs'))
if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/research-map-plus-grid/research-map-plus-grid.js`))) {
console.log('SKIP check-export-one-click: no built research-map-plus-grid'); process.exit(2)
}
let chromium
try { ({ chromium } = await import('playwright')) } catch { console.log('SKIP check-export-one-click: playwright not installed'); process.exit(2) }
const freePort = () => new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) })
const PROVIDER = process.argv.includes('--provider') ? process.argv[process.argv.indexOf('--provider') + 1] : '' // maplibre = the keyless engine
const TP = await freePort(), HP = await freePort()
const TREE = `http://127.0.0.1:${TP}/${TREE_NAME}`
let lang = 'en'
const srv = spawn(process.execPath, ['internal/serve-strict.mjs', '--port', String(TP)], { cwd: ROOT, stdio: 'ignore' })
const host = createServer((_, r) => { r.writeHead(200, { 'content-type': 'text/html' }); r.end(`<!doctype html><html lang="${lang}"><body style="margin:0"><script src="${TREE}/doxa-maps-spa/doxa-maps-spa.js"></script><doxa-maps-spa tab="research-map-plus-grid"${PROVIDER ? ` provider="${PROVIDER}"` : ''} style="height:900px"></doxa-maps-spa></body></html>`) }).listen(HP, 'localhost')
const cleanup = () => { try { srv.kill() } catch {} try { host.close() } catch {} }
await new Promise(r => setTimeout(r, 800))
// Server identity by CONTENT: the served manifest must be this checkout's.
{
const disk = readFileSync(resolve(ROOT, `public/${TREE_NAME}/manifest.json`), 'utf8')
let served = ''
for (let i = 0; i < 20 && !served; i++) {
try { const r = await fetch(`${TREE}/manifest.json`); if (r.ok) served = await r.text() } catch { /* not up yet */ }
if (!served) await new Promise(r => setTimeout(r, 300))
}
if (served !== disk) { console.log(`NOT MEASURED: :${TP} is not serving this checkout's tree. Re-run.`); cleanup(); process.exit(75) }
}
const b = await chromium.launch({ args: ['--use-angle=swiftshader', '--enable-unsafe-swiftshader'] })
const fails = []
try {
for (lang of ['en', 'ar']) for (const where of ['leaf', 'gallery']) {
const url = where === 'leaf' ? `${TREE}/research-map-plus-grid/research-map-plus-grid/index.html?lang=${lang}${PROVIDER ? `&provider=${PROVIDER}` : ''}` : `http://localhost:${HP}/`
const ctx = await b.newContext({ acceptDownloads: true, viewport: { width: 1400, height: 900 } })
const p = await ctx.newPage(); const errs = []
p.on('pageerror', e => errs.push(String(e).slice(0, 160)))
// A failed export is CAUGHT and logged (console.error), never thrown: read the console, or a dead
// button reports "no file" with no reason.
const consoleErrs = []; p.on('console', m => { if (m.type() === 'error' || m.type() === 'warning') consoleErrs.push(m.text().slice(0, 200)) })
const downloads = []; p.on('download', d => downloads.push(d))
await p.goto(url); await p.waitForTimeout(12000)
const btn = p.locator('button.export-poster-btn').first()
const onScreen = await btn.evaluate(el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0 && r.left >= 0 && r.top >= 0 && r.right <= innerWidth && r.bottom <= innerHeight }).catch(() => false)
if (!onScreen) errs.push('the export button is not on screen without a prior click')
const label = ((await btn.getAttribute('title', { timeout: 20000 }).catch(() => '')) || '').trim()
let clicks = 0
// ONE PHYSICAL CLICK at the button's centre, as a reader makes it. Not locator.click(): that
// re-checks its hit target and RETRIES, and by the retry the button is already disabled (the
// export is running), so it reported "timeout" on exports whose file then arrived — measured
// 2026-09-25 in gallery-ar and leaf-en. What the click achieved is judged by the file below.
try {
const box = await btn.boundingBox({ timeout: 20000 })
if (!box) throw new Error('the export button has no box')
await p.mouse.click(box.x + box.width / 2, box.y + box.height / 2); clicks = 1
} catch (e) { errs.push('could not click: ' + String(e.message || e).split('\n')[0]) }
for (let i = 0; i < 120 && !downloads.length; i++) await p.waitForTimeout(1000)
let file = null, info = ''
if (downloads.length) {
file = downloads[0].suggestedFilename()
const path = await downloads[0].path()
const buf = readFileSync(path)
if (process.env.EXPORT_SAVE_DIR) writeFileSync(resolve(process.env.EXPORT_SAVE_DIR, `${where}-${lang}${PROVIDER ? '-' + PROVIDER : ''}-${file}`), buf) // to LOOK at the poster
const png = buf.length > 24 && buf.slice(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
const w = png ? buf.readUInt32BE(16) : 0, h = png ? buf.readUInt32BE(20) : 0
info = `${png ? 'PNG' : 'NOT-PNG'} ${w}x${h} ${Math.round(buf.length / 1024)}KB`
if (!png || w < 1600 || h < 2100) errs.push(`file is not the expected print PNG (${info})`)
}
if (!file) {
for (const c of consoleErrs.filter(c => /poster|useMapPoster/i.test(c)).slice(-2)) errs.push(c)
// SLOW or DEAD? A button still busy at the deadline is a render still running; an idle one is an
// export that ended without a file. The verdict is FAIL either way; the reason says which.
const busy = await btn.evaluate(el => el.disabled || el.getAttribute('aria-busy') === 'true').catch(() => null)
errs.push(busy ? 'still rendering at the deadline (button busy)' : 'the export ended with no file (button idle)')
}
const ok = clicks === 1 && !!file && !errs.length
console.log(`${ok ? 'PASS' : 'FAIL'} [${where} ${lang}${PROVIDER ? ' ' + PROVIDER : ''}] "${label}" · 1 click → ${file ? `${file} (${info})` : 'NO FILE within 120s'}${errs.length ? ' · ' + [...new Set(errs)].slice(0, 3).join(' | ') : ' · 0 errors'}`)
if (!ok) fails.push(`${where} ${lang}`)
await ctx.close()
}
} finally { await b.close(); cleanup() }
if (fails.length) { console.log(`\ncheck-export-one-click: FAIL (${fails.join(', ')})`); process.exit(1) }
console.log('\ncheck-export-one-click: PASS — one click on Export poster gives a print PNG, leaf and gallery, en and ar')

View file

@ -0,0 +1,156 @@
#!/usr/bin/env node
/**
* check-export-poster-prints.mjs — the Simple Research "Export poster" button opens the PRINT DIALOG
* with the poster in it, on ONE click, in its own leaf AND inside the Research Gallery, in en AND ar.
* It must NEVER hand over a file.
*
* ★ THE RULE CHANGED, AND THIS GATE ASSERTED THE OLD ONE (the Driver, 2026-09-28: "an export-poster
* button must open the print dialog with a poster preview, never a direct download"). It was
* check-export-one-click, and it passed while the button did exactly what the Driver has now ruled
* against: render a PNG and drop it in the downloads folder. A gate that encodes a superseded ruling
* is worse than no gate — it defends the defect. The earlier history is still why the click path is
* measured so carefully: before 2026-09-25 one click produced NOTHING, no dialog, no file, no error.
*
* WHAT IT ASSERTS, per host × language:
* 1. exactly ONE click on the ALWAYS-VISIBLE export button in the map toolbar
* (button.export-poster-btn). The drawer's "Export poster…" does not count: the drawer is CLOSED
* by default (translated off screen), so it costs an extra click to reach — measured 2026-09-25,
* and in Arabic the button could not be clicked at all;
* 2. within 120s and with no further click, some frame calls window.print();
* 3. the printing document IS the poster: an <img> at the print size (US Letter at 200 DPI =
* 1700x2200) on an @page sized 8.5in x 11in. A dialog over a blank or letter-defaulted sheet is
* not a preview of what was rendered;
* 4. NO download arrives. This is the half that encodes the new rule, and it is asserted for the
* whole run rather than at a moment, because a download that lands late is still a download;
* 5. 0 page errors.
*
* ★ window.print IS STUBBED, not called through. The native dialog is modal and would hang the run
* forever, so the init script records the call and returns. That makes this a test of everything up to
* the dialog — the frame, the sheet size and the image — and not of the browser's own chrome, which is
* the correct boundary for a headless gate.
*
* Runs through internal/browser-slot.sh like every browser gate. Starts its own servers on
* OS-assigned ports and proves the server is THIS checkout's tree before measuring.
* --provider maplibre runs the same four on the keyless engine.
* Exit: 0 pass · 1 fail · 2 SKIP (no built tree / no playwright) · 75 NOT MEASURED (server identity)
*/
import { createServer } from 'node:http'
import { spawn } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
// The tree's name comes from the build config, never a literal: the doxa-maps -> dx-maps rename
// turned the literal into a silent SKIP (exit 2) that measured nothing.
const { TREE_NAME } = await import(resolve(ROOT, 'internal/_tree-name.mjs'))
if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/research-map-plus-grid/research-map-plus-grid.js`))) {
console.log('SKIP check-export-poster-prints: no built research-map-plus-grid'); process.exit(2)
}
let chromium
try { ({ chromium } = await import('playwright')) } catch { console.log('SKIP check-export-poster-prints: playwright not installed'); process.exit(2) }
const freePort = () => new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) })
const PROVIDER = process.argv.includes('--provider') ? process.argv[process.argv.indexOf('--provider') + 1] : '' // maplibre = the keyless engine
const TP = await freePort(), HP = await freePort()
const TREE = `http://127.0.0.1:${TP}/${TREE_NAME}`
let lang = 'en'
const srv = spawn(process.execPath, ['internal/serve-strict.mjs', '--port', String(TP)], { cwd: ROOT, stdio: 'ignore' })
const host = createServer((_, r) => { r.writeHead(200, { 'content-type': 'text/html' }); r.end(`<!doctype html><html lang="${lang}"><body style="margin:0"><script src="${TREE}/doxa-maps-spa/doxa-maps-spa.js"></script><doxa-maps-spa tab="research-map-plus-grid"${PROVIDER ? ` provider="${PROVIDER}"` : ''} style="height:900px"></doxa-maps-spa></body></html>`) }).listen(HP, 'localhost')
const cleanup = () => { try { srv.kill() } catch {} try { host.close() } catch {} }
await new Promise(r => setTimeout(r, 800))
// Server identity by CONTENT: the served manifest must be this checkout's.
{
const disk = readFileSync(resolve(ROOT, `public/${TREE_NAME}/manifest.json`), 'utf8')
let served = ''
for (let i = 0; i < 20 && !served; i++) {
try { const r = await fetch(`${TREE}/manifest.json`); if (r.ok) served = await r.text() } catch { /* not up yet */ }
if (!served) await new Promise(r => setTimeout(r, 300))
}
if (served !== disk) { console.log(`NOT MEASURED: :${TP} is not serving this checkout's tree. Re-run.`); cleanup(); process.exit(75) }
}
const b = await chromium.launch({ args: ['--use-angle=swiftshader', '--enable-unsafe-swiftshader'] })
const fails = []
try {
for (lang of ['en', 'ar']) for (const where of ['leaf', 'gallery']) {
const url = where === 'leaf' ? `${TREE}/research-map-plus-grid/research-map-plus-grid/index.html?lang=${lang}${PROVIDER ? `&provider=${PROVIDER}` : ''}` : `http://localhost:${HP}/`
const ctx = await b.newContext({ acceptDownloads: true, viewport: { width: 1400, height: 900 } })
// Every frame, because the poster prints from an iframe the app creates (printInPlace.js). The
// override lives on the WINDOW, which survives the document.open()/write() that fills that frame.
// acceptDownloads stays TRUE on purpose: a download must be observable to be failed on.
await ctx.addInitScript(() => {
window.__printed = []
window.print = function () {
const img = document.querySelector('img')
const style = document.querySelector('style')
window.__printed.push({
imgW: img ? img.naturalWidth : 0,
imgH: img ? img.naturalHeight : 0,
page: style ? (String(style.textContent).match(/@page\{size:([^;}]+)/) || [])[1] || '' : '',
})
}
})
const p = await ctx.newPage(); const errs = []
p.on('pageerror', e => errs.push(String(e).slice(0, 160)))
// A failed export is CAUGHT and logged (console.error), never thrown: read the console, or a dead
// button reports "no file" with no reason.
const consoleErrs = []; p.on('console', m => { if (m.type() === 'error' || m.type() === 'warning') consoleErrs.push(m.text().slice(0, 200)) })
const downloads = []; p.on('download', d => downloads.push(d))
await p.goto(url); await p.waitForTimeout(12000)
const btn = p.locator('button.export-poster-btn').first()
const onScreen = await btn.evaluate(el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0 && r.left >= 0 && r.top >= 0 && r.right <= innerWidth && r.bottom <= innerHeight }).catch(() => false)
if (!onScreen) errs.push('the export button is not on screen without a prior click')
const label = ((await btn.getAttribute('title', { timeout: 20000 }).catch(() => '')) || '').trim()
let clicks = 0
// ONE PHYSICAL CLICK at the button's centre, as a reader makes it. Not locator.click(): that
// re-checks its hit target and RETRIES, and by the retry the button is already disabled (the
// export is running), so it reported "timeout" on exports whose file then arrived — measured
// 2026-09-25 in gallery-ar and leaf-en. What the click achieved is judged by the file below.
try {
const box = await btn.boundingBox({ timeout: 20000 })
if (!box) throw new Error('the export button has no box')
await p.mouse.click(box.x + box.width / 2, box.y + box.height / 2); clicks = 1
} catch (e) { errs.push('could not click: ' + String(e.message || e).split('\n')[0]) }
// Poll every frame: the print call happens in the iframe printInPlace creates, which is not
// attached when the click is made. A frame that has gone away is skipped rather than failing the
// poll — the app reclaims its print frame, and a reclaimed frame is a completed print, not an error.
const readPrints = async () => {
const out = []
for (const f of p.frames()) {
const got = await f.evaluate(() => window.__printed || []).catch(() => [])
out.push(...got)
}
return out
}
let prints = []
for (let i = 0; i < 120 && !prints.length; i++) { await p.waitForTimeout(1000); prints = await readPrints() }
let info = ''
if (prints.length) {
const pr = prints[0]
info = `${pr.imgW}x${pr.imgH} on @page ${pr.page || '(none)'}`
// The poster, at the size it was rendered, on the sheet it was rendered for.
if (pr.imgW < 1600 || pr.imgH < 2100) errs.push(`the printed document is not the poster (${info})`)
if (!/8\.5in\s+11in/.test(pr.page || '')) errs.push(`the sheet is not the poster's page size (@page ${pr.page || 'missing'})`)
} else {
for (const c of consoleErrs.filter(c => /poster|print|useMapPoster/i.test(c)).slice(-2)) errs.push(c)
// SLOW or DEAD? A button still busy at the deadline is a render still running; an idle one is an
// export that ended without opening anything. The verdict is FAIL either way; the reason says which.
const busy = await btn.evaluate(el => el.disabled || el.getAttribute('aria-busy') === 'true').catch(() => null)
errs.push(busy ? 'still rendering at the deadline (button busy)' : 'the export ended with no print dialog (button idle)')
}
// ★ THE NEW RULE'S OTHER HALF. A button that opens the dialog AND saves a file still breaks it.
if (downloads.length) errs.push(`the button downloaded a file (${downloads[0].suggestedFilename()}) instead of only printing`)
const ok = clicks === 1 && prints.length === 1 && !downloads.length && !errs.length
// Exactly one dialog: two print frames would mean the click was handled twice, which a reader sees
// as a second dialog appearing behind the first.
if (clicks === 1 && prints.length > 1) errs.push(`${prints.length} print dialogs opened on one click`)
console.log(`${ok ? 'PASS' : 'FAIL'} [${where} ${lang}${PROVIDER ? ' ' + PROVIDER : ''}] "${label}" · 1 click → ${prints.length ? `print dialog (${info})` : 'NO PRINT DIALOG within 120s'}${errs.length ? ' · ' + [...new Set(errs)].slice(0, 3).join(' | ') : ' · 0 errors, 0 downloads'}`)
if (!ok) fails.push(`${where} ${lang}`)
await ctx.close()
}
} finally { await b.close(); cleanup() }
if (fails.length) { console.log(`\ncheck-export-poster-prints: FAIL (${fails.join(', ')})`); process.exit(1) }
console.log('\ncheck-export-poster-prints: PASS — one click opens the print dialog on the poster and downloads nothing, leaf and gallery, en and ar')

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
/**
* region-dashboards.vue — Tab 2 ("Region Dashboards") of the regions-dashboard bundle.
*
* @description The dashboard half of the Driver's 2-tab Regions element: given a region (or a
* @description The dashboard half of the 2-tab Regions element: given a region (or a
* single country inside one), it summarises that selection — headline stats plus a comparison
* of its countries — using the SAME live people-groups endpoint and the SAME rollup fields as
* regions-list.vue (tab 1), so the two tabs can never disagree about a number.

View file

@ -2,7 +2,7 @@
/**
* regions-mfe.vue — the regions-dashboard NESTED-ELEMENTS MFE (the 2-tab Regions element).
*
* @description The composite the Driver asked for: ONE element carrying the SELECT VIEW switcher
* @description The composite: ONE element carrying the SELECT VIEW switcher
* over two panes — tab 1 "Regions List" (regions-list) and tab 2 "Region Dashboards"
* (region-dashboards). Clicking a region or a country on tab 1 swaps to tab 2 with that
* selection already applied.

View file

@ -1,6 +1,8 @@
<script setup>
import { religionFamilyLabel } from '@map/colors/religion.js'
import { tStatic } from '@map/i18n/index.js'
import { WAGF_REGIONS } from '@map/colors/wagf-region.js'
import countryName, { iso3ForEnglishName } from '@map/utils/countryName.js'
import { tStatic, getRuntimeLocale } from '@map/i18n/index.js'
/**
* ChartsRail — RDASH phase 5: cross-filtered charts rail. Religion donut + Region donut (doxa-region
* palette) + Top-country bar, all re-aggregating on filterSet and brushable back into it (click a
@ -98,9 +100,45 @@ const isActive = (facet, key) => bus.isFilterActive(facet, key)
// Two projections, one function, and both follow the same rule: the key crosses, the label is read.
// OTHER_KEY is the rollup sentinel aggregate() produces; it must stay English on the wire (this
// component refuses to filter on it by testing `key === 'Other'`) and be localized for a reader.
// ★ TWO MORE FACETS CARRY ENGLISH DATA LABELS, and rega read both on ?lang=ar (KA3, 3d64bcad).
// They are not chrome: no catalogue held them, so every i18n gate passed while the chart spoke English.
// · wagf_region — the prayer API's seven region names. A closed set, so they are CATALOGUED, five of
// them from vendored CLDR (internal/generate-wagf-region-names.mjs); middle_east and
// north_america_&_non-spanish_caribbean have no CLDR code that means them and stay English.
// · country_code — the API's English country name. The alpha-3 is gone by aggregation time, so
// library/utils/countryName.js reverses its own table and then asks CLDR, per the ✓ case in that
// file's header: a label that arrives in English regardless of the reader.
// English is left EXACTLY as authored: at locale 'en' both projections return the key untouched, which
// also keeps the 42 countries whose CLDR name differs from ours (see countryName.js) reading as before.
// The five keys are written out rather than assembled from the slug: a template-literal key is
// invisible to check:i18n-keys-exist (it counts such expressions as dynamic and will not guess at
// them), and these entries are generated, so the gate is the only thing standing between a regenerated
// catalogue and a chart silently back in English. Written out, deleting one fails the build.
const REGION_SLUG = new Map(Object.entries(WAGF_REGIONS).map(([slug, label]) => [label, slug]))
const REGION_KEY = {
africa: 'dashboard.wagfRegion.africa',
asia: 'dashboard.wagfRegion.asia',
europe: 'dashboard.wagfRegion.europe',
'latin_america_&_caribbean': 'dashboard.wagfRegion.latin_america_&_caribbean',
oceania: 'dashboard.wagfRegion.oceania',
}
const regionLabel = (key) => {
const slug = REGION_SLUG.get(key)
const k = slug && REGION_KEY[slug]
return (k && tStatic(k)) || key
}
const countryLabel = (key) => {
const lc = getRuntimeLocale()
if (!lc || lc === 'en') return key
const iso3 = iso3ForEnglishName(key)
return iso3 ? countryName(iso3, lc) : key
}
const legendLabel = (facet, key) => {
if (key === OTHER_KEY) return otherLabel()
return facet === 'religion_family' ? religionFamilyLabel(key) : key
if (facet === 'religion_family') return religionFamilyLabel(key)
if (facet === 'wagf_region') return regionLabel(key)
if (facet === 'country_code') return countryLabel(key)
return key
}
// true when this chart's own facet has an active selection → drives the pop-out / dim-others state.
const hasSelection = (facet) => filterSet[facet] != null // any selection, scalar or array
@ -372,7 +410,11 @@ useShadowStyles(`
<div class="rd-bars">
<div v-for="b in chart.geometry" :key="b.key" class="rd-bar" :class="{ on: isActive(chart.facet, b.key) }"
:data-facet="chart.facet" :data-key="b.key">
<span class="rd-bl">{{ b.key }}</span>
<!-- ★ THE BAR LABEL WAS THE ONE CHART SURFACE STILL PRINTING THE RAW KEY. The donut, its
legend and both tooltips already read legendLabel(); the bar list did not, which is why
the top-countries and region charts stayed English after the donut was fixed. Same
projector, so the two charts cannot disagree about a name again. -->
<span class="rd-bl">{{ legendLabel(chart.facet, b.key) }}</span>
<span class="rd-bt"><span class="rd-bf" :style="{ width: (b.frac * 100) + '%', background: b.color }"></span></span>
<span class="rd-bv">{{ b.value }}</span>
</div>

View file

@ -919,7 +919,13 @@ useShadowStyles(`
<div class="rd-td chk"></div>
<div v-for="(col, i) in COLUMNS" :key="'f-' + col.key" class="rd-td"
:class="{ num: col.type === 'num' }" :style="colFlex(col)">
<template v-if="i === 0">Σ {{ fmt(rowCount) }} groups</template>
<!-- ★ "groups" WAS THE LAST ENGLISH WORD IN THIS TABLE (rega KA3, ?lang=ar): the footer read
"Σ 2,107 groups" on an Arabic page. dashboard.groups already ships in all eleven locales
(ar "مجموعة"), so this is a call site that never read its own catalogue -- no new
string, no new key. Singular/plural is written the same way as the header count at
L794 -- the two say the same thing about the same number and would otherwise disagree at
one row. Σ and the numeral stay as they are: NUMERALS ARE HELD. -->
<template v-if="i === 0">Σ {{ fmt(rowCount) }} {{ rowCount === 1 ? T('dashboard.group', 'group') : T('dashboard.groups', 'groups') }}</template>
<template v-else-if="col.key === 'population'">{{ fmt(sumPop) }}</template>
</div>
</div>

View file

@ -1384,6 +1384,12 @@ function applyProjection() {
// the engine by trial instead of by identity. The helper instance-tests instead (C2).
setProjectionOn(m, projection.value)
}
// The pill's accessible name: the action, from the catalogue, in the reader's language. Both keys
// ship in all eleven locales, so this never falls back to English.
const projName = computed(() => (projection.value === 'globe'
? T('mapui.disableGlobe', 'Disable globe')
: T('mapui.enableGlobe', 'Enable globe')))
function toggleProjection() { projection.value = projection.value === 'globe' ? 'mercator' : 'globe'; applyProjection() }
// ── RDASH-ENHANCE: fly-to on selection. Any facet brush (tree node / chart slice / region / row)
@ -1590,7 +1596,10 @@ useShadowStyles(`
the media query meant phones passed at 34px while desktop stayed at 73.5x23, the same one-pixel
failure, now invisible to a phone sweep. Measured again at 1440x900 by upg100 (2a2ea36e). One
rule, every width, no query to be right about. */
.rd-proj{ min-height:24px; display:inline-flex; align-items:center; }
/* ★ AND min-WIDTH now that the word is gone. The 24 used to come for free from the text
("🌐 Globe" measured 74px wide); a glyph-only pill is about 29px with its padding, which passes,
but only by accident of the font. Stating both axes keeps 2.5.8 AA true if the glyph changes. */
.rd-proj{ min-height:24px; min-width:24px; display:inline-flex; align-items:center; justify-content:center; }
/* Zoom cluster. MapToolbar defaults to top:6px on the inline-end edge, which is exactly where
the Globe/Flat pill already sits, so drop this instance below it. 34px clears the pill
(top:8px + roughly 22px tall) with a small breathing gap.
@ -1691,8 +1700,20 @@ useShadowStyles(`
<ZoomInButton :map="map" :disabled="!canZoomIn" />
<ZoomOutButton :map="map" :disabled="!canZoomOut" />
</MapToolbar>
<span class="rd-proj" role="button" tabindex="0" data-proj-toggle @click="toggleProjection">
{{ projection === 'globe' ? '🌐 Globe' : '▭ Flat' }}
<!-- ★ THE ICON IS THE LABEL, because the word could not be translated without inventing one.
rega saw "🌐 Globe" in Arabic on ?lang=ar (KA3, 3d64bcad): hardcoded English chrome on a
localized map. "Globe" has no catalogue entry and "Flat" has none in any of the eleven
locales, so translating the visible text meant either authoring ten new strings for a
two-state toggle or leaving nine locales reading English. The catalogue DOES already ship
mapui.enableGlobe / mapui.disableGlobe in all eleven — as the ACTION, which is what a button
should announce anyway — so the accessible name and the tooltip come from there and the
visible pill carries the glyph alone. A reader now sees no English, and a screen reader hears
the action in their own language rather than the state in someone else's.
The label names what the click DOES, not where you are: in globe mode the button turns the
globe off. -->
<span class="rd-proj" role="button" tabindex="0" data-proj-toggle @click="toggleProjection"
:title="projName" :aria-label="projName">
<span aria-hidden="true">{{ projection === 'globe' ? '🌐' : '▭' }}</span>
</span>
<div class="rd-legend-map" data-map-legend :aria-label="T('aria.upgCountLegend', 'UPG count legend')">
<!-- ★ COMPOSED FROM TWO EXISTING CATALOGUE ENTRIES, not from new wording. This legend title

View file

@ -0,0 +1,61 @@
BOUNDARY DATA — SOURCES AND ATTRIBUTION
=======================================
The .geojson files in this folder are third-party boundary data. Every value below is taken
VERBATIM from the upstream metadata (the geoBoundaries API, https://www.geoboundaries.org/api/current/gbOpen/<ISO3>/<LEVEL>/).
Nothing here is our own wording about a licence.
gb-IND-ADM1-full.geojson, gb-IND-ADM1-full-web.geojson
source geoBoundaries gbOpen (www.geoboundaries.org)
boundaryID IND-ADM1-1811400
licence Creative Commons Attribution 2.5 India (CC BY 2.5 IN)
licenceSource github.com/datameet/maps
year 2011
ATTRIBUTION IS REQUIRED for this file: it is Creative Commons Attribution, so a work that
redistributes or displays it must credit the source above.
gb-IND-ADM1.geojson, gb-IND-ADM1-web.geojson
The same India ADM1 boundary as above, curated to the UPG-100 list's partition.
Same source, same boundaryID, same licence, same attribution duty.
gb-CHN-ADM1.geojson
source geoBoundaries gbOpen (www.geoboundaries.org)
boundaryID CHN-ADM1-43563684
licence Public Domain
licenceSource commons.wikimedia.org/wiki/File
year 2019
No attribution duty: upstream records this boundary as Public Domain.
world-adm0.geojson, world-adm0-web.geojson
source geoBoundaries gbOpen (www.geoboundaries.org), assembled per country
features 162 countries
licence MIXED — and now ESTABLISHED per country, not merely acknowledged.
THE PER-COUNTRY TABLE IS THE AUTHORITY, and it lives in ONE place on purpose:
plugin-designs/my-upg-100-list/data/adm0/PROVENANCE.json
It records boundaryLicense, licenseSource and boundaryID for all 162, each value taken
verbatim from https://www.geoboundaries.org/api/current/gbOpen/<ISO3>/<LEVEL>/ (162 queries,
0 failures, one spot-check re-verified against the live API). This file deliberately does NOT
copy that table: one table in two places drifts.
130 OF THE 162 CARRY A DUTY. Only 32 are duty-free (28 Public Domain, 4 CC0). By class:
ATTRIBUTION-ONLY (credit the source)
44 CC BY 4.0 13 CC BY 3.0 IGO 4 CC BY 2.5 2 CC BY 3.0
2 Open Government Licence v3
SHARE-ALIKE CLASS — A STRONGER DUTY: a derivative may have to be licensed alike, which is
the class that actually constrains giving this repository away.
58 Open Data Commons ODbL 1.0
7 Creative Commons ShareAlike 2.0 / 3.0 — AGO GAB GEO GIN MMR NER TUR
ODbL IS LISTED IN THAT CLASS DELIBERATELY. Alongside attribution it requires a Derivative
Database to be offered under ODbL and carries a keep-open provision, so by volume the
share-alike question covers 65 of 162 countries, not 7. This repository has already acted on
that reading once: library/scripts/posters/data/README.md records that Natural Earth's 1:10m
ROADS were requested and deliberately NOT shipped because of an ODbL obligation.
WHETHER 'licensed alike' binds a giveaway of this repository is a legal question, not a
measurement, and it is open. It is recorded here so nobody has to rediscover it.
duty-free: 28 Public Domain, 4 CC0.
See documentation/THIRD-PARTY.md for the whole shipped tree.

View file

@ -1,24 +1,93 @@
Bebas Neue — SIL Open Font License, Version 1.1
Designer: Ryoichi Tsunekawa (Dharma Type). Distributed via Google Fonts.
Full text: https://openfontlicense.org/ (OFL-1.1)
Copyright © 2010 by Dharma Type.
WHY THIS FILE IS IN THIS REPO
DX-life's /regions pages set --font-family-heading to 'Bebas Neue'. Their own site does not
ship the file either — @nuxt/fonts downloads it at build and self-hosts it, which is why their
computed font-family reads "Bebas Neue", "Bebas Neue Fallback: ...". We cannot @font-face from
their origin: their font file is served without CORS headers, so a cross-origin face silently
fails and every heading fell back to a condensed system stack. That substitution was the ONLY
remaining difference between our regions dashboard and theirs (7 computed properties, all of
them this one font and its knock-on sizes).
So the font is vendored here, from Google Fonts, under the OFL that permits redistribution —
self-hosted, never a CDN, the same rule public/assets/vendor/tabulator/ follows.
Subsets and unicode-ranges are Google's own (css2 API, v16), reproduced verbatim in the
@font-face declared by library/composables/useDocumentFont.js.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
NOTE ON THIS FILENAME — it is deliberately extensionless.
check:public-hygiene refuses *.md / *.markdown / *.txt anywhere in the served tree ("no docs in a
served folder", Driver rule 2026-09-16), and it is right to: prose does not belong in a public
mirror. But the OFL requires this licence to accompany the font files wherever they are
redistributed, and the font files DO ship — they are fetched by the @font-face at runtime. So the
licence travels with them under the conventional extensionless name, which satisfies the rule
without weakening it. Do not rename this to LICENSE.txt.
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -1,10 +1,3 @@
Figtree — SIL Open Font License, Version 1.1
Designer: Erik D. Kennedy. Distributed via Google Fonts.
Vendored as the free stand-in for Brandon Grotesque (commercial), by Driver decision
2026-09-24. Chosen by MEASUREMENT, not by name: at 500/19.5312px Figtree renders
'FULL PROFILE' at 126px against Brandon's 128px on live doxa.life — the closest of 19
libre grotesques tested. See _probe-glyph-width.mjs.
Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree)
This Font Software is licensed under the SIL Open Font License, Version 1.1.

View file

@ -1,29 +1,93 @@
Poppins — SIL Open Font License, Version 1.1
Designers: Indian Type Foundry, Jonny Pinhorn. Distributed via Google Fonts.
Full text: https://openfontlicense.org/ (OFL-1.1)
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
WHY THIS FILE IS IN THIS REPO
Poppins is DX-life's --font-family-base: the body face of every line of text on the pages the
regions plugin clones. Their own site self-hosts it via @nuxt/fonts; we could not use theirs, so
it is vendored here from Google Fonts under the OFL, which permits redistribution.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
WHY IT WAS NEEDED — and why no CSS check caught it.
A computed-style diff of our region dashboard against live doxa.life reported 68 of 68 properties
IDENTICAL while our body text was rendering in a system fallback, because BOTH sides merely NAME
"Poppins" first in font-family and a first-family comparison therefore matched. The difference only
became visible against a glyph-width oracle: the same string at the same size measured 334.58px
here against 317px on their page — 5.5% wider, i.e. a different face.
document.fonts.check() is NOT a usable oracle for this: it returned true for families never
vendored (Oswald, Brandon Grotesque) and false for Bebas Neue on DX-life's own page.
See _qa-cycle/02-builder-cycle/_probe-glyph-width.mjs.
WHAT IS HERE, AND WHY ONLY THIS
Weights 400/500/600/700 only — the four the regions surface actually asks for (measured across
their _globals, _cards, _buttons and _filters SCSS: medium x10, regular x4, bold x2, plus the
600 in the prayer-coverage legend). Subsets latin + latin-ext only. 44 KB in total, with Google's
own unicode-ranges reproduced in the @font-face so the subset split behaves as it does on their
site. Extra weights and scripts are deliberately NOT vendored — bloat is a repo rule here.
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
FILENAME: extensionless on purpose. check:public-hygiene refuses *.txt in a served folder and is
right to, while the OFL requires this licence to accompany redistributed font files. Do not
rename this to LICENSE.txt.
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2026 Oli Folkerd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because one or more lines are too long

View file

@ -43,7 +43,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <bp-big-picture> custom element. -->
<script src="../../big-picture.js" integrity="sha384-vq5/y8EkBMQyCQFvBmoWBZhBX73c4vln8KD5tuMyMdhzMDf25zyUTdUx4XRAEibP" crossorigin="anonymous"></script>
<script src="../../big-picture.js" integrity="sha384-ok/EhX34F4Novw0CMxEoYa69SLQmXtc4QzI1zPW1KV9t5bKlnxsoMxSNO68ea29O" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -43,7 +43,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <bp-big-picture> custom element. -->
<script src="../../big-picture.js" integrity="sha384-vq5/y8EkBMQyCQFvBmoWBZhBX73c4vln8KD5tuMyMdhzMDf25zyUTdUx4XRAEibP" crossorigin="anonymous"></script>
<script src="../../big-picture.js" integrity="sha384-ok/EhX34F4Novw0CMxEoYa69SLQmXtc4QzI1zPW1KV9t5bKlnxsoMxSNO68ea29O" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -43,7 +43,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <bp-big-picture> custom element. -->
<script src="../big-picture.js" integrity="sha384-vq5/y8EkBMQyCQFvBmoWBZhBX73c4vln8KD5tuMyMdhzMDf25zyUTdUx4XRAEibP" crossorigin="anonymous"></script>
<script src="../big-picture.js" integrity="sha384-ok/EhX34F4Novw0CMxEoYa69SLQmXtc4QzI1zPW1KV9t5bKlnxsoMxSNO68ea29O" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -32,7 +32,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -47,7 +47,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -47,7 +47,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
<script src="../../dashboard-plus-grid.js" integrity="sha384-nr6tMv3MzTLdSlvOzIeG2D/HWyjrT2O2VWT9GqYcFt5Hifgo1IT+y8hPAdhncmEr" crossorigin="anonymous"></script>
<script src="../../dashboard-plus-grid.js" integrity="sha384-+CKECgEuJ5pKzR11N0RMEEAEvBKz+F1S3xbVub8k0RAF+tSEV8fW6W2CVCTdJ1lZ" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -47,7 +47,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
<script src="../dashboard-plus-grid.js" integrity="sha384-nr6tMv3MzTLdSlvOzIeG2D/HWyjrT2O2VWT9GqYcFt5Hifgo1IT+y8hPAdhncmEr" crossorigin="anonymous"></script>
<script src="../dashboard-plus-grid.js" integrity="sha384-+CKECgEuJ5pKzR11N0RMEEAEvBKz+F1S3xbVub8k0RAF+tSEV8fW6W2CVCTdJ1lZ" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
"bundle": "big-picture",
"scriptUrl": "./big-picture/big-picture.js",
"output": "dx-maps/big-picture/big-picture.js",
"integrity": "sha384-vq5/y8EkBMQyCQFvBmoWBZhBX73c4vln8KD5tuMyMdhzMDf25zyUTdUx4XRAEibP",
"integrity": "sha384-ok/EhX34F4Novw0CMxEoYa69SLQmXtc4QzI1zPW1KV9t5bKlnxsoMxSNO68ea29O",
"elements": [
"bp-big-picture",
"bp-research-header",
@ -64,7 +64,7 @@
"scriptUrl": "./big-picture/big-picture.js",
"embed": {
"iframe": "<iframe src=\"./big-picture/big-picture/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./big-picture/big-picture.js\" integrity=\"sha384-vq5/y8EkBMQyCQFvBmoWBZhBX73c4vln8KD5tuMyMdhzMDf25zyUTdUx4XRAEibP\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./big-picture/big-picture.js\" integrity=\"sha384-ok/EhX34F4Novw0CMxEoYa69SLQmXtc4QzI1zPW1KV9t5bKlnxsoMxSNO68ea29O\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
},
"instances": [
{
@ -165,7 +165,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA",
"integrity": "sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh",
"elements": [
"country-posters-map"
],
@ -187,7 +187,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -202,7 +202,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -217,7 +217,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -226,7 +226,7 @@
"bundle": "dashboard-plus-grid",
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
"output": "dx-maps/dashboard-plus-grid/dashboard-plus-grid.js",
"integrity": "sha384-nr6tMv3MzTLdSlvOzIeG2D/HWyjrT2O2VWT9GqYcFt5Hifgo1IT+y8hPAdhncmEr",
"integrity": "sha384-+CKECgEuJ5pKzR11N0RMEEAEvBKz+F1S3xbVub8k0RAF+tSEV8fW6W2CVCTdJ1lZ",
"elements": [
"dashboard-plus-grid"
],
@ -248,7 +248,7 @@
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
"embed": {
"iframe": "<iframe src=\"./dashboard-plus-grid/dashboard-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-nr6tMv3MzTLdSlvOzIeG2D/HWyjrT2O2VWT9GqYcFt5Hifgo1IT+y8hPAdhncmEr\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-+CKECgEuJ5pKzR11N0RMEEAEvBKz+F1S3xbVub8k0RAF+tSEV8fW6W2CVCTdJ1lZ\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
},
"instances": [
{
@ -371,7 +371,7 @@
"bundle": "doxa-research-map",
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"output": "dx-maps/doxa-research-map/doxa-research-map.js",
"integrity": "sha384-zU1NN/Z+gtNTLnjiCWaRd+Vxh7IjVZvgcnEm8ApmXPxQP4TazThQt0LjgF95vJzk",
"integrity": "sha384-tD9vkhv3FNeVuKgdRQnpH/Ut0Zxh/BNqPl2lf8rOidmgj3831MsDD96m+4zv7Pr4",
"elements": [
"doxa-research-map"
],
@ -389,7 +389,7 @@
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-zU1NN/Z+gtNTLnjiCWaRd+Vxh7IjVZvgcnEm8ApmXPxQP4TazThQt0LjgF95vJzk\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-tD9vkhv3FNeVuKgdRQnpH/Ut0Zxh/BNqPl2lf8rOidmgj3831MsDD96m+4zv7Pr4\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
},
"instances": [
{
@ -604,7 +604,7 @@
"bundle": "gospel-internet",
"scriptUrl": "./gospel-internet/gospel-internet.js",
"output": "dx-maps/gospel-internet/gospel-internet.js",
"integrity": "sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a",
"integrity": "sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3",
"elements": [
"gospel-internet-board"
],
@ -623,7 +623,7 @@
"scriptUrl": "./gospel-internet/gospel-internet.js",
"embed": {
"iframe": "<iframe src=\"./gospel-internet/board/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
},
"instances": [
{
@ -632,8 +632,7 @@
"apiBaseUrl": "",
"props": {
"profile": "board",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/board/index.html",
@ -648,8 +647,7 @@
"props": {
"profile": "board",
"view": "zero",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/zero/index.html",
@ -665,8 +663,7 @@
"profile": "board",
"view": "province",
"panel": "map",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/india-china/index.html",
@ -681,8 +678,7 @@
"props": {
"profile": "board",
"panel": "tree",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/language-tree/index.html",
@ -697,8 +693,7 @@
"props": {
"profile": "board",
"view": "platforms",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/platforms/index.html",
@ -713,8 +708,7 @@
"props": {
"profile": "board",
"list": "700",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/project-700/index.html",
@ -1057,7 +1051,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR",
"integrity": "sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK",
"elements": [
"regions-dashboard"
],
@ -1071,7 +1065,7 @@
"profile": "region-dashboards",
"stage": "experimental",
"file": "region-dashboards.vue",
"description": "The dashboard half of the Driver's 2-tab Regions element: given a region (or a",
"description": "The dashboard half of the 2-tab Regions element: given a region (or a",
"element": "regions-dashboard",
"type": "single",
"isMap": true,
@ -1079,7 +1073,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1094,14 +1088,14 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
"profile": "regions-mfe",
"stage": "production",
"file": "regions-mfe.vue",
"description": "The composite the Driver asked for: ONE element carrying the SELECT VIEW switcher",
"description": "The composite: ONE element carrying the SELECT VIEW switcher",
"element": "regions-dashboard",
"type": "nested-elements-mfe",
"isMap": true,
@ -1109,7 +1103,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]
@ -1118,7 +1112,7 @@
"bundle": "research-dashboard",
"scriptUrl": "./research-dashboard/research-dashboard.js",
"output": "dx-maps/research-dashboard/research-dashboard.js",
"integrity": "sha384-VjTrJSY6kovHUE0P6oWg1Px4MiJPId7KsKQZWT3EskLPhoRejSQS+6/TtJfMWBc1",
"integrity": "sha384-FmogM7ZH4D9phv7a6r8Yux2r9NxFFcIrdSgSjqpIQGRdwIdEyxTv0dTEHwW1aUs8",
"elements": [
"research-dashboard-map"
],
@ -1136,7 +1130,7 @@
"scriptUrl": "./research-dashboard/research-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-VjTrJSY6kovHUE0P6oWg1Px4MiJPId7KsKQZWT3EskLPhoRejSQS+6/TtJfMWBc1\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-FmogM7ZH4D9phv7a6r8Yux2r9NxFFcIrdSgSjqpIQGRdwIdEyxTv0dTEHwW1aUs8\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
},
"bundle": "research-dashboard"
}

File diff suppressed because one or more lines are too long

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
<script src="../doxa-research-map.js" integrity="sha384-zU1NN/Z+gtNTLnjiCWaRd+Vxh7IjVZvgcnEm8ApmXPxQP4TazThQt0LjgF95vJzk" crossorigin="anonymous"></script>
<script src="../doxa-research-map.js" integrity="sha384-tD9vkhv3FNeVuKgdRQnpH/Ut0Zxh/BNqPl2lf8rOidmgj3831MsDD96m+4zv7Pr4" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -33,7 +33,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
<script src="../../doxa-research-map.js" integrity="sha384-zU1NN/Z+gtNTLnjiCWaRd+Vxh7IjVZvgcnEm8ApmXPxQP4TazThQt0LjgF95vJzk" crossorigin="anonymous"></script>
<script src="../../doxa-research-map.js" integrity="sha384-tD9vkhv3FNeVuKgdRQnpH/Ut0Zxh/BNqPl2lf8rOidmgj3831MsDD96m+4zv7Pr4" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:
@ -52,7 +52,7 @@
(function () {
var slot = document.getElementById('slot');
var status = document.getElementById('status');
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","urlState":"query","pinHint":false};
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","urlState":"query"};
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
// the ladder below is skipped entirely and the element mounts immediately.
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../gospel-internet.js" integrity="sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a" crossorigin="anonymous"></script>
<script src="../gospel-internet.js" integrity="sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:
@ -52,7 +52,7 @@
(function () {
var slot = document.getElementById('slot');
var status = document.getElementById('status');
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","view":"province","panel":"map","urlState":"query","pinHint":false};
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","view":"province","panel":"map","urlState":"query"};
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
// the ladder below is skipped entirely and the element mounts immediately.
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:
@ -52,7 +52,7 @@
(function () {
var slot = document.getElementById('slot');
var status = document.getElementById('status');
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","panel":"tree","urlState":"query","pinHint":false};
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","panel":"tree","urlState":"query"};
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
// the ladder below is skipped entirely and the element mounts immediately.
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:
@ -52,7 +52,7 @@
(function () {
var slot = document.getElementById('slot');
var status = document.getElementById('status');
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","view":"platforms","urlState":"query","pinHint":false};
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","view":"platforms","urlState":"query"};
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
// the ladder below is skipped entirely and the element mounts immediately.
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:
@ -52,7 +52,7 @@
(function () {
var slot = document.getElementById('slot');
var status = document.getElementById('status');
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","list":"700","urlState":"query","pinHint":false};
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","list":"700","urlState":"query"};
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
// the ladder below is skipped entirely and the element mounts immediately.
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-internet-board> custom element. -->
<script src="../../gospel-internet.js" integrity="sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a" crossorigin="anonymous"></script>
<script src="../../gospel-internet.js" integrity="sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:
@ -52,7 +52,7 @@
(function () {
var slot = document.getElementById('slot');
var status = document.getElementById('status');
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","view":"zero","urlState":"query","pinHint":false};
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"board","view":"zero","urlState":"query"};
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
// the ladder below is skipped entirely and the element mounts immediately.
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)

File diff suppressed because one or more lines are too long

View file

@ -40,7 +40,7 @@
"bundle": "big-picture",
"scriptUrl": "./big-picture/big-picture.js",
"output": "dx-maps/big-picture/big-picture.js",
"integrity": "sha384-vq5/y8EkBMQyCQFvBmoWBZhBX73c4vln8KD5tuMyMdhzMDf25zyUTdUx4XRAEibP",
"integrity": "sha384-ok/EhX34F4Novw0CMxEoYa69SLQmXtc4QzI1zPW1KV9t5bKlnxsoMxSNO68ea29O",
"elements": [
"bp-big-picture",
"bp-research-header",
@ -64,7 +64,7 @@
"scriptUrl": "./big-picture/big-picture.js",
"embed": {
"iframe": "<iframe src=\"./big-picture/big-picture/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./big-picture/big-picture.js\" integrity=\"sha384-vq5/y8EkBMQyCQFvBmoWBZhBX73c4vln8KD5tuMyMdhzMDf25zyUTdUx4XRAEibP\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./big-picture/big-picture.js\" integrity=\"sha384-ok/EhX34F4Novw0CMxEoYa69SLQmXtc4QzI1zPW1KV9t5bKlnxsoMxSNO68ea29O\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
},
"instances": [
{
@ -165,7 +165,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA",
"integrity": "sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh",
"elements": [
"country-posters-map"
],
@ -187,7 +187,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -202,7 +202,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -217,7 +217,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-mkjfczD2UgdXg1PKUHLuf5fFMs3W7UjGtDKJ9KVTWnTJ0SoQk6LPWMy+ijlkOGsA\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-OCkvmeiqULZJ8S6Qw2JNlbt1PlS8rfc7ME6CwAVJfv0NkWEGbdDm/bWrGpEqAOyh\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -226,7 +226,7 @@
"bundle": "dashboard-plus-grid",
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
"output": "dx-maps/dashboard-plus-grid/dashboard-plus-grid.js",
"integrity": "sha384-nr6tMv3MzTLdSlvOzIeG2D/HWyjrT2O2VWT9GqYcFt5Hifgo1IT+y8hPAdhncmEr",
"integrity": "sha384-+CKECgEuJ5pKzR11N0RMEEAEvBKz+F1S3xbVub8k0RAF+tSEV8fW6W2CVCTdJ1lZ",
"elements": [
"dashboard-plus-grid"
],
@ -248,7 +248,7 @@
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
"embed": {
"iframe": "<iframe src=\"./dashboard-plus-grid/dashboard-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-nr6tMv3MzTLdSlvOzIeG2D/HWyjrT2O2VWT9GqYcFt5Hifgo1IT+y8hPAdhncmEr\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-+CKECgEuJ5pKzR11N0RMEEAEvBKz+F1S3xbVub8k0RAF+tSEV8fW6W2CVCTdJ1lZ\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
},
"instances": [
{
@ -371,7 +371,7 @@
"bundle": "doxa-research-map",
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"output": "dx-maps/doxa-research-map/doxa-research-map.js",
"integrity": "sha384-zU1NN/Z+gtNTLnjiCWaRd+Vxh7IjVZvgcnEm8ApmXPxQP4TazThQt0LjgF95vJzk",
"integrity": "sha384-tD9vkhv3FNeVuKgdRQnpH/Ut0Zxh/BNqPl2lf8rOidmgj3831MsDD96m+4zv7Pr4",
"elements": [
"doxa-research-map"
],
@ -389,7 +389,7 @@
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-zU1NN/Z+gtNTLnjiCWaRd+Vxh7IjVZvgcnEm8ApmXPxQP4TazThQt0LjgF95vJzk\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-tD9vkhv3FNeVuKgdRQnpH/Ut0Zxh/BNqPl2lf8rOidmgj3831MsDD96m+4zv7Pr4\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
},
"instances": [
{
@ -604,7 +604,7 @@
"bundle": "gospel-internet",
"scriptUrl": "./gospel-internet/gospel-internet.js",
"output": "dx-maps/gospel-internet/gospel-internet.js",
"integrity": "sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a",
"integrity": "sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3",
"elements": [
"gospel-internet-board"
],
@ -623,7 +623,7 @@
"scriptUrl": "./gospel-internet/gospel-internet.js",
"embed": {
"iframe": "<iframe src=\"./gospel-internet/board/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-ZQF3CkN2yU6wgCToYTuCukqTHLBieBtrymjLPCtnF0ccWItHxiQC1Gr5UrdSPS+a\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-internet/gospel-internet.js\" integrity=\"sha384-d40eQO58ZZ9VbuAIYGirFAtLmuR3YiXj7olTWzzAKN8eqeH30b6bE6xSjQg6htI3\" crossorigin=\"anonymous\"></script>\n<gospel-internet-board profile-config='{\"profile\":\"board\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-internet-board>"
},
"instances": [
{
@ -632,8 +632,7 @@
"apiBaseUrl": "",
"props": {
"profile": "board",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/board/index.html",
@ -648,8 +647,7 @@
"props": {
"profile": "board",
"view": "zero",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/zero/index.html",
@ -665,8 +663,7 @@
"profile": "board",
"view": "province",
"panel": "map",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/india-china/index.html",
@ -681,8 +678,7 @@
"props": {
"profile": "board",
"panel": "tree",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/language-tree/index.html",
@ -697,8 +693,7 @@
"props": {
"profile": "board",
"view": "platforms",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/platforms/index.html",
@ -713,8 +708,7 @@
"props": {
"profile": "board",
"list": "700",
"urlState": "query",
"pinHint": false
"urlState": "query"
},
"element": "gospel-internet-board",
"iframeUrl": "./gospel-internet/board/project-700/index.html",
@ -1057,7 +1051,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR",
"integrity": "sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK",
"elements": [
"regions-dashboard"
],
@ -1071,7 +1065,7 @@
"profile": "region-dashboards",
"stage": "experimental",
"file": "region-dashboards.vue",
"description": "The dashboard half of the Driver's 2-tab Regions element: given a region (or a",
"description": "The dashboard half of the 2-tab Regions element: given a region (or a",
"element": "regions-dashboard",
"type": "single",
"isMap": true,
@ -1079,7 +1073,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1094,14 +1088,14 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
"profile": "regions-mfe",
"stage": "production",
"file": "regions-mfe.vue",
"description": "The composite the Driver asked for: ONE element carrying the SELECT VIEW switcher",
"description": "The composite: ONE element carrying the SELECT VIEW switcher",
"element": "regions-dashboard",
"type": "nested-elements-mfe",
"isMap": true,
@ -1109,7 +1103,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]
@ -1118,7 +1112,7 @@
"bundle": "research-dashboard",
"scriptUrl": "./research-dashboard/research-dashboard.js",
"output": "dx-maps/research-dashboard/research-dashboard.js",
"integrity": "sha384-VjTrJSY6kovHUE0P6oWg1Px4MiJPId7KsKQZWT3EskLPhoRejSQS+6/TtJfMWBc1",
"integrity": "sha384-FmogM7ZH4D9phv7a6r8Yux2r9NxFFcIrdSgSjqpIQGRdwIdEyxTv0dTEHwW1aUs8",
"elements": [
"research-dashboard-map"
],
@ -1136,7 +1130,7 @@
"scriptUrl": "./research-dashboard/research-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-VjTrJSY6kovHUE0P6oWg1Px4MiJPId7KsKQZWT3EskLPhoRejSQS+6/TtJfMWBc1\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-FmogM7ZH4D9phv7a6r8Yux2r9NxFFcIrdSgSjqpIQGRdwIdEyxTv0dTEHwW1aUs8\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
},
"bundle": "research-dashboard"
}

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -32,7 +32,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -47,7 +47,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-BKvyqmem1gAeYIpv+N7hUWezGfuToUwRjtpyN38YCa1TqdM4rA1zty9cNm8kpzOR" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-xr+Cao4cF71XZpfQ0iZCQi6ULw2Thnrl0JXxtAwxzkAJRVYbArdWXA9Q8CBwLIPK" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <research-dashboard-map> custom element. -->
<script src="../research-dashboard.js" integrity="sha384-VjTrJSY6kovHUE0P6oWg1Px4MiJPId7KsKQZWT3EskLPhoRejSQS+6/TtJfMWBc1" crossorigin="anonymous"></script>
<script src="../research-dashboard.js" integrity="sha384-FmogM7ZH4D9phv7a6r8Yux2r9NxFFcIrdSgSjqpIQGRdwIdEyxTv0dTEHwW1aUs8" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: