Update from source 53d386262
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

This commit is contained in:
Doxa Maps 2026-09-28 14:27:42 +00:00
parent 140d38e0b2
commit 891f7535d8
52 changed files with 1456 additions and 546 deletions

View file

@ -4,6 +4,26 @@ Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His
**Isaiah 40:3 “...Prepare the way for the LORD in the wilderness; make a straight highway for our God in the desert."**
## Bottom line up front
- **Why:** we believe the answer to the Great Commission is multiplying indigenous disciples,
churches and leaders among every remaining people group. That can't be done without maps, so
dx-maps gives the church map solutions to help finish world evangelization.
- **How it was made:** no human wrote this code. It is about 100% AI-coded, and about 90% of the
maps began as a vision spoken aloud (voice-to-text, such as Rudder) and handed to an AI agent that
built it with dx-maps. If you can picture a map and say it in plain English, an agent can build it.
- **Our favorite:** the experimental Gospel × Internet dashboard: the most experimental, and likely
the most impactful for the Great Commission, because everything comes together there.
It is inspired by the Perspectives course and book, and by Curtis Sergeant's 100-list and
relational-stewardship training (bounded and centered sets).
- **What each map is for:**
- **Big Picture:** the library's own interface, the one agents use to build maps.
- **UPG-100:** the most strategic. It divides the task into prayer packets.
- **Gospel maps:** the resources that exist in each language.
- **UPG Neighbor:** the reached neighbors near each unreached group.
- **Gospel × Internet:** all of it, together. Built for linguists, catalysts, translators (human
and AI) and prayer warriors.
Built with love, with the prayer that these maps bless the nations.
# dx-maps — one build, maps that embed everywhere

View file

@ -0,0 +1,117 @@
#!/usr/bin/env node
/**
* check-describe-group — the packet's generated profile description, asserted per level, per missing
* field and per locale.
*
* WHY A TEST AND NOT A GLANCE: this string is generated for ~2,000 people groups in eleven languages
* and printed ~20 to a page. Nobody proofreads two thousand of them, so every failure mode here is
* silent — a missing field blanks a row, a wrong GSEC branch states the wrong fact about a real people,
* and an English fragment in an Arabic packet reads as a bug in the data rather than in the code.
*
* Headless, no network, no map. Strings are INJECTED, so this tests the function and not the catalogue.
* Exit 0 pass · 1 fail.
*/
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const { describeGroup } = await import(resolve(ROOT, 'library/packets/describeGroup.js'))
// Two locales is enough to prove the lookup: one that exists, one that does not (falls back to en).
const S = {
en: {
desc_where_both: 'The {people} live in {country} and speak {language}.',
desc_where_country: 'The {people} live in {country}.',
desc_where_language: 'The {people} speak {language}.',
desc_gsec_0: 'There are no known evangelical Christians among them.',
desc_gsec_1: 'Fewer than 2% are evangelical, and no one has been planting churches.',
desc_gsec_4: 'Between 2% and 5% are evangelical.',
desc_gsec_6: 'More than 10% are evangelical.',
},
ar: {
desc_where_both: 'يعيش {people} في {country} ويتحدثون {language}.',
desc_where_country: 'يعيش {people} في {country}.',
desc_gsec_1: 'أقل من 2% إنجيليون.',
},
}
// The country resolver is INJECTED (see describeGroup's header: countryName.js is Vite-alias-only and
// imports JSON without an attribute, so importing it here would make this gate unrunnable). A two-entry
// stub proves the wiring and the localization; the ladder below proves what happens without it.
const CN = (iso, lc) => ({ MYS: { en: 'Malaysia', ar: 'ماليزيا' } }[iso] || {})[lc] || ''
const O = { strings: S, countryName: CN }
const G = { name: 'Abai Sungai', country_code: 'MYS', primary_language_label: 'Abai Sungai', imb_gsec: 1 }
const fails = []
const eq = (what, got, want) => { if (got !== want) fails.push(`${what}\n got: ${JSON.stringify(got)}\n want: ${JSON.stringify(want)}`) }
const has = (what, got, sub) => { if (!String(got).includes(sub)) fails.push(`${what}: ${JSON.stringify(sub)} missing from ${JSON.stringify(got)}`) }
const hasNot = (what, got, sub) => { if (String(got).includes(sub)) fails.push(`${what}: ${JSON.stringify(sub)} SHOULD NOT be in ${JSON.stringify(got)}`) }
// 1. every GSEC level the catalogue defines resolves to ITS OWN sentence
for (const [g, frag] of [[0, 'no known evangelical'], [1, 'no one has been planting'], [4, 'Between 2% and 5%'], [6, 'More than 10%']]) {
has(`gsec ${g} picks its own level sentence`, describeGroup({ ...G, imb_gsec: g }, 'en', O), frag)
}
eq('gsec 1 in full', describeGroup(G, 'en', O),
'The Abai Sungai live in Malaysia and speak Abai Sungai. Fewer than 2% are evangelical, and no one has been planting churches.')
// 2. the country is resolved to the READER's language from its code, not left English
has('country localized from the code (ar)', describeGroup(G, 'ar', O), 'ماليزيا')
hasNot('and the English name is gone', describeGroup(G, 'ar', O), 'Malaysia')
// 3. a missing clause is DROPPED, never blanked — the row still describes the people
const noLang = { ...G }; delete noLang.primary_language_label
eq('language missing -> country sentence only', describeGroup(noLang, 'en', O),
'The Abai Sungai live in Malaysia. Fewer than 2% are evangelical, and no one has been planting churches.')
const noCountry = { ...G }; delete noCountry.country_code
eq('country missing -> language sentence only', describeGroup(noCountry, 'en', O),
'The Abai Sungai speak Abai Sungai. Fewer than 2% are evangelical, and no one has been planting churches.')
has('level survives when both clauses are gone', describeGroup({ imb_gsec: 4 }, 'en', O), 'Between 2% and 5%')
// 3b. THE LANGUAGE LABEL IS INDEX-ORDERED AND MUST BE UN-INVERTED BEFORE IT IS PRINTED.
// The API returns "Arabic, Shihhi"; the sentence has to read "Shihhi Arabic". 725 of the 12,379 rows
// carry an inverted label, so this is the single most-printed transformation in the description.
const shihhi = describeGroup({ ...G, name: 'Shihuh', primary_language_label: 'Arabic, Shihhi' }, 'en', O)
has('inverted label reads naturally', shihhi, 'speak Shihhi Arabic.')
hasNot('and the raw index ordering is NOT printed', shihhi, 'Arabic, Shihhi')
// A comma INSIDE a parenthetical is not an inversion marker — 13 real names would be shredded by a
// naive split ("Central Sulawesi) Boano (Indonesia"). These must pass through untouched.
has('parenthetical comma is left alone', describeGroup({ ...G, primary_language_label: 'Boano (Indonesia, Central Sulawesi)' }, 'en', O), 'speak Boano (Indonesia, Central Sulawesi).')
has('a trailing parenthetical stays at the end', describeGroup({ ...G, primary_language_label: 'Naga, Tangkhul (India)' }, 'en', O), 'speak Tangkhul Naga (India).')
has('an uninverted label is unchanged', describeGroup({ ...G, primary_language_label: 'Ainu (China)' }, 'en', O), 'speak Ainu (China).')
// 3c. ★ THE FLATTENED CODE IS NEVER PRINTED. normalize.js flattens primary_language to its .value, an
// ISO 639-3 code; reading that field would print "and speak abf". A group carrying ONLY the code must
// fall to the country sentence — dropping the clause, which is the rule, rather than printing a code.
const codeOnly = { ...G }; delete codeOnly.primary_language_label; codeOnly.primary_language = 'abf'
eq('a bare language code is dropped, not printed', describeGroup(codeOnly, 'en', O),
'The Abai Sungai live in Malaysia. Fewer than 2% are evangelical, and no one has been planting churches.')
// 3d. NO RESOLVER: the clause degrades down countryName.js's own ladder instead of blanking.
eq('no resolver, no label -> the alpha-3 code, and the level still states its fact', describeGroup(G, 'ar', { strings: S }),
'يعيش Abai Sungai في MYS ويتحدثون Abai Sungai. أقل من 2% إنجيليون.')
eq('no resolver, label present -> the label', describeGroup({ ...G, country_code_label: 'Malaysia' }, 'en', { strings: S }),
'The Abai Sungai live in Malaysia and speak Abai Sungai. Fewer than 2% are evangelical, and no one has been planting churches.')
has('resolver returning nothing falls through to the code', describeGroup(G, 'en', { strings: S, countryName: () => '' }), 'live in MYS')
// 4. missing or nonsense GSEC omits the level rather than inventing one or throwing
hasNot('no gsec -> no level sentence', describeGroup({ ...G, imb_gsec: undefined }, 'en', O), 'evangelical')
hasNot('gsec 9 is not a level', describeGroup({ ...G, imb_gsec: 9 }, 'en', O), 'evangelical')
eq('an empty group is an empty string, not a crash', describeGroup({}, 'en', O), '')
eq('a null group is an empty string', describeGroup(null, 'en', O), '')
// 5. a locale the catalogue lacks falls back to English rather than printing the key
has('unknown locale falls back to en', describeGroup(G, 'zz', O), 'The Abai Sungai live in')
has('partial locale falls back PER KEY', describeGroup(G, 'ar', O), 'أقل من 2%')
// 6. truncation cuts at a word boundary and only when asked
const long = describeGroup(G, 'en', O)
eq('no cap -> untouched', describeGroup(G, 'en', { ...O, maxChars: 0 }), long)
const cut = describeGroup(G, 'en', { ...O, maxChars: 40 })
if (cut.length > 40) fails.push(`maxChars 40 honoured: got ${cut.length}`)
if (!cut.endsWith('…')) fails.push(`truncation is marked: ${JSON.stringify(cut)}`)
if (/\s$/.test(cut.slice(0, -1))) fails.push(`truncation trims its tail: ${JSON.stringify(cut)}`)
if (fails.length) {
console.error('DESCRIBE-GROUP: FAIL')
for (const f of fails) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log('DESCRIBE-GROUP: PASS — every GSEC level states its own fact, an index-ordered language label is un-inverted without shredding a parenthetical, a missing clause is dropped rather than blanked, the country is localized from its code, and an unknown locale falls back per key.')

View file

@ -0,0 +1,130 @@
#!/usr/bin/env node
/**
* check-list-selection — what the vertical legend emits, and the zones a region tree forgets. Headless.
*
* ★ WHY THIS IS IN internal/ AND NOT internal-tracker/. It began as a internal-tracker probe, which is GITIGNORED
* in this repo (.gitignore:113) — so it ran green on my disk and would NOT have landed with the
* component. studio caught that while staging: "the 13 assertions are not in either commit". I had
* reported them as evidence for code that was about to ship untested, which is the same failure this
* whole file guards against — a green result measured somewhere other than where it matters.
* A probe proves something once. A registered check keeps proving it.
*/
// What the vertical legend emits, and the zones a region tree forgets.
//
// The second half is the one that matters most: four joinable UPG-100 zones are absent from
// zone-regions.json today, one of them the consumer's largest at 100 groups. A region-grouped tree
// drops them with no error and no empty state — just a shorter tree — so this asserts they survive.
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { leavesOf, zoneIdsIn, withUnassigned, selectedId, UNASSIGNED_ID } from '../library/components/legends/listSelection.js'
const DATA = join(dirname(fileURLToPath(import.meta.url)), '..', 'plugin-designs', 'my-upg-100-list', 'data')
const readJson = (f) => JSON.parse(readFileSync(join(DATA, f), 'utf8'))
let fails = 0
const ok = (c, m) => { console.log(` ${c ? 'ok ' : 'FAIL'} - ${m}`); if (!c) fails++ }
const TREE = [{
id: 'asia', label: 'Asia', depth: 0,
children: [
{ id: 'z1', label: 'Zone 1', zoneKey: 'Z1' },
{ id: 'sub', label: 'Sub', depth: 1, children: [{ id: 'z2' }, { id: 'z3' }] },
],
}]
// The real gap, named as it appears in the data.
const MISSING = [
'asia-china-guizhou-province',
'asia-china-xinjiang-uyghur-autonomous-region',
'asia-india-bihar',
'asia-india-madhya-pradesh',
]
ok(leavesOf(TREE[0]).map((l) => l.id).join(',') === 'z1,z2,z3', 'leavesOf walks NESTED children, not one level')
ok([...zoneIdsIn(TREE)].sort().join(',') === 'z1,z2,z3', 'zoneIdsIn collects every leaf id in the tree')
// ── the emit contract ───────────────────────────────────────────────────────────────────────────
ok(selectedId({ id: 'z1' }) === 'z1', 'a LEAF emits its zone id')
ok(selectedId({ id: 'asia' }) === 'asia', 'a REGION row emits the region id — the consumer expands it, where the join lives')
ok(selectedId(null) === null, 'and a CLEARED selection emits null, so a filter can be dropped')
ok(typeof selectedId({ id: 'z1' }) === 'string',
'the value is a STRING, so it survives ?zone=, profile-config and a deep link')
// ── never drop a zone ───────────────────────────────────────────────────────────────────────────
const all = ['z1', 'z2', 'z3', ...MISSING]
const withBucket = withUnassigned(TREE, all, 'Unassigned')
ok(withBucket.length === TREE.length + 1, 'zones absent from the tree get an explicit bucket')
const bucket = withBucket[withBucket.length - 1]
ok(bucket.id === UNASSIGNED_ID && bucket.children.length === 4, 'and all four missing zones are in it')
ok(bucket.children.map((c) => c.id).includes('asia-china-xinjiang-uyghur-autonomous-region'),
'including xinjiang — the consumer\'s largest zone at 100 groups, which a region tree loses silently')
ok(bucket.children.every((c) => c.id && c.zoneKey),
'each is a SELECTABLE leaf, not a lump — a reader wants xinjiang, not "the four nobody filed"')
ok(withUnassigned(TREE, ['z1', 'z2', 'z3']).length === TREE.length,
'CONTROL: no bucket appears when nothing is missing')
ok(withUnassigned(TREE, null).length === TREE.length && withUnassigned(TREE, []).length === TREE.length,
'CONTROL: omitting the zone set leaves the tree untouched, so this is additive')
// NEG CONTROL: the drop this exists to prevent must be detectable.
const naive = TREE // what a region-grouped tree gives you, unaugmented
ok(!zoneIdsIn(naive).has('asia-china-xinjiang-uyghur-autonomous-region'),
'NEG CONTROL: the unaugmented tree really does omit xinjiang, so the bucket is not decoration')
// ── a region sweep is NOT a zone sweep (gospel, END-GOS-VLG2) ───────────────────────────────────
// ★ The consequence of the bucket that neither of us should leave implicit: ticking every REGION
// still does not select every ZONE, because the four unfiled zones have no region BY DEFINITION.
// Nothing on screen would say so — the reader just gets a smaller total, which is the exact
// silent-wrong-answer shape the bucket exists to end. So if a "select all" is ever added it must
// sweep ZONES, not regions, or it inherits the bug. Pinned here so the wrong one cannot ship
// quietly; gospel pins the mirror of it on the expansion side.
const regionRows = withBucket.filter((n) => n.id !== UNASSIGNED_ID)
const byRegionSweep = zoneIdsIn(regionRows)
ok(byRegionSweep.size < all.length,
'selecting every REGION reaches fewer zones than exist — a region sweep is not a zone sweep')
ok(!byRegionSweep.has('asia-china-xinjiang-uyghur-autonomous-region'),
'and the 100 groups it misses are xinjiang\'s, so a "select all" over regions must be over zones')
ok(zoneIdsIn(withBucket).size === all.length,
'sweeping the WHOLE tree, bucket included, does reach all of them — what the bucket buys')
// ── a zone with no members stays SELECTABLE (gospel: 16 of 33 resolve today) ────────────────────
// Of the 33 zones, members resolve for 16; 5 await a wagf field and 9 have no selector anywhere.
// Those zones must render as EMPTY, not vanish — same reason as the bucket. A tree that hides what
// it cannot resolve tells the reader the zone does not exist, when the truth is that we cannot
// currently join it. Nothing here filters on member count, and this fails if anything starts to.
const EMPTYZONE = [{ id: 'asia', label: 'Asia', depth: 0, children: [{ id: 'z9', label: 'Zone 9', zoneKey: 'Z9', members: [] }] }]
ok(leavesOf(EMPTYZONE[0]).map((l) => l.id).join(',') === 'z9',
'a zero-member zone is still a LEAF, so a reader can select it and see an empty result')
ok(selectedId(leavesOf(EMPTYZONE[0])[0]) === 'z9', 'and it still emits its own id')
ok(withUnassigned(EMPTYZONE, ['z9']).length === EMPTYZONE.length,
'presence is by ID, not by member count, so an unresolvable zone is not ALSO dumped in the bucket')
// ── the gap is REAL TODAY, not just real in my fixture (gospel, END-GOS-VLG3) ───────────────────
// ★ EVERY ASSERTION ABOVE RUNS AGAINST A FIXTURE. That proves the MODULE behaves, and it proves the
// fixture models a gap — it does not prove the gap still exists in the data we ship. So the negative
// control above could stay green forever while the bucket quietly became dead code, or while a FIFTH
// zone went missing and nobody heard. That is the same failure in a new costume: evidence that lives
// somewhere other than where the thing it describes lives. gospel named it — find out where your
// evidence lives — after catching the identical shape in their own work.
//
// This does NOT resolve the three disagreeing lists (33 in zone-regions, 12 reachable via
// admin-to-list, 14 in groupings, disagreeing in BOTH directions). That is a data-ownership ruling
// for upg100 and the agent, and picking a winner here would be exactly the silent join we have both
// refused. It only compares the region tree's own source against the joinable set and RECORDS the
// difference, so the disagreement cannot drift without someone being told.
const regionOf = readJson('zone-regions.json') // the region tree's source: zone id -> region
const adminToList = readJson('admin-to-list.json') // admin name -> zone id; its VALUES are joinable
const filed = new Set(Object.keys(regionOf))
const joinable = new Set(Object.values(adminToList).map(String))
const realGap = [...joinable].filter((id) => !filed.has(id)).sort()
ok(realGap.length > 0,
realGap.length > 0
? `the region tree really does omit ${realGap.length} joinable zone(s) TODAY — the bucket is load-bearing in the shipped data, not only in the fixture`
: 'INERT ARM: every joinable zone is now filed under a region, so this check no longer measures anything. That is not a pass. Re-derive the bucket against the current data or DELETE it and its component — do not leave a green arm guarding nothing.')
ok(realGap.join(',') === MISSING.join(','),
realGap.join(',') === MISSING.join(',')
? 'and it is exactly the four this check names, so the fixture above models the shipped data rather than a story about it'
: `THE GAP MOVED — re-derive. shipped now: [${realGap.join(', ')}] but this check asserts: [${MISSING.join(', ')}]. A zone ADDED here is groups that just became unreachable; a zone REMOVED got filed and the fixture is stale. Either way the numbers in the commit body are no longer true.`)
ok(joinable.has('asia-china-xinjiang-uyghur-autonomous-region') && !filed.has('asia-china-xinjiang-uyghur-autonomous-region'),
'xinjiang specifically: joinable in admin-to-list, absent from zone-regions — its 100 groups are reachable ONLY through the bucket')
console.log(fails ? `\nLIST-SELECTION: ${fails} FAIL(s)` : '\nLIST-SELECTION: serialisable ids out, and no joinable zone can be dropped')
process.exit(fails ? 1 : 0)

View file

@ -78,9 +78,42 @@ for (const [label, src] of [['mapbox', { _data: COLLECTION }], ['maplibre', { _d
}
eq('expressions are ANDed', matchesExpression(['all', ['==', ['get', 'uniqueId'], 'a'], ['==', ['get', 'languageFamily'], 'Indo-European']], FEATURES[0].properties), false)
// ── PACKETSEL: the prayer count is LIVE, so the packet must read feature-state ──────────────────
// rega, RQ3(b): the packet's "Has Prayer" selection showed 50 of 997 where the legend row said 699.
// The legend is right. Polling writes each pin's current count with setFeatureState() and
// prayer-progress.js:94 colours from ['feature-state','peoplePraying']; the GeoJSON property is
// whatever was true at page load. Reading the property selects the wrong people to pray for, which is
// the one thing this packet exists to get right.
const HAS_PRAYER = ['>', ['to-number', ['feature-state', 'peoplePraying'], 0], 0]
// 'a' has prayer only in the live state, 'b' only in the stale property, 'c' in neither.
const PRAY_FEATURES = [
{ type: 'Feature', properties: { uniqueId: 'a', peoplePraying: 0 }, geometry: { type: 'Point', coordinates: [1, 1] } },
{ type: 'Feature', properties: { uniqueId: 'b', peoplePraying: 12 }, geometry: { type: 'Point', coordinates: [2, 2] } },
{ type: 'Feature', properties: { uniqueId: 'c', peoplePraying: 0 }, geometry: { type: 'Point', coordinates: [3, 3] } },
]
const STATE = { a: { peoplePraying: 7 } }
const prayMap = {
getSource: (id) => (id === 'pins' ? { _data: { type: 'FeatureCollection', features: PRAY_FEATURES } } : null),
getFeatureState: ({ id }) => STATE[id] || {},
}
eq('feature-state selects the LIVE prayer count, not the loaded one',
[...selectedPinIds(prayMap, { sourceId: 'pins', expressions: [HAS_PRAYER] })].sort().join(','), 'a')
eq('the stale property alone does NOT select a pin',
selectedPinIds(prayMap, { sourceId: 'pins', expressions: [HAS_PRAYER] }).has('b'), false)
// A map with no feature-state at all (no polling yet) must still select on the loaded property rather
// than collapsing to nothing — a packet printed before the first poll is thinner, never empty.
const noState = { getSource: prayMap.getSource }
eq('no getFeatureState -> property expression still works',
[...selectedPinIds(noState, { sourceId: 'pins', expressions: [['>', ['to-number', ['get', 'peoplePraying'], 0], 0]] })].join(','), 'b')
eq('feature-state on a map that has none is undefined, not a throw',
matchesExpression(HAS_PRAYER, PRAY_FEATURES[1].properties, undefined), false)
eq('a source that throws on getFeatureState degrades instead of aborting the print',
selectedPinIds({ getSource: prayMap.getSource, getFeatureState: () => { throw new Error('no such source') } },
{ sourceId: 'pins', expressions: [HAS_PRAYER] }).size, 0)
if (fails.length) {
console.error(`${TAG}: FAIL`)
for (const f of fails) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log(`${TAG}: PASS — both engine shapes read the same features, a remote source says so rather than looking empty, and selectedPinIds agrees with the reader.`)
console.log(`${TAG}: PASS — both engine shapes read the same features, a remote source says so rather than looking empty, selectedPinIds agrees with the reader, and the prayer count is read LIVE from feature-state rather than frozen at load.`)

View file

@ -0,0 +1,164 @@
#!/usr/bin/env node
/**
* check-selector-fields — every field a zone SELECTOR reads must be in every FETCH that evaluates it.
*
* THE DEFECT THIS CATCHES, measured 2026-09-28. The UPG-100 packet fetch requested
* slug,name,country_code,population,religion,image_url while SIX of the 27 printable zones select on
* wagf fields it never asked for (west-africa / east-africa / central-africa via wagf_block, europe via
* wagf_region, southern-africa-islands and oceania-indonesia via unions of them). evaluate() matched
* NOTHING, so those packets printed EMPTY — while the SAME selectors resolved correctly on the map,
* whose fetch had carried both fields all along. One file, two fetches, two field sets, nothing
* comparing them.
*
* ★ oceania-indonesia IS WHY THIS IS A GATE AND NOT A NOTE. Its union still matched country_code IDN,
* so it did not print empty — it printed a PLAUSIBLE packet silently missing every Oceania group. An
* empty packet is caught on page one; a packet quietly 40% short is not. Same asymmetry gospel measured
* the same day joining admin-to-list.json: 514 of 1009 rows matched and THREE whole zones vanished while
* every surviving zone still looked populated. A result that returns HALF looks like a smaller dataset.
*
* ★ FIELDS ARE READ FROM THE EVALUATOR, NOT FROM A TABLE IN THIS FILE. A hardcoded type→field map is a
* second source of truth and drifts silently the day a case is edited. This parses selector-evaluator.js
* and collects the `r.<field>` reads inside each `case '<type>':` block, so a new field in a case is
* picked up without touching this gate. It is also how the non-obvious ones are caught: deaf_predicate
* reads wagf_region AND slug AND name; affinity_bloc reads rop1. Neither is guessable from the name.
*
* ⚠ LIMIT, stated so a green run is not over-read: it matches `const fields = '…'` string literals. A
* fetch that builds its field list dynamically is NOT checked, and is reported as UNCHECKED rather than
* passed — absence of evidence is not evidence.
*
* --self-test plants a wagf_block selector against a fetch missing the field. Exit 0 clean · 1 findings.
*/
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
import { join, relative, resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
// ── the evaluator's own field reads, per selector type ──────────────────────────────────────────
export function fieldsByType(evaluatorSrc) {
const out = new Map()
// Split on `case '<type>':` and read each block up to the next case.
const re = /case\s+'([a-z_]+)'\s*:\s*\{/g
const marks = []
let m
while ((m = re.exec(evaluatorSrc))) marks.push({ type: m[1], at: m.index })
for (let i = 0; i < marks.length; i++) {
const body = evaluatorSrc.slice(marks[i].at, i + 1 < marks.length ? marks[i + 1].at : evaluatorSrc.length)
const fields = new Set()
for (const f of body.matchAll(/\br\.([a-zA-Z_][\w]*)/g)) fields.add(f[1])
out.set(marks[i].type, fields)
}
return out
}
/** Every field a selector tree needs, following union/nested selectors. */
export function fieldsForSelector(sel, byType, acc = new Set()) {
if (!sel || typeof sel !== 'object') return acc
const t = sel.type
if (t && byType.has(t)) for (const f of byType.get(t)) acc.add(f)
for (const child of sel.selectors || []) fieldsForSelector(child, byType, acc)
return acc
}
/** `const fields = 'a,b,c'` / `const _fields = '…'` occurrences, with their line. */
export function fetchFieldLists(src) {
const out = []
for (const m of src.matchAll(/const\s+_?fields\s*=\s*'([^']*)'/g)) {
out.push({ line: src.slice(0, m.index).split('\n').length, fields: new Set(m[1].split(',').map((s) => s.trim()).filter(Boolean)) })
}
// A dynamically-built list is reported, never silently passed. ⚠ The lookahead must reject a
// literal that is merely INDENTED differently — an earlier version matched the same
// lines it had already parsed and reported them as dynamic, which would have
// trained a reader to ignore the ⊘ marker. Anchor on the assignment actually not being a quote.
for (const m of src.matchAll(/const\s+_?fields\s*=\s*([^'\s])/g)) {
out.push({ line: src.slice(0, m.index).split('\n').length, dynamic: true })
}
return out
}
// ── SELF-TEST ───────────────────────────────────────────────────────────────────────────────────
const EVAL_FIXTURE = `
switch (selector.type) {
case 'country_code': { for (const r of rows) { if (r.country_code === want) {} } }
case 'wagf_block': { for (const r of rows) { if (r.wagf_block === want) {} } }
case 'deaf_predicate': { for (const r of rows) { if (containsDeaf(r.slug) || containsDeaf(r.name)) { if (regions.has(r.wagf_region)) {} } } }
}`
const CASES = [
['reads wagf_block from the evaluator', () => fieldsByType(EVAL_FIXTURE).get('wagf_block').has('wagf_block'), true],
['deaf_predicate reads 3 fields, not 1', () => fieldsByType(EVAL_FIXTURE).get('deaf_predicate').size, 3],
['union follows its children', () => {
const t = fieldsByType(EVAL_FIXTURE)
return [...fieldsForSelector({ type: 'union', selectors: [{ type: 'wagf_block' }, { type: 'country_code' }] }, t)].sort().join(',')
}, 'country_code,wagf_block'],
['BAD a fetch missing wagf_block is caught', () => {
const t = fieldsByType(EVAL_FIXTURE)
const need = fieldsForSelector({ type: 'wagf_block' }, t)
const have = fetchFieldLists("const fields = 'slug,name,country_code';")[0].fields
return [...need].filter((f) => !have.has(f)).join(',')
}, 'wagf_block'],
['GOOD a fetch carrying it passes', () => {
const t = fieldsByType(EVAL_FIXTURE)
const need = fieldsForSelector({ type: 'wagf_block' }, t)
const have = fetchFieldLists("const fields = 'slug,wagf_block';")[0].fields
return [...need].filter((f) => !have.has(f)).length
}, 0],
['a DYNAMIC field list is flagged, not passed', () => fetchFieldLists('const fields = buildFields();')[0].dynamic, true],
]
if (process.argv.includes('--self-test')) {
let ok = 0
for (const [name, run, want] of CASES) {
let got
try { got = run() } catch (e) { got = `ERR ${e.message}` }
const pass = got === want
console.log(` ${pass ? '✓' : '✗'} ${name} -> ${JSON.stringify(got)} (want ${JSON.stringify(want)})`)
if (pass) ok++
}
console.log(ok === CASES.length
? `SELECTOR-FIELDS SELF-TEST: PASS — ${ok}/${CASES.length}`
: `SELECTOR-FIELDS SELF-TEST: FAIL — ${ok}/${CASES.length}`)
process.exit(ok === CASES.length ? 0 : 1)
}
// ── RUN ─────────────────────────────────────────────────────────────────────────────────────────
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const DESIGNS = join(ROOT, 'plugin-designs')
let bad = 0, unchecked = 0
for (const bundle of existsSync(DESIGNS) ? readdirSync(DESIGNS) : []) {
const dir = join(DESIGNS, bundle)
if (!statSync(dir).isDirectory()) continue
const evalPath = join(dir, 'data/selector-evaluator.js')
const clustersPath = join(dir, 'data/clusters.json')
if (!existsSync(evalPath) || !existsSync(clustersPath)) continue
const byType = fieldsByType(readFileSync(evalPath, 'utf8'))
const c = JSON.parse(readFileSync(clustersPath, 'utf8'))
const zones = ['lists', 'deaf_lists', 'asia_lists'].flatMap((k) => c[k] || [])
const need = new Map() // field -> the zones that need it
for (const z of zones) for (const f of fieldsForSelector(z.selector, byType)) {
if (!need.has(f)) need.set(f, [])
need.get(f).push(z.id)
}
// Every .vue in the bundle that both fetches AND evaluates.
for (const file of readdirSync(dir).filter((f) => f.endsWith('.vue'))) {
const src = readFileSync(join(dir, file), 'utf8')
if (!/selector-evaluator|evaluate\(/.test(src)) continue
for (const fetchDef of fetchFieldLists(src)) {
const where = `${relative(ROOT, join(dir, file))}:${fetchDef.line}`
if (fetchDef.dynamic) { console.log(` ⊘ ${where} field list is built dynamically — UNCHECKED, not passed`); unchecked++; continue }
const missing = [...need.keys()].filter((f) => !fetchDef.fields.has(f))
for (const f of missing) {
console.log(` ✗ ${where} does not request '${f}', needed by: ${need.get(f).slice(0, 4).join(', ')}${need.get(f).length > 4 ? ` (+${need.get(f).length - 4})` : ''}`)
bad++
}
}
}
}
if (bad) {
console.log(`SELECTOR-FIELDS: FAIL — ${bad} fetch/selector mismatch(es). A zone whose selector field is absent resolves to NOBODY, or worse, to HALF.`)
process.exit(1)
}
console.log(`SELECTOR-FIELDS: PASS — every selector's fields are requested by every fetch that evaluates them${unchecked ? `, ${unchecked} dynamic list(s) unchecked` : ''}.`)
process.exit(0)

View file

@ -0,0 +1,92 @@
<script setup>
/**
* ListSelectorLegend.vue — the agent's VERTICAL LEGEND, used to SELECT a list and filter by it.
*
* @description The shared vertical legend wired as a SELECTOR: it renders the region → zone tree of
* a people-group list and emits the selection, so any dashboard can filter its groups by it.
*
* ★ IT WRAPS <SemanticTreeLegend>, IT DOES NOT REPLACE IT. That component is already the agent's
* vertical legend and already drives my-upg-100-list's 26-zone tapestry, so every fix it receives —
* theme-awareness, the progress bar, the mobile-sheet path, the scroll-to-selected watcher — keeps
* applying here. A second widget would have to re-earn all of it.
*
* ★ IT EMITS A ZONE ID, NOT A SELECTOR OR RESOLVED GROUPS (gospel's contract). An id is
* SERIALISABLE — it survives ?zone=, profile-config and a deep link, which is the chain this
* dashboard already resolves its list from — and a predicate is none of those. It also keeps this
* component promotable: the moment it emits a selector it has to know what it selects OVER, and the
* consuming dashboard holds four different row shapes across its tabs.
* And the join stays in ONE place. Resolving zones to groups means folding diacritics (the
* membership keys are ASCII while the province data is not — a naive join silently lost 495 groups),
* bridging provinces, and asserting a known total. That logic lives in the consumer; a second copy
* here would drift, and the drift would surface as a wrong COUNT, which nobody notices.
* ★ AND IT NEVER DROPS A ZONE THE TREE FORGOT. Four joinable zones are absent from zone-regions.json
* today — guizhou, xinjiang (100 groups on its own), bihar, madhya-pradesh — so grouping by region
* loses them with no error and no empty state, just a shorter tree. Pass `allZoneIds` and they
* appear in an explicit bucket instead. The consumer said it plainly: better the legend look wrong
* than look fine.
*
* A REGION ROW SELECTS THE UNION OF ITS ZONES (dx-shepherd, provisional and reversible): clicking a
* parent means "everything under here", which is what a reader expects of a tree and what makes the
* top row useful as "all of this list".
*/
import { computed } from 'vue'
import SemanticTreeLegend from './SemanticTreeLegend.vue'
// The selection rule lives beside this file so it can be tested without mounting a component.
import { withUnassigned, selectedId } from './listSelection.js'
const props = defineProps({
// The region → zone tree, as built from the list's own zoning. Passed in rather than built here:
// the builder lives in the plugin that owns the zoning today, and a cross-plugin import is barred
// by check:no-cross-plugin-imports — so promoting it to library/ is a separate, coordinated move.
nodes: { type: Array, default: () => [] },
// Every zone the data can join, tree or no tree. Any id absent from `nodes` is rendered in an
// explicit bucket rather than dropped — see withUnassigned(). Omit it and the legend behaves as
// before, which is why this is a prop and not a rewrite.
allZoneIds: { type: Array, default: null },
unassignedLabel: { type: String, default: 'Unassigned' },
// Which list this tree belongs to. Emitted back with every selection so a consumer holding more
// than one list can tell them apart without tracking it itself.
listId: { type: String, default: '' },
title: { type: String, default: 'Select a list' },
// Which generation the legend opens on; passed through (country-posters needed this).
defaultTab: { type: Number, default: 0 },
columns: { type: Array, default: () => ['count'] },
columnLabel: { type: String, default: '' },
open: { type: Boolean, default: null },
})
const emit = defineEmits(['zone-change', 'update:open'])
// The tree the legend actually renders: the caller's, plus a bucket for any joinable zone it omits.
const treeNodes = computed(() => (
props.allZoneIds ? withUnassigned(props.nodes, props.allZoneIds, props.unassignedLabel) : (props.nodes || [])
))
const hasNodes = computed(() => treeNodes.value.length > 0)
function onSelect(node) {
// null is a real value — "cleared" — and the consumer needs it to drop its filter. Emitting
// nothing on clear would leave a dashboard stuck on the last zone with no way back.
emit('zone-change', selectedId(node))
}
</script>
<template>
<SemanticTreeLegend
v-if="hasNodes"
:nodes="treeNodes"
:title="title"
:default-tab="defaultTab"
:columns="columns"
:column-label="columnLabel"
:open="open"
@select="onSelect"
@update:open="$emit('update:open', $event)"
/>
<!-- No tree yet: say so rather than rendering an empty frame that looks like a broken legend. -->
<p v-else class="lsl-empty" role="status">{{ $t ? $t('legend.noList') : 'No list loaded.' }}</p>
</template>
<style scoped>
.lsl-empty { padding: 12px; font-size: 13px; color: var(--dx-muted, #6d766e); }
</style>

View file

@ -0,0 +1,70 @@
/**
* listSelection.js — what a vertical-legend row selects, and the zones a tree forgot.
*
* Extracted from ListSelectorLegend.vue so both rules can be tested without mounting a component.
* Small rules, large consequences: a dashboard filters thousands of rows by the first, and the
* second decides whether 100+ groups are reachable at all.
*/
/** Every leaf under a node, or the node itself when it is a leaf. */
export function leavesOf(node) {
if (!node) return []
const kids = node.children || node.nodes || []
if (!kids.length) return [node]
const out = []
for (const k of kids) out.push(...leavesOf(k))
return out
}
/** Every zone id present anywhere in a tree. */
export function zoneIdsIn(nodes) {
const out = new Set()
for (const n of nodes || []) for (const l of leavesOf(n)) if (l && l.id) out.add(String(l.id))
return out
}
export const UNASSIGNED_ID = '__unassigned__'
/**
* ★ A ZONE THE TREE DOES NOT CONTAIN MUST STILL BE REACHABLE. Four joinable zones are absent from
* zone-regions.json today — guizhou, xinjiang (100+ groups on its own), bihar, madhya-pradesh — so a
* legend that renders only the region tree DROPS them, silently, and a reader has no way to select
* groups that plainly exist. The fix is not to remember: it is to hand this function the full zone
* set and let it render the difference. A drop then becomes structurally impossible rather than a
* thing someone has to keep checking.
*
* Returns the tree unchanged when nothing is missing, so the bucket never appears without cause.
*
* @param {Array} nodes the region-grouped tree
* @param {Array<string>} allZoneIds every zone the data can join, tree or no tree
* @param {string} label what to call the bucket (the caller localises it)
*/
export function withUnassigned(nodes, allZoneIds, label = 'Unassigned') {
const present = zoneIdsIn(nodes)
const missing = (allZoneIds || []).map(String).filter((id) => !present.has(id))
if (!missing.length) return nodes || []
return [
...(nodes || []),
{
id: UNASSIGNED_ID,
label,
depth: 0,
// Each missing zone is a selectable leaf, not a lump: a reader wants xinjiang, not "the four
// zones nobody filed".
children: missing.map((id) => ({ id, label: id, zoneKey: id })),
},
]
}
/**
* The value the legend emits. gospel's contract: a single SERIALISABLE id, so it survives ?zone=,
* profile-config and a deep link, and stays usable across their four tab shapes.
*
* A leaf emits its zone id; a REGION row emits the region's own id (dx-shepherd: a region selects
* the union of its zones). The consumer expands a region id to its zones using the zone data it
* already holds — which is also where the diacritic-folding join lives, so there is no second copy
* of it here to drift.
*/
export function selectedId(node) {
return node && node.id != null ? String(node.id) : null
}

View file

@ -44,6 +44,7 @@
import { computed, reactive } from 'vue'
import { getLanguageFamilyColor, LANGUAGE_FAMILY_COLORS, canonicalFamilyName } from '../../colors/language-family.js'
import langFamilyByLanguage from '../../data/langFamilyByLanguage.json'
import { splitLanguageLabelParts } from '../../utils/languageLabel.js'
// ─────────────────────────────────────────────────────────────────────────────
// Field readers — defensive against the four shapes we currently see in the
@ -64,9 +65,14 @@ function unwrap(v) {
// "Arabic, Shihhi" instead of the lookup key "Shihhi Arabic".
// Strategy: reverse all comma-separated parts, join with space, then look up.
// For parenthetical names like "Ainu (China)", also try stripping the parenthetical.
//
// The split is shared with the packet's describeGroup, which needs the same un-inversion for PRINTED
// prose: see library/utils/languageLabel.js for why a top-level-only split is the correct one. The
// candidates below are unchanged -- a parenthesis-internal comma used to yield an unbalanced candidate
// that could never match a key, and now yields no candidate at all, which is the same miss.
function lookupFamilyFromLanguageLabel(label) {
if (!label || typeof label !== 'string') return null
const parts = label.split(',').map(s => s.trim()).filter(Boolean)
const parts = splitLanguageLabelParts(label)
if (parts.length >= 2) {
// Try full reversal: "Arabic, Shihhi" → "Shihhi Arabic"
const fullReversed = [...parts].reverse().join(' ')

View file

@ -233,9 +233,14 @@ export function usePosterPrints(opts = {}) {
set(item, kind, 'fetch', 'fetching people groups', 30);
try {
const data = await rows(item);
const [{ createPacketRenderer }, strings] = await Promise.all([
// `christian` is the flock-wide evangelical share behind the packet's %-evangelical page. The
// Node generator passes it (packet.mjs reads data/people-groups/christian-share.json); this
// path did not, so buildPacket's christianPage returned '' and a browser print had one sheet
// fewer with nothing to say so. Same seam as strings: a host may supply its own figure.
const [{ createPacketRenderer }, strings, christian] = await Promise.all([
import('../packet/packet-render.js'),
opts.strings ? opts.strings() : import('../scripts/posters/packet-strings.json').then((m) => m.default || m),
opts.christian ? opts.christian() : import('../../data/people-groups/christian-share.json').then((m) => m.default || m),
]);
const R = createPacketRenderer({ strings, lang: lang() });
// Photos stay as URLs. The Node generator base64-inlines them so its file is
@ -245,7 +250,7 @@ export function usePosterPrints(opts = {}) {
data.filter((g) => g.image_url).map((g) => [g.slug, g.image_url]),
);
set(item, kind, 'render', `printing ${data.length} people groups`, 70);
openPrintFrame(R.buildPacket(item, data, photos, mapSvg, funnel), { autoprint: true });
openPrintFrame(R.buildPacket(item, data, photos, mapSvg, funnel, christian), { autoprint: true });
done(item, kind);
} catch (e) { fail(item, kind, e); }
}

View file

@ -19,7 +19,7 @@
* is in library/ rather than in one plugin.
*
* The evaluator covers the expression subset this product's filters use — all/any/!/has/comparisons/
* in/match, plus get/literal/coalesce/to-number. Anything else evaluates false rather than throwing:
* in/match, plus get/literal/feature-state/coalesce/to-number. Anything else evaluates false rather than throwing:
* a packet that prints fewer rows is recoverable, a map that throws mid-print is not.
*/
@ -28,65 +28,72 @@
// regions silently no-op when the user is zoomed in. We evaluate the Mapbox
// filter expression against the full source data instead, so fitBounds can
// zoom OUT to a different region or IN to a tight cluster uniformly.
export function resolveExpr(expr, props) {
export function resolveExpr(expr, props, state) {
if (!Array.isArray(expr)) return expr;
const op = expr[0];
switch (op) {
case 'get': return props?.[expr[1]];
case 'literal': return expr[1];
// ★ THE LIVE VALUE, NOT THE ONE FROZEN AT LOAD. A pin's prayer count arrives twice: as a GeoJSON
// property at initial load, and thereafter through setFeatureState() as polling updates it.
// prayer-progress.js:94 colours from ['feature-state','peoplePraying'] once polling is on, so a
// reader looking at 699 lit "Has Prayer" pins is looking at feature-state. Without this case the
// expression resolved to undefined, to-number defaulted it to 0, and the packet selected only
// the pins whose FROZEN property happened to be > 0 — 50 of 997 where the legend said 699.
case 'feature-state': return state == null ? undefined : state[expr[1]];
case 'has': return props != null && expr[1] in props;
case '!has': return props == null || !(expr[1] in props);
case '!': return !resolveExpr(expr[1], props);
case '!': return !resolveExpr(expr[1], props, state);
case 'coalesce': {
for (let i = 1; i < expr.length; i++) {
const v = resolveExpr(expr[i], props);
const v = resolveExpr(expr[i], props, state);
if (v != null) return v;
}
return null;
}
case 'to-number': {
const v = resolveExpr(expr[1], props);
const v = resolveExpr(expr[1], props, state);
const n = Number(v);
if (Number.isFinite(n)) return n;
if (expr.length > 2) {
const fb = Number(resolveExpr(expr[2], props));
const fb = Number(resolveExpr(expr[2], props, state));
return Number.isFinite(fb) ? fb : 0;
}
return NaN;
}
case 'to-string': {
const v = resolveExpr(expr[1], props);
const v = resolveExpr(expr[1], props, state);
return v == null ? '' : String(v);
}
case 'to-boolean': return Boolean(resolveExpr(expr[1], props));
case 'to-boolean': return Boolean(resolveExpr(expr[1], props, state));
case 'slice': {
const s = resolveExpr(expr[1], props);
const i = resolveExpr(expr[2], props);
const j = expr.length > 3 ? resolveExpr(expr[3], props) : undefined;
const s = resolveExpr(expr[1], props, state);
const i = resolveExpr(expr[2], props, state);
const j = expr.length > 3 ? resolveExpr(expr[3], props, state) : undefined;
if (typeof s !== 'string') return '';
return j != null ? s.slice(i, j) : s.slice(i);
}
default: return expr;
}
}
export function matchesExpression(filter, props) {
export function matchesExpression(filter, props, state) {
if (filter === true) return true;
if (filter === false) return false;
if (!Array.isArray(filter)) return Boolean(filter);
const op = filter[0];
if (op === 'all') {
for (let i = 1; i < filter.length; i++) {
if (!matchesExpression(filter[i], props)) return false;
if (!matchesExpression(filter[i], props, state)) return false;
}
return true;
}
if (op === 'any') {
for (let i = 1; i < filter.length; i++) {
if (matchesExpression(filter[i], props)) return true;
if (matchesExpression(filter[i], props, state)) return true;
}
return false;
}
if (op === '!') return !matchesExpression(filter[1], props);
if (op === '!') return !matchesExpression(filter[1], props, state);
if (op === 'has') return props != null && filter[1] in props;
if (op === '!has') return props == null || !(filter[1] in props);
@ -95,8 +102,8 @@ export function matchesExpression(filter, props) {
// Legacy form: ['==', 'propName', value] (no ['get', ...])
const left = (typeof filter[1] === 'string')
? props?.[filter[1]]
: resolveExpr(filter[1], props);
const right = resolveExpr(filter[2], props);
: resolveExpr(filter[1], props, state);
const right = resolveExpr(filter[2], props, state);
switch (op) {
case '==': return left === right || (left == null && right == null);
case '!=': return !(left === right || (left == null && right == null));
@ -107,21 +114,21 @@ export function matchesExpression(filter, props) {
}
}
if (op === 'in') {
const v = resolveExpr(filter[1], props);
const haystack = resolveExpr(filter[2], props);
const v = resolveExpr(filter[1], props, state);
const haystack = resolveExpr(filter[2], props, state);
if (Array.isArray(haystack)) return haystack.includes(v);
if (typeof haystack === 'string') return haystack.includes(v);
return false;
}
if (op === 'match') {
const input = resolveExpr(filter[1], props);
const input = resolveExpr(filter[1], props, state);
const lastIdx = filter.length - 1;
for (let i = 2; i < lastIdx; i += 2) {
const labelRaw = filter[i];
const labels = Array.isArray(labelRaw) ? labelRaw : [labelRaw];
if (labels.includes(input)) return Boolean(resolveExpr(filter[i + 1], props));
if (labels.includes(input)) return Boolean(resolveExpr(filter[i + 1], props, state));
}
return Boolean(resolveExpr(filter[lastIdx], props));
return Boolean(resolveExpr(filter[lastIdx], props, state));
}
return false;
}
@ -172,11 +179,23 @@ export function readSourceFeatures(map, sourceId) {
export function selectedPinIds(map, { sourceId = 'language-families', expressions = [], idKey = 'uniqueId' } = {}) {
const ids = new Set()
const exprs = (expressions || []).filter((e) => e != null && e !== true)
// ★ FEATURE-STATE IS READ PER PIN, and it has to come off the MAP rather than off the feature: the
// GeoJSON the source holds is what was loaded, and setFeatureState writes to a separate per-feature
// store that never touches it. useMapLayers adds this source with promoteId:'uniqueId', so the id
// feature-state is keyed by IS props[idKey] -- if a caller ever passes a different idKey the lookup
// yields {} and the evaluation falls back to the loaded properties, which is the old behaviour and
// not a new failure. getFeatureState throws on an unknown source, so it is guarded once, not per pin.
const canReadState = typeof map?.getFeatureState === 'function'
const stateOf = (id) => {
if (!canReadState || id == null) return undefined
try { return map.getFeatureState({ source: sourceId, id }) } catch (_) { return undefined }
}
for (const f of readSourceFeatures(map, sourceId)) {
const props = f && f.properties
if (!props) continue
if (exprs.length && !exprs.every((e) => matchesExpression(e, props))) continue
const id = props[idKey]
const state = stateOf(id)
if (exprs.length && !exprs.every((e) => matchesExpression(e, props, state))) continue
if (id != null) ids.add(String(id))
}
return ids

View file

@ -0,0 +1,131 @@
/**
* describeGroup.js — one localized sentence pair describing a people group, built ONLY from API fields.
*
* DRIVER BRIEF 2026-09-28 §A1.5: the packet's profile rows carry "a short GENERATED description built
* from the API variables, localized: how lost/unreached they are, where (country), what language they
* speak". ~20 rows per page, so it has to be short, and it has to be right in eleven languages.
*
* PURE. (group, locale) -> string. No refs, no map, no fetch. The strings object is the third argument
* so a test can inject one; it defaults to the packet's own catalogue, which is what the renderer
* already loads (usePosterPrints.js:209).
*
* ★ THE LEVEL IS PHRASED FROM GSEC, A NUMBER — never from EvangelicalLevel or GSECDescription, which are
* ENGLISH PROSE the API and the CSV hand us ("2% or Greater but Less than 5%"). Interpolating either
* leaves the widest column of every non-English packet in English, which is the leak class this repo
* spent 2026-09-28 removing. GSEC is 0-6 and present on all 12,379 rows, so it maps to a key per level.
*
* ★ AND IT DOES NOT SAY "unreached" OR "frontier". The packet's funnel page already defines both
* (packet-strings funnel_unreached "under 2% evangelical", funnel_frontier "0.1% or less"). GSEC carries
* the 2% line — levels 0-3 are exactly "fewer than 2%", 7,307 of 12,379 rows — but NOTHING in GSEC
* encodes 0.1%: no GSECDescription or GSECLongDescription mentions it (checked by upg100 and by me,
* independently). Deriving "frontier" from GSEC would assert something the field does not carry, and
* §E of the same brief has the UPG/FPG calculation open. So the wording agrees with the funnel's 2%
* line and stops there; when E lands it is one more key, not a rewrite.
*
* ★ TWO SENTENCES, NOT ONE, AND THAT IS WHY THE FALLBACKS WORK. upg100 authored seven whole sentences
* whose FIRST half is identical in all seven, deliberately, so a translator writes it once. Splitting
* that shared half into its own key makes the saving real instead of aspirational, and it is the only
* shape in which their own fallback rule can be expressed: drop the clause, never blank the row. Their
* English is preserved verbatim; only the seam moved. Each key is a COMPLETE sentence — the thing rule 1
* forbids is assembling one sentence from fragments, which is how a Hindi verb ends up in the wrong
* place; two finished sentences in sequence carry no such risk.
*
* KEYS (packet-strings.json, beside funnel_*):
* desc_where_both "The {people} live in {country} and speak {language}."
* desc_where_country "The {people} live in {country}." — language unknown (1 row of 12,379)
* desc_where_language "The {people} speak {language}." — country unknown
* desc_gsec_0 … _6 the level sentence, one per GSEC value
*/
import { naturalLanguageLabel } from '../utils/languageLabel.js'
const LEVELS = [0, 1, 2, 3, 4, 5, 6]
/** The first field present, trimmed; '' when none is. Accepts both the API and normalized spellings. */
const pick = (group, ...names) => {
for (const n of names) {
const v = group && group[n]
if (v !== undefined && v !== null && String(v).trim() !== '') return String(v).trim()
}
return ''
}
const fill = (tpl, params) =>
String(tpl).replace(/\{(\w+)\}/g, (m, k) => (params[k] !== undefined ? params[k] : m))
/**
* Cut at a WORD boundary and mark it. Physical width is the real budget (the profile column is ~1/5 of
* a card in packet-render's 2x10 grid), so the caller measures and decides; this only guarantees that a
* limit, when given, is honoured without slicing a word in half. No limit by default.
*/
const clip = (s, max) => {
if (!max || s.length <= max) return s
const cut = s.slice(0, max - 1)
const sp = cut.lastIndexOf(' ')
return (sp > max * 0.6 ? cut.slice(0, sp) : cut).trimEnd() + '…'
}
/**
* @param {Object} group an API/normalized people-group row
* @param {string} locale e.g. 'ar'; falls back to 'en' when the catalogue has no such block
* @param {Object} [opts]
* @param {Object} [opts.strings] the packet strings object ({ en: {...}, ar: {...} })
* @param {function} [opts.countryName] (iso3, locale) => localized country name; see below for why
* this is injected and what happens when it is not
* @param {number} [opts.maxChars] optional hard cap, cut at a word boundary
* @returns {string} '' when the group carries none of the three facts
*/
export function describeGroup(group, locale, opts = {}) {
const strings = opts.strings || {}
const lc = String(locale || 'en').split('-')[0].toLowerCase()
const block = strings[lc] || strings.en || {}
const en = strings.en || {}
const key = (k) => block[k] || en[k] || ''
const people = pick(group, 'name', 'Name', 'peopleName')
// The country is resolved to the READER's language from its code — the packet fetch carries
// country_code, and country_code_label is the API's ENGLISH name, which is a leak in a Hindi packet.
//
// ★ THE RESOLVER IS INJECTED RATHER THAN IMPORTED, and that is a testability decision, not a style
// one. library/utils/countryName.js is reachable only through the Vite '@map/' alias and imports two
// JSON files without an import attribute — both fine in a bundle, both fatal to `node internal/
// check-describe-group.mjs`, which is the gate that proves this function right in eleven languages.
// Importing it would make this module unloadable by its own test. So the packet renderer, which runs
// inside Vite and can import it, passes it in: opts.countryName = (iso, locale) => string.
//
// ★ AND THE FALLBACK IS THE SAME LADDER countryName.js WALKS, so forgetting to inject degrades the
// way that file already documents rather than blanking the clause: localized name → the canonical
// English name we already show in our own tables → the alpha-3 code, so something identifiable
// always renders. A missing injection is an English country name, never an empty row.
const iso = pick(group, 'country_code', 'countryIso', 'ISOAlpha3', 'countryCode')
const country = (iso && opts.countryName ? opts.countryName(iso, lc) : '')
|| pick(group, 'country_code_label', 'country', 'CountryName', 'countryName')
|| iso
// A language NAME is a proper noun; it is not translated, and there is no source that would.
//
// ★ IT IS primary_language_LABEL, NOT primary_language. primary_language is in normalize.js's
// NESTED_FIELDS, so the API's { value, label } is flattened to its VALUE -- an ISO 639-3 code. Reading
// it would print "and speak abf". The label is the name. There is deliberately no fall-back to the
// code: upg100's rule for a missing field is DROP THE CLAUSE, never blank the row, and a bare code is
// worse for a reader than no clause at all -- so an unresolvable language falls to desc_where_country.
//
// ★ AND THE LABEL IS INDEX-ORDERED: the API hands us "Arabic, Shihhi" for what reads "Shihhi Arabic".
// 725 of 12,379 rows carry one. naturalLanguageLabel un-inverts it, sharing its split with the
// language-family legend so the two readings of the same field cannot drift apart.
const language = naturalLanguageLabel(
pick(group, 'primary_language_label', 'LanguageName', 'languageName'),
)
const whereKey = language && country ? 'desc_where_both'
: country ? 'desc_where_country'
: language ? 'desc_where_language' : ''
const where = whereKey && people ? fill(key(whereKey), { people, country, language }) : ''
// imb_gsec is the API's spelling (upg100 added it to the packet fetch); GSEC is the CSV's.
const gsecRaw = pick(group, 'imb_gsec', 'gsec', 'GSEC')
const gsec = gsecRaw === '' ? null : Number(gsecRaw)
const level = LEVELS.includes(gsec) ? key(`desc_gsec_${gsec}`) : ''
return clip([where, level].filter(Boolean).join(' ').replace(/\s+/g, ' ').trim(), opts.maxChars)
}
export default describeGroup

View file

@ -0,0 +1,67 @@
/**
* languageLabel.js — turn the API's comma-inverted language label into the natural reading.
*
* The pray-tools API returns primary_language.label in an INDEX ordering, surname-first:
* "Arabic, Shihhi" where a reader expects "Shihhi Arabic". It is the ordering a printed language
* catalogue uses so that every Arabic variety files together, and it is wrong in a sentence.
* 725 of the 12,379 rows carry one (measured on data/people-groups/people_groups.csv, LanguageName,
* the same vocabulary), so this is not an edge case: it is one row in seventeen.
*
* ★ THE SPLIT IS PARENTHESIS-AWARE, AND THAT IS THE WHOLE REASON THIS IS A MODULE. 13 names carry a
* comma INSIDE a parenthetical -- "Boano (Indonesia, Central Sulawesi)", "Kango (DRC, Tshopo district)",
* "Mari (PNG, Madang Province)". A naive label.split(',') reverses those into
* "Central Sulawesi) Boano (Indonesia": unbalanced, unreadable, and printed. The comma in those names
* is not an inversion marker, it separates a disambiguating region, so only a TOP-LEVEL comma counts.
*
* ★ THIS IS HARMLESS IN A LOOKUP AND FATAL IN A SENTENCE, which is why the rule lives here rather than
* staying where it was. useLanguageFamilyLegendData built the same reversal to produce a KEY for
* langFamilyByLanguage, where a garbage candidate simply misses and the next one is tried -- the
* corruption never reaches a reader. describeGroup interpolates the result into printed prose, where
* there is no such safety net. Both now split through splitLanguageLabelParts so the two cannot drift.
*/
/**
* Split a label on its TOP-LEVEL commas — commas inside parentheses are text, not separators.
* "Arabic, Shihhi" -> ['Arabic', 'Shihhi']; "Boano (Indonesia, Central Sulawesi)" -> one part.
* @param {string} label
* @returns {string[]} trimmed, empty parts dropped ("Kango (DRC, , Bas-Uélé district)" is real data)
*/
export function splitLanguageLabelParts(label) {
if (!label || typeof label !== 'string') return []
const parts = []
let depth = 0
let cur = ''
for (const ch of label) {
if (ch === '(') depth++
else if (ch === ')') depth = Math.max(0, depth - 1)
if (ch === ',' && depth === 0) {
parts.push(cur)
cur = ''
} else {
cur += ch
}
}
parts.push(cur)
return parts.map((s) => s.trim()).filter(Boolean)
}
/**
* The natural reading of a language label, for display.
* Un-inverted when the label is inverted; returned unchanged when it is not, so it is safe to call on
* any label from any source. A trailing parenthetical stays at the END, where it qualifies the whole
* name: "Naga, Tangkhul (India)" -> "Tangkhul Naga (India)", not "Tangkhul (India) Naga".
* @param {string} label
* @returns {string} '' for a missing or non-string label
*/
export function naturalLanguageLabel(label) {
if (!label || typeof label !== 'string') return ''
const parts = splitLanguageLabelParts(label)
if (parts.length < 2) return label.trim()
const last = parts[parts.length - 1]
const paren = last.match(/\s*(\(.*\))$/)
if (paren) parts[parts.length - 1] = last.slice(0, paren.index).trim()
const reversed = parts.filter(Boolean).reverse().join(' ')
return paren ? `${reversed} ${paren[1]}` : reversed
}
export default naturalLanguageLabel

View file

@ -51,6 +51,7 @@
"check:dashboard-geocoder-provider": "node plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs",
"check:deck-click": "node internal/check-deck-click.mjs",
"check:dep-names": "node internal/check-dependency-names.mjs",
"check:describe-group": "node internal/check-describe-group.mjs",
"check:descriptions": "node internal/check-profile-descriptions.mjs",
"check:dev-assets": "node internal/check-dev-assets.mjs",
"check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs",
@ -140,6 +141,7 @@
"check:link-rot": "node internal/check-link-rot-sample.mjs",
"check:linked-vocabulary": "node plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs",
"check:list-prop-unit": "node internal/check-list-prop-unit.mjs",
"check:list-selection": "node internal/check-list-selection.mjs",
"check:locale-reachable": "node internal/check-locale-reachable.mjs",
"check:manifest-covers": "node internal/check-manifest-covers-discovery.mjs",
"check:map-data-painted": "node internal/check-map-data-painted.mjs",
@ -182,6 +184,8 @@
"check:packet-render": "node internal/check-packet-render.mjs",
"check:packet-render:expect-funnel": "node internal/check-packet-render.mjs --expect-funnel",
"check:series-isolation": "node internal/check-series-isolation.mjs",
"check:selector-fields": "node internal/check-selector-fields.mjs",
"check:selector-fields:self-test": "node internal/check-selector-fields.mjs --self-test",
"check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs",
"check:packets-all": "node internal/check-packets-all.mjs",
"check:parse": "node internal/inline-parse-check.mjs",
@ -263,7 +267,7 @@
"fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.mjs",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:frozen-labels",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs",
@ -339,6 +343,7 @@
"check:dashboard-filter-a11y": "the dashboard's cross-filter is operable and audible: every filter chip is a <button type=\"button\"> with an aria-label naming the ACTION (a filter that can be set by keyboard must be clearable by keyboard), the shell carries exactly ONE always-rendered polite role=\"status\" region so a brush that changes a number far from the control is announced once rather than six times or not at all, and no resize handle's accessible name is a raw English literal. AST over the parsed templates plus the built bundle — no browser (rega's alone).",
"check:dashboard-geocoder-provider": "the research dashboard leaf at ?provider=maplibre makes ZERO requests to api.mapbox.com, and its search bars still suggest (built tree; browser gate). It was kept out of gates:post while the generated leaf hard-linked the Mapbox engine; the leaf now loads only the engine it is asked for.",
"check:dep-names": "every declared dep is a REAL package name (a rename once rewrote @capacitor/* and broke bun install in a clone)",
"check:describe-group": "the prayer packet's generated profile sentence (library/packets/describeGroup.js): each GSEC level states its own fact, and an index-ordered language name (\"Arabic, Moroccan\") is un-inverted by library/utils/languageLabel.js without shredding a comma inside parentheses, the 13 names the old split printed reversed. Headless.",
"check:descriptions": "every shipped profile must say what it is (reads the BUILT manifest)",
"check:done-evidence": "a card may not reach status:done with NO bluf AND NO fields — the Doxa team inherits this board and an evidence-less 'done' cannot be reconciled against the code. 10 pre-existing are baselined by id; the list should only ever SHRINK.",
"check:dxlife": "⚠ NOT WAVE-SAFE, deliberately unwired: exit codes 0/1/2 are a DOCUMENTED contract (0 unchanged · 1 drift · 2 clone not found), and run-parallel grades any non-zero as FAILURE — so a checkout without the sibling /root-dx-life clone goes red on a precondition. Changing the contract could break another consumer; hand-run it, or give it a recognised SKIP first.",
@ -390,6 +395,7 @@
"check:library-index": "the DERIVED library menu must not DRIFT from library/. REGENERATES the index into a throwaway tree (the generator, unmodified, over a symlinked library/) and diffs the BYTES — it used to compare mtimes, so `touch` or a rebase turned it red on a byte-identical doc. Fix: bun run library-index",
"check:link-rot": "re-tests a stratified sample of links we already VERIFIED, because nothing else ever re-tests one: a publisher can retire a page and the map keeps offering it, still labelled 'link checked'. THREE BANDS, each measured rather than assumed, each with its controls in band every run: plain STATUS where a status is evidence (jesusfilm.org among them — it does 404 on the /watch/jesus/<slug> shape the map ships, and the old exclusion was inherited from the retired ?languageId= URL); API-MAPPED for live.bible.is, whose page answers 200 for any fileset while /api/bibles/<id> answers 404; and REDIRECT-LANDING for fivefish.org, where a published code lands on its numbered language page and an unknown one is swept to /regions. If a control misbehaves it reports PROVED NOTHING rather than a pass. Only knowgod.com is left out — byte-identical page for any URL; the verifier proves those through Cru's API. ~3 minutes, network.",
"check:linked-vocabulary": "the two halves of research-map-plus-grid must mean the SAME THING by a religion family. The map filters by the legend's FIRST-LETTER key ('B'), the grid by a DERIVED family name ('Buddhism'), and both read ONE shared table (library/colors/religion.js) in opposite directions - so a wrong link stays invisible: both halves look filtered and show different peoples. Compares the total reached over the composite's wire against the total the grid gives for the same facet on its own, and runs the MUTANT first (the legend key instead of the name), failing as NO-TEETH if the wrong mapping scores the same as the right one.",
"check:list-selection": "what a vertical-legend row emits and the zones a region tree forgets, in Node with no mount: a leaf emits its zone id, a region emits its own, a cleared selection emits null, and every value is a serialisable string so it survives ?zone= and a deep link. Then the part that earns it: the four joinable UPG-100 zones missing from zone-regions.json (xinjiang among them, 100 groups) get an explicit selectable bucket, a NEGATIVE CONTROL proves the unaugmented tree really does drop them, ticking every region is shown to reach FEWER zones than exist (so a select-all must sweep zones, not regions), and a zone whose members do not resolve stays selectable and empty instead of vanishing.",
"check:list-prop-unit": "the people-group LIST prop, measured in Node with no browser: the ladder (profile-config list > ?list= > window.MAP_LIST > doxa) and aliases, the default list untouched and unfetched, a list keeping exactly its membership, an unloadable list failing closed (no rows, never DOXA's whole list), and every imb list having a membership file that matches its registry filter.",
"check:manifest-covers": "every DISCOVERED (non-gated) bundle must be in the built manifest",
"check:map-attribution": "the map credit line names the basemap of the engine that drew the map: under MapLibre OpenFreeMap + OpenMapTiles + OpenStreetMap contributors (never Mapbox), under Mapbox Mapbox + OpenStreetMap; geoBoundaries on both. Headless.",
@ -439,6 +445,7 @@
"check:religion-label-not-value": "the library's two RELIGION_FAMILIES sites (useLegendData, useSearch) show religionFamilyLabel() to the reader, while the value a filter or search matches on stays the canonical English family name: a translated label must never become a wire value.",
"check:scrim-viewport": "a full-screen SCRIM must cover the VIEWPORT, not just the content pane. A position:fixed;inset:0 scrim is PANE-relative wherever an ancestor sets contain/transform — VerticalTabsLayout sets contain:layout on .vtl-content — so a scrim dimmed 1024x793 @ (256,107) at a 1280x900 viewport and left the 256px rail CLICKABLE behind a modal. A source grep cannot see it: the declaration is correct, the ANCESTOR is the variable. Needs `bun run dev`; browser gate, not in build:unlocked.",
"check:scrim-viewport:self-test": "reverts each scrim to plain inset:0 and requires EVERY case to FAIL — a gate that cannot fail is worse than no gate, and this one measures a property that looks fine in a screenshot either way.",
"check:selector-fields": "every UPG-100 selector's fields, read FROM the evaluator (not a table in this file), must be requested by every fetch that evaluates it. A missing field matches nothing and prints an EMPTY packet with no error: eight zones did, until the fetch carried wagf_region/wagf_block. Headless; in gates:pre. :self-test has 6 cases.",
"check:setup-order": "a line's POSITION in a setup block is not the MOMENT it runs, and two P0s from the agent's console came from that gap. ARM A: `watch(..., { immediate: true })` runs during setup, so anything it reaches must be declared ABOVE it - research-map.vue threw `Cannot access '_lastFitKey' before initialization` and blanked the whole map, because the watcher called a HOISTED function 350 lines down whose own constants were still in their temporal dead zone. A hoisted function promises that its NAME resolves early, not that its BODY can run early, and that asymmetry is invisible from both ends. ARM B: a lifecycle registrar closes over Vue's current instance, which exists only while setup is on the stack, so `onMounted(async () => { await init(); onBeforeUnmount(clean) })` drops the registration silently - DashboardMapPanel.vue leaked a ResizeObserver on document.body plus a window listener, warning only, feature working, and research-map.vue had a second copy this gate found. The fix is never 'move the line': declare the handle at setup scope and tear it down in a hook registered before the first await. Its own false positives are the interesting part - it went 6 hits -> 1 over three rounds, and every one it dropped was prose or shadowing read as code: `onBeforeUnmount` inside a console.error STRING, `table` inside `new Error(`table not found (${r.status})`)` in four files, and a `const [pins, byCountry]` destructured INSIDE the function, shadowing a computed 220 lines away. So it blanks comments, string text and template text (keeping ${...}, which is code) and subtracts every name a span declares for itself. Self-tests on both shipped defects, both fixes, and all three decoys; exits 3 if any arm goes blind.",
"check:sfc-parse": "every .vue script block must parse as the BUILD parses it — catches the LIVE-CSS backtick that check:template-literal cannot see (it reads CSS comments only). 0 false positives on 83 files, where 3 lookahead heuristics scored 98 and 136.",
"check:shadow-css-parses": "the CSS we assemble as JS STRINGS must parse as CSS — brace balance and declarations stranded outside any rule. check:sfc-parse proves only that the JS parses, so it passed a stylesheet with 9 orphaned declarations and braces at 12 open / 21 close (REGLIB batch 1); nothing else in the tree validates CSS syntax. Reads every useShadowStyles() argument off the @babel/parser AST the build already uses, because three hand-rolled lexers each produced a confident FALSE RED: a /* CSS comment */ read as a declaration, an apostrophe in a .vue prose header opening a phantom string literal, and a regex literal containing a quote. 9 self-tests run inline on every invocation, including the exact batch-1 damage, so it cannot silently stop being able to fail.",

View file

@ -70,7 +70,7 @@ const accent = computed(() => config.value.accent || '#8b5cf6')
// `title` still wins — that is what the config key is for — but an absent one now resolves through
// the catalogue rather than through a literal. The English wording is unchanged: this is the
// shepherd's standing rule that translating EXISTING wording is not a product decision.
const heading = computed(() => config.value.title || tStatic('dashboard.bp_title') || 'Big Picture — Unified Research Dashboard')
// ★ THE LOCALE THE HOST ASKED FOR, AND THE ONE ELEMENT ON THE PAGE THAT CAN ACT ON IT.
// MEASURED with check:host-locale-contract: this leaf drew 0 Arabic characters for
// profile-config {"locale":"ar"} against 273 for the same page reached with ?lang=ar. Its siblings
@ -86,6 +86,17 @@ const hostLang = computed(() => {
})
watchEffect(() => { if (hostLang.value) setRuntimeLocale(hostLang.value) })
// ★ THE LOCALE IS PASSED, NOT ASSUMED, AND THAT IS WHAT KEEPS THIS LIVE. tStatic resolves through
// getRuntimeLocale() at CALL time, but a computed only calls again when a REACTIVE dependency changes —
// and config.value does not change when the host switches language. So this heading resolved once and
// stayed in the boot language while everything around it moved (upg100 measured it; check:frozen-labels
// catches the shape). hostLang IS reactive, so reading it both re-runs this computed and tells tStatic
// which catalogue to read. useI18n is not an option here: this bundle installs no i18n (index.js), which
// is why the whole element reads through tStatic in the first place.
const heading = computed(() => config.value.title
|| tStatic('dashboard.bp_title', hostLang.value || undefined)
|| 'Big Picture — Unified Research Dashboard')
// ★ RTL IS A DIRECTION, NOT A TRANSLATION — the half this element was still missing.
// Its three sibling composites (dashboard-plus-grid, research-map-plus-grid) and the grid itself all
// reflect `dir` onto their custom-element host; this one did not, so a host asking for Arabic got

View file

@ -1136,11 +1136,26 @@ function prewarmRegionPaint(m) {
// what my first version did.
const readsFeature = (e) => Array.isArray(e)
&& (e[0] === 'get' || e[0] === 'has' || e[0] === 'feature-state' || e.some(readsFeature))
// ★ AND A ZOOM EXPRESSION MUST NOT BE WRAPPED, WHICH IS THE OPPOSITE OF WHAT THE NOTE ABOVE ARGUED.
// I reasoned that a zoom interpolate is an array but not data-driven, so skipping it would leave a
// variant to compile on the first gesture — true, and beside the point, because the wrap is not legal.
// useMapLayers.js:319 already carries the rule, written for the pin ring: zoom interpolation is kept
// by putting the 'case' in the interpolate OUTPUTS; "the reverse (zoom nested inside a 'case') is what
// breaks the layer, so don't do that." ['case', cond, ['interpolate',['linear'],['zoom'],…], …] is
// exactly that reverse. Mapbox then classifies the property as composite and reaches for an
// interpolation factor the expression cannot supply — the class of
// this.expression.interpolationFactor is not a function
// rega saw once on Mapbox at L54, on the clear → other-row path. A missed pre-warm costs a frame; an
// illegal paint costs the layer, so when the two rules disagree this one wins.
//
// exp-shepherd's port skips ANY array and is immune to this by accident; the difference between us
// was never about zoom expressions being data-driven, it was about which mistake is recoverable.
const readsZoom = (e) => Array.isArray(e) && (e[0] === 'zoom' || e.some(readsZoom))
const warm = (layer, prop) => {
if (!m.getLayer(layer)) return
const cur = m.getPaintProperty(layer, prop)
if (cur === undefined || cur === null) return
if (readsFeature(cur)) return
if (readsFeature(cur) || readsZoom(cur)) return
m.setPaintProperty(layer, prop, wrap(cur))
}
try {

View file

@ -1366,7 +1366,15 @@ async function packetRecords(zone) {
// API ('2% or Greater but Less than 5%'), so interpolating it would leave every non-English packet
// half-English in its widest column (sr measured this across 12,379 rows). primary_language is the
// same field poster-wall already fetches (:171), so this adds no new API surface.
const fields = 'slug,name,country_code,population,religion,image_url,primary_language,imb_gsec';
// ★ wagf_region + wagf_block ARE SELECTOR INPUTS, not display fields. SIX of the 27 printable
// zones select on them — west-africa / east-africa / central-africa (wagf_block), europe
// (wagf_region), southern-africa-islands (union of two wagf_block) and oceania-indonesia (union of
// wagf_region + country_code). Without them here, evaluate() matched NOTHING on packet rows and
// those packets printed EMPTY, while the same selectors resolved correctly on the map — whose
// fetch (:1988) has always carried both. Measured: a wagf_block selector returns 0 members on
// packet rows and 1 on map rows. oceania-indonesia was the worst of the six because its union
// still matched IDN, so it printed a PLAUSIBLE packet that was silently missing all of Oceania.
const fields = 'slug,name,country_code,wagf_region,wagf_block,population,religion,image_url,primary_language,imb_gsec';
const resp = await fetch(`${API_BASE}/api/people-groups/list?fields=${fields}&lang=${packetLang()}`,
{ cache: 'no-store' });
if (!resp.ok) throw new Error(`people-groups fetch failed (${resp.status})`);
@ -1597,7 +1605,15 @@ async function selectionAsList() {
// API ('2% or Greater but Less than 5%'), so interpolating it would leave every non-English packet
// half-English in its widest column (sr measured this across 12,379 rows). primary_language is the
// same field poster-wall already fetches (:171), so this adds no new API surface.
const fields = 'slug,name,country_code,population,religion,image_url,primary_language,imb_gsec';
// ★ wagf_region + wagf_block ARE SELECTOR INPUTS, not display fields. SIX of the 27 printable
// zones select on them — west-africa / east-africa / central-africa (wagf_block), europe
// (wagf_region), southern-africa-islands (union of two wagf_block) and oceania-indonesia (union of
// wagf_region + country_code). Without them here, evaluate() matched NOTHING on packet rows and
// those packets printed EMPTY, while the same selectors resolved correctly on the map — whose
// fetch (:1988) has always carried both. Measured: a wagf_block selector returns 0 members on
// packet rows and 1 on map rows. oceania-indonesia was the worst of the six because its union
// still matched IDN, so it printed a PLAUSIBLE packet that was silently missing all of Oceania.
const fields = 'slug,name,country_code,wagf_region,wagf_block,population,religion,image_url,primary_language,imb_gsec';
const resp = await fetch(`${API_BASE}/api/people-groups/list?fields=${fields}&lang=${packetLang()}`,
{ cache: 'no-store' });
if (!resp.ok) throw new Error(`people-groups fetch failed (${resp.status})`);

View file

@ -36,12 +36,14 @@
* mapped to this dashboard's facet keys). Nothing here is dashboard-specific beyond that map.
*/
import { inject, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { tStatic } from '@map/i18n/index.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import SearchFilterHeader from '@map/components/search/SearchFilterHeader.vue'
import { getPrayerLevel, LABELS as PRAYER_LABELS } from '@map/colors/prayer-progress.js'
import { useTreeLegendLabels } from '../../composables/useTreeLegendLabels.js'
const { locale } = useI18n()
const bus = inject('selectionBus', null)
const dash = inject('dashboardData', null)
const cfg = inject('dashboardConfig', {})
@ -205,7 +207,12 @@ const activeChips = computed(() => {
// in code would get both wrong even with a translated word. It was simply never wired, so ten
// locales rendered an English label beside localized facet chips. tStatic takes no params, so
// {term} is filled here rather than by the catalogue.
if (term) out.push({ name: '__search__', label: (tStatic('dashboard.search_live') || 'Search: {term}').replace('{term}', term) })
// ★ locale.value is READ so this computed re-runs on a language switch, and PASSED so tStatic reads
// the catalogue the reader just chose. Without it the chip resolved once and stayed in the boot
// language beside chips that had moved — a frozen label, which check:frozen-labels now catches.
// research-dashboard installs i18n (index.js:83), so useI18n resolves here; big-picture does not,
// which is why its sibling fix pins the locale from hostLang instead.
if (term) out.push({ name: '__search__', label: (tStatic('dashboard.search_live', locale.value) || 'Search: {term}').replace('{term}', term) })
return out
})

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-O85EMbrvfaLa3rpO62i1Cu3jET7G+UFNSFoP5oEUoA1Go//jfTubBu6+O8IzP9tn" crossorigin="anonymous"></script>
<script src="../../big-picture.js" integrity="sha384-rLqvs6L5S0351npiIxHUwCji55L+SF16b1f14GRw2VebQ1UAE7M0lDq4KAWRoiSP" 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-O85EMbrvfaLa3rpO62i1Cu3jET7G+UFNSFoP5oEUoA1Go//jfTubBu6+O8IzP9tn" crossorigin="anonymous"></script>
<script src="../../big-picture.js" integrity="sha384-rLqvs6L5S0351npiIxHUwCji55L+SF16b1f14GRw2VebQ1UAE7M0lDq4KAWRoiSP" 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-O85EMbrvfaLa3rpO62i1Cu3jET7G+UFNSFoP5oEUoA1Go//jfTubBu6+O8IzP9tn" crossorigin="anonymous"></script>
<script src="../big-picture.js" integrity="sha384-rLqvs6L5S0351npiIxHUwCji55L+SF16b1f14GRw2VebQ1UAE7M0lDq4KAWRoiSP" 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 <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/" 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 <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/" 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 <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/" 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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C" 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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C" 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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C" 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-+96qtmbSzb1JdCcUl3KEDi3BnER9AN+DgiRgdeKMDWC0E46ocPO6SLELjneyow3K" crossorigin="anonymous"></script>
<script src="../../dashboard-plus-grid.js" integrity="sha384-nRhkUPUEe4CK6dQkzjWOyNJXtYanz+S+aAAw3nuOFvZl+hkKN3CjMhgtzbcpjXzB" 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-+96qtmbSzb1JdCcUl3KEDi3BnER9AN+DgiRgdeKMDWC0E46ocPO6SLELjneyow3K" crossorigin="anonymous"></script>
<script src="../dashboard-plus-grid.js" integrity="sha384-nRhkUPUEe4CK6dQkzjWOyNJXtYanz+S+aAAw3nuOFvZl+hkKN3CjMhgtzbcpjXzB" 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-O85EMbrvfaLa3rpO62i1Cu3jET7G+UFNSFoP5oEUoA1Go//jfTubBu6+O8IzP9tn",
"integrity": "sha384-rLqvs6L5S0351npiIxHUwCji55L+SF16b1f14GRw2VebQ1UAE7M0lDq4KAWRoiSP",
"elements": [
"bp-big-picture",
"bp-research-header",
@ -65,7 +65,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-O85EMbrvfaLa3rpO62i1Cu3jET7G+UFNSFoP5oEUoA1Go//jfTubBu6+O8IzP9tn\" 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-rLqvs6L5S0351npiIxHUwCji55L+SF16b1f14GRw2VebQ1UAE7M0lDq4KAWRoiSP\" 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": [
{
@ -109,7 +109,7 @@
"bundle": "cities-internet",
"scriptUrl": "./cities-internet/cities-internet.js",
"output": "dx-maps/cities-internet/cities-internet.js",
"integrity": "sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X",
"integrity": "sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/",
"elements": [
"cities-internet-map"
],
@ -128,7 +128,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/connectivity-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
},
{
@ -144,7 +144,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/connectivity-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
},
{
@ -160,7 +160,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/data-tables/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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
}
]
@ -169,7 +169,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ",
"integrity": "sha384-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C",
"elements": [
"country-posters-map"
],
@ -192,7 +192,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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ\" 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-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C\" 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>"
}
},
{
@ -208,7 +208,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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ\" 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-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C\" 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>"
}
},
{
@ -224,7 +224,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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ\" 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-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C\" 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>"
}
}
]
@ -233,7 +233,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-+96qtmbSzb1JdCcUl3KEDi3BnER9AN+DgiRgdeKMDWC0E46ocPO6SLELjneyow3K",
"integrity": "sha384-nRhkUPUEe4CK6dQkzjWOyNJXtYanz+S+aAAw3nuOFvZl+hkKN3CjMhgtzbcpjXzB",
"elements": [
"dashboard-plus-grid"
],
@ -256,7 +256,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-+96qtmbSzb1JdCcUl3KEDi3BnER9AN+DgiRgdeKMDWC0E46ocPO6SLELjneyow3K\" 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-nRhkUPUEe4CK6dQkzjWOyNJXtYanz+S+aAAw3nuOFvZl+hkKN3CjMhgtzbcpjXzB\" 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": [
{
@ -380,7 +380,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-0aunFrm5r3fMq6L6RL61lldOre+6+emthRTgb/9VY37/LnZkzx6jznEfAPacBjXQ",
"integrity": "sha384-c5pnq4MW+/bXzx52a9vsIgI++p81BWZcMo9khwaTGyQMNHsod3cYw76bC7E3w/Ew",
"elements": [
"doxa-research-map"
],
@ -399,7 +399,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-0aunFrm5r3fMq6L6RL61lldOre+6+emthRTgb/9VY37/LnZkzx6jznEfAPacBjXQ\" 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-c5pnq4MW+/bXzx52a9vsIgI++p81BWZcMo9khwaTGyQMNHsod3cYw76bC7E3w/Ew\" 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": [
{
@ -425,7 +425,7 @@
"bundle": "doxa-simple-map",
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"output": "dx-maps/doxa-simple-map/doxa-simple-map.js",
"integrity": "sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF",
"integrity": "sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5",
"elements": [
"doxa-map",
"doxa-simple-map"
@ -445,7 +445,7 @@
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
},
"instances": [
{
@ -838,7 +838,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
"integrity": "sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc",
"integrity": "sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/",
"elements": [
"my-upg-100-list-map"
],
@ -861,7 +861,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -877,7 +877,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -893,7 +893,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]
@ -1080,7 +1080,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g",
"integrity": "sha384-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R",
"elements": [
"regions-dashboard"
],
@ -1103,7 +1103,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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g\" 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-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R\" 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>"
}
},
{
@ -1119,7 +1119,7 @@
"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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g\" 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-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R\" 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>"
}
},
{
@ -1135,7 +1135,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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g\" 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-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R\" 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>"
}
}
]
@ -1144,7 +1144,7 @@
"bundle": "research-dashboard",
"scriptUrl": "./research-dashboard/research-dashboard.js",
"output": "dx-maps/research-dashboard/research-dashboard.js",
"integrity": "sha384-bWO+cg+k/ftmOyeaxqh8LxYvb20jh6jDFqtZfcY22iqAjHMls46Xx6Yv1+xBgzXt",
"integrity": "sha384-21+7Zl2lTl/DU7bZ6xPRrrW+T2Py4XQRfgPFKHQOtGyv+6AD1OlbxesGrSiV9OfR",
"elements": [
"research-dashboard-map"
],
@ -1163,7 +1163,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-bWO+cg+k/ftmOyeaxqh8LxYvb20jh6jDFqtZfcY22iqAjHMls46Xx6Yv1+xBgzXt\" 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-21+7Zl2lTl/DU7bZ6xPRrrW+T2Py4XQRfgPFKHQOtGyv+6AD1OlbxesGrSiV9OfR\" 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-0aunFrm5r3fMq6L6RL61lldOre+6+emthRTgb/9VY37/LnZkzx6jznEfAPacBjXQ" crossorigin="anonymous"></script>
<script src="../doxa-research-map.js" integrity="sha384-c5pnq4MW+/bXzx52a9vsIgI++p81BWZcMo9khwaTGyQMNHsod3cYw76bC7E3w/Ew" 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-0aunFrm5r3fMq6L6RL61lldOre+6+emthRTgb/9VY37/LnZkzx6jznEfAPacBjXQ" crossorigin="anonymous"></script>
<script src="../../doxa-research-map.js" integrity="sha384-c5pnq4MW+/bXzx52a9vsIgI++p81BWZcMo9khwaTGyQMNHsod3cYw76bC7E3w/Ew" 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 <doxa-map> custom element. -->
<script src="../../doxa-simple-map.js" integrity="sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5" 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 <doxa-map> custom element. -->
<script src="../../doxa-simple-map.js" integrity="sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5" 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 <doxa-map> custom element. -->
<script src="../doxa-simple-map.js" integrity="sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF" crossorigin="anonymous"></script>
<script src="../doxa-simple-map.js" integrity="sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5" 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 <doxa-map> custom element. -->
<script src="../../doxa-simple-map.js" integrity="sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5" 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-O85EMbrvfaLa3rpO62i1Cu3jET7G+UFNSFoP5oEUoA1Go//jfTubBu6+O8IzP9tn",
"integrity": "sha384-rLqvs6L5S0351npiIxHUwCji55L+SF16b1f14GRw2VebQ1UAE7M0lDq4KAWRoiSP",
"elements": [
"bp-big-picture",
"bp-research-header",
@ -65,7 +65,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-O85EMbrvfaLa3rpO62i1Cu3jET7G+UFNSFoP5oEUoA1Go//jfTubBu6+O8IzP9tn\" 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-rLqvs6L5S0351npiIxHUwCji55L+SF16b1f14GRw2VebQ1UAE7M0lDq4KAWRoiSP\" 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": [
{
@ -109,7 +109,7 @@
"bundle": "cities-internet",
"scriptUrl": "./cities-internet/cities-internet.js",
"output": "dx-maps/cities-internet/cities-internet.js",
"integrity": "sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X",
"integrity": "sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/",
"elements": [
"cities-internet-map"
],
@ -128,7 +128,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/connectivity-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
},
{
@ -144,7 +144,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/connectivity-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
},
{
@ -160,7 +160,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/data-tables/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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-lSc3CG6hkpVa7oNEcRaIN5H72jPJP0Ei+gnHflfoIPEQd50e7qL1hd4EhBw02L4X\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-p8Kw95mpnuGAUwuScENFmco2EH5Rw7UPnqpJct5n7uoWGVo4eDG56FNAYe+Gi0F/\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
}
]
@ -169,7 +169,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ",
"integrity": "sha384-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C",
"elements": [
"country-posters-map"
],
@ -192,7 +192,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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ\" 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-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C\" 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>"
}
},
{
@ -208,7 +208,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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ\" 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-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C\" 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>"
}
},
{
@ -224,7 +224,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-oH+j/lu+6jgIJnsdx1kAYURsEPwaDfKfQ3Ii8hbY8133LSODGzZr3Flav2O79ULZ\" 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-ahzVB31p60j2LWiugq2Khm2/EyvAS7mst3N09Vg2y+toyEMmYb0YMFp3qTaHSX+C\" 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>"
}
}
]
@ -233,7 +233,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-+96qtmbSzb1JdCcUl3KEDi3BnER9AN+DgiRgdeKMDWC0E46ocPO6SLELjneyow3K",
"integrity": "sha384-nRhkUPUEe4CK6dQkzjWOyNJXtYanz+S+aAAw3nuOFvZl+hkKN3CjMhgtzbcpjXzB",
"elements": [
"dashboard-plus-grid"
],
@ -256,7 +256,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-+96qtmbSzb1JdCcUl3KEDi3BnER9AN+DgiRgdeKMDWC0E46ocPO6SLELjneyow3K\" 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-nRhkUPUEe4CK6dQkzjWOyNJXtYanz+S+aAAw3nuOFvZl+hkKN3CjMhgtzbcpjXzB\" 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": [
{
@ -380,7 +380,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-0aunFrm5r3fMq6L6RL61lldOre+6+emthRTgb/9VY37/LnZkzx6jznEfAPacBjXQ",
"integrity": "sha384-c5pnq4MW+/bXzx52a9vsIgI++p81BWZcMo9khwaTGyQMNHsod3cYw76bC7E3w/Ew",
"elements": [
"doxa-research-map"
],
@ -399,7 +399,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-0aunFrm5r3fMq6L6RL61lldOre+6+emthRTgb/9VY37/LnZkzx6jznEfAPacBjXQ\" 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-c5pnq4MW+/bXzx52a9vsIgI++p81BWZcMo9khwaTGyQMNHsod3cYw76bC7E3w/Ew\" 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": [
{
@ -425,7 +425,7 @@
"bundle": "doxa-simple-map",
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"output": "dx-maps/doxa-simple-map/doxa-simple-map.js",
"integrity": "sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF",
"integrity": "sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5",
"elements": [
"doxa-map",
"doxa-simple-map"
@ -445,7 +445,7 @@
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-jCiBqpY5I4lWFYjkHDsviahZ8RhhEDtkIAqKo6cK1BsjuzsJ0VR+0J+qqecqrDYF\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-ZlIDGoHb7SpMprp7+sEsLAUhOQ1wnyZ8ivCMQhxFslhxqXZqzg7+oA6K7VUwuOv5\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
},
"instances": [
{
@ -838,7 +838,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
"integrity": "sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc",
"integrity": "sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/",
"elements": [
"my-upg-100-list-map"
],
@ -861,7 +861,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -877,7 +877,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -893,7 +893,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]
@ -1080,7 +1080,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g",
"integrity": "sha384-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R",
"elements": [
"regions-dashboard"
],
@ -1103,7 +1103,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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g\" 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-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R\" 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>"
}
},
{
@ -1119,7 +1119,7 @@
"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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g\" 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-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R\" 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>"
}
},
{
@ -1135,7 +1135,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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g\" 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-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R\" 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>"
}
}
]
@ -1144,7 +1144,7 @@
"bundle": "research-dashboard",
"scriptUrl": "./research-dashboard/research-dashboard.js",
"output": "dx-maps/research-dashboard/research-dashboard.js",
"integrity": "sha384-bWO+cg+k/ftmOyeaxqh8LxYvb20jh6jDFqtZfcY22iqAjHMls46Xx6Yv1+xBgzXt",
"integrity": "sha384-21+7Zl2lTl/DU7bZ6xPRrrW+T2Py4XQRfgPFKHQOtGyv+6AD1OlbxesGrSiV9OfR",
"elements": [
"research-dashboard-map"
],
@ -1163,7 +1163,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-bWO+cg+k/ftmOyeaxqh8LxYvb20jh6jDFqtZfcY22iqAjHMls46Xx6Yv1+xBgzXt\" 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-21+7Zl2lTl/DU7bZ6xPRrrW+T2Py4XQRfgPFKHQOtGyv+6AD1OlbxesGrSiV9OfR\" 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

@ -32,7 +32,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/" 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 <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/" 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 <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-kE+aCfF8eqiGOK0t1evHOeUgxAaLi5Hi6wvsSmBk3cT9D3oYanblRsxQ3qrZROcc" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-p9gFobeBxg6b6Vsp+4MKljTgjuV3xZWJCelIHCJxPiH71hIbFz/Ktwu32+LcaMQ/" 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 <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R" 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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R" 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-PpqIUfLtva+loQ2R2Sbf/6eR/2y1k2rP3a4H+7ZTJIMdbkBvmKUxwhOURswuTc7g" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-nndcayGqcVXiqMHpOQlf31zbkTOiK2KmkTRrj60ZK2ISPugQZtXn2paK6CXYHt9R" 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-bWO+cg+k/ftmOyeaxqh8LxYvb20jh6jDFqtZfcY22iqAjHMls46Xx6Yv1+xBgzXt" crossorigin="anonymous"></script>
<script src="../research-dashboard.js" integrity="sha384-21+7Zl2lTl/DU7bZ6xPRrrW+T2Py4XQRfgPFKHQOtGyv+6AD1OlbxesGrSiV9OfR" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: