Update from source 52eeb0db
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-26 18:08:09 +00:00
parent 31f98c51ec
commit 28d4c83d96
128 changed files with 1487 additions and 760 deletions

View file

@ -81,9 +81,9 @@ apply it. Rule of thumb: **a path with ≥2 directory segments under the tree ro
page → serve it framable.**
Portable ways to implement the split, by host:
- **Traefik** (nuxtbase/go-base): a higher-priority router on `PathRegexp(^/plugin-assets/[^/]+/[^/]+/)`
sets `Content-Security-Policy: frame-ancestors *` + clears `X-Frame-Options`. (Live example:
`/data/coolify/proxy/dynamic/doxa-maps-embed.yaml`.)
- **Traefik** (a self-hosted reverse proxy): a higher-priority router on `PathRegexp(^/plugin-assets/[^/]+/[^/]+/)`
sets `Content-Security-Policy: frame-ancestors *` + clears `X-Frame-Options`. (Live example: a Traefik
dynamic-configuration file on the test host.)
- **Cloudflare Pages / Netlify**: a `_headers` file — `/<bundle>/*/* Content-Security-Policy: frame-ancestors *`.
- **Nginx**: `location ~ ^/[^/]+/[^/]+/ { add_header Content-Security-Policy "frame-ancestors *"; }`
- **GitHub Pages**: serves no custom headers → pages are framable by default (no frame-ancestors), which is already correct for direct embeds.

View file

@ -215,8 +215,8 @@ there are options:**
site serves them, so **doxa-life is the CDN**. No third party.
- **GitHub.** Push the built maps to a public GitHub repo and serve them free through
**GitHub Pages or jsDelivr** — **GitHub is the CDN**.
- **doxa-base's built-in home (Forgejo).** doxa-base ships **Forgejo as a CDN** — push the maps
there and it serves them, with no extra service to set up.
- **A Git host you run (for example Forgejo).** Push the maps there and serve them from it, with
no extra service to set up.
- **Anywhere else.** Any static host works — Cloudflare, an S3-style bucket, etc.
Pick one home and **every partner site embeds the same map from it** — change the map once and it

View file

@ -28,9 +28,11 @@ and a change here can break other maps.
`import … from '@map/components/charts/BarChart.vue'`
- **PinHint** — PinHint.vue — the conversion coach-mark (bottom-center of any map).
`import … from '@map/components/hints/PinHint.vue'`
- **ApproxPinNote** — ApproxPinNote.vue — the ONE wording every map with people-group pins uses (APPROXPIN, 2026-09-26).
`import … from '@map/components/legends/ApproxPinNote.vue'`
- **LegendDesktop** — This component's chrome, read out of the catalogue. tStatic and not useI18n: these mount inside
`import … from '@map/components/legends/LegendDesktop.vue'`
- **LegendMobile** — useShadowStyles(
- **LegendMobile** — (no description)
`import … from '@map/components/legends/LegendMobile.vue'`
- **LegendProgressBar** — LegendProgressBar — the prayer legend's header progress bar.
`import … from '@map/components/legends/LegendProgressBar.vue'`
@ -155,6 +157,8 @@ and a change here can break other maps.
`import … from '@map/composables/useSearch.js'`
- **useSelectedPin** — useSelectedPin.js - Selected Pin Highlight + Animated GO Marker
`import … from '@map/composables/useSelectedPin.js'`
- **useSelectionBus** — useSelectionBus.js — hand-rolled pub/sub brushing-and-linking bus.
`import … from '@map/composables/useSelectionBus.js'`
- **useShadowStyles** — useShadowStyles.js — Shadow DOM Style Injection
`import … from '@map/composables/useShadowStyles.js'`
- **useSharedMapToken** — useSharedMapToken.js — ONE page, ONE map token, shared by every element on it.
@ -172,6 +176,8 @@ and a change here can break other maps.
`import … from '@map/utils/NetworkClusteringUtil.js'`
- **apiBaseUrl** — Back-compat shim. The real apiBaseUrl.js lives in the api/ seam folder.
`import … from '@map/utils/apiBaseUrl.js'`
- **colocation** — colocation.js — which people groups share ONE coordinate on a map (APPROXPIN, 2026-09-26).
`import … from '@map/utils/colocation.js'`
- **countries-meta** — countries-meta.js — a PORT of DX-life's config/countries-meta.ts (326 lines), the rollup
`import … from '@map/utils/countries-meta.js'`
- **embedBase** — embedBase.js — Where THIS bundle's built tree is served from, resolved at runtime.

View file

@ -91,7 +91,7 @@ const eq = (label, got, want) => (String(got) === String(want) ? ok(`${label} =
// changed. The accessors are re-derived here from the same row fields the config declares, and
// assertion A cross-checks the extracted drill arrays against EXPECT, so a silent extraction
// failure shows up as a named failure rather than a skipped check.
const BUS_SRC = join(ROOT, 'plugin-designs/research-dashboard/src/composables/useSelectionBus.js')
const BUS_SRC = join(ROOT, 'library/composables/useSelectionBus.js')
const CFG_SRC = join(ROOT, 'plugin-designs/research-dashboard/dashboard.config.js')
const pathToUrl = (p) => 'file://' + p + '?t=' + Date.now()

View file

@ -0,0 +1,111 @@
#!/usr/bin/env node
/* check-provider-for.mjs — run as `bun run check:provider-for`. Source-only, headless, ~0.1s.
Placed in internal/ with studio's explicit authorisation for this one gate (END-STU-C3AB): it spans
library/ plus three separate plugins, so no single plugin's scripts/ folder owns it. */
// C3: do the library helpers behave like the raw-global code they replace, on BOTH engine shapes AND
// on the ambiguous case that is neither?
//
// Same condition the shepherd set for C2: an equivalence gate, old pattern vs new, both engines. The
// three migrated sites, verbatim in shape from main 1a761741:
// A1 pplrData.js:115 window.maplibregl && window.maplibregl.Map && map instanceof window.maplibregl.Map ? 'maplibre' : 'mapbox'
// A2 research-map.vue:1322 !!(window.maplibregl && map.value instanceof window.maplibregl.Map)
// B all-people-groups:298 mapProvider === 'maplibre' ? window.maplibregl : window.mapboxgl
//
// ★ THE AMBIGUOUS CASE IS THE POINT OF THIS GATE, and it nearly cost a silent behaviour change.
// My first plan was to rewrite A1 and A2 in terms of the EXISTING isMapboxMap(). That is wrong in a
// way no per-engine test would have caught: both originals test for MAPLIBRE and fall through to
// mapbox/false, so a stub, a destroyed map, or a page where neither global loaded reads as 'mapbox'.
// An isMapboxMap() rewrite inverts the fall-through to 'maplibre'/true — changing what a diagnostic
// field reports and which attribution a poster PRINTS. Hence providerFor() returns undefined and each
// call site keeps its own default visible. Changing a default is not a curation migration.
//
// ★ AND THE PREMISE I HAD WRONG BEFORE THAT: I recorded in SPEC-LIBRARY-CURATION that "engineFor
// returns a NAME". It does not — it returns the engine NAMESPACE OBJECT (`return ml` / `return mb`).
// Two of my four bucket-D "must not migrate" conclusions rested on that false premise. Corrected in
// the spec; the lesson is that a helper's RETURN TYPE is a fact to read, not to infer from its name.
import { readFileSync } from 'node:fs'
import { providerFor, engineFor, isMapboxMap } from '../library/composables/useMapProvider.js'
// ★ engineGlobal CANNOT BE IMPORTED HERE, and the workaround matters. It lives in useMapInstance.js,
// whose import chain reaches library/i18n/index.js, which calls `import.meta.glob` -- a VITE
// transform, so plain Node throws "(intermediate value).glob is not a function". C2's gate never hit
// this because useMapProvider.js has no such chain.
// So this EXTRACTS AND EXECUTES engineGlobal's REAL SOURCE LINES rather than a paraphrase of them.
// Re-typing a three-line function into this gate would test my copy, and a copy silently drifts from
// the thing it stands for -- the same trap as a tool copied to a new filename that no-ops and exits 0.
// CDN is stubbed because engineGlobal reads only `.global` from the ENGINES table; the urls are
// irrelevant to it, which is checkable in the extracted text printed below on failure.
const umiSrc = readFileSync(new URL('../library/composables/useMapInstance.js', import.meta.url), 'utf8')
const enginesBlock = /const ENGINES = \{[\s\S]*?\n\}/.exec(umiSrc)
const engineGlobalSrc = /export function engineGlobal\(provider\) \{[\s\S]*?\n\}/.exec(umiSrc)
if (!enginesBlock || !engineGlobalSrc) {
console.error(' FAIL - could not extract ENGINES / engineGlobal from useMapInstance.js -- the gate is NOT MEASURED, not passing')
process.exit(2)
}
const engineGlobal = new Function(
'const CDN = { mapboxGlJs:{url:""}, mapboxGlCss:{url:""}, maplibreGlJs:{url:""}, maplibreGlCss:{url:""} };\n'
+ enginesBlock[0] + ';\n'
+ engineGlobalSrc[0].replace(/^export /, '') + ';\n'
+ 'return engineGlobal;'
)()
let fails = 0
const ok = (c, m) => { console.log(` ${c ? 'ok ' : 'FAIL'} - ${m}`); if (!c) fails++ }
class MapboxMap {}
class MaplibreMap {}
globalThis.window = { mapboxgl: { Map: MapboxMap }, maplibregl: { Map: MaplibreMap } }
// the THREE old patterns, as they stood
const oldA1 = (map) => (window.maplibregl && window.maplibregl.Map && map instanceof window.maplibregl.Map ? 'maplibre' : 'mapbox')
const oldA2 = (map) => !!(window.maplibregl && map instanceof window.maplibregl.Map)
const oldB = (provider) => (provider === 'maplibre' ? window.maplibregl : window.mapboxgl)
// the NEW expressions, exactly as they now read at the call sites
const newA1 = (map) => providerFor(map) || 'mapbox'
const newA2 = (map) => providerFor(map) === 'maplibre'
const newB = (provider) => engineGlobal(provider)
// ── 1. A1 and A2 across every map shape, INCLUDING neither ──────────────────────────────────────
const CASES = [
['a Mapbox map', new MapboxMap()],
['a MapLibre map', new MaplibreMap()],
['AMBIGUOUS: a plain object that is neither', {}],
['AMBIGUOUS: null', null],
['AMBIGUOUS: undefined', undefined]
]
for (const [label, map] of CASES) {
ok(oldA1(map) === newA1(map), `A1 ${label}: old "${oldA1(map)}" === new "${newA1(map)}"`)
ok(oldA2(map) === newA2(map), `A2 ${label}: old ${oldA2(map)} === new ${newA2(map)}`)
}
// ── 2. B across every provider name, including the unrecognised one ─────────────────────────────
for (const p of ['maplibre', 'mapbox', 'auto', '', undefined, 'nonsense']) {
ok(oldB(p) === newB(p), `B provider=${JSON.stringify(p)}: both resolve to the same namespace (${oldB(p) === window.maplibregl ? 'maplibregl' : 'mapboxgl'})`)
}
// ── 3. THE INVERSION THIS GATE EXISTS TO PREVENT ────────────────────────────────────────────────
// Documented as an executable claim, not as prose: isMapboxMap() is NOT a drop-in for these sites.
const wouldHaveBeen = (map) => (isMapboxMap(map) ? 'mapbox' : 'maplibre')
ok(wouldHaveBeen({}) !== oldA1({}),
`PROOF: the isMapboxMap() rewrite I nearly shipped disagrees with the original on the ambiguous case (it says "${wouldHaveBeen({})}", the original says "${oldA1({})}")`)
ok(wouldHaveBeen(new MapboxMap()) === oldA1(new MapboxMap()) && wouldHaveBeen(new MaplibreMap()) === oldA1(new MaplibreMap()),
'and it agrees on BOTH real engines -- which is exactly why a per-engine-only gate would have passed it')
// ── 4. return TYPES, since inferring one from a name is what went wrong ─────────────────────────
ok(typeof providerFor(new MaplibreMap()) === 'string', 'providerFor returns a STRING name')
ok(providerFor(new MaplibreMap()) === 'maplibre' && providerFor(new MapboxMap()) === 'mapbox', 'and the right one per engine')
ok(providerFor({}) === undefined, 'and undefined for neither -- never a guessed default')
ok(engineFor(new MaplibreMap()) === window.maplibregl, 'engineFor returns the NAMESPACE OBJECT, not a name (the premise I had backwards)')
ok(typeof engineGlobal('maplibre') === 'object', 'engineGlobal returns a namespace OBJECT from a name')
ok(/ENGINES\[provider\]\?\.global \|\| ENGINES\.mapbox\.global/.test(engineGlobalSrc[0]),
'CONTROL: the EXTRACTED source really is the mapbox-defaulting version -- this is what makes the bucket-C fallback finding true, and it is read from the file, not remembered')
// ── 5. NEG CONTROLS: the comparisons above must be capable of failing ───────────────────────────
ok(oldA1(new MaplibreMap()) !== oldA1(new MapboxMap()), 'NEG CONTROL: A1 discriminates the two engines at all')
ok(oldB('maplibre') !== oldB('mapbox'), 'NEG CONTROL: B discriminates the two namespaces at all')
const brokenNewA1 = () => 'mapbox' // a helper that always says mapbox
ok(!CASES.every(([, m]) => oldA1(m) === brokenNewA1(m)),
'NEG CONTROL: a helper that always answered "mapbox" would FAIL this gate -- so passing means something')
console.log(fails ? `\nC3-EQUIVALENCE: ${fails} FAIL(s)` : '\nC3-EQUIVALENCE: all three migrated sites match the raw-global code they replace, on both engines and on every ambiguous input')
process.exit(fails ? 1 : 0)

View file

@ -141,7 +141,7 @@ watch(() => config.value?.tokenUrl, (url) => {
// host override via profile-config.dataSource. (doxa-csv stays the final offline fallback.)
const dataSource = computed(() => config.value?.dataSource || sourcesConfig.activeSource || 'doxa-csv')
const colorSet = computed(() => config.value?.colorSet || 'default')
// QA corner widget: OPT-IN via profile-config.qa (default OFF). On for the DOXA go-base
// QA corner widget: OPT-IN via profile-config.qa (default OFF). On for our own
// surfaces (SPA + plugins); OFF for partner embeds so no QA button appears on their sites.
const showQa = computed(() => !!QaCorner && config.value?.qa === true)
// Conversion coach-mark: DEFAULT ON (opt-out via profile-config pinHint:false).

View file

@ -16,7 +16,15 @@ const props = defineProps({
shared: { type: Array, default: () => [] },
dark: { type: Boolean, default: false },
})
const { t } = useI18n()
// i18n is OPTIONAL here, as in PinHint: an unguarded useI18n() throws on a host that never installed
// vue-i18n, inside the custom-element boundary, and the reader gets an empty shadow root. Without it
// the English below renders, with the same placeholders filled.
const APPROX_FALLBACK = {
'pins.approximate': 'The dot marks where this people is found, not a town or a boundary.',
'pins.sharedPoint': 'It sits on the same point as {names} — {n} groups share this coordinate, so the position tells you the locality and not which group is where.',
}
let t = (key, vars = {}) => (APPROX_FALLBACK[key] || key).replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m))
try { t = useI18n().t } catch (_) { /* no i18n on this host */ }
const second = computed(() => props.shared.length
? t('pins.sharedPoint', { names: props.shared.join(', '), n: props.shared.length + 1 })
: '')

View file

@ -5,7 +5,7 @@
* Visible shell of the QA-agent bridge (see internal-tracker/01-ideation-research/
* qa-loops/qa-embeddable-qa-agent-bridge.md). OIDC-gated onboarding — NEVER a key.
*
* AUTH: the widget detects the go-base session via a same-origin GET /api/auth/me.
* AUTH: the widget detects the host's session via a same-origin GET /api/auth/me.
* That only succeeds when the plugin iframe is SAME-ORIGIN (allow-same-origin). When
* the app-shell runs with NUXT_PUBLIC_PLUGIN_SANDBOX_ISOLATE_ORIGIN=true the frame is
* origin-'null' and the call is blocked — login is then impossible from inside the

View file

@ -106,6 +106,36 @@ export function isMapboxMap(map) {
* @param {object} map the live map instance
* @returns {object|undefined} the engine namespace, or undefined when neither is loaded
*/
/**
* The provider NAME that built THIS map: 'mapbox', 'maplibre', or undefined when neither global is
* loaded or `map` is not a map at all.
*
* ★ WHY THIS EXISTS, given engineFor() is right above it. engineFor returns the engine NAMESPACE,
* which is what you want for constructing things (Marker, Popup, a second Map). Two call sites want
* the opposite: a NAME, to put in a diagnostic field and to choose an attribution string. They were
* each doing their own `map instanceof window.maplibregl.Map` sniff, which is the raw-global coupling
* C3 exists to remove — and `resolveMapProvider()` cannot serve them because it reads a CONFIG, not a
* live map, so on a dual-engine page it answers about the wrong thing.
*
* ★ THE AMBIGUOUS CASE IS THE WHOLE REASON THIS RETURNS undefined RATHER THAN A DEFAULT. Both call
* sites tested for MAPLIBRE and fell through to 'mapbox', so a stub, a destroyed map or a page where
* neither global loaded read as 'mapbox'. A rewrite in terms of the existing isMapboxMap() would have
* inverted that — falling through to 'maplibre' — and silently changed what a diagnostic reports and
* which attribution a poster prints. Returning undefined forces each caller to keep its OWN default
* visible at the call site, so the migration cannot quietly move a default. Changing a default is not
* a curation migration.
*
* @param {object} map the live map instance
* @returns {'mapbox'|'maplibre'|undefined}
*/
export function providerFor(map) {
if (typeof window === 'undefined') return undefined
const ml = window.maplibregl, mb = window.mapboxgl
if (ml && ml.Map && map instanceof ml.Map) return 'maplibre'
if (mb && mb.Map && map instanceof mb.Map) return 'mapbox'
return undefined
}
export function engineFor(map) {
if (typeof window === 'undefined') return undefined
const ml = window.maplibregl, mb = window.mapboxgl

View file

@ -0,0 +1,279 @@
/**
* useSelectionBus.js — hand-rolled pub/sub brushing-and-linking bus.
*
* ★ SHARED AS OF DASHBOARD-KIT LANDING 1. This is the first piece of Big Picture to become
* library code, and it went first for one reason: it has ZERO domain references and imports
* nothing but `vue`, so it is the only piece that could move with no adapter and no behaviour
* change. Everything below is the file as research-dashboard shipped it — not a rewrite, not a
* generalisation. A move that also edits is a move nobody can verify.
*
* plugin-designs/research-dashboard/src/composables/useSelectionBus.js still exists and
* re-exports this file for one cycle, so nothing breaks mid-flight; that shim is deleted in the
* next landing. (internal measurement) sections 5.2 and 8.
*
* ONE THING IS STILL COMING, and it is deliberately NOT in this commit: SPEC-DASHBOARD-KIT D1
* makes `filterSet` multi-value (an array meaning OR-within-key, AND-across-keys). That changes
* this file's CONTENTS; this commit changes only its LOCATION. Keeping the two apart is what
* makes 'Big Picture is unchanged' provable rather than asserted.
*
* Two DISTINCT global reactive objects, two distinct visual languages (plan §INTERACTION):
* - filterSet : active facet predicates (tree node / map region / chart slice).
* NARROWS scope — panels render excluded context by FADING, never removing.
* - selectionSet : explicit set of slugs (table checkbox / map lasso / chart pick).
* MARKS individuals — panels render with RING/OUTLINE.
*
* Export priority (later phases): selectionSet (if non-empty) > filterSet result > full dataset.
* Every panel is both publisher and subscriber. All client-side over one in-memory row store
* keyed on slug (PK) — no per-brush round-trips.
*
* ── PERFORMANCE CONTRACT (BUG-TABLE-BATCH-SELECTION-FREEZES-20S) ──────────────────────────
* selectionSet is a PLAIN Set, not `reactive(new Set())`, and `selectionVersion` is the single
* reactive dependency that stands in for it. Read it in any computed that inspects the set:
*
* const isSel = computed(() => { bus.selectionVersion.value; return bus.selectionSet.has(x) })
*
* WHY. A `reactive(new Set())` fires Vue's ITERATE_KEY on EVERY add/delete, and the old
* per-row `toggleSelect()` loop also fired a SYNCHRONOUS `emit('select')` per row. Selecting
* all 2,107 people groups therefore ran 2,107 full map-panel rebuilds — each one re-scanning
* every row and calling MapLibre `setData()` — allocating ~2.2 MILLION GeoJSON features to
* arrive at 2,107. Measured at 5,323 ms of pure JS in node (~20 s in the browser with the real
* map + charts + DOM). See selectMany/deselectMany/setSelection: each mutates the set fully,
* THEN bumps the version and emits ONCE. Same work, measured at 2.9 ms — a ~1,800x speedup.
*
* RULE: never loop `toggleSelect()` over many slugs. Use the bulk ops. They are the only
* reason batch selection is interactive.
*
* ── HIERARCHY CONTRACT (BUG-DESELECTING-A-PARENT-FACET-ORPHANS-ITS-CHILD-SELECTION) ────────
* A CHILD FACET SELECTION MUST NOT OUTLIVE ITS PARENT. Clearing a parent facet cascade-clears
* every descendant, transitively, in one commit.
*
* WHY IT LIVES HERE AND NOWHERE ELSE. filterSet is AND-stacked: filteredRows keeps a row only
* when EVERY active key matches. So an orphaned child keeps filtering after its parent is gone —
* deselect "Indo-European" while "Marathi" is picked and Marathi survives; pick a DIFFERENT
* family and the two predicates can never both hold, so the map goes empty with no error. The
* user reads that as "there are no people groups here" rather than "my filter state is
* inconsistent" — a SILENT WRONG RESULT, which is why this is a data-trust bug, not a nit.
*
* Four separate controls clear a filter — the tree rail (DashboardFacetRail), the dropdowns
* (DashboardResearchFilters), the per-chip ✕ (SearchFilterHeader/DashboardFilterChips) and
* Clear All / the breadcrumb — and ALL FOUR funnel through setFilter()/clearFilters() here.
* Implementing the cascade in any one component would fix one button and leave the identical
* bug reachable through the other three. One chokepoint, one rule.
*
* The hierarchy is NOT declared twice: `configureHierarchy()` derives it from the config's
* existing `facetRail[].drill` arrays, which are already ordered parent→child (the rail renders
* them as tiers). The config stays the single source of truth for what nests inside what.
*
* Transitivity is the whole point: People is THREE deep (affinity_bloc → cluster → people_group)
* and Place is three deep (wagf_region → wagf_block → country_code). A one-level cascade would
* look fixed from the top tier while leaving the same orphan one rung down.
*/
import { reactive, ref } from 'vue'
/**
* Derive facetKey → [descendant keys, nearest-first] from a dashboard config.
* Reads `facetRail[].drill` (already ordered parent→child) so the hierarchy is declared ONCE,
* in the config, and can never drift from the tiers the rail actually renders.
* A section with no `drill` (e.g. the flat Status list) contributes nothing — a facet absent
* from the map simply has no descendants and clears alone, exactly as before.
*/
export function deriveHierarchy(config) {
const children = {}
for (const section of config?.facetRail || []) {
const drill = Array.isArray(section.drill) ? section.drill.filter(Boolean) : []
// every tier below i is a descendant of tier i (transitive closure, nearest-first)
for (let i = 0; i < drill.length - 1; i++) {
const rest = drill.slice(i + 1)
children[drill[i]] = children[drill[i]] ? children[drill[i]].concat(rest.filter((k) => !children[drill[i]].includes(k))) : rest
}
}
return children
}
export function createSelectionBus() {
const filterSet = reactive({}) // { facetKey: value | value[] } — AND-stacked facets
// PLAIN Set (see the performance contract above) — pair every read with `selectionVersion`.
const selectionSet = new Set() // Set<slug> — explicit picks
// Bumped once per committed selection change. THE reactive handle on selectionSet: one
// dependency to invalidate instead of a collection proxy that fires per element.
const selectionVersion = ref(0)
// Free-text search term (DX-life research-page search bar → header-1). Distinct from the
// facet filterSet: it applies a substring/label match across the config's `searchKeys` in
// useDashboardShell.filteredRows. Empty string = no text predicate (identical to before).
const searchTerm = ref('')
const subs = new Map() // event -> Set<cb>
function on(event, cb) {
if (!subs.has(event)) subs.set(event, new Set())
subs.get(event).add(cb)
return () => subs.get(event)?.delete(cb) // returns an unsubscribe
}
function emit(event, payload) {
subs.get(event)?.forEach((cb) => { try { cb(payload) } catch (_) { /* isolate subscribers */ } })
}
// ── facet hierarchy (see the HIERARCHY CONTRACT above) ──
// facetKey -> [descendant keys]. Empty until the shell calls configureHierarchy(config), so a
// bus used without a config behaves exactly as it did before (every facet clears alone).
let childFacets = {}
function configureHierarchy(config) { childFacets = deriveHierarchy(config) }
// ── filterSet ops (scope) ──
/**
* Set or clear one facet. CLEARING CASCADES: removing a parent removes every descendant in the
* same call, so a child predicate can never outlive the parent that scoped it.
*
* Setting is deliberately NOT cascading in either direction. Selecting a child with no parent
* set is a legitimate, self-consistent state — the child alone is strictly narrower than its
* parent, so the result is never wrong, merely more specific (the tree rail, the dropdowns and
* the geocoder bars all allow it today, and it is how "search language → Marathi" works without
* first picking a family). The invariant is one-directional by design: no ORPHANS, but
* stand-alone children are fine.
*
* One 'filter' emit for the whole cascade — subscribers rebuild the map/table once, not once
* per cleared tier (same batching discipline as commitSelection).
*/
function setFilter(key, value) {
// ★ AN ARRAY MEANS OR-WITHIN-KEY (SPEC-DASHBOARD-KIT D1). An EMPTY array is not a filter that
// matches nothing — it is no filter, and it clears like null. Treating [] as "narrow to zero"
// is the same silent-empty-screen failure the hierarchy contract above exists to prevent, and
// a UI that deselects its last chip produces exactly that array.
if (Array.isArray(value) && value.length === 0) value = null
// A one-element array is stored as the SCALAR it means. The hash, the chips and every adapter
// then keep their existing single-value shape for the overwhelmingly common case, so D1 costs
// nothing to read until somebody actually picks two.
if (Array.isArray(value) && value.length === 1) value = value[0]
if (value === null || value === undefined) {
delete filterSet[key]
// transitive: childFacets[key] already holds the full descendant closure
for (const child of childFacets[key] || []) delete filterSet[child]
} else {
filterSet[key] = value
}
emit('filter', filterSet)
}
function clearFilters() {
Object.keys(filterSet).forEach((k) => delete filterSet[k])
emit('filter', filterSet)
}
// ── free-text search (header-1) ──
function setSearch(term) {
searchTerm.value = term == null ? '' : String(term)
emit('search', searchTerm.value)
}
// ── selectionSet ops (marks) ──
// The ONE place a selection change becomes visible: bump the version, notify subscribers once.
// Every mutator below finishes its whole batch before calling this exactly once.
function commitSelection() {
selectionVersion.value++
emit('select', selectionSet)
}
function toggleSelect(slug) {
selectionSet.has(slug) ? selectionSet.delete(slug) : selectionSet.add(slug)
commitSelection()
}
function clearSelection() {
if (!selectionSet.size) return // no-op stays silent (no wasted map rebuild)
selectionSet.clear()
commitSelection()
}
/**
* Add many slugs in ONE commit. `additive:false` replaces the selection outright.
* Returns true when something actually changed (a no-op never repaints the map).
*/
function selectMany(slugs, { additive = true } = {}) {
let changed = false
if (!additive) {
if (selectionSet.size) { selectionSet.clear(); changed = true }
}
for (const s of slugs) if (!selectionSet.has(s)) { selectionSet.add(s); changed = true }
if (changed) commitSelection()
return changed
}
/** Remove many slugs in ONE commit. Returns true when something actually changed. */
function deselectMany(slugs) {
let changed = false
for (const s of slugs) if (selectionSet.delete(s)) changed = true
if (changed) commitSelection()
return changed
}
/**
* Replace the whole selection with `slugs` in ONE commit — the primitive the table's
* shift+click range-select is built on (base snapshot ⊕ range, applied atomically).
* Skips the commit when the new set is identical to the current one.
*/
function setSelection(slugs) {
const next = slugs instanceof Set ? slugs : new Set(slugs)
if (next.size === selectionSet.size) {
let same = true
for (const s of next) if (!selectionSet.has(s)) { same = false; break }
if (same) return false
}
selectionSet.clear()
for (const s of next) selectionSet.add(s)
commitSelection()
return true
}
return {
filterSet, selectionSet, selectionVersion, searchTerm, on, emit,
setFilter, clearFilters, setSearch, configureHierarchy,
toggleSelect, clearSelection, selectMany, deselectMany, setSelection,
toggleFilterValue, facetMatches, isFilterActive, rowMatchesFilters,
}
// ── THE PREDICATE, AND WHY IT LIVES HERE ───────────────────────────────────────────────────
// `a(r) === filterSet[k]` was written out SEVEN times: the shell's filteredRows, the facet
// rail twice, the map panel, the table, the charts rail and the CSV exporter. Seven copies of
// one rule is survivable while the rule is one character long. It stops being survivable the
// moment the rule grows an array case — six of the seven would keep comparing an array with
// `===`, every one of them would quietly match nothing, and the six panels would disagree with
// each other about what is on screen. That is not a hypothetical: the facet rail computes the
// COUNTS the user reads before clicking, so a rail that still used `===` would print counts for
// a scope the map and table no longer show.
//
// So D1 is not only "filterSet may hold an array". It is "there is one function that answers
// does this row match, and every panel calls it". The array support is four lines; the
// de-duplication is the part that makes it safe.
function facetMatches(rowValue, selected) {
if (selected === null || selected === undefined) return true
// OR within the key. Compared with ===, exactly as the scalar path always has been: the bus
// stores whatever the facet accessor produced, and a looser comparison here would silently
// widen every existing filter.
if (Array.isArray(selected)) return selected.some((v) => rowValue === v)
return rowValue === selected
}
/** Does `row` survive every active facet? `ACC` is facetKey -> accessor (the shell's FACET_ACC).
* A key with no accessor is skipped, not failed — a facet declared for the rail but not for the
* data must not empty the board. */
function rowMatchesFilters(row, ACC) {
for (const k of Object.keys(filterSet)) {
const a = ACC && ACC[k]
if (a && !facetMatches(a(row), filterSet[k])) return false
}
return true
}
/** Is `value` part of the current selection for `key`? The UI's "is this row lit" question,
* which was `filterSet[facet] === value` in four places and has the same array problem. */
function isFilterActive(key, value) {
const cur = filterSet[key]
if (cur === null || cur === undefined) return false
return Array.isArray(cur) ? cur.includes(value) : cur === value
}
/** Add or remove ONE value from a key's selection — the multi-select primitive a rail row or a
* chart slice needs. Removing the last value clears the key (and cascades), so there is no way
* to arrive at an empty array through the UI. */
function toggleFilterValue(key, value) {
const cur = filterSet[key]
if (cur === null || cur === undefined) return setFilter(key, value)
const list = Array.isArray(cur) ? cur : [cur]
const next = list.includes(value) ? list.filter((v) => v !== value) : list.concat(value)
setFilter(key, next.length ? next : null)
}
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "كل نقطة هي شعب واحد من شعوب دوكسا ولغته؛ أما لون البلد فهو عدد تلك الشعوب.",
"noneFoundShort": "«لا شيء» تعني غير موجود في المصادر التي بحثنا فيها — وليست دليلًا على عدم وجوده.",
"tapCountry": "اضغط على بلد لترى الموارد الفعلية، شعبًا شعبًا."
"tapCountry": "اضغط على بلد لترى الموارد الفعلية، شعبًا شعبًا.",
"howNumbersMade": "كيف تُحسب هذه الأرقام",
"isSignLanguage": "هذه لغة إشارة."
},
"tables": {
"title": "موارد الإنجيل — البيانات",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "تعبئة للصلاة",
"upg100WallIntro": "وزّع مهمة الصلاة في مجموعات من خمسين ومئة — كما أطعم يسوع الخمسة آلاف (لوقا ٩:١٤). كل ملصق هو قائمة من 100 يمكنك أن تصلي بها وتطبعها وتعلّقها على الحائط — مصلّيًا لأجلهم <strong>باسم شعبهم</strong>.",
"loadingNamed": "جارٍ تحميل {name}…",
"couldNotLoadNamed": "تعذّر تحميل {name}."
"couldNotLoadNamed": "تعذّر تحميل {name}.",
"engineMissing": "لم يتم تحميل محرك الخرائط {engine}."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "Ein Punkt ist EINE Volksgruppe von Doxa mit ihrer eigenen Sprache; ein Land ist die ANZAHL dieser Gruppen.",
"noneFoundShort": "„Nichts gefunden“ heißt: nicht in den geprüften Quellen — nie ein Beweis, dass es nichts gibt.",
"tapCountry": "Tippen Sie auf ein Land, um die Ressourcen Gruppe für Gruppe zu sehen."
"tapCountry": "Tippen Sie auf ein Land, um die Ressourcen Gruppe für Gruppe zu sehen.",
"howNumbersMade": "Wie diese Zahlen entstehen",
"isSignLanguage": "Dies ist eine Gebärdensprache."
},
"tables": {
"title": "Evangeliums-Ressourcen — Daten",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Gebetsmobilisierung",
"upg100WallIntro": "Teilen Sie die Gebetsaufgabe in Gruppen zu fünfzig und hundert auf — so wie Jesus die 5.000 speiste (Lukas 9,14). Jedes Poster ist eine 100er-Liste, die Sie beten, drucken und an die Wand hängen können — und für sie <strong>mit dem Namen ihrer Volksgruppe</strong> beten.",
"loadingNamed": "{name} wird geladen…",
"couldNotLoadNamed": "{name} konnte nicht geladen werden."
"couldNotLoadNamed": "{name} konnte nicht geladen werden.",
"engineMissing": "Die Karten-Engine {engine} wurde nicht geladen."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "A dot is ONE of Doxa's people groups and its own language; a country is a COUNT of those groups.",
"noneFoundShort": "“None found” means not present in the pools this build checked — never proof that nothing exists.",
"tapCountry": "Tap a country to see the actual resources, group by group."
"tapCountry": "Tap a country to see the actual resources, group by group.",
"howNumbersMade": "How these numbers are made",
"isSignLanguage": "This is a sign language."
},
"tables": {
"title": "Gospel Resources — Data",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Prayer Mobilization",
"upg100WallIntro": "Hand away the prayer task in clusters of fifties and hundreds — the way Jesus fed the 5,000 (Luke 9:14). Each poster is a 100-list you can pray, print, and put on a wall — praying for them <strong>by their people group name</strong>.",
"loadingNamed": "Loading {name}…",
"couldNotLoadNamed": "Could not load {name}."
"couldNotLoadNamed": "Could not load {name}.",
"engineMissing": "The {engine} map engine did not load."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "Cada punto es UN pueblo de Doxa y su propio idioma; un país es el NÚMERO de esos pueblos.",
"noneFoundShort": "«Nada encontrado» significa ausente de las fuentes consultadas, nunca prueba de que no exista.",
"tapCountry": "Toca un país para ver los recursos, pueblo por pueblo."
"tapCountry": "Toca un país para ver los recursos, pueblo por pueblo.",
"howNumbersMade": "Cómo se obtienen estas cifras",
"isSignLanguage": "Esta es una lengua de señas."
},
"tables": {
"title": "Recursos del evangelio — Datos",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Movilización de oración",
"upg100WallIntro": "Reparte la tarea de la oración en grupos de cincuenta y de cien, como Jesús alimentó a los 5.000 (Lucas 9:14). Cada cartel es una lista de 100 que puedes orar, imprimir y poner en la pared — orando por ellos <strong>por el nombre de su pueblo</strong>.",
"loadingNamed": "Cargando {name}…",
"couldNotLoadNamed": "No se pudo cargar {name}."
"couldNotLoadNamed": "No se pudo cargar {name}.",
"engineMissing": "El motor de mapas {engine} no se cargó."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "Un point est UN peuple de Doxa et sa propre langue ; un pays est le NOMBRE de ces peuples.",
"noneFoundShort": "« Rien trouvé » signifie absent des sources consultées, jamais la preuve que rien n'existe.",
"tapCountry": "Touchez un pays pour voir les ressources, peuple par peuple."
"tapCountry": "Touchez un pays pour voir les ressources, peuple par peuple.",
"howNumbersMade": "Comment ces chiffres sont établis",
"isSignLanguage": "Il s’agit d’une langue des signes."
},
"tables": {
"title": "Ressources de l'Évangile — Données",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Mobilisation pour la prière",
"upg100WallIntro": "Répartissez la tâche de la prière par groupes de cinquante et de cent — comme Jésus a nourri les 5 000 (Luc 9:14). Chaque affiche est une liste de 100 que vous pouvez prier, imprimer et afficher au mur — en priant pour eux <strong>par le nom de leur peuple</strong>.",
"loadingNamed": "Chargement de {name}…",
"couldNotLoadNamed": "Impossible de charger {name}."
"couldNotLoadNamed": "Impossible de charger {name}.",
"engineMissing": "Le moteur de carte {engine} ne s’est pas chargé."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "हर बिंदु दोक्सा का एक जनसमूह और उसकी अपनी भाषा है; देश का रंग उन जनसमूहों की संख्या है।",
"noneFoundShort": "“कुछ नहीं मिला” का अर्थ है जाँचे गए स्रोतों में नहीं — यह प्रमाण नहीं कि कुछ है ही नहीं।",
"tapCountry": "संसाधन देखने के लिए किसी देश पर टैप करें, जनसमूह दर जनसमूह।"
"tapCountry": "संसाधन देखने के लिए किसी देश पर टैप करें, जनसमूह दर जनसमूह।",
"howNumbersMade": "ये आँकड़े कैसे बनते हैं",
"isSignLanguage": "यह एक सांकेतिक भाषा है।"
},
"tables": {
"title": "सुसमाचार संसाधन — डेटा",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "प्रार्थना लामबंदी",
"upg100WallIntro": "प्रार्थना के काम को पचास-पचास और सौ-सौ के समूहों में बाँटें — जैसे यीशु ने 5,000 लोगों को खिलाया (लूका 9:14)। हर पोस्टर 100 की एक सूची है जिसके लिए आप प्रार्थना कर सकते हैं, छाप सकते हैं और दीवार पर लगा सकते हैं — उनके लिए <strong>उनके जनसमूह के नाम से</strong> प्रार्थना करते हुए।",
"loadingNamed": "{name} लोड हो रहा है…",
"couldNotLoadNamed": "{name} लोड नहीं हो सका।"
"couldNotLoadNamed": "{name} लोड नहीं हो सका।",
"engineMissing": "{engine} नक्शा इंजन लोड नहीं हुआ।"
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "Ogni punto è UN popolo di Doxa con la sua lingua; un paese è il NUMERO di quei popoli.",
"noneFoundShort": "«Nulla trovato» significa assente dalle fonti controllate, mai la prova che non esista.",
"tapCountry": "Tocca un paese per vedere le risorse, popolo per popolo."
"tapCountry": "Tocca un paese per vedere le risorse, popolo per popolo.",
"howNumbersMade": "Come si ottengono questi numeri",
"isSignLanguage": "Questa è una lingua dei segni."
},
"tables": {
"title": "Risorse del Vangelo — Dati",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Mobilitazione alla preghiera",
"upg100WallIntro": "Distribuisci il compito della preghiera in gruppi di cinquanta e di cento — come Gesù sfamò i 5.000 (Luca 9:14). Ogni poster è una lista di 100 che puoi pregare, stampare e appendere al muro — pregando per loro <strong>con il nome del loro popolo</strong>.",
"loadingNamed": "Caricamento di {name}…",
"couldNotLoadNamed": "Impossibile caricare {name}."
"couldNotLoadNamed": "Impossibile caricare {name}.",
"engineMissing": "Il motore di mappa {engine} non è stato caricato."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "Cada ponto é UM povo da Doxa e o seu próprio idioma; um país é o NÚMERO desses povos.",
"noneFoundShort": "«Nada encontrado» significa ausente das fontes verificadas, nunca prova de que não exista.",
"tapCountry": "Toque num país para ver os recursos, povo a povo."
"tapCountry": "Toque num país para ver os recursos, povo a povo.",
"howNumbersMade": "Como estes números são obtidos",
"isSignLanguage": "Esta é uma língua de sinais."
},
"tables": {
"title": "Recursos do evangelho — Dados",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Mobilização de oração",
"upg100WallIntro": "Reparta a tarefa da oração em grupos de cinquenta e de cem — como Jesus alimentou os 5.000 (Lucas 9:14). Cada cartaz é uma lista de 100 que pode orar, imprimir e pôr na parede — orando por eles <strong>pelo nome do seu povo</strong>.",
"loadingNamed": "Carregando {name}…",
"couldNotLoadNamed": "Não foi possível carregar {name}."
"couldNotLoadNamed": "Não foi possível carregar {name}.",
"engineMissing": "O motor de mapa {engine} não foi carregado."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "Fiecare punct este UN popor Doxa și limba lui; o țară este NUMĂRUL acelor popoare.",
"noneFoundShort": "„Nimic găsit” înseamnă absent din sursele verificate, niciodată dovada că nu există.",
"tapCountry": "Atinge o țară pentru a vedea resursele, popor cu popor."
"tapCountry": "Atinge o țară pentru a vedea resursele, popor cu popor.",
"howNumbersMade": "Cum se obțin aceste cifre",
"isSignLanguage": "Aceasta este o limbă a semnelor."
},
"tables": {
"title": "Resurse ale Evangheliei — Date",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Mobilizare pentru rugăciune",
"upg100WallIntro": "Împarte sarcina rugăciunii în grupuri de câte cincizeci și o sută — așa cum Isus a hrănit cei 5.000 (Luca 9:14). Fiecare afiș este o listă de 100 pe care o poți ruga, tipări și pune pe perete — rugându-te pentru ei <strong>pe numele poporului lor</strong>.",
"loadingNamed": "Se încarcă {name}…",
"couldNotLoadNamed": "Nu s-a putut încărca {name}."
"couldNotLoadNamed": "Nu s-a putut încărca {name}.",
"engineMissing": "Motorul de hartă {engine} nu s-a încărcat."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "Точка — это ОДИН народ Doxa и его родной язык; цвет страны — это ЧИСЛО таких народов.",
"noneFoundShort": "«Ничего не найдено» значит: нет в проверенных источниках — но не доказательство отсутствия.",
"tapCountry": "Нажмите на страну, чтобы увидеть ресурсы по каждому народу."
"tapCountry": "Нажмите на страну, чтобы увидеть ресурсы по каждому народу.",
"howNumbersMade": "Как получены эти цифры",
"isSignLanguage": "Это жестовый язык."
},
"tables": {
"title": "Ресурсы Евангелия — данные",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "Молитвенная мобилизация",
"upg100WallIntro": "Разделите молитвенный труд на группы по пятьдесят и по сто — так Иисус накормил 5000 (Луки 9:14). Каждый плакат — это список из 100, о котором можно молиться, распечатать и повесить на стену, молясь о них <strong>по имени их народа</strong>.",
"loadingNamed": "Загрузка {name}…",
"couldNotLoadNamed": "Не удалось загрузить {name}."
"couldNotLoadNamed": "Не удалось загрузить {name}.",
"engineMissing": "Картографический движок {engine} не загрузился."
}

View file

@ -105,7 +105,9 @@
"guide": {
"dotVsCountry": "每个圆点代表 Doxa 的一个族群及其语言;国家的颜色代表这类族群的数量。",
"noneFoundShort": "“一无所获”指未见于本次查询的资源库,并不证明其不存在。",
"tapCountry": "点按国家,逐个族群查看实际资源。"
"tapCountry": "点按国家,逐个族群查看实际资源。",
"howNumbersMade": "这些数字是如何得出的",
"isSignLanguage": "这是一种手语。"
},
"tables": {
"title": "福音资源 — 数据",

View file

@ -7,5 +7,6 @@
"upg100WallTag": "禱告動員",
"upg100WallIntro": "將禱告的工作分成五十人、一百人一組 — 就像耶穌餵飽五千人那樣(路加福音 9:14)。每一張海報都是一份 100 人名單,你可以為他們禱告、列印出來、貼在牆上 — <strong>按著他們族群的名字</strong>為他們禱告。",
"loadingNamed": "正在加载{name}…",
"couldNotLoadNamed": "无法加载{name}。"
"couldNotLoadNamed": "无法加载{name}。",
"engineMissing": "{engine} 地图引擎未能加载。"
}

View file

@ -4,7 +4,7 @@
★ WHY THIS EXISTS (the second shared layout, alongside VerticalTabsLayout). VerticalTabsLayout shows
ONE element at a time (tabs, fixed-viewport panes). StackLayout does the OPPOSITE: it renders ALL of
its elements STACKED VERTICALLY, each at its NATURAL height, so the whole thing grows the page and the
HOST page scrolls through them — exactly how a normal document / a doxa-life / nuxtinator page flows.
HOST page scrolls through them — exactly how a normal host page flows.
Use it to GROUP two (or more) elements on top of each other and embed the group on a page you scroll.
★ SAME NESTED-ELEMENT CONTRACT as VerticalTabsLayout (so a shell composes other element-plugins). Pass
@ -32,7 +32,7 @@
self-sizing element that still can't measure its own height in flow, pass :min-section-height (or a
section's own `minHeight`) to reserve room. Optionally cap a section with :max-section-height to give
a genuinely tall element an inner scroll instead of an endless page (default: no cap = pure page
scroll — the doxa-life / nuxtinator page-flow model).
scroll — a normal host page's flow).
★ EMBEDDING A LAYOUT INSIDE A LAYOUT (scroll-page in the cap-5 main). The cap-5 SPA's shell is
VerticalTabsLayout (sidebar + a FIXED-VIEWPORT content frame whose pane is `position:absolute;
@ -210,7 +210,7 @@ export default defineComponent({
// Optional top-bar title (omit for a bare stack with no header).
title: { type: String, default: '' },
// Optional per-section max height (e.g. '70vh') → that section gets an inner scroll instead of an
// unbounded page. Default '' = no cap, pure page scroll (the doxa-life/nuxtinator page-flow model).
// unbounded page. Default '' = no cap, pure page scroll (a normal host page's flow).
maxSectionHeight: { type: String, default: '' },
// Optional default MIN height for every section (e.g. '60vh') → reserves room for a self-sizing
// element that ships its own fixed layout and can't measure its natural height in flow. A section

View file

@ -1060,7 +1060,7 @@ async function mount(id){
// a subpath (e.g. /plugin-assets/doxa-maps-spa/), a bare '/assets' or './assets' misresolves,
// so hand each bundle an ABSOLUTE base derived from THIS shell's own document — correct at any depth.
if (!cfg.assetsBase) cfg.assetsBase = new URL('assets', document.baseURI).href.replace(/\/+$/, '')
// QA corner widget ON for the go-base SPA surface (opt-in flag; off for partner embeds).
// QA corner widget ON for our own SPA surface (opt-in flag; off for partner embeds).
cfg.qa = (new URLSearchParams(location.search).get('qa') === '1') /* QA opt-in via ?qa=1; widget exists only in DOXA_QA=1 builds */; cfg.qaLabel = spec.label || cfg.profile
el.setAttribute('profile-config', JSON.stringify(cfg))

View file

@ -75,6 +75,7 @@
"check:facet-one-field": "node internal/check-facet-one-field.mjs",
"check:facet-one-field:self-test": "node internal/check-facet-one-field.mjs --self-test",
"check:filter-width": "node internal/check-filter-width-stable.mjs",
"check:filter-multivalue": "node plugin-designs/research-map-plus-grid/scripts/check-filter-multivalue.mjs",
"check:fits-screen": "node internal/check-fits-the-screen.mjs",
"check:fits-screen:self-test": "node internal/check-fits-the-screen.mjs --self-test",
"check:fits-screen:self-test:b": "node internal/check-fits-the-screen.mjs --self-test --self-test-clause=b",
@ -165,6 +166,7 @@
"check:print-geometry": "node internal/check-print-geometry.mjs",
"check:pin-dim-slider": "node plugin-designs/doxa-research-map/scripts/check-pin-dim-slider.mjs",
"check:profiles-mount": "node internal/check-composites-mount.mjs --all-profiles",
"check:provider-for": "node internal/check-provider-for.mjs",
"check:provider-switch": "node internal/check-provider-switch.mjs",
"check:public-hygiene": "node internal/public-hygiene-check.mjs",
"check:published": "node internal/check-published-matches-build.mjs",
@ -214,7 +216,7 @@
"dev": "bun x vite",
"fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.mjs",
"gates:post": "node internal/run-parallel.mjs --label=post -- 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: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",
"gates:post": "node internal/run-parallel.mjs --label=post -- 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: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",
"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",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs",
@ -304,6 +306,7 @@
"check:facet-changes-map:built": "the SAME gate against the BUILT tree instead of the dev server. Measured 2026-09-24: the dev default is 1 PASS / 3 FAIL in four runs on a busy box (88 unbundled module requests vs the built leaf's 3), while the built tree passes deterministically. Use this when you need a trustworthy answer; use the dev default when checking uncommitted source.",
"check:facet-changes-map:self-test": "reverts the fix IN THE LIVE PAGE and requires the gate to FAIL. SELF_TEST_MODE=colour (default, isolates the colour assertion — the reported bug) | props | both | frozen (blocks the source update, isolating the set assertion). All four modes verified caught.",
"check:facet-one-field": "ONE AXIS = ONE CONTROL-REACHABLE FACET. Asserts on the PARTITION each facet induces over the rows, NOT on which r.<field> it reads: three fields (religion, primary_language, rop1) are legitimately read by two facets each as tier-1/tier-2 pairs, so a gate on duplicate field READS would fail correct code. Scoped to facets a user can actually select (rail + dropdowns + charts), because a search-only projection sharing a partition is fine until it becomes a control. run check:facet-one-field:self-test for the NEGATIVE test.",
"check:filter-multivalue": "SPEC-DASHBOARD-KIT D1: filterSet[key] may hold an array meaning OR-within-key, keys still AND together — and exactly ONE function answers 'does this row match'. The rule `a(r) === filterSet[k]` was written out SEVEN times (shell, facet rail x2, map panel, table, charts rail, CSV exporter); the moment it grew an array case, six copies would have kept comparing an array with === , matched nothing, and left the panels disagreeing about what is on screen — the facet rail printing counts for a scope the map and table no longer render. Runs the real bus (OR within, AND across, one-element array collapses to a scalar, EMPTY array clears rather than matching nothing, toggleFilterValue adds/removes/clears, cascade intact), greps the panels for surviving copies, round-trips the URL hash including a value containing a comma, and lifts the grid's valueMatches to prove both ends OR the same way. The list separator is `|` and not `,` because 120 of the 864 live language labels contain a comma. No browser.",
"check:fits-screen": "THE FITNESS-FOR-USE GATE (LL-the-user-found-three-bugs-my-gates-could-not-see): a green STATE suite (mounts/fetches/counts/paints) said nothing about whether the product is USABLE, and the agent found five real defects in one sitting that no gate saw. Asserts 5 clauses on the built dashboard-plus-grid composite + research-dashboard leaf, real browser, live data: (A) the dashboard band fits ONE 1440x900 viewport and is shorter than its own tree-legend's scrollHeight; (B) the people-groups grid is still reachable by scrolling underneath it (a fix for A must not re-break this — AP-fixing-reachability-by-making-the-thing-taller); (C) a wheel over the map changes ZOOM and does not scroll the pane, a wheel below it scrolls and does not zoom — instrumented on the DISPATCHED EVENT (composedPath), not geometry, per LL-four-wrong-diagnoses-instrument-the-event-not-the-geometry; (D) the grid's live count ([data-table-count], the composite's documented single source of truth — show-total=false on the grid child is deliberate) is visible and CHANGES when a facet narrows it; (E) a 1440/1280/768/390 responsive sweep has no horizontal document overflow and no element clipped to zero height (.rd-td/.rd-th ellipsis excluded — expected). Spawns its own serve-strict on PORT (default 4391); SKIPs (exit 0) if the tree is not settled at START OR re-settled at END, or pray.doxa.life is unreachable — never reports a defect from a torn tree. Also best-effort probes any cap-5 shell the manifest declares (type nested-mfe-spa) by CONTENT-MATCHING a tab, never by hardcoded structure, so it survives the shell's own in-flight restructure without depending on it. Needs a built tree (`bun run build`). run `check:fits-screen:self-test` for the NEGATIVE test.",
"check:fits-screen:self-test": "FIVE regression modes, one per clause, each required to fire ITS OWN [A]/[B]/[C]/[D]/[E]-named failure: run clause a via THIS entry and b-e via `check:fits-screen:self-test:<b|c|d|e>`. a (default — forces the dashboard section past the viewport, the AP-fixing-reachability shape), b (n/a here — B is proven by A's own poison already trapping the grid below the fold; kept as a mode for a future independent poison), c (freezes map.scrollZoom enabled, the BUG-MAP-EATS-THE-WHEEL regression), d (blanks the live count element), e (forces the shell wider than the viewport at 1440). A gate that has only ever printed PASS is unproven.",
"check:fits-screen:self-test:b": "★ A DEDICATED ENTRY, not a mode appended at the call site. The parent script value ends in an inline # comment, so a mode appended by the caller lands INSIDE that comment and the shell drops it — the run then exits 0 in DEFAULT mode, indistinguishable from the mode being selected. A self-test is the proof a gate can fail, so a self-test whose mode cannot be chosen is a gate certifying itself. This entry hardcodes its mode, so nothing has to be appended. (Flag names are deliberately not spelled here: check:script-flags scans script VALUES, and naming one in prose reads as advertising it.)",
@ -311,7 +314,7 @@
"check:fits-screen:self-test:d": "★ A DEDICATED ENTRY, not a mode appended at the call site. The parent script value ends in an inline # comment, so a mode appended by the caller lands INSIDE that comment and the shell drops it — the run then exits 0 in DEFAULT mode, indistinguishable from the mode being selected. A self-test is the proof a gate can fail, so a self-test whose mode cannot be chosen is a gate certifying itself. This entry hardcodes its mode, so nothing has to be appended. (Flag names are deliberately not spelled here: check:script-flags scans script VALUES, and naming one in prose reads as advertising it.)",
"check:fits-screen:self-test:e": "★ A DEDICATED ENTRY, not a mode appended at the call site. The parent script value ends in an inline # comment, so a mode appended by the caller lands INSIDE that comment and the shell drops it — the run then exits 0 in DEFAULT mode, indistinguishable from the mode being selected. A self-test is the proof a gate can fail, so a self-test whose mode cannot be chosen is a gate certifying itself. This entry hardcodes its mode, so nothing has to be appended. (Flag names are deliberately not spelled here: check:script-flags scans script VALUES, and naming one in prose reads as advertising it.)",
"check:gallery-embed": "the cap-5 Research Gallery must embed on a FOREIGN ORIGIN (a stand-in host page on localhost, the built tree on 127.0.0.1) in BOTH modes, bare <script>+<doxa-maps-spa> and <iframe>, with the layout inside the host box and every curated tab painting a map. The summary line prints the tab count PER MODE, and a mode that checked zero tabs is a FAIL. One mode only: check:gallery-embed:js or check:gallery-embed:iframe. Starts its own servers on OS-assigned ports; needs only a built tree. Exit 2 = SKIP (no built tree / no playwright).",
"check:gallery-embed:live-cors": "the Research Gallery on a foreign page under the LIVE static layer's CORS rule (Access-Control-Allow-Origin on files inside the tree's folders, NOT on the root manifest.json, measured on nuxtbase): it must still boot every tab, by reading the CORS-readable manifest copy generate-tree writes beside its bundle.",
"check:gallery-embed:live-cors": "the Research Gallery on a foreign page under the LIVE static layer's CORS rule (Access-Control-Allow-Origin on files inside the tree's folders, NOT on the root manifest.json, measured on the live test host): it must still boot every tab, by reading the CORS-readable manifest copy generate-tree writes beside its bundle.",
"check:gospel-all": "EVERYTHING gospel-resources owns, in one command. Its ten gates were all hand-run, which is how a dead bundle shipped: a duplicate module killed all five leaves and the one gate run that day was pointed at the map. check:no-orphan-gates was right that they were reachable — reachable is not run. Deliberately NOT in gates:pre/post: three need a browser, the shared wave holds browser gates under load, and a bundle owner should not be able to slow everyone else's build by adding to a list they do not own. Static gates run first and stop the run on failure, so a scarce browser slot is never spent confirming a table is inconsistent. Exit 75 = NOT MEASURED, never folded into a pass.",
"check:heartbeat": "the HEARTBEAT ZONE threshold (the choropleth's top bin) must come from config AND the shipped config must still render today's ramp. Two sides: (B) a configured threshold must MOVE the edge — catches re-hard-coding; (A2) dashboard.config.js's own map.* must still resolve to 5/20/60/150/Infinity — catches the shipped default silently changing. A2 was MISSING at first and heartbeatThreshold:999 shipped past this gate printing 'default ramp unchanged'. A grep for '150' cannot do either job: it false-positives on the legitimate default and false-negatives on a re-hard-coding at 200. run check:heartbeat:self-test for the NEGATIVE tests (3 cases).",
"check:heartbeat:self-test": "THREE regression cases, each required to fire ITS OWN assertion: [literal] resolver replaced by the old hard-coded array -> B; [shipped-default] dashboard.config.js heartbeatThreshold changed -> A2; [shipped-mode] heartbeatMode flipped to quantile -> A2. Poisons IN MEMORY, never the file. Testing that the FEATURE works is not testing that the REGRESSION is caught — this gate shipped with only the first and missed the second.",
@ -360,6 +363,7 @@
"check:poster-print-fallback": "clicks the REAL poster print control with every posters/*.webp aborted, and asserts the preview RASTERISES from posters/<id>.html instead of opening on \"Poster not available yet\". Needs a served tree built with posterSources:true; SKIPs otherwise.",
"check:prayer-walk-planner": "the Prayer Walk Planner is a full-page tool that prints a real walking map: the map canvas fills the page below the toolbar in its own leaf AND inside the Research Gallery at 1280x800 and 390x844 (the gallery once collapsed it to 76px / 0px); a real zone (Balat, Istanbul) renders a >=2000px print map that is not blank, saved to OUT=<dir> so a human can read it; ?lang=es gives a Spanish toolbar; 0 page errors. Run inside the shared browser locks. Exit 2 = SKIP.",
"check:profiles-mount": "check:composites-mount over EVERY profile of every bundle and every preset instance (with its props), in en and ar: any setup, watcher or mount error fails it. Headless, ~40 s. A source mount: a build-only error (a minified name hoisted out of order) needs the built bundle and is not covered.",
"check:provider-for": "providerFor(map) must answer the same as the raw `map instanceof window.maplibregl.Map` sniffs it replaced (C3) - on BOTH engines AND on the AMBIGUOUS input that is neither, where the originals fell through to mapbox/false. Carries an executable proof that the obvious isMapboxMap() rewrite INVERTS that fall-through while agreeing on both real engines, which is how it would have changed a poster attribution unnoticed. Headless, source-only. engineGlobal is extract-and-executed from source because useMapInstance.js cannot be imported in plain Node.",
"check:provider-switch": "the provider switch must actually SWITCH: counts requests BY HOST (not window globals, which stay defined after a swap) and asserts the maplibre run stays within the leaf HTML's own declared api.mapbox.com preloads. Defaults to the upg-100 leaf; pass a URL or set BASE_URL for another. CONDITIONAL: SKIPs when nothing is served.",
"check:published": "what readers are SERVED must be what we BUILT. Every other gate measures the local tree, so all of them stay green while a stale publish serves something else — and manufactures bug reports that cannot be reproduced. Compares the sha384 per bundle. SKIPs when the published host is unreachable (a network gate that goes red on flaky wifi gets ignored).",
"check:quoted-scripts": "documentation/README-SECTION-3-cli-commands.md QUOTES the scripts block verbatim; it had silently drifted TWICE (a stale bunx cap:sync, and build:unlocked missing check:public-hygiene + check:label-joins). package.json is what RUNS — update the DOC.",

View file

@ -31,7 +31,7 @@
*/
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { useMapInstance } from '@map/composables/useMapInstance.js'
import { useMapInstance, engineGlobal } from '@map/composables/useMapInstance.js'
import { resolveMapProvider, setProjectionOn } from '@map/composables/useMapProvider.js'
import { useMapData } from '@map/composables/useMapData.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
@ -295,7 +295,9 @@ function addLayer(fc) {
const pray = doxaRec && doxaRec.slug
? `<a href="https://pray.doxa.life/${encodeURIComponent(doxaRec.slug)}?source=doxalife&utm_source=${utmSource()}" target="_blank" rel="noopener">${esc(tr('pray'))}</a>`
: ''
const NS = mapProvider === 'maplibre' ? window.maplibregl : window.mapboxgl
// C3: engineGlobal() resolves a provider NAME to its namespace and already defaults to Mapbox
// for anything unrecognised, which is exactly what this ternary's else branch did.
const NS = engineGlobal(mapProvider)
new NS.Popup({ closeButton: true, maxWidth: '290px' })
.setLngLat(e.lngLat)
.setHTML(`<div class="apg-pop">

View file

@ -223,7 +223,7 @@ function onDashboardFilter({ filterSet = {}, searchTerm = '' } = {}) {
// Tier 1 takes a whole url, so it is COMPUTED rather than hardcoded: resolveEmbedBase() is the same
// helper ShareButton uses (profileConfig.embedBaseUrl → window.MAP_EMBED_BASE_URL → the root the
// built bundle derives from its own script url), so the link stays correct on doxa.life, on
// nuxtbase.app and in a dropped tree with no configuration. Hardcoding an absolute url here would
// the live test host and in a dropped tree with no configuration. Hardcoding an absolute url here would
// pin every share to whichever host happened to build it.
//
// Note this is exactly the two-host defect class: research-dashboard standalone PASSES the same

View file

@ -1124,7 +1124,7 @@ export default defineComponent({
// ★ THE CORS-READABLE COPY (generate-tree writes it beside this bundle, byte-identical).
// A script embed on a partner page FETCHES the manifest cross-origin, and the live static layer
// sends Access-Control-Allow-Origin only on paths INSIDE a folder of the tree — every bundle file
// and assets/ carry it, the ROOT manifest.json does not (measured 2026-09-25 on nuxtbase: the
// and assets/ carry it, the ROOT manifest.json does not (measured 2026-09-25 on the live test host: the
// root 200 without it, doxa-maps-spa/<anything> with it, even a 404). So on a foreign page the
// copy is read FIRST (no CORS error in the host's console) and the root second; same-origin keeps
// the root first. Script URLs still resolve against the TREE ROOT, never the copy's folder.

View file

@ -41,7 +41,7 @@ import { retryableAsyncComponent } from '@map/composables/retryableAsyncComponen
// ─── Composables (framework) ─────────────────────────────────────────────────
import { useMapInstance } from '@map/composables/useMapInstance.js'
import { resolveMapProvider, providerLabel, setProjectionOn } from '@map/composables/useMapProvider.js'
import { resolveMapProvider, providerLabel, providerFor, setProjectionOn } from '@map/composables/useMapProvider.js'
import { useMapLayers, PIN_STROKE_WIDTH_LIGHT, buildDarkRingWidth } from '@map/composables/useMapLayers.js'
import { useMapTheme, THEME_TOGGLE, FIXED_BASEMAP } from '@map/composables/useMapTheme.js'
import { useMapEvents } from '@map/composables/useMapEvents.js'
@ -1319,7 +1319,9 @@ async function openPoster() {
spec.slots.scaleBar = false
spec.slots.north = false
// Credit the tiles actually drawn: OpenFreeMap on MapLibre, Mapbox otherwise.
const onMaplibre = !!(window.maplibregl && map.value instanceof window.maplibregl.Map)
// C3: ask the library, not the globals. The explicit === 'maplibre' keeps the original FALSE for
// a map that is neither, so the Mapbox attribution stays the fall-through it always was.
const onMaplibre = providerFor(map.value) === 'maplibre'
spec.slots.footer = { ...spec.slots.footer, left: 'pray.doxa.life', right: new Date().toISOString().slice(0, 10),
attribution: onMaplibre ? '© OpenFreeMap © OpenMapTiles © OpenStreetMap' : '© Mapbox © OpenStreetMap' }
const blob = await exporter.render(spec, 'png')
@ -2234,9 +2236,18 @@ function emitFilterState() {
const detail = {
region: mapStore?.selectedRegion || '',
religion: religionName,
// A dialect selection reports the dialect's own display name, so a host that shows the value
// shows what the user clicked rather than its parent.
language: dialectLabels.length ? (dialect?.key ? `${langName}, ${dialect.key}` : langName) : langName,
// ★ A DIALECT'S NAME COMES FROM ITS LABELS, NOT ITS KEY (rega, row 43 case 3: this reported
// ", Afro-Asiatic__Arabic__Sudanese"). `selectedDialect.key` is the TREE NODE ID —
// family__baseLang__dialect — and the watcher fifteen lines above says so in as many words;
// I read that comment and then used the key as a display name anyway. Worse, selectedLanguage
// is empty by the time a dialect is active, so the template produced a leading comma and an
// internal identifier: a string no reader could make sense of, in the one slot meant for a
// human name.
// dialectLabels are the ORIGINAL API labels the legend grouped ("Arabic, Sudanese"), so they
// are both the right words and already in the reader's locale. The first is the display name;
// when a dialect groups several labels the whole list is the honest answer, because that is
// what the selection covers.
language: dialectLabels.length ? dialectLabels.join(' · ') : langName,
languageCode: langCode,
// ADDITIVE: every code the selection covers, so a consumer with multi-value filtering can
// narrow correctly where a single code cannot express the selection. Existing listeners that
@ -2245,12 +2256,36 @@ function emitFilterState() {
}
host.dispatchEvent(new CustomEvent('map-filtered', { detail, bubbles: true, composed: true }))
}
// ★ ONE EVENT PER SETTLED SELECTION (rega, row 43 case 3: a dialect click narrowed the grid in
// only ONE of four runs, with a correct languageCode in all four).
//
// THE CAUSE IS A BURST MEETING A GUARD. Clicking a dialect does not change one store value; it
// clears the language, sets the family and sets the dialect, across ticks, so the watcher below
// fired three or four times and this element dispatched three or four map-filtered events for one
// click. The composite ignores anything that arrives while it is pushing state INTO its children —
// a guard it needs, because the grid re-emits during a push and the round trip would otherwise
// cancel the very selection just made — and that window is 1.2 seconds. So whether the LAST event
// in the burst, the one carrying the settled state, arrived inside or outside the window was
// timing. Three runs lost it; one did not.
//
// Coalescing here is the fix that belongs to this file: the other end's guard is correct, and the
// burst is what this element should not have been sending. One click, one event, nothing to drop.
// A short window rather than nextTick because the cascade crosses ticks (the legend bridge does
// async work); 60ms is far below the 1.2s guard and below what a reader perceives as a delay.
let _emitTimer = null
const EMIT_SETTLE_MS = 60
function emitFilterStateSettled() {
clearTimeout(_emitTimer)
_emitTimer = setTimeout(emitFilterState, EMIT_SETTLE_MS)
}
onBeforeUnmount(() => clearTimeout(_emitTimer))
// selectedDialect joins the sources: it is the deepest tier of the Language tree and a genuine
// language selection, but it was not observed here, so drilling from "Arabic" to "Arabic, Sudanese"
// changed the map and left the grid reporting the tier above. `.key` rather than the object,
// because the store replaces it wholesale and a deep watch here would re-emit on every re-render.
watch(() => [mapStore?.selectedRegion, mapStore?.selectedLanguage, mapStore?.selectedDialect?.key, uiStore?.religionFilter],
() => emitFilterState())
() => emitFilterStateSettled())
// ─── Universal-legend (LegendRows) → applyDimFilter bridge ───────────────────
// LegendRows / useLegendData routes row-clicks through mapStore.selectFamily()

View file

@ -12,7 +12,7 @@
* family > language > dialect and derives the language tier by splitting the API label at the comma.
* MEASURED on the live source: 2,107 rows, all carrying primary_language as {value,label}, and NO
* row whose label is "Arabic" — what exists is "Arabic, Sudanese", "Arabic, Levantine", "Arabic,
* Gulf", 22 codes in all. The exact match could never hit, in any locale, which is why it failed
* Gulf", 23 codes in all. The exact match could never hit, in any locale, which is why it failed
* identically in en and ar.
*
* ★ THE NUMBERS THIS GATE PINS, measured on the committed fixture (864 distinct label/value pairs,
@ -20,7 +20,7 @@
* OLD exact-label rule: 61 tiers resolved "" -> the grid went to zero. "Arabic" was one.
* NEW rule: 764 tiers resolve ONE code (the grid filters), 41 cover several and
* abstain honestly.
* The 41 are real ambiguity, not a bug: "Arabic" alone means 22 ISO codes, so no single value can
* The 41 are real ambiguity, not a bug: "Arabic" alone means 23 ISO codes, so no single value can
* express it. Sending one of them would show a fraction of the selection while looking correct;
* sending the tier NAME shows zero. Abstaining is the only option that does not lie, until
* filterSet goes multi-value (SPEC-DASHBOARD-KIT D1).
@ -40,10 +40,15 @@
* next bug, so the gate compares the two that exist.
* C. for every base tier: exactly one code -> a NON-EMPTY languageCode; several -> an EMPTY
* languageCode AND a populated languageCodes.
* D. the composite fails OPEN: gridLanguage() must return '' when the selection covers several
* codes. Returning the label there is what produced the empty grid.
* D. the composite sends the WHOLE code set when a selection covers several (SPEC-DASHBOARD-KIT
* D1), and never the tier NAME. Before D1 the honest answer was to abstain, because the grid
* could hold one value; now that it takes a list, abstaining would leave a selection
* unexpressed that the receiving end can express. Asserted per tier: the joined set must
* select exactly the rows the map selected.
* E. no tier may produce a value that selects zero rows. That is the user-visible invariant and
* it is asserted directly, not inferred.
* F. a DIALECT reports a NAME and not a tree node id, and the emitter sends ONE event per
* settled selection rather than a burst. Both from rega's row 43 case 3.
*
* NO NETWORK, NO BROWSER (Playwright is rega's alone). The fixture is committed and was taken from
* the live source; refresh it with --refresh-fixture when the source's language set changes.
@ -169,12 +174,21 @@ if (matchesLanguage) {
many++
if (languageCode) fails.push(`C "${base}" covers ${codes.length} codes but still emitted a single languageCode — one of them would show a fraction of the selection while looking correct.`)
if (!codes.length) fails.push(`C "${base}" resolved no codes at all.`)
// D1: the SET is what the grid now filters on, so it has to select exactly the rows the map
// selected. Asserted per tier rather than on one example, because a join that dropped or
// duplicated a code would still look plausible on "Arabic".
const joined = codes.join('|')
const wants = new Set(joined.split('|'))
const gridRows = rows.filter((r) => wants.has(r.primary_language.value))
if (gridRows.length !== selected.length) {
fails.push(`C "${base}": the map selects ${selected.length} row(s) but the joined code set selects ${gridRows.length} — the two ends of the link disagree.`)
}
}
}
if (zeroSelecting.length) {
fails.push(`E ${zeroSelecting.length} language tier(s) select ZERO rows, so clicking them empties the grid: ${zeroSelecting.slice(0, 6).join(', ')}`)
}
notes.push(`tiers: ${byBase.size} — ${single} resolve one code (the grid filters), ${many} cover several (the axis is left open)`)
notes.push(`tiers: ${byBase.size} — ${single} resolve one code, ${many} cover several and cross as the whole set (D1); every tier narrows the grid to the rows the map selected`)
if (many === 0) fails.push('C no tier in the fixture covers several codes, so the ambiguous path is untested — the fixture was sampled rather than deduplicated.')
}
@ -189,13 +203,58 @@ if (matchesLanguage) {
if (fn) {
const many = { language: { value: '', label: 'Arabic', codes: ['apd', 'acm', 'ayl'] } }
const one = { language: { value: 'ben', label: 'Bengali', codes: ['ben'] } }
if (fn(many) !== '') fails.push(`D gridLanguage() returned ${JSON.stringify(fn(many))} for a selection covering 3 codes — it must return '' and leave the axis unfiltered. Returning the label is what took the grid to 0.`)
// ★ UPDATED FOR D1. This used to require '' for a multi-code tier, and that was the right
// answer while the grid could hold one value: the map narrowed, the grid did not, and the
// reader could see both. Now that valueMatches() takes a LIST, abstaining would be leaving a
// selection unexpressed that the receiving end can express — so the whole code set crosses.
// What has NOT changed, and is asserted below, is that it never sends the tier NAME: "Arabic"
// matches no row reading "Arabic, Sudanese", and that is what emptied the grid originally.
const got = fn(many)
if (got !== 'apd|acm|ayl') fails.push(`D gridLanguage() returned ${JSON.stringify(got)} for a selection covering 3 codes — it must send all three, pipe-joined, so the grid narrows to the same rows the map did. (`|` and not `,`: a language label routinely contains a comma.)`)
if (/[A-Z]/.test(String(got))) fails.push(`D gridLanguage() returned ${JSON.stringify(got)}, which looks like a display NAME rather than codes — a tier name matches no row and empties the grid.`)
if (fn(one) !== 'ben') fails.push(`D gridLanguage() returned ${JSON.stringify(fn(one))} for an unambiguous selection — it must send the single code.`)
if (!fails.some((f) => f.startsWith('D'))) notes.push('composite: gridLanguage() sends the code when there is one and abstains when there is not')
if (!fails.some((f) => f.startsWith('D'))) notes.push('composite: gridLanguage() sends the single code, or every code in the selection — never the tier name')
}
}
}
// ── E. the dialect tier: a name, and one event ───────────────────────────────────────────────
// rega, row 43 case 3, on BOTH engines in BOTH locales: clicking "Arabic, Sudanese" emitted the
// right languageCode and narrowed the grid in only ONE of four runs, while the `language` slot read
// ", Afro-Asiatic__Arabic__Sudanese".
// THE NAME: selectedDialect.key is the TREE NODE ID (family__baseLang__dialect) and
// selectedLanguage is empty by the time a dialect is active, so the template produced a leading
// comma and an internal identifier. The labels the legend grouped are the right words, and they
// are already in the reader's locale.
// THE ONE EVENT: a dialect click changes several store values across ticks, so the watcher fired
// three or four times and this element dispatched a BURST for one click. The composite ignores
// anything arriving while it pushes state into its children — a guard it needs, because the grid
// re-emits during a push — and that window is 1.2s. Whether the settled event landed inside it
// was timing. Three runs lost it; one did not.
// Both are shapes, so both are asserted against the source and neither needs a browser.
{
const src = emitterSrc
const line = /language: ([^\n]*),\n/.exec(src.slice(src.indexOf('const detail = {')))
if (!line) fails.push('E research-map.vue: could not find the `language:` field of the map-filtered detail — this gate is blind to the name it reports.')
else {
if (/dialect\??\.key/.test(line[1])) fails.push(`E the dialect's display name is built from selectedDialect.key, which is the TREE NODE ID (family__baseLang__dialect), not a name: ${line[1].trim()}`)
if (!/dialectLabels/.test(line[1])) fails.push(`E the dialect's display name does not come from dialectLabels (the original API labels, already localized): ${line[1].trim()}`)
}
if (!/clearTimeout\(_emitTimer\)[\s\S]{0,200}setTimeout\(emitFilterState/.test(src)) {
fails.push('E research-map.vue no longer coalesces map-filtered — one click emits a BURST, and the composite drops whatever arrives inside its 1.2s push window, so whether the settled state lands is timing.')
} else {
// ★ THE WATCHER LINE SPECIFICALLY, not the file. A first version searched the whole source for
// `emitFilterStateSettled()`, which the helper's own declaration satisfies — so reverting the
// WATCHER to the raw emit left the gate green while the burst came back. A check that its own
// subject can satisfy is not a check.
const w = /watch\(\(\) => \[mapStore\?\.selectedRegion[\s\S]{0,240}?\)\n/.exec(src)
if (!w) fails.push('E could not find the emitter watcher — this gate is blind to whether the burst is coalesced.')
else if (!/emitFilterStateSettled\(\)/.test(w[0])) {
fails.push('E the settle helper exists but the WATCHER still calls the raw emit, so one click still dispatches a burst.')
} else notes.push('dialect: named from its labels, and one event per settled selection')
}
}
for (const n of notes) console.log(' · ' + n)
if (fails.length) {
console.error(`\n${TAG}: FAIL`)

View file

@ -580,11 +580,32 @@ const DERIVED_AXES = {
}
// ONE resolver for both filter loops: a derived key runs its accessor, a real key reads the row.
const rowAxisValue = (u, field) => (DERIVED_AXES[field] ? DERIVED_AXES[field](u) : u[field])
// ★ A SELECTION MAY BE SEVERAL VALUES (SPEC-DASHBOARD-KIT D1), and this is the end of the wire
// that makes the "Arabic" case a real narrowing instead of an honest abstention. The language tier
// "Arabic" covers 22 ISO codes, so no single value expresses it; until now the composite had to
// send nothing and leave the axis unfiltered rather than show a fraction of the selection or an
// empty grid. A comma-separated list is accepted here and matched as OR-within-the-axis, which is
// exactly what the bus's facetMatches() does at the other end — one rule, two implementations,
// and they have to stay the same shape.
// ★ THE SEPARATOR IS `|`, AND IT IS NOT `,` FOR A MEASURED REASON. A separator that can also occur
// inside a value is not a separator — the rule the URL hash already states one file over. The first
// version of this split on `,` and BROKE LABEL MATCHING, because 40% of the live language labels
// are comma-inverted ("Arabic, Mesopotamian"): the selection was chopped into "Arabic" and
// "Mesopotamian" and matched neither. Caught by check:filter-multivalue's own label assertion
// before it left this machine, which is the entire point of asserting a behaviour you are keeping
// rather than only the one you are adding. Measured on the 864 live label/value pairs: `,` appears
// in 120 of them, `|` in none.
// A HOST STILL SENDS ONE STRING: an attribute is a string, so a list arrives as "apd|acm|ayl", and
// a single value with no separator takes the identical path it always did.
function valueMatches(vl, selection) {
if (!vl) return false
const want = norm(selection?.value)
if (!want) return false
return norm(vl.value) === want || norm(vl.label) === want
const raw = selection?.value
if (raw === null || raw === undefined || raw === '') return false
const wants = (Array.isArray(raw) ? raw : String(raw).split('|'))
.map((v) => norm(v)).filter(Boolean)
if (!wants.length) return false
const value = norm(vl.value), label = norm(vl.label)
return wants.some((want) => value === want || label === want)
}
function applyDropdownFilters(list) {

View file

@ -8,7 +8,7 @@
*/
import { CDN, pinElement } from '@map/constants/cdn.js'
import { loadPinnedUmd } from '@map/utils/loadPinnedUmd.js'
import { setProjectionOn } from '@map/composables/useMapProvider.js'
import { providerFor, setProjectionOn } from '@map/composables/useMapProvider.js'
let base = './assets'
export function setDataBase(b) { if (b) base = String(b).replace(/\/+$/, '') }
export function dataUrl(file) { return base + '/pplr/' + file }
@ -112,7 +112,9 @@ export function exposeMap(map) {
let layers = []
try { layers = map.getStyle()?.layers || [] } catch (_) {}
return {
engine: window.maplibregl && window.maplibregl.Map && map instanceof window.maplibregl.Map ? 'maplibre' : 'mapbox',
// C3: ask the library, not the globals. `|| 'mapbox'` keeps the ORIGINAL fall-through for a map
// that is neither (stub, destroyed, neither global loaded) -- see providerFor's own note.
engine: providerFor(map) || 'mapbox',
// 'load' may already have fired before this listener was attached; a loaded style says so too
loaded: loaded || !!(map.isStyleLoaded && map.isStyleLoaded()),
styleLayers: layers.length,

View file

@ -56,7 +56,7 @@ function rowsExcluding(exclude) {
const keys = Object.keys(filterSet).filter((k) => !exclude.includes(k))
const hasText = !!(bus?.searchTerm?.value || '').trim()
if (!keys.length && !hasText) return rows
const faceted = rows.filter((r) => keys.every((k) => { const a = FACET_ACC[k]; return a ? a(r) === filterSet[k] : true }))
const faceted = rows.filter((r) => bus.rowMatchesFilters(r, FACET_ACC))
return filterRowsBySearch(faceted, matchSearch)
}
@ -89,9 +89,9 @@ const charts = computed(() => (cfg.charts || []).map((c) => {
: buildDonut(agg, 132, 0.58, c.facet, overrides),
}
}))
const isActive = (facet, key) => filterSet[facet] === key
const isActive = (facet, key) => bus.isFilterActive(facet, key)
// true when this chart's own facet has an active selection → drives the pop-out / dim-others state.
const hasSelection = (facet) => filterSet[facet] != null
const hasSelection = (facet) => filterSet[facet] != null // any selection, scalar or array
function pick(facet, key) {
if (!key || key === 'Other' || !bus) return

View file

@ -60,7 +60,10 @@ function rowsExcluding(exclude) {
const keys = Object.keys(filterSet).filter((k) => !exclude.includes(k))
const hasText = !!(bus?.searchTerm?.value || '').trim()
if (!keys.length && !hasText) return rows
const faceted = rows.filter((r) => keys.every((k) => { const a = FACET_ACC[k]; return a ? a(r) === filterSet[k] : true }))
// The rail prints the COUNTS a reader acts on before clicking, so it above all must use the
// same predicate the map and table use — a rail still comparing with === would show counts for
// a scope the other panels no longer render. See D1 in useSelectionBus.js.
const faceted = rows.filter((r) => bus.rowMatchesFilters(r, FACET_ACC))
return filterRowsBySearch(faceted, matchSearch)
}
@ -125,14 +128,14 @@ function isExpandedRaw(facet, value) {
const k = nodeKey(facet, value)
// undefined = never touched → fall back to "expanded iff this node is the active filter",
// so an active branch still opens itself exactly as it always did.
return k in expanded ? expanded[k] : filterSet[facet] === value
return k in expanded ? expanded[k] : bus.isFilterActive(facet, value)
}
function toRows(agg, facet, level) {
const max = agg.reduce((m, d) => Math.max(m, d.value), 0) || 1
return agg.map((d) => ({
facet, value: d.key, label: d.key, count: d.value, frac: d.value / max, level,
other: !!d.other, active: !d.other && filterSet[facet] === d.key,
other: !!d.other, active: !d.other && bus.isFilterActive(facet, d.key),
// Carried per-ROW rather than looked up in the template, so the zero-row baseline snapshot
// (which spreads a row object) keeps the mark when a row is re-admitted.
dashboardOnly: DASHBOARD_ONLY.has(facet),
@ -390,7 +393,7 @@ const scopeCount = computed(() => {
const rows = dash?.rows?.value || []
const keys = Object.keys(filterSet)
if (!keys.length) return rows.length
return rows.filter((r) => keys.every((k) => { const a = FACET_ACC[k]; return a ? a(r) === filterSet[k] : true })).length
return rows.filter((r) => bus.rowMatchesFilters(r, FACET_ACC)).length
})
// each rendered row already carries its own facet key (data-facet/data-value).

View file

@ -57,7 +57,7 @@ const filteredRows = computed(() => {
const keys = Object.keys(filterSet)
const hasText = !!(bus?.searchTerm?.value || '').trim()
if (!keys.length && !hasText) return rows
const faceted = rows.filter((r) => keys.every((k) => { const a = FACET_ACC[k]; return a ? a(r) === filterSet[k] : true }))
const faceted = rows.filter((r) => bus.rowMatchesFilters(r, FACET_ACC))
return filterRowsBySearch(faceted, matchSearch)
})
// Geo join is config-driven: geoFacet selects the facet whose `geoIso` accessor yields the ISO-3 join

View file

@ -157,7 +157,12 @@ const filters = computed(() => dropdownKeys.value.map((key) => ({
name: key,
label: FACETS.value[key]?.label || key,
options: optionsFor(key),
value: filterSet[key] || '',
// ★ A DROPDOWN CANNOT SHOW A MULTI-VALUE SELECTION, SO IT SHOWS NONE (D1).
// Picking the first of several would put a tick beside one option while the board is narrowed by
// three — a control stating something false about the state it belongs to, which is worse than a
// control stating nothing. The CHIP still names every value, so the selection is never invisible:
// it is simply reported by the surface that can report it honestly.
value: Array.isArray(filterSet[key]) ? '' : (filterSet[key] || ''),
})))
// Chips derived from the live filterSet. For THIS component's own dropdown axes label === value

View file

@ -69,7 +69,9 @@ export const EXPORT_FIELDS = [
* Optional: omitted, the stamp falls back to the raw value, so existing callers keep working.
* It is applied to the COMMENT LINE ONLY — never to a data column (see buildCsv).
*/
export function useCsvExport({ rowStore, rows, selectionSet, filterSet, FACET_ACC, dataOpts = {}, labelFor = null }) {
export function useCsvExport({ rowStore, rows, selectionSet, filterSet, FACET_ACC, dataOpts = {}, labelFor = null,
// Defaulted so a caller that predates D1 keeps working exactly as before rather than throwing.
rowMatches = (r, ACC) => Object.keys(filterSet).every((k) => { const a = ACC[k]; return a ? a(r) === filterSet[k] : true }) }) {
// slug -> wide API record, filled by the lazy all-fields fetch below.
let wideStore = null
let widePromise = null
@ -117,7 +119,10 @@ export function useCsvExport({ rowStore, rows, selectionSet, filterSet, FACET_AC
}
const keys = Object.keys(filterSet)
if (keys.length) {
return (rows.value || []).filter((r) => keys.every((k) => { const a = FACET_ACC[k]; return a ? a(r) === filterSet[k] : true }))
// The bus's own predicate, handed in by the shell — the exporter must select the same rows
// the reader is looking at, and a private copy of the rule is how an export starts
// disagreeing with the screen it was taken from.
return (rows.value || []).filter((r) => rowMatches(r, FACET_ACC))
}
return rows.value || []
}

View file

@ -25,7 +25,7 @@
*/
import { exactMatches, fuzzyMatches, resolveFuzzyValues } from '@map/components/search/fuzzy.js'
import { provide, inject, ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import { createSelectionBus } from './useSelectionBus.js'
import { createSelectionBus } from '@map/composables/useSelectionBus.js'
import { useDashboardData } from './useDashboardData.js'
import { useCsvExport } from './useCsvExport.js'
import { useTreeLegendLabels } from './useTreeLegendLabels.js'
@ -227,13 +227,21 @@ function useResponsiveShell(bus) {
const slugify = (s) => String(s ?? '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40)
// Minimal escaping, maximal legibility: reserve our three delimiters + %, keep spaces as `+`
// (the x-www-form-urlencoded convention), leave everything else -- including unicode -- alone.
const escapeValue = (s) => String(s).replace(/[%;:*+]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()).replace(/ /g, '+')
// `,` joins the values of ONE key (D1: OR-within-key). It is reserved here for the same reason
// `;` `:` and `*` already are — a delimiter that can also occur inside a value is not a
// delimiter — so a literal comma in a facet value is percent-encoded before the join.
const escapeValue = (s) => String(s).replace(/[%;:*+,]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()).replace(/ /g, '+')
const unescapeValue = (s) => { try { return decodeURIComponent(String(s).replace(/\+/g, '%20')) } catch (_) { return String(s).replace(/\+/g, ' ') } }
function useHashSync(bus, labelFor) {
function syncHash() {
try {
const pairs = Object.entries(bus.filterSet).map(([k, v]) => {
// A MULTI-VALUE KEY KEEPS THE CODE CANONICAL AND DROPS THE SLUG. The `*slug` is
// display-only and singular by construction — one human name for one code — so there is no
// honest slug for "these twenty-two codes". Writing one would put a label in a URL that
// does not describe what the URL selects, and the slug is already stripped on parse.
if (Array.isArray(v)) return k + ':' + v.map(escapeValue).join(',')
const label = typeof labelFor === 'function' ? labelFor(k, v) : null
const slug = label && String(label) !== String(v) ? slugify(label) : ''
return k + ':' + escapeValue(v) + (slug ? '*' + slug : '')
@ -257,8 +265,12 @@ function useHashSync(bus, labelFor) {
const i = pair.indexOf(':')
if (i < 1) continue
const key = pair.slice(0, i)
const value = unescapeValue(pair.slice(i + 1).split('*')[0])
if (key && value) bus.setFilter(key, value)
const raw = pair.slice(i + 1).split('*')[0]
// Split BEFORE unescaping: a literal comma inside a value is percent-encoded by
// escapeValue, so every `,` still present here is a separator. Unescaping first would
// resurrect the content commas and split the value in half.
const parts = raw.split(',').map(unescapeValue).filter(Boolean)
if (key && parts.length) bus.setFilter(key, parts.length > 1 ? parts : parts[0])
}
} catch (_) { /* ignore bad hash */ }
})
@ -293,7 +305,9 @@ export function useDashboardShell(config) {
const hasText = !!(bus.searchTerm?.value || '').trim()
if (!keys.length && !hasText) return rows
const faceted = keys.length
? rows.filter((r) => keys.every((k) => { const a = FACET_ACC[k]; return a ? a(r) === bus.filterSet[k] : true }))
// ONE predicate, owned by the bus (SPEC-DASHBOARD-KIT D1). This line was the seventh copy
// of `a(r) === filterSet[k]`; with multi-value filters the copies would have disagreed.
? rows.filter((r) => bus.rowMatchesFilters(r, FACET_ACC))
: rows
// Tiered search over the faceted scope: exact first, fuzzy only if exact finds nothing.
return filterRowsBySearch(faceted, matchSearch)
@ -355,11 +369,12 @@ export function useDashboardShell(config) {
// here, below `dataStore`, because the resolver reads the normalized features it exposes.
// Data columns are unaffected (see useCsvExport.buildCsv).
const { labelFor } = useTreeLegendLabels(config, dataStore, searchSourceId)
// The exporter selects rows with the BUS's predicate, not a copy of it (D1).
// shareable view-state hash URL -- below labelFor on purpose: the hash's display-only slugs
// resolve through the SAME code->name resolver the chips and the CSV provenance line use.
useHashSync(bus, labelFor)
const exporter = useCsvExport({ rowStore: data.rowStore, rows: data.rows, selectionSet: bus.selectionSet, filterSet: bus.filterSet, FACET_ACC, dataOpts: config.data, labelFor })
const exporter = useCsvExport({ rowStore: data.rowStore, rows: data.rows, selectionSet: bus.selectionSet, filterSet: bus.filterSet, FACET_ACC, dataOpts: config.data, labelFor, rowMatches: bus.rowMatchesFilters })
provide('selectionBus', bus)
provide('dashboardData', data)

View file

@ -1,205 +1,18 @@
/**
* useSelectionBus.js — hand-rolled pub/sub brushing-and-linking bus (RDASH phase 1 stub).
* useSelectionBus.js — RE-EXPORT SHIM (dashboard-kit landing 1).
*
* Two DISTINCT global reactive objects, two distinct visual languages (plan §INTERACTION):
* - filterSet : active facet predicates (tree node / map region / chart slice).
* NARROWS scope — panels render excluded context by FADING, never removing.
* - selectionSet : explicit set of slugs (table checkbox / map lasso / chart pick).
* MARKS individuals — panels render with RING/OUTLINE.
* The bus now lives at library/composables/useSelectionBus.js. This file exists so the move and
* its consumers do not have to land in the same commit: every importer keeps working unchanged for
* one cycle, and the ones that still point here are migrated in the next landing, after which this
* file is deleted.
*
* Export priority (later phases): selectionSet (if non-empty) > filterSet result > full dataset.
* Every panel is both publisher and subscriber. All client-side over one in-memory row store
* keyed on slug (PK) — no per-brush round-trips.
* WHY A SHIM AND NOT A SIMULTANEOUS REWRITE. The extraction's governing constraint is that Big
* Picture must not change, and the only way to prove that for nine landings in a row is to make
* each one small enough to verify on its own. A landing that moves a file AND rewrites every
* import is two changes whose failures look identical from the outside.
*
* ── PERFORMANCE CONTRACT (BUG-TABLE-BATCH-SELECTION-FREEZES-20S) ──────────────────────────
* selectionSet is a PLAIN Set, not `reactive(new Set())`, and `selectionVersion` is the single
* reactive dependency that stands in for it. Read it in any computed that inspects the set:
*
* const isSel = computed(() => { bus.selectionVersion.value; return bus.selectionSet.has(x) })
*
* WHY. A `reactive(new Set())` fires Vue's ITERATE_KEY on EVERY add/delete, and the old
* per-row `toggleSelect()` loop also fired a SYNCHRONOUS `emit('select')` per row. Selecting
* all 2,107 people groups therefore ran 2,107 full map-panel rebuilds — each one re-scanning
* every row and calling MapLibre `setData()` — allocating ~2.2 MILLION GeoJSON features to
* arrive at 2,107. Measured at 5,323 ms of pure JS in node (~20 s in the browser with the real
* map + charts + DOM). See selectMany/deselectMany/setSelection: each mutates the set fully,
* THEN bumps the version and emits ONCE. Same work, measured at 2.9 ms — a ~1,800x speedup.
*
* RULE: never loop `toggleSelect()` over many slugs. Use the bulk ops. They are the only
* reason batch selection is interactive.
*
* ── HIERARCHY CONTRACT (BUG-DESELECTING-A-PARENT-FACET-ORPHANS-ITS-CHILD-SELECTION) ────────
* A CHILD FACET SELECTION MUST NOT OUTLIVE ITS PARENT. Clearing a parent facet cascade-clears
* every descendant, transitively, in one commit.
*
* WHY IT LIVES HERE AND NOWHERE ELSE. filterSet is AND-stacked: filteredRows keeps a row only
* when EVERY active key matches. So an orphaned child keeps filtering after its parent is gone —
* deselect "Indo-European" while "Marathi" is picked and Marathi survives; pick a DIFFERENT
* family and the two predicates can never both hold, so the map goes empty with no error. The
* user reads that as "there are no people groups here" rather than "my filter state is
* inconsistent" — a SILENT WRONG RESULT, which is why this is a data-trust bug, not a nit.
*
* Four separate controls clear a filter — the tree rail (DashboardFacetRail), the dropdowns
* (DashboardResearchFilters), the per-chip ✕ (SearchFilterHeader/DashboardFilterChips) and
* Clear All / the breadcrumb — and ALL FOUR funnel through setFilter()/clearFilters() here.
* Implementing the cascade in any one component would fix one button and leave the identical
* bug reachable through the other three. One chokepoint, one rule.
*
* The hierarchy is NOT declared twice: `configureHierarchy()` derives it from the config's
* existing `facetRail[].drill` arrays, which are already ordered parent→child (the rail renders
* them as tiers). The config stays the single source of truth for what nests inside what.
*
* Transitivity is the whole point: People is THREE deep (affinity_bloc → cluster → people_group)
* and Place is three deep (wagf_region → wagf_block → country_code). A one-level cascade would
* look fixed from the top tier while leaving the same orphan one rung down.
* NOT A SECOND COPY. It re-exports; it does not duplicate. A copy would drift silently and both
* halves would keep passing their gates — which is the exact failure check:no-cross-plugin-imports
* and check:shared-reached exist to stop.
*/
import { reactive, ref } from 'vue'
/**
* Derive facetKey → [descendant keys, nearest-first] from a dashboard config.
* Reads `facetRail[].drill` (already ordered parent→child) so the hierarchy is declared ONCE,
* in the config, and can never drift from the tiers the rail actually renders.
* A section with no `drill` (e.g. the flat Status list) contributes nothing — a facet absent
* from the map simply has no descendants and clears alone, exactly as before.
*/
export function deriveHierarchy(config) {
const children = {}
for (const section of config?.facetRail || []) {
const drill = Array.isArray(section.drill) ? section.drill.filter(Boolean) : []
// every tier below i is a descendant of tier i (transitive closure, nearest-first)
for (let i = 0; i < drill.length - 1; i++) {
const rest = drill.slice(i + 1)
children[drill[i]] = children[drill[i]] ? children[drill[i]].concat(rest.filter((k) => !children[drill[i]].includes(k))) : rest
}
}
return children
}
export function createSelectionBus() {
const filterSet = reactive({}) // { facetKey: value | value[] } — AND-stacked facets
// PLAIN Set (see the performance contract above) — pair every read with `selectionVersion`.
const selectionSet = new Set() // Set<slug> — explicit picks
// Bumped once per committed selection change. THE reactive handle on selectionSet: one
// dependency to invalidate instead of a collection proxy that fires per element.
const selectionVersion = ref(0)
// Free-text search term (DX-life research-page search bar → header-1). Distinct from the
// facet filterSet: it applies a substring/label match across the config's `searchKeys` in
// useDashboardShell.filteredRows. Empty string = no text predicate (identical to before).
const searchTerm = ref('')
const subs = new Map() // event -> Set<cb>
function on(event, cb) {
if (!subs.has(event)) subs.set(event, new Set())
subs.get(event).add(cb)
return () => subs.get(event)?.delete(cb) // returns an unsubscribe
}
function emit(event, payload) {
subs.get(event)?.forEach((cb) => { try { cb(payload) } catch (_) { /* isolate subscribers */ } })
}
// ── facet hierarchy (see the HIERARCHY CONTRACT above) ──
// facetKey -> [descendant keys]. Empty until the shell calls configureHierarchy(config), so a
// bus used without a config behaves exactly as it did before (every facet clears alone).
let childFacets = {}
function configureHierarchy(config) { childFacets = deriveHierarchy(config) }
// ── filterSet ops (scope) ──
/**
* Set or clear one facet. CLEARING CASCADES: removing a parent removes every descendant in the
* same call, so a child predicate can never outlive the parent that scoped it.
*
* Setting is deliberately NOT cascading in either direction. Selecting a child with no parent
* set is a legitimate, self-consistent state — the child alone is strictly narrower than its
* parent, so the result is never wrong, merely more specific (the tree rail, the dropdowns and
* the geocoder bars all allow it today, and it is how "search language → Marathi" works without
* first picking a family). The invariant is one-directional by design: no ORPHANS, but
* stand-alone children are fine.
*
* One 'filter' emit for the whole cascade — subscribers rebuild the map/table once, not once
* per cleared tier (same batching discipline as commitSelection).
*/
function setFilter(key, value) {
if (value === null || value === undefined) {
delete filterSet[key]
// transitive: childFacets[key] already holds the full descendant closure
for (const child of childFacets[key] || []) delete filterSet[child]
} else {
filterSet[key] = value
}
emit('filter', filterSet)
}
function clearFilters() {
Object.keys(filterSet).forEach((k) => delete filterSet[k])
emit('filter', filterSet)
}
// ── free-text search (header-1) ──
function setSearch(term) {
searchTerm.value = term == null ? '' : String(term)
emit('search', searchTerm.value)
}
// ── selectionSet ops (marks) ──
// The ONE place a selection change becomes visible: bump the version, notify subscribers once.
// Every mutator below finishes its whole batch before calling this exactly once.
function commitSelection() {
selectionVersion.value++
emit('select', selectionSet)
}
function toggleSelect(slug) {
selectionSet.has(slug) ? selectionSet.delete(slug) : selectionSet.add(slug)
commitSelection()
}
function clearSelection() {
if (!selectionSet.size) return // no-op stays silent (no wasted map rebuild)
selectionSet.clear()
commitSelection()
}
/**
* Add many slugs in ONE commit. `additive:false` replaces the selection outright.
* Returns true when something actually changed (a no-op never repaints the map).
*/
function selectMany(slugs, { additive = true } = {}) {
let changed = false
if (!additive) {
if (selectionSet.size) { selectionSet.clear(); changed = true }
}
for (const s of slugs) if (!selectionSet.has(s)) { selectionSet.add(s); changed = true }
if (changed) commitSelection()
return changed
}
/** Remove many slugs in ONE commit. Returns true when something actually changed. */
function deselectMany(slugs) {
let changed = false
for (const s of slugs) if (selectionSet.delete(s)) changed = true
if (changed) commitSelection()
return changed
}
/**
* Replace the whole selection with `slugs` in ONE commit — the primitive the table's
* shift+click range-select is built on (base snapshot ⊕ range, applied atomically).
* Skips the commit when the new set is identical to the current one.
*/
function setSelection(slugs) {
const next = slugs instanceof Set ? slugs : new Set(slugs)
if (next.size === selectionSet.size) {
let same = true
for (const s of next) if (!selectionSet.has(s)) { same = false; break }
if (same) return false
}
selectionSet.clear()
for (const s of next) selectionSet.add(s)
commitSelection()
return true
}
return {
filterSet, selectionSet, selectionVersion, searchTerm, on, emit,
setFilter, clearFilters, setSearch, configureHierarchy,
toggleSelect, clearSelection, selectMany, deselectMany, setSelection,
}
}
export { createSelectionBus, deriveHierarchy } from '@map/composables/useSelectionBus.js'

View file

@ -77,6 +77,13 @@ export function useTreeLegendLabels(cfg, dataStore, searchSourceId) {
*/
function labelFor(facetKey, value) {
const map = affinityLabelMaps.value[facetKey]
// ★ ARRAY-AWARE AT THE ONE OWNER (SPEC-DASHBOARD-KIT D1). Every surface that turns a filter
// into words goes through here — the chip bar, the research-filter chips and the CSV
// provenance comment — so resolving the list once is what keeps the three saying the same
// thing. Without it an array fell through to its default toString and a chip read
// "Religion: Islam,Hinduism": the codes joined by a bare comma, with no spacing and no code
// resolved to its human name.
if (Array.isArray(value)) return value.map((v) => (map && map.get(v)) || v).join(', ')
if (!map) return value
return map.get(value) || value
}

View file

@ -159,7 +159,7 @@ watch(textDir, reflectDir)
// The absolute url this composite is shared and iframed as. Built here rather than written as a
// literal because resolveEmbedBase() walks profileConfig.embedBaseUrl → window.MAP_EMBED_BASE_URL →
// the root this bundle derives from its OWN script url, so the link is right on doxa.life, on
// nuxtbase.app and in a dropped tree with nothing configured.
// the live test host and in a dropped tree with nothing configured.
//
// It MUST be absolute, which is the non-obvious part. resolveShareUrl returns a tier-1 shareUrl
// VERBATIM (embedBase.js:95 — `return whole.trim()`, no resolution), and ShareButton feeds the same
@ -370,7 +370,16 @@ function onMapFiltered(ev) {
// then takes the whole set and this becomes a real narrowing instead of an honest abstention.
function gridLanguage() {
const codes = Array.isArray(linked.language.codes) ? linked.language.codes : []
if (codes.length > 1) return ''
// ★ THE ABSTENTION IS OVER (SPEC-DASHBOARD-KIT D1). When the map reported a language TIER —
// "Arabic", 22 ISO codes — there was no single value that meant it, so this left the axis
// unfiltered: honest, and visibly incomplete, because the map narrowed and the grid did not.
// The grid's valueMatches() now takes a LIST and matches OR-within-the-axis, so the whole
// selection crosses as the codes it actually is. Still codes and never the tier NAME: the name
// matches no row that reads "Arabic, Sudanese", which is what emptied the grid in the first
// place.
// `|`, not `,`: a language LABEL routinely contains a comma ("Arabic, Mesopotamian"), so a
// comma-joined list is ambiguous with a single value. See valueMatches() in people-groups-grid.
if (codes.length > 1) return codes.join('|')
return linked.language.value || linked.language.label
}

View file

@ -0,0 +1,157 @@
#!/usr/bin/env node
/**
* check-filter-multivalue.mjs — OR-within-key, AND-across-keys, and ONE predicate that says so.
*
* SPEC-DASHBOARD-KIT D1. `filterSet[key]` may hold an array meaning "any of these"; keys still
* AND together. This gate exists because the change is four lines of behaviour sitting on top of a
* rule that was WRITTEN OUT SEVEN TIMES.
*
* THE DANGER D1 CREATED, which is the whole reason this is gated rather than reviewed:
* `a(r) === filterSet[k]` appeared in the shell's filteredRows, the facet rail twice, the map
* panel, the table, the charts rail and the CSV exporter. Seven copies of a one-character rule are
* survivable. The moment the rule grows an array case, six of the seven keep comparing an array
* with `===`, every one of them quietly matches NOTHING, and the panels disagree with each other
* about what is on screen — the facet rail printing counts for a scope the map and table no longer
* render, which is a number a reader acts on before clicking. So D1 is not only "arrays are
* allowed"; it is "there is one function that answers *does this row match*, and every panel calls
* it". This gate asserts the second half, because only the second half can be silently undone.
*
* WHAT IT CHECKS:
* A. BEHAVIOUR, by running the real bus: an array ORs within the key; keys AND; a one-element
* array stores as the scalar it means; an EMPTY array clears rather than matching nothing;
* toggleFilterValue adds, removes, and clears on the last value; the cascade still fires.
* B. NO SURVIVING COPIES: `=== filterSet[k]` / `=== bus.filterSet[k]` must not appear in any
* panel. One allowed exception, named: useCsvExport's parameter default, which exists so a
* caller predating D1 does not throw.
* C. THE HASH round-trips a list, including a value that CONTAINS a comma — a delimiter that can
* also occur inside a value is not a delimiter.
* D. THE GRID'S END matches the same way: valueMatches() takes a list and ORs it, so the two
* implementations of one rule cannot drift into disagreeing.
*
* NOT CHECKED, because it is deliberately NOT part of D1: a plain click still REPLACES rather than
* adds. D1 gives the bus the capability; making a rail row additive on an unmodified click would
* change what every existing click does to the flagship, which is a product decision and not this
* one. The array path is reached programmatically today (the composite's language code set).
*
* NEGATIVE-TESTED: restoring `=== filterSet[k]` in any panel fails B naming the file; making
* facetMatches use `includes` on a scalar fails A; dropping the comma from escapeValue's reserved
* set fails C. No browser.
*/
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const TAG = 'FILTER-MULTIVALUE'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
const fails = []
const notes = []
const eq = (what, got, want) => { if (JSON.stringify(got) !== JSON.stringify(want)) fails.push(`${what} — got ${JSON.stringify(got)}, want ${JSON.stringify(want)}`) }
// ── A. the real bus, exercised ───────────────────────────────────────────────────────────────
const BUS = join(ROOT, 'library/composables/useSelectionBus.js')
if (!existsSync(BUS)) { console.log(`${TAG}: SKIP — no ${BUS.replace(ROOT + '/', '')} (kit landing 1 not applied).`); process.exit(0) }
const { createSelectionBus } = await import(BUS)
{
const bus = createSelectionBus()
bus.configureHierarchy({ facetRail: [{ id: 'place', drill: ['region', 'block', 'country'] }] })
bus.setFilter('religion', ['Islam', 'Hinduism'])
eq('A array is stored as an array', bus.filterSet.religion, ['Islam', 'Hinduism'])
eq('A OR within the key (member)', bus.facetMatches('Hinduism', bus.filterSet.religion), true)
eq('A OR within the key (non-member)', bus.facetMatches('Jewish', bus.filterSet.religion), false)
eq('A isFilterActive sees into the array', bus.isFilterActive('religion', 'Islam'), true)
bus.setFilter('region', 'Asia')
const ACC = { religion: (r) => r.religion, region: (r) => r.region }
eq('A AND across keys (both hold)', bus.rowMatchesFilters({ religion: 'Islam', region: 'Asia' }, ACC), true)
eq('A AND across keys (one fails)', bus.rowMatchesFilters({ religion: 'Islam', region: 'Africa' }, ACC), false)
eq('A a key with no accessor is skipped, not failed', bus.rowMatchesFilters({ religion: 'Islam', region: 'Asia' }, { religion: ACC.religion }), true)
bus.setFilter('religion', ['Islam'])
eq('A a one-element array stores as the scalar it means', bus.filterSet.religion, 'Islam')
bus.setFilter('religion', [])
eq('A an EMPTY array CLEARS rather than matching nothing', 'religion' in bus.filterSet, false)
bus.setFilter('tags', 'a'); bus.toggleFilterValue('tags', 'b')
eq('A toggle adds', bus.filterSet.tags, ['a', 'b'])
bus.toggleFilterValue('tags', 'b')
eq('A toggle removes, collapsing to the scalar', bus.filterSet.tags, 'a')
bus.toggleFilterValue('tags', 'a')
eq('A toggling the last value clears the key', 'tags' in bus.filterSet, false)
// The hierarchy contract must still hold with arrays in play.
bus.setFilter('region', ['Asia', 'Africa']); bus.setFilter('block', 'South Asia'); bus.setFilter('country', 'IND')
bus.setFilter('region', null)
eq('A clearing a multi-value parent still cascades', Object.keys(bus.filterSet).sort(), [])
notes.push('bus: OR within a key, AND across keys, empty array clears, cascade intact')
}
// ── B. no surviving copies of the predicate ──────────────────────────────────────────────────
{
const PANELS = [
'plugin-designs/research-dashboard/src/composables/useDashboardShell.js',
'plugin-designs/research-dashboard/src/composables/useCsvExport.js',
'plugin-designs/research-dashboard/src/components/dashboards/DashboardFacetRail.vue',
'plugin-designs/research-dashboard/src/components/dashboards/DashboardMapPanel.vue',
'plugin-designs/research-dashboard/src/components/dashboards/DashboardChartsRail.vue',
]
for (const rel of PANELS) {
const p = join(ROOT, rel)
if (!existsSync(p)) { fails.push(`B ${rel} is missing — this gate is blind to it.`); continue }
for (const [i, line] of readFileSync(p, 'utf8').split('\n').entries()) {
if (/^\s*(\/\/|\*)/.test(line)) continue // a comment quoting the old rule is fine
if (!/===\s*(bus\.)?filterSet\[/.test(line)) continue
// THE ONE ALLOWED SITE, named rather than pattern-matched: the exporter's parameter default,
// which keeps a pre-D1 caller working instead of throwing.
if (rel.endsWith('useCsvExport.js') && /rowMatches\s*=/.test(line)) continue
fails.push(`B ${rel}:${i + 1} still compares with === against filterSet — with an array that matches NOTHING, and this panel would disagree with the others about what is on screen.\n ${line.trim().slice(0, 140)}`)
}
}
if (!fails.some((f) => f.startsWith('B'))) notes.push(`panels: ${PANELS.length} checked, no surviving copy of the predicate`)
}
// ── C. the hash round-trips a list, commas and all ───────────────────────────────────────────
{
const src = readFileSync(join(ROOT, 'plugin-designs/research-dashboard/src/composables/useDashboardShell.js'), 'utf8')
const esc = /const escapeValue = (\(s\) =>[^\n]*)/.exec(src)
const unesc = /const unescapeValue = (\(s\) =>[^\n]*)/.exec(src)
if (!esc || !unesc) fails.push('C could not lift escapeValue/unescapeValue from useDashboardShell.js — the hash format moved and this gate is blind to it.')
else {
const escapeValue = new Function('return ' + esc[1])()
const unescapeValue = new Function('return ' + unesc[1])()
for (const list of [['apd', 'acm', 'ayl'], ['Arabic, Sudanese'], ['Islam', 'Hinduism'], ['a+b', 'c;d']]) {
const back = list.map(escapeValue).join(',').split(',').map(unescapeValue)
eq(`C hash round-trip ${JSON.stringify(list)}`, back, list)
}
if (!fails.some((f) => f.startsWith('C'))) notes.push('hash: a list round-trips, including a value that contains a comma')
}
}
// ── D. the grid ORs the same way ─────────────────────────────────────────────────────────────
{
const src = readFileSync(join(ROOT, 'plugin-designs/people-groups-grid/people-groups-grid.vue'), 'utf8')
const m = /function valueMatches\(vl, selection\) \{([\s\S]*?)\n\}/.exec(src)
const n = /const norm = ([^\n]*)/.exec(src)
if (!m || !n) fails.push('D could not lift valueMatches/norm from people-groups-grid.vue — the grid matcher moved.')
else {
const valueMatches = new Function('norm', `return function valueMatches(vl, selection) {${m[1]}\n}`)(new Function('return ' + n[1])())
const row = { value: 'acm', label: 'Arabic, Mesopotamian' }
eq('D grid matches a member of the list', valueMatches(row, { value: 'apd|acm|ayl' }), true)
eq('D grid rejects a non-member', valueMatches(row, { value: 'apd|ayl' }), false)
eq('D grid still matches a single value', valueMatches(row, { value: 'acm' }), true)
// ★ THIS ASSERTION IS THE ONE THAT EARNED ITS KEEP. The first D1 draft split the selection on
// `,` and broke label matching outright — 40% of live language labels are comma-inverted, so
// the selection was chopped in half and matched nothing. Asserting a behaviour you are KEEPING
// is what caught it; asserting only the behaviour you are adding would have shipped it.
eq('D grid still matches on the LABEL, commas and all', valueMatches(row, { value: 'Arabic, Mesopotamian' }), true)
eq('D an empty selection matches nothing', valueMatches(row, { value: '' }), false)
if (!fails.some((f) => f.startsWith('D'))) notes.push('grid: valueMatches ORs a list, and a single value takes the path it always did')
}
}
for (const n of notes) console.log(' · ' + n)
if (fails.length) {
console.error(`\n${TAG}: FAIL`)
for (const f of fails) console.error(' ✗ ' + f)
process.exit(1)
}
console.log(`${TAG}: PASS — one predicate, OR within a key, AND across keys, and both ends of the link agree.`)

View file

@ -36,97 +36,220 @@
* naming the key and the call site; restoring it returns PASS. It FAILS on today's tree.
*/
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'
import { execFileSync } from 'node:child_process'
import { resolve, dirname, join, relative } from 'node:path'
import { fileURLToPath } from 'node:url'
const TAG = 'I18N-KEYS-EXIST'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
const EN = join(ROOT, 'library/i18n/locales/en')
if (!existsSync(EN)) { console.log(`${TAG}: SKIP — no English catalogue at ${relative(ROOT, EN)}.`); process.exit(0) }
const argOf = (flag) => { const i = process.argv.indexOf(flag); return i >= 0 ? process.argv[i + 1] : '' }
// ★ --rev READS A TREE YOU ARE NOT STANDING IN, straight from the object store.
// A fleet-wide census has to be taken against CURRENT main, not against one owner's worktree: every
// bundle but mine is stale here, and a stale scan reports keys another owner added an hour ago. That
// would mean mailing seven owners a list of defects they had already fixed — the cried-wolf failure,
// arriving in somebody else's inbox. Checking out a second tree was not an option either (the box is
// at 97% disk), so the files are read as blobs. No worktree, no temp copy, no writes.
const REV = argOf('--rev')
const BY_BUNDLE = process.argv.includes('--by-bundle')
const git = (args) => execFileSync('git', args, { cwd: ROOT, maxBuffer: 1 << 28 })
// ── the tree under measurement: a path -> contents reader, from disk or from a revision ──────
let listFiles, readFile, describeTree
if (REV) {
// One ls-tree, then ONE cat-file --batch for every blob: ~400 files in two processes rather than
// 400. The batch stream is "<sha> blob <size>\n<bytes>\n", so it is parsed by length, never by
// scanning for a delimiter that could occur inside a file.
const entries = String(git(['ls-tree', '-r', REV, '--', 'plugin-designs', 'library']))
.split('\n').filter(Boolean)
.map((l) => { const [meta, path] = l.split('\t'); const [, type, sha] = meta.split(/\s+/); return { type, sha, path } })
.filter((e) => e.type === 'blob')
const wanted = entries.filter((e) => /\.(vue|js|mjs|json)$/.test(e.path))
const batch = execFileSync('git', ['cat-file', '--batch'], { cwd: ROOT, input: wanted.map((e) => e.sha).join('\n') + '\n', maxBuffer: 1 << 28 })
const contents = new Map()
let off = 0
for (const e of wanted) {
const nl = batch.indexOf(0x0a, off)
const size = Number(String(batch.subarray(off, nl)).split(' ')[2])
contents.set(e.path, batch.subarray(nl + 1, nl + 1 + size).toString('utf8'))
off = nl + 1 + size + 1
}
listFiles = (pred) => [...contents.keys()].filter(pred)
readFile = (p) => contents.get(p)
describeTree = `${REV} (${String(git(['rev-parse', '--short', REV])).trim()})`
} else {
const all = []
const walkDir = (d) => {
for (const e of readdirSync(d, { withFileTypes: true })) {
if (e.name === 'node_modules' || e.name.startsWith('.')) continue
const p = join(d, e.name)
if (e.isDirectory()) walkDir(p)
else all.push(relative(ROOT, p))
}
}
for (const s of ['plugin-designs', 'library']) { const p = join(ROOT, s); if (existsSync(p) && statSync(p).isDirectory()) walkDir(p) }
listFiles = (pred) => all.filter(pred)
readFile = (p) => readFileSync(join(ROOT, p), 'utf8')
describeTree = 'the working tree'
}
// ── the English catalogue, as one flat key set: "<area>.<path.to.key>" ───────────────────────
// The area is the FILE NAME, because that is how every call site spells it: tStatic('aria.foo')
// reads aria.json. Nested objects flatten with dots, the way tStatic walks them.
const CAT = /^library\/i18n\/locales\/en\/[^/]+\.json$/
const catalogues = listFiles((p) => CAT.test(p))
if (!catalogues.length) { console.log(`${TAG}: SKIP — no English catalogue in ${describeTree}.`); process.exit(0) }
const AREAS = new Set()
const KEYS = new Set()
for (const f of readdirSync(EN)) {
if (!f.endsWith('.json')) continue
const area = f.replace(/\.json$/, '')
for (const p of catalogues) {
const area = p.split('/').pop().replace(/\.json$/, '')
AREAS.add(area)
const walk = (node, path) => {
if (node && typeof node === 'object' && !Array.isArray(node)) {
for (const [k, v] of Object.entries(node)) walk(v, path ? path + '.' + k : k)
} else if (typeof node === 'string') {
KEYS.add(area + '.' + path)
}
} else if (typeof node === 'string') KEYS.add(area + '.' + path)
}
try { walk(JSON.parse(readFileSync(join(EN, f), 'utf8')), '') } catch (e) {
console.error(`${TAG}: ${f} does not parse — ${e.message}`); process.exit(1)
try { walk(JSON.parse(readFile(p)), '') } catch (e) {
console.error(`${TAG}: ${p} does not parse — ${e.message}`); process.exit(1)
}
}
// ── every source file that could name a key ──────────────────────────────────────────────────
const SCAN = ['plugin-designs', 'library']
const files = []
// `scripts/` is build and gate TOOLING, not product UI. Skipping it is not a convenience: this
// gate's own header quotes two real call sites as examples, and scanning itself reported them as
// missing keys — a check that fails on its own documentation is a check nobody trusts.
const walkDir = (d) => {
for (const e of readdirSync(d, { withFileTypes: true })) {
if (e.name === 'node_modules' || e.name === 'locales' || e.name === 'scripts' || e.name.startsWith('.')) continue
const p = join(d, e.name)
if (e.isDirectory()) walkDir(p)
else if (/\.(vue|js|mjs)$/.test(e.name)) files.push(p)
const files = listFiles((p) => /\.(vue|js|mjs)$/.test(p) && !p.includes('/scripts/') && !p.includes('/locales/'))
// ── ★ ALIASES ARE DISCOVERED PER FILE, NOT HARDCODED ─────────────────────────────────────────
// The first version of this gate matched tStatic/$t/t and found four missing keys while sailing
// past the ten it was written for, because the house pattern is a file-local wrapper. Adding `T` by
// hand took it to 22 — and that fix was the same mistake one level up, because a census of the tree
// finds FOUR wrapper names, not one: `const T` x16, `const L` x6, `const tr` x1
// (library/layouts/VerticalTabsLayout.vue) and `const v` x1. A gate that knows about T and not tr is
// blind to whichever bundle used the other name, and reports it CLEAN. So the names are read out of
// each file instead of listed here, and the next author's alias is covered the day they write it.
//
// TWO SHAPES, because the tree has two:
// FULL-KEY const T = (key, en) => tStatic(key) || en -> T('aria.foo')
// PREFIXED const L = (key, en) => tStatic('dashboard.' + key) -> L('foo') => dashboard.foo
// The prefixed form may carry a LADDER — ResearchMapSideMenu's is
// `tStatic('controls.' + key) || tStatic('dashboard.' + key) || en` — and at runtime the first hit
// wins, so a key RESOLVES if ANY prefix in the ladder holds it. Those were previously counted as
// unresolved and left unchecked; they are ordinary keys with a known prefix, and declining to
// resolve them was leaving most of this gate's coverage on the floor.
const BASE_FULL = ['tStatic', '$t', 't']
function aliasesIn(src) {
const full = new Set(BASE_FULL)
const prefixed = new Map() // name -> [prefix, …]
// `const NAME = ... tStatic(...)` on one line — all eight declarations in the tree are one-liners.
for (const m of src.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^\n]*tStatic[^\n]*)/g)) {
const [, name, body] = m
const prefixes = [...body.matchAll(/tStatic\(\s*'([A-Za-z_][\w]*)\.'\s*\+/g)].map((x) => x[1])
if (prefixes.length) prefixed.set(name, prefixes)
// A wrapper passes its FIRST PARAMETER straight through: `(key, en) => tStatic(key)`. Matching
// on the parameter NAME rather than on the word "key" keeps it honest if someone calls it `k`.
else {
const param = /\(\s*([A-Za-z_$][\w$]*)/.exec(body)?.[1]
if (param && new RegExp(`tStatic\\(\\s*${param}\\b`).test(body)) full.add(name)
else if (/=\s*tStatic\s*$/.test(body.trim())) full.add(name)
}
}
return { full, prefixed }
}
for (const s of SCAN) { const p = join(ROOT, s); if (existsSync(p) && statSync(p).isDirectory()) walkDir(p) }
// A literal key: the first argument is a single-quoted FULL dotted key.
// ★ `T(` IS IN THE LIST, AND LEAVING IT OUT WOULD HAVE MADE THIS GATE USELESS ON THE VERY DEFECT
// THAT PROMPTED IT. The house pattern in this repo is a file-local alias —
// `const T = (key, en) => tStatic(key) || en` — so almost every call site in the dashboard reads
// `T('aria.resizeFiltersRail', …)`, not `tStatic(...)`. A first draft matched only tStatic/$t/t and
// reported four missing keys while sailing past the ten resize keys it was written for. The alias
// takes a full key, exactly as tStatic does, so it belongs here.
const LITERAL = /(?:^|[^\w$.])(?:tStatic|\$t|t|T)\(\s*'([a-zA-Z_][\w]*(?:\.[\w-]+)+)'/g
// A DYNAMIC key: the first argument is a concatenation or a template literal.
// ★ `L(` IS DELIBERATELY NOT RESOLVED. BpResearchHeader declares
// `const L = (key, en) => tStatic('grid.' + key) || tStatic('dashboard.' + key) || en`, so its
// first argument is a BARE key with a file-local prefix that this gate would have to guess at —
// and guessing is how a gate starts reporting keys nobody wrote. Counted as unresolved, like any
// other runtime expression.
const DYNAMIC = /(?:^|[^\w$.])(?:tStatic|\$t|t|T|L)\(\s*(?:'[^']*'\s*\+|`|'[a-z_]+'\s*,)/g
const missing = new Map() // key -> { sites: [], via: Set }
let literalCount = 0, prefixedCount = 0, dynamicCount = 0
const lineAt = (src, i) => src.slice(0, i).split('\n').length
const record = (key, site, via) => {
if (!missing.has(key)) missing.set(key, { sites: [], via: new Set() })
const e = missing.get(key); e.sites.push(site); e.via.add(via)
}
const missing = new Map() // key -> [call sites]
let literalCount = 0
let dynamicCount = 0
// ★ A BUNDLE WITH NO CALL SITES SCORES PERFECTLY HERE, AND THAT IS NOT THE SAME AS CLEAN.
// This gate asks whether the keys a bundle NAMES exist. A bundle that names none — upg-neighbor and
// wall-of-people-groups have no t() anywhere, per the universal-props matrix — has nothing to be
// missing and passes in silence. Reporting only the misses would let "0 missing keys" be read as
// "fully localized", which is the opposite of true for exactly those bundles. So participation is
// counted and printed alongside, and the two are never merged into one number.
const callSites = new Map() // bundle -> count
for (const f of files) {
const src = readFileSync(f, 'utf8')
const rel = relative(ROOT, f)
for (const m of src.matchAll(LITERAL)) {
const src = readFile(f)
if (!src || !src.includes('tStatic') && !src.includes('$t(') && !src.includes(' t(')) continue
const { full, prefixed } = aliasesIn(src)
const names = [...full, ...prefixed.keys()].map((n) => n.replace('$', '\\$')).join('|')
if (!names) continue
// FULL dotted key.
for (const m of src.matchAll(new RegExp(`(?:^|[^\\w$.])(?:${names})\\(\\s*'([A-Za-z_][\\w]*(?:\\.[\\w-]+)+)'`, 'g'))) {
const key = m[1]
literalCount++
if (KEYS.has(key)) continue
// Only keys whose AREA is a real catalogue file are this gate's business: `props.t.total`,
// `config.map.provider` and friends are not i18n keys and must not be reported as missing.
const area = key.split('.')[0]
if (!existsSync(join(EN, area + '.json'))) continue
const line = src.slice(0, m.index).split('\n').length
if (!missing.has(key)) missing.set(key, [])
missing.get(key).push(`${rel}:${line}`)
if (!AREAS.has(key.split('.')[0])) continue
record(key, `${f}:${lineAt(src, m.index)}`, 'literal')
}
for (const _ of src.matchAll(DYNAMIC)) dynamicCount++
// BARE key through a prefix helper.
for (const [name, prefixes] of prefixed) {
// ★ AN EMPTY ENGLISH FALLBACK IS A DELIBERATE FALLTHROUGH, NOT A MISSING KEY.
// BpResearchHeader renders `L('reset_all', '') || tStatic('controls.reset_all') || 'Reset'`:
// the author is saying "if this prefix has nothing, fall to the next rung", and controls.reset_all
// does hold it. A first run of the prefix resolution reported that as a defect — a gate inventing
// a problem out of a working ladder, which is the exact failure I am about to mail seven owners
// about. `,''` is the author's own signal that the miss is intended.
for (const m of src.matchAll(new RegExp(`(?:^|[^\\w$.])${name}\\(\\s*'([\\w-]+)'\\s*(,\\s*''\\s*\\))?`, 'g'))) {
const bare = m[1]
if (m[2]) continue
prefixedCount++
if (prefixes.some((p) => KEYS.has(p + '.' + bare))) continue
record(prefixes.map((p) => p + '.' + bare).join(' | '), `${f}:${lineAt(src, m.index)}`, `${name}()`)
}
}
// Genuinely DYNAMIC: a concatenation or a template literal as the first argument.
for (const _ of src.matchAll(new RegExp(`(?:^|[^\\w$.])(?:${names})\\(\\s*(?:'[^']*'\\s*\\+|\`)`, 'g'))) dynamicCount++
const b = f.startsWith('plugin-designs/') ? f.split('/')[1] : 'library'
const n = (src.match(new RegExp(`(?:^|[^\\w$.])(?:${names})\\(`, 'g')) || []).length
callSites.set(b, (callSites.get(b) || 0) + n)
}
console.log(` · ${KEYS.size} English keys across ${readdirSync(EN).filter((f) => f.endsWith('.json')).length} catalogue(s); ${literalCount} literal call site(s) in ${files.length} file(s)`)
const bundleOf = (site) => {
const p = site.split(':')[0]
if (p.startsWith('plugin-designs/')) return p.split('/')[1]
return 'library'
}
console.log(` · tree: ${describeTree}`)
console.log(` · ${KEYS.size} English keys across ${catalogues.length} catalogue(s); ${literalCount} full-key and ${prefixedCount} prefixed call site(s) in ${files.length} file(s)`)
if (dynamicCount) {
console.log(` · ${dynamicCount} DYNAMIC key expression(s) not checked — this gate will not invent the key set a runtime expression might produce.`)
}
{
const bundles = listFiles((p) => /^plugin-designs\/[^/]+\//.test(p)).map((p) => p.split('/')[1])
const silent = [...new Set(bundles)].filter((b) => !callSites.get(b)).sort()
if (silent.length) {
console.log(` · ${silent.length} bundle(s) name NO catalogue key at all, so they cannot fail this gate and their pass means nothing: ${silent.join(', ')}`)
}
}
if (missing.size) {
if (BY_BUNDLE) {
const byBundle = new Map()
for (const [key, e] of missing) for (const s of e.sites) {
const b = bundleOf(s)
if (!byBundle.has(b)) byBundle.set(b, new Map())
if (!byBundle.get(b).has(key)) byBundle.get(b).set(key, [])
byBundle.get(b).get(key).push(s)
}
console.error('')
for (const [b, keys] of [...byBundle].sort((a, c) => c[1].size - a[1].size)) {
console.error(`── ${b} — ${keys.size} missing key(s)`)
for (const [key, sites] of [...keys].sort()) console.error(` ${key}\n ${sites.join('\n ')}`)
}
}
console.error(`\n${TAG}: FAIL — ${missing.size} key(s) are named in code and exist in NO catalogue, so every locale renders the English fallback:`)
for (const [key, sites] of [...missing].sort()) {
console.error(` ✗ ${key}`)
for (const s of sites.slice(0, 4)) console.error(` ${s}`)
if (sites.length > 4) console.error(` … and ${sites.length - 4} more`)
if (!BY_BUNDLE) for (const [key, e] of [...missing].sort()) {
console.error(` ✗ ${key}${e.via.has('literal') ? '' : ' (via ' + [...e.via].join(', ') + ')'}`)
for (const s of e.sites.slice(0, 4)) console.error(` ${s}`)
if (e.sites.length > 4) console.error(` … and ${e.sites.length - 4} more`)
}
console.error('\n The call sites are RIGHT; the catalogue never caught up. Add the key to')
console.error(' library/i18n/locales/*/<area>.json in all eleven locales, keeping the English')
@ -134,4 +257,4 @@ if (missing.size) {
console.error(' product decision, but changing it is.')
process.exit(1)
}
console.log(`${TAG}: PASS — every literal catalogue key named in code exists in English.`)
console.log(`${TAG}: PASS — every catalogue key named in code exists in English.`)

View file

@ -24,7 +24,7 @@
<p class="sub">Capability&nbsp;4, STACK variant — the shared <code>StackLayout</code> renders two
elements (<code>&lt;research-dashboard-map&gt;</code> then <code>&lt;people-groups-grid&gt;</code>)
<strong>on top of each other</strong>, each at its natural height, so you
<strong>scroll down</strong> through both on one page (the doxa-life / nuxtinator page-flow model).
<strong>scroll down</strong> through both on one page, as on a normal web page.
Embed <code>&lt;stack-demo&gt;</code> anywhere and the host page provides the scroll.</p>
<div class="host">
<stack-demo></stack-demo>

View file

@ -4,7 +4,7 @@
*
* @element stack-demo
* @description Groups TWO independent elements TOP-TO-BOTTOM so you can embed the pair on a page and
* SCROLL down through all of it (the doxa-life / nuxtinator page-flow model), instead of switching
* SCROLL down through all of it (the page-flow model of a normal host page), instead of switching
* between them with tabs. Section 1 = the rich <research-dashboard-map> (facets + map + charts +
* table); section 2 = the cap-2 <people-groups-grid> (live pray.doxa.life). Each renders at its
* NATURAL height; the host page scrolls.

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 <all-people-groups> custom element. -->
<script src="../all-people-groups.js" integrity="sha384-s3dDxH0p2dV2Qhy5ifnaqo69+zEbYgL0xNRIDFrKkakanDTDubuoSAsQxRjr2NRN" crossorigin="anonymous"></script>
<script src="../all-people-groups.js" integrity="sha384-vE315hvzd08MBT2C1DsQ8ULHLmH9Q/uqkFo9+qX0y14fBJb8I6Zsb270TwkxHfGY" 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

@ -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-aFfWPXUQqu/JmYKiT5gUT1t/TuyQ9sq8CTubsVNVmDgxIeLx38I7A324Q3NLOc+o" crossorigin="anonymous"></script>
<script src="../../big-picture.js" integrity="sha384-m3ZXEY2hDwpe+EHCKPWcsYdbsaAOpQ/8XyB4oTYL8H/6tYUeKups1lNvGP8oA9WP" 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-aFfWPXUQqu/JmYKiT5gUT1t/TuyQ9sq8CTubsVNVmDgxIeLx38I7A324Q3NLOc+o" crossorigin="anonymous"></script>
<script src="../../big-picture.js" integrity="sha384-m3ZXEY2hDwpe+EHCKPWcsYdbsaAOpQ/8XyB4oTYL8H/6tYUeKups1lNvGP8oA9WP" 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-aFfWPXUQqu/JmYKiT5gUT1t/TuyQ9sq8CTubsVNVmDgxIeLx38I7A324Q3NLOc+o" crossorigin="anonymous"></script>
<script src="../big-picture.js" integrity="sha384-m3ZXEY2hDwpe+EHCKPWcsYdbsaAOpQ/8XyB4oTYL8H/6tYUeKups1lNvGP8oA9WP" 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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x" 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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x" 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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x" 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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J" 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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J" 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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J" 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-N6PZWvL+Hi0th2zzlBMHA6Zy+vQ8wESEGxRuIVW2J4z5owLqtYtEDZazVSuopP1K" crossorigin="anonymous"></script>
<script src="../../dashboard-plus-grid.js" integrity="sha384-EiM47KQgmavyRxJCBufmN+X5kPCYcj9FrG93XFOA4LSxLYukkolGT5q62vPlVAi2" 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-N6PZWvL+Hi0th2zzlBMHA6Zy+vQ8wESEGxRuIVW2J4z5owLqtYtEDZazVSuopP1K" crossorigin="anonymous"></script>
<script src="../dashboard-plus-grid.js" integrity="sha384-EiM47KQgmavyRxJCBufmN+X5kPCYcj9FrG93XFOA4LSxLYukkolGT5q62vPlVAi2" 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 <demo-map> custom element. -->
<script src="../demo-map.js" integrity="sha384-+wOjOukTVMn2RUu++wAw9cuau0tCJtPhz/DBiOF7htSVb2Zm7MHvwYqVsCa/UhRK" crossorigin="anonymous"></script>
<script src="../demo-map.js" integrity="sha384-1tCOfaHYNEb7+Ki6KCsynhOjo6Rfs3+qhwYD+tYZOH2wuRw3JLvFN9G1KUd2RtmL" 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 <demo-map> custom element. -->
<script src="../../demo-map.js" integrity="sha384-+wOjOukTVMn2RUu++wAw9cuau0tCJtPhz/DBiOF7htSVb2Zm7MHvwYqVsCa/UhRK" crossorigin="anonymous"></script>
<script src="../../demo-map.js" integrity="sha384-1tCOfaHYNEb7+Ki6KCsynhOjo6Rfs3+qhwYD+tYZOH2wuRw3JLvFN9G1KUd2RtmL" 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 <doxa-maps-spa> custom element. -->
<script src="../../doxa-maps-spa.js" integrity="sha384-4NdEx4nqYac4pMuGcH65dVtseImXuF0V1vLRVAR4grs37oi7XiXTftCAiVd4Lgcg" crossorigin="anonymous"></script>
<script src="../../doxa-maps-spa.js" integrity="sha384-GW1J6Ve1RxaNRa37GoJzu+YAD9wyc+P3vR2qlc1kX9VzCi9ShmmxPOrWruItRJKX" 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 <doxa-maps-spa> custom element. -->
<script src="../doxa-maps-spa.js" integrity="sha384-4NdEx4nqYac4pMuGcH65dVtseImXuF0V1vLRVAR4grs37oi7XiXTftCAiVd4Lgcg" crossorigin="anonymous"></script>
<script src="../doxa-maps-spa.js" integrity="sha384-GW1J6Ve1RxaNRa37GoJzu+YAD9wyc+P3vR2qlc1kX9VzCi9ShmmxPOrWruItRJKX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -12,7 +12,7 @@
"bundle": "all-people-groups",
"scriptUrl": "./all-people-groups/all-people-groups.js",
"output": "dx-maps/all-people-groups/all-people-groups.js",
"integrity": "sha384-s3dDxH0p2dV2Qhy5ifnaqo69+zEbYgL0xNRIDFrKkakanDTDubuoSAsQxRjr2NRN",
"integrity": "sha384-vE315hvzd08MBT2C1DsQ8ULHLmH9Q/uqkFo9+qX0y14fBJb8I6Zsb270TwkxHfGY",
"elements": [
"all-people-groups"
],
@ -28,7 +28,7 @@
"scriptUrl": "./all-people-groups/all-people-groups.js",
"embed": {
"iframe": "<iframe src=\"./all-people-groups/all-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- 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=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-s3dDxH0p2dV2Qhy5ifnaqo69+zEbYgL0xNRIDFrKkakanDTDubuoSAsQxRjr2NRN\" crossorigin=\"anonymous\"></script>\n<all-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></all-people-groups>"
"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=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-vE315hvzd08MBT2C1DsQ8ULHLmH9Q/uqkFo9+qX0y14fBJb8I6Zsb270TwkxHfGY\" crossorigin=\"anonymous\"></script>\n<all-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></all-people-groups>"
},
"bundle": "all-people-groups"
}
@ -38,7 +38,7 @@
"bundle": "big-picture",
"scriptUrl": "./big-picture/big-picture.js",
"output": "dx-maps/big-picture/big-picture.js",
"integrity": "sha384-aFfWPXUQqu/JmYKiT5gUT1t/TuyQ9sq8CTubsVNVmDgxIeLx38I7A324Q3NLOc+o",
"integrity": "sha384-m3ZXEY2hDwpe+EHCKPWcsYdbsaAOpQ/8XyB4oTYL8H/6tYUeKups1lNvGP8oA9WP",
"elements": [
"bp-big-picture",
"bp-research-header",
@ -60,7 +60,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-aFfWPXUQqu/JmYKiT5gUT1t/TuyQ9sq8CTubsVNVmDgxIeLx38I7A324Q3NLOc+o\" 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-m3ZXEY2hDwpe+EHCKPWcsYdbsaAOpQ/8XyB4oTYL8H/6tYUeKups1lNvGP8oA9WP\" 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": [
{
@ -104,7 +104,7 @@
"bundle": "cities-internet",
"scriptUrl": "./cities-internet/cities-internet.js",
"output": "dx-maps/cities-internet/cities-internet.js",
"integrity": "sha384-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId",
"integrity": "sha384-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x",
"elements": [
"cities-internet-map"
],
@ -120,7 +120,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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId\" 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-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x\" 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>"
}
},
{
@ -134,7 +134,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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId\" 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-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x\" 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>"
}
},
{
@ -148,7 +148,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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId\" 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-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x\" 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>"
}
}
]
@ -157,7 +157,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72",
"integrity": "sha384-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J",
"elements": [
"country-posters-map"
],
@ -177,7 +177,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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72\" 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-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J\" 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>"
}
},
{
@ -191,7 +191,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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72\" 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-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J\" 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>"
}
},
{
@ -205,7 +205,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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72\" 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-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J\" 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>"
}
}
]
@ -214,7 +214,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-N6PZWvL+Hi0th2zzlBMHA6Zy+vQ8wESEGxRuIVW2J4z5owLqtYtEDZazVSuopP1K",
"integrity": "sha384-EiM47KQgmavyRxJCBufmN+X5kPCYcj9FrG93XFOA4LSxLYukkolGT5q62vPlVAi2",
"elements": [
"dashboard-plus-grid"
],
@ -234,7 +234,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-N6PZWvL+Hi0th2zzlBMHA6Zy+vQ8wESEGxRuIVW2J4z5owLqtYtEDZazVSuopP1K\" 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-EiM47KQgmavyRxJCBufmN+X5kPCYcj9FrG93XFOA4LSxLYukkolGT5q62vPlVAi2\" 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": [
{
@ -258,7 +258,7 @@
"bundle": "demo-map",
"scriptUrl": "./demo-map/demo-map.js",
"output": "dx-maps/demo-map/demo-map.js",
"integrity": "sha384-+wOjOukTVMn2RUu++wAw9cuau0tCJtPhz/DBiOF7htSVb2Zm7MHvwYqVsCa/UhRK",
"integrity": "sha384-1tCOfaHYNEb7+Ki6KCsynhOjo6Rfs3+qhwYD+tYZOH2wuRw3JLvFN9G1KUd2RtmL",
"elements": [
"demo-map"
],
@ -274,7 +274,7 @@
"scriptUrl": "./demo-map/demo-map.js",
"embed": {
"iframe": "<iframe src=\"./demo-map/demo-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=\"./demo-map/demo-map.js\" integrity=\"sha384-+wOjOukTVMn2RUu++wAw9cuau0tCJtPhz/DBiOF7htSVb2Zm7MHvwYqVsCa/UhRK\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></demo-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=\"./demo-map/demo-map.js\" integrity=\"sha384-1tCOfaHYNEb7+Ki6KCsynhOjo6Rfs3+qhwYD+tYZOH2wuRw3JLvFN9G1KUd2RtmL\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></demo-map>"
},
"instances": [
{
@ -301,7 +301,7 @@
"bundle": "doxa-maps-spa",
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"output": "dx-maps/doxa-maps-spa/doxa-maps-spa.js",
"integrity": "sha384-4NdEx4nqYac4pMuGcH65dVtseImXuF0V1vLRVAR4grs37oi7XiXTftCAiVd4Lgcg",
"integrity": "sha384-GW1J6Ve1RxaNRa37GoJzu+YAD9wyc+P3vR2qlc1kX9VzCi9ShmmxPOrWruItRJKX",
"elements": [
"doxa-maps-spa"
],
@ -326,7 +326,7 @@
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"embed": {
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/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=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-4NdEx4nqYac4pMuGcH65dVtseImXuF0V1vLRVAR4grs37oi7XiXTftCAiVd4Lgcg\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-GW1J6Ve1RxaNRa37GoJzu+YAD9wyc+P3vR2qlc1kX9VzCi9ShmmxPOrWruItRJKX\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
},
"instances": [
{
@ -353,7 +353,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-OaDWOkfP27Nn/zldYaQNLZDlg6vNQHfE55oFT7ifRFRDNb7oy929I01mXu/BGFvd",
"integrity": "sha384-cWtToAuh7tz2z/miAhM5HVg+5SXW+ECiQSpqI26JrQlZvAVxsuOJvpVKqjkFzCbz",
"elements": [
"doxa-research-map"
],
@ -369,7 +369,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-OaDWOkfP27Nn/zldYaQNLZDlg6vNQHfE55oFT7ifRFRDNb7oy929I01mXu/BGFvd\" 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-cWtToAuh7tz2z/miAhM5HVg+5SXW+ECiQSpqI26JrQlZvAVxsuOJvpVKqjkFzCbz\" 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": [
{
@ -395,7 +395,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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM",
"integrity": "sha384-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN",
"elements": [
"doxa-map",
"doxa-simple-map"
@ -412,7 +412,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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM\" 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-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN\" 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": [
{
@ -578,7 +578,7 @@
"bundle": "gospel-resources",
"scriptUrl": "./gospel-resources/gospel-resources.js",
"output": "dx-maps/gospel-resources/gospel-resources.js",
"integrity": "sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ",
"integrity": "sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63",
"elements": [
"gospel-resources-map"
],
@ -598,7 +598,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/charts/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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -612,7 +612,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -626,7 +626,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/gospel-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -640,7 +640,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -654,7 +654,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
}
]
@ -663,7 +663,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH",
"integrity": "sha384-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA",
"elements": [
"my-upg-100-list-map"
],
@ -683,7 +683,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH\" 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-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA\" 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>"
}
},
{
@ -697,7 +697,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH\" 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-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA\" 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>"
}
},
{
@ -711,7 +711,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH\" 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-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA\" 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>"
}
}
]
@ -720,7 +720,7 @@
"bundle": "people-groups-grid",
"scriptUrl": "./people-groups-grid/people-groups-grid.js",
"output": "dx-maps/people-groups-grid/people-groups-grid.js",
"integrity": "sha384-dgRIP0FFdZjyJEGvFECVTsM/XfUqaL0mpKjwGN/sK7ymtz143AGFbniLL/0ffaZK",
"integrity": "sha384-rgMFgukVmFCDPWYtJdcI0zoiKic20G+OA3OBrZcI2IZHNpC6uJzxjHQ/Ogj1aUsD",
"elements": [
"people-groups-grid"
],
@ -736,7 +736,7 @@
"scriptUrl": "./people-groups-grid/people-groups-grid.js",
"embed": {
"iframe": "<iframe src=\"./people-groups-grid/people-groups-grid/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=\"./people-groups-grid/people-groups-grid.js\" integrity=\"sha384-dgRIP0FFdZjyJEGvFECVTsM/XfUqaL0mpKjwGN/sK7ymtz143AGFbniLL/0ffaZK\" crossorigin=\"anonymous\"></script>\n<people-groups-grid profile-config='{\"profile\":\"people-groups-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></people-groups-grid>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./people-groups-grid/people-groups-grid.js\" integrity=\"sha384-rgMFgukVmFCDPWYtJdcI0zoiKic20G+OA3OBrZcI2IZHNpC6uJzxjHQ/Ogj1aUsD\" crossorigin=\"anonymous\"></script>\n<people-groups-grid profile-config='{\"profile\":\"people-groups-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></people-groups-grid>"
},
"instances": [
{
@ -781,7 +781,7 @@
"bundle": "plugin-studio",
"scriptUrl": "./plugin-studio/plugin-studio.js",
"output": "dx-maps/plugin-studio/plugin-studio.js",
"integrity": "sha384-jom/Fa3lojAtexmOjCCVrifCiRNzi9M5wXWZZ09YE/BqNWB9+PYftWWgOhGFb8Ac",
"integrity": "sha384-JnZ9FLDjoHmQ4yHi1qanuE3385XGCYolv5vjaGdpW1oCa6G0IxnXZJw6NWgfCyKs",
"elements": [
"plugin-studio"
],
@ -809,7 +809,7 @@
"scriptUrl": "./plugin-studio/plugin-studio.js",
"embed": {
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/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=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-jom/Fa3lojAtexmOjCCVrifCiRNzi9M5wXWZZ09YE/BqNWB9+PYftWWgOhGFb8Ac\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-JnZ9FLDjoHmQ4yHi1qanuE3385XGCYolv5vjaGdpW1oCa6G0IxnXZJw6NWgfCyKs\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
},
"instances": [
{
@ -836,7 +836,7 @@
"bundle": "pplr-data-maps",
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"output": "dx-maps/pplr-data-maps/pplr-data-maps.js",
"integrity": "sha384-QzFC7Omi9TNxOdwPzE3wEdM6fAbEXSC4F8GxVPIta/ZrUfmbsVSPTeI/KPfMlQwD",
"integrity": "sha384-Ux8QUMBZW4mmt9sRX15d7YIPplLKX23bgQYq9PQt2aH735FNoH9zthvLcMmM9PCA",
"elements": [
"pplr-data-maps"
],
@ -852,7 +852,7 @@
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"embed": {
"iframe": "<iframe src=\"./pplr-data-maps/pplr-data-maps/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=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-QzFC7Omi9TNxOdwPzE3wEdM6fAbEXSC4F8GxVPIta/ZrUfmbsVSPTeI/KPfMlQwD\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
"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=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-Ux8QUMBZW4mmt9sRX15d7YIPplLKX23bgQYq9PQt2aH735FNoH9zthvLcMmM9PCA\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
},
"bundle": "pplr-data-maps"
}
@ -887,7 +887,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa",
"integrity": "sha384-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1",
"elements": [
"regions-dashboard"
],
@ -907,7 +907,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-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa\" 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-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1\" 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>"
}
},
{
@ -921,7 +921,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-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa\" 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-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1\" 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>"
}
},
{
@ -935,7 +935,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-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa\" 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-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1\" 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>"
}
}
]
@ -944,7 +944,7 @@
"bundle": "research-dashboard",
"scriptUrl": "./research-dashboard/research-dashboard.js",
"output": "dx-maps/research-dashboard/research-dashboard.js",
"integrity": "sha384-fh/MUmjOqoJuyYSzvLV2pVo/K6IyfFMh8EGuk5fe4zH5RLRS2HWTcOST+/5rcIp/",
"integrity": "sha384-SvumKjqLxt34l5vhqEEPLUwhrlDIGJv8usiB5gRA9pyQ9P3eza4mB4W7KUhzPygB",
"elements": [
"research-dashboard-map"
],
@ -960,7 +960,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-fh/MUmjOqoJuyYSzvLV2pVo/K6IyfFMh8EGuk5fe4zH5RLRS2HWTcOST+/5rcIp/\" 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-SvumKjqLxt34l5vhqEEPLUwhrlDIGJv8usiB5gRA9pyQ9P3eza4mB4W7KUhzPygB\" 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"
}
@ -970,7 +970,7 @@
"bundle": "research-map-plus-grid",
"scriptUrl": "./research-map-plus-grid/research-map-plus-grid.js",
"output": "dx-maps/research-map-plus-grid/research-map-plus-grid.js",
"integrity": "sha384-MZuYms3ihyjWC7Qf946V1ykPMoKrvBxanLdNJjadVwZ7jAJNQw5MHXRrKi1WXChm",
"integrity": "sha384-mQ5oaa4Ew2CSwX4Jt1zM2M+rLZfRS1TA/0irMWeoJmf+e+0eYIwFTEgUCaYrTN1n",
"elements": [
"research-map-plus-grid"
],
@ -990,7 +990,7 @@
"scriptUrl": "./research-map-plus-grid/research-map-plus-grid.js",
"embed": {
"iframe": "<iframe src=\"./research-map-plus-grid/research-map-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-map-plus-grid/research-map-plus-grid.js\" integrity=\"sha384-MZuYms3ihyjWC7Qf946V1ykPMoKrvBxanLdNJjadVwZ7jAJNQw5MHXRrKi1WXChm\" crossorigin=\"anonymous\"></script>\n<research-map-plus-grid profile-config='{\"profile\":\"research-map-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-map-plus-grid>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-map-plus-grid/research-map-plus-grid.js\" integrity=\"sha384-mQ5oaa4Ew2CSwX4Jt1zM2M+rLZfRS1TA/0irMWeoJmf+e+0eYIwFTEgUCaYrTN1n\" crossorigin=\"anonymous\"></script>\n<research-map-plus-grid profile-config='{\"profile\":\"research-map-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-map-plus-grid>"
},
"instances": [
{
@ -1058,7 +1058,7 @@
"bundle": "upg-neighbor",
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
"output": "dx-maps/upg-neighbor/upg-neighbor.js",
"integrity": "sha384-yqAjvmkBANT6GFCynk5+2PnAK6JYeS6i2bsSIKdBCI7iIE7BSrQo63F3uGhOMt2P",
"integrity": "sha384-Lu7UYPvGvqKGzv1m97kyJGvHo30NI4kGy3s6GAMQ/IFCUddQlq25OlU10lBA6nFn",
"elements": [
"upg-neighbor"
],
@ -1074,7 +1074,7 @@
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
"embed": {
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/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=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-yqAjvmkBANT6GFCynk5+2PnAK6JYeS6i2bsSIKdBCI7iIE7BSrQo63F3uGhOMt2P\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
"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=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-Lu7UYPvGvqKGzv1m97kyJGvHo30NI4kGy3s6GAMQ/IFCUddQlq25OlU10lBA6nFn\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
},
"instances": [
{
@ -1157,7 +1157,7 @@
"bundle": "wall-of-people-groups",
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"output": "dx-maps/wall-of-people-groups/wall-of-people-groups.js",
"integrity": "sha384-OcQp6Bh/Gmx9jQ6sdud82QlFWKJ8KkWWd3WtMGfulpwTgK7Yco9VaRDC4SmGyC/c",
"integrity": "sha384-H6hiLFIFtwycB2u9AUoqEhJAKw2WUWKXvc0CCyCXuNUXcqCLDNpSeYblTLLnIdAa",
"elements": [
"wall-of-people-groups"
],
@ -1173,7 +1173,7 @@
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"embed": {
"iframe": "<iframe src=\"./wall-of-people-groups/wall-of-people-groups/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=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-OcQp6Bh/Gmx9jQ6sdud82QlFWKJ8KkWWd3WtMGfulpwTgK7Yco9VaRDC4SmGyC/c\" crossorigin=\"anonymous\"></script>\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-H6hiLFIFtwycB2u9AUoqEhJAKw2WUWKXvc0CCyCXuNUXcqCLDNpSeYblTLLnIdAa\" crossorigin=\"anonymous\"></script>\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
},
"bundle": "wall-of-people-groups"
}

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-OaDWOkfP27Nn/zldYaQNLZDlg6vNQHfE55oFT7ifRFRDNb7oy929I01mXu/BGFvd" crossorigin="anonymous"></script>
<script src="../doxa-research-map.js" integrity="sha384-cWtToAuh7tz2z/miAhM5HVg+5SXW+ECiQSpqI26JrQlZvAVxsuOJvpVKqjkFzCbz" 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-OaDWOkfP27Nn/zldYaQNLZDlg6vNQHfE55oFT7ifRFRDNb7oy929I01mXu/BGFvd" crossorigin="anonymous"></script>
<script src="../../doxa-research-map.js" integrity="sha384-cWtToAuh7tz2z/miAhM5HVg+5SXW+ECiQSpqI26JrQlZvAVxsuOJvpVKqjkFzCbz" 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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN" 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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN" 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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM" crossorigin="anonymous"></script>
<script src="../doxa-simple-map.js" integrity="sha384-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN" 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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN" 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 <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63" 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 <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63" 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 <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63" 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 <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

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

View file

@ -19,6 +19,6 @@
</head>
<body>
<plugin-studio tree-base="."></plugin-studio>
<script src="./plugin-studio/plugin-studio.js" integrity="sha384-jom/Fa3lojAtexmOjCCVrifCiRNzi9M5wXWZZ09YE/BqNWB9+PYftWWgOhGFb8Ac" crossorigin="anonymous"></script>
<script src="./plugin-studio/plugin-studio.js" integrity="sha384-JnZ9FLDjoHmQ4yHi1qanuE3385XGCYolv5vjaGdpW1oCa6G0IxnXZJw6NWgfCyKs" crossorigin="anonymous"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -12,7 +12,7 @@
"bundle": "all-people-groups",
"scriptUrl": "./all-people-groups/all-people-groups.js",
"output": "dx-maps/all-people-groups/all-people-groups.js",
"integrity": "sha384-s3dDxH0p2dV2Qhy5ifnaqo69+zEbYgL0xNRIDFrKkakanDTDubuoSAsQxRjr2NRN",
"integrity": "sha384-vE315hvzd08MBT2C1DsQ8ULHLmH9Q/uqkFo9+qX0y14fBJb8I6Zsb270TwkxHfGY",
"elements": [
"all-people-groups"
],
@ -28,7 +28,7 @@
"scriptUrl": "./all-people-groups/all-people-groups.js",
"embed": {
"iframe": "<iframe src=\"./all-people-groups/all-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- 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=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-s3dDxH0p2dV2Qhy5ifnaqo69+zEbYgL0xNRIDFrKkakanDTDubuoSAsQxRjr2NRN\" crossorigin=\"anonymous\"></script>\n<all-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></all-people-groups>"
"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=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-vE315hvzd08MBT2C1DsQ8ULHLmH9Q/uqkFo9+qX0y14fBJb8I6Zsb270TwkxHfGY\" crossorigin=\"anonymous\"></script>\n<all-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></all-people-groups>"
},
"bundle": "all-people-groups"
}
@ -38,7 +38,7 @@
"bundle": "big-picture",
"scriptUrl": "./big-picture/big-picture.js",
"output": "dx-maps/big-picture/big-picture.js",
"integrity": "sha384-aFfWPXUQqu/JmYKiT5gUT1t/TuyQ9sq8CTubsVNVmDgxIeLx38I7A324Q3NLOc+o",
"integrity": "sha384-m3ZXEY2hDwpe+EHCKPWcsYdbsaAOpQ/8XyB4oTYL8H/6tYUeKups1lNvGP8oA9WP",
"elements": [
"bp-big-picture",
"bp-research-header",
@ -60,7 +60,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-aFfWPXUQqu/JmYKiT5gUT1t/TuyQ9sq8CTubsVNVmDgxIeLx38I7A324Q3NLOc+o\" 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-m3ZXEY2hDwpe+EHCKPWcsYdbsaAOpQ/8XyB4oTYL8H/6tYUeKups1lNvGP8oA9WP\" 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": [
{
@ -104,7 +104,7 @@
"bundle": "cities-internet",
"scriptUrl": "./cities-internet/cities-internet.js",
"output": "dx-maps/cities-internet/cities-internet.js",
"integrity": "sha384-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId",
"integrity": "sha384-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x",
"elements": [
"cities-internet-map"
],
@ -120,7 +120,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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId\" 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-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x\" 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>"
}
},
{
@ -134,7 +134,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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId\" 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-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x\" 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>"
}
},
{
@ -148,7 +148,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-B6HHkN90h22iKq0xA3irWWqibeDUaBp65UoQwZAKleAlXLJfGzCJqxnKVvrYRHId\" 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-b73spJI8AEYCTGla3A/8iq60DZUgJjhtwste7fZVm5XD0539IubtdsVq3PqOc40x\" 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>"
}
}
]
@ -157,7 +157,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72",
"integrity": "sha384-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J",
"elements": [
"country-posters-map"
],
@ -177,7 +177,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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72\" 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-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J\" 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>"
}
},
{
@ -191,7 +191,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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72\" 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-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J\" 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>"
}
},
{
@ -205,7 +205,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-yvTVWgqG/265R81lxyPaXu1qWmcHcHRICyC8OexhPlww0EvbCQAViSN5LKh08t72\" 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-BwWUTJFjm++IaFIfrKfe94AMYtbspLqkuLdSvfdQopa2qf5PIWBu/NKCEM3fYo3J\" 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>"
}
}
]
@ -214,7 +214,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-N6PZWvL+Hi0th2zzlBMHA6Zy+vQ8wESEGxRuIVW2J4z5owLqtYtEDZazVSuopP1K",
"integrity": "sha384-EiM47KQgmavyRxJCBufmN+X5kPCYcj9FrG93XFOA4LSxLYukkolGT5q62vPlVAi2",
"elements": [
"dashboard-plus-grid"
],
@ -234,7 +234,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-N6PZWvL+Hi0th2zzlBMHA6Zy+vQ8wESEGxRuIVW2J4z5owLqtYtEDZazVSuopP1K\" 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-EiM47KQgmavyRxJCBufmN+X5kPCYcj9FrG93XFOA4LSxLYukkolGT5q62vPlVAi2\" 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": [
{
@ -258,7 +258,7 @@
"bundle": "demo-map",
"scriptUrl": "./demo-map/demo-map.js",
"output": "dx-maps/demo-map/demo-map.js",
"integrity": "sha384-+wOjOukTVMn2RUu++wAw9cuau0tCJtPhz/DBiOF7htSVb2Zm7MHvwYqVsCa/UhRK",
"integrity": "sha384-1tCOfaHYNEb7+Ki6KCsynhOjo6Rfs3+qhwYD+tYZOH2wuRw3JLvFN9G1KUd2RtmL",
"elements": [
"demo-map"
],
@ -274,7 +274,7 @@
"scriptUrl": "./demo-map/demo-map.js",
"embed": {
"iframe": "<iframe src=\"./demo-map/demo-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=\"./demo-map/demo-map.js\" integrity=\"sha384-+wOjOukTVMn2RUu++wAw9cuau0tCJtPhz/DBiOF7htSVb2Zm7MHvwYqVsCa/UhRK\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></demo-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=\"./demo-map/demo-map.js\" integrity=\"sha384-1tCOfaHYNEb7+Ki6KCsynhOjo6Rfs3+qhwYD+tYZOH2wuRw3JLvFN9G1KUd2RtmL\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></demo-map>"
},
"instances": [
{
@ -301,7 +301,7 @@
"bundle": "doxa-maps-spa",
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"output": "dx-maps/doxa-maps-spa/doxa-maps-spa.js",
"integrity": "sha384-4NdEx4nqYac4pMuGcH65dVtseImXuF0V1vLRVAR4grs37oi7XiXTftCAiVd4Lgcg",
"integrity": "sha384-GW1J6Ve1RxaNRa37GoJzu+YAD9wyc+P3vR2qlc1kX9VzCi9ShmmxPOrWruItRJKX",
"elements": [
"doxa-maps-spa"
],
@ -326,7 +326,7 @@
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"embed": {
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/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=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-4NdEx4nqYac4pMuGcH65dVtseImXuF0V1vLRVAR4grs37oi7XiXTftCAiVd4Lgcg\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-GW1J6Ve1RxaNRa37GoJzu+YAD9wyc+P3vR2qlc1kX9VzCi9ShmmxPOrWruItRJKX\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
},
"instances": [
{
@ -353,7 +353,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-OaDWOkfP27Nn/zldYaQNLZDlg6vNQHfE55oFT7ifRFRDNb7oy929I01mXu/BGFvd",
"integrity": "sha384-cWtToAuh7tz2z/miAhM5HVg+5SXW+ECiQSpqI26JrQlZvAVxsuOJvpVKqjkFzCbz",
"elements": [
"doxa-research-map"
],
@ -369,7 +369,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-OaDWOkfP27Nn/zldYaQNLZDlg6vNQHfE55oFT7ifRFRDNb7oy929I01mXu/BGFvd\" 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-cWtToAuh7tz2z/miAhM5HVg+5SXW+ECiQSpqI26JrQlZvAVxsuOJvpVKqjkFzCbz\" 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": [
{
@ -395,7 +395,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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM",
"integrity": "sha384-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN",
"elements": [
"doxa-map",
"doxa-simple-map"
@ -412,7 +412,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-gO8etEAvAN+WncAHul+piX4UX678AYOGBsO58cmmJbSnrcX0QmSylDZEhDJr/rnM\" 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-luXPhMnUcYlqA6e90hmPZQwC3/jBQJ/usI2eheMbGlBnQRDEpY5aW/hAU12TJmUN\" 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": [
{
@ -578,7 +578,7 @@
"bundle": "gospel-resources",
"scriptUrl": "./gospel-resources/gospel-resources.js",
"output": "dx-maps/gospel-resources/gospel-resources.js",
"integrity": "sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ",
"integrity": "sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63",
"elements": [
"gospel-resources-map"
],
@ -598,7 +598,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/charts/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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -612,7 +612,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -626,7 +626,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/gospel-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -640,7 +640,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -654,7 +654,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-yHt4ooXKsxjsgc/lus9TLVUnaYXpVVGcdxaE603xPbrf+VS6nYBtI3XcOkK71NNJ\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fzeKwwOl9ilsOllSAArMQkFIHvKmJfXgFkdj4IEqR9SpA5WUfsZmVJ20Q8xBir63\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
}
]
@ -663,7 +663,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH",
"integrity": "sha384-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA",
"elements": [
"my-upg-100-list-map"
],
@ -683,7 +683,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH\" 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-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA\" 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>"
}
},
{
@ -697,7 +697,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH\" 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-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA\" 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>"
}
},
{
@ -711,7 +711,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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH\" 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-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA\" 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>"
}
}
]
@ -720,7 +720,7 @@
"bundle": "people-groups-grid",
"scriptUrl": "./people-groups-grid/people-groups-grid.js",
"output": "dx-maps/people-groups-grid/people-groups-grid.js",
"integrity": "sha384-dgRIP0FFdZjyJEGvFECVTsM/XfUqaL0mpKjwGN/sK7ymtz143AGFbniLL/0ffaZK",
"integrity": "sha384-rgMFgukVmFCDPWYtJdcI0zoiKic20G+OA3OBrZcI2IZHNpC6uJzxjHQ/Ogj1aUsD",
"elements": [
"people-groups-grid"
],
@ -736,7 +736,7 @@
"scriptUrl": "./people-groups-grid/people-groups-grid.js",
"embed": {
"iframe": "<iframe src=\"./people-groups-grid/people-groups-grid/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=\"./people-groups-grid/people-groups-grid.js\" integrity=\"sha384-dgRIP0FFdZjyJEGvFECVTsM/XfUqaL0mpKjwGN/sK7ymtz143AGFbniLL/0ffaZK\" crossorigin=\"anonymous\"></script>\n<people-groups-grid profile-config='{\"profile\":\"people-groups-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></people-groups-grid>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./people-groups-grid/people-groups-grid.js\" integrity=\"sha384-rgMFgukVmFCDPWYtJdcI0zoiKic20G+OA3OBrZcI2IZHNpC6uJzxjHQ/Ogj1aUsD\" crossorigin=\"anonymous\"></script>\n<people-groups-grid profile-config='{\"profile\":\"people-groups-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></people-groups-grid>"
},
"instances": [
{
@ -781,7 +781,7 @@
"bundle": "plugin-studio",
"scriptUrl": "./plugin-studio/plugin-studio.js",
"output": "dx-maps/plugin-studio/plugin-studio.js",
"integrity": "sha384-jom/Fa3lojAtexmOjCCVrifCiRNzi9M5wXWZZ09YE/BqNWB9+PYftWWgOhGFb8Ac",
"integrity": "sha384-JnZ9FLDjoHmQ4yHi1qanuE3385XGCYolv5vjaGdpW1oCa6G0IxnXZJw6NWgfCyKs",
"elements": [
"plugin-studio"
],
@ -809,7 +809,7 @@
"scriptUrl": "./plugin-studio/plugin-studio.js",
"embed": {
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/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=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-jom/Fa3lojAtexmOjCCVrifCiRNzi9M5wXWZZ09YE/BqNWB9+PYftWWgOhGFb8Ac\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-JnZ9FLDjoHmQ4yHi1qanuE3385XGCYolv5vjaGdpW1oCa6G0IxnXZJw6NWgfCyKs\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
},
"instances": [
{
@ -836,7 +836,7 @@
"bundle": "pplr-data-maps",
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"output": "dx-maps/pplr-data-maps/pplr-data-maps.js",
"integrity": "sha384-QzFC7Omi9TNxOdwPzE3wEdM6fAbEXSC4F8GxVPIta/ZrUfmbsVSPTeI/KPfMlQwD",
"integrity": "sha384-Ux8QUMBZW4mmt9sRX15d7YIPplLKX23bgQYq9PQt2aH735FNoH9zthvLcMmM9PCA",
"elements": [
"pplr-data-maps"
],
@ -852,7 +852,7 @@
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"embed": {
"iframe": "<iframe src=\"./pplr-data-maps/pplr-data-maps/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=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-QzFC7Omi9TNxOdwPzE3wEdM6fAbEXSC4F8GxVPIta/ZrUfmbsVSPTeI/KPfMlQwD\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
"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=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-Ux8QUMBZW4mmt9sRX15d7YIPplLKX23bgQYq9PQt2aH735FNoH9zthvLcMmM9PCA\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
},
"bundle": "pplr-data-maps"
}
@ -887,7 +887,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa",
"integrity": "sha384-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1",
"elements": [
"regions-dashboard"
],
@ -907,7 +907,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-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa\" 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-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1\" 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>"
}
},
{
@ -921,7 +921,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-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa\" 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-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1\" 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>"
}
},
{
@ -935,7 +935,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-cwH4QEctBH2e3tmZQpakBmfC3U9fIGRx/uoB6l2QOg9xYl1Wb0kQxLCM5Mh0rpFa\" 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-vRrOkxaF9tyQQfHNEK4ZJQ1wCP/YeUcs/k8jeF57WCAVFNzoMIubgYVW1bZ98qy1\" 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>"
}
}
]
@ -944,7 +944,7 @@
"bundle": "research-dashboard",
"scriptUrl": "./research-dashboard/research-dashboard.js",
"output": "dx-maps/research-dashboard/research-dashboard.js",
"integrity": "sha384-fh/MUmjOqoJuyYSzvLV2pVo/K6IyfFMh8EGuk5fe4zH5RLRS2HWTcOST+/5rcIp/",
"integrity": "sha384-SvumKjqLxt34l5vhqEEPLUwhrlDIGJv8usiB5gRA9pyQ9P3eza4mB4W7KUhzPygB",
"elements": [
"research-dashboard-map"
],
@ -960,7 +960,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-fh/MUmjOqoJuyYSzvLV2pVo/K6IyfFMh8EGuk5fe4zH5RLRS2HWTcOST+/5rcIp/\" 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-SvumKjqLxt34l5vhqEEPLUwhrlDIGJv8usiB5gRA9pyQ9P3eza4mB4W7KUhzPygB\" 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"
}
@ -970,7 +970,7 @@
"bundle": "research-map-plus-grid",
"scriptUrl": "./research-map-plus-grid/research-map-plus-grid.js",
"output": "dx-maps/research-map-plus-grid/research-map-plus-grid.js",
"integrity": "sha384-MZuYms3ihyjWC7Qf946V1ykPMoKrvBxanLdNJjadVwZ7jAJNQw5MHXRrKi1WXChm",
"integrity": "sha384-mQ5oaa4Ew2CSwX4Jt1zM2M+rLZfRS1TA/0irMWeoJmf+e+0eYIwFTEgUCaYrTN1n",
"elements": [
"research-map-plus-grid"
],
@ -990,7 +990,7 @@
"scriptUrl": "./research-map-plus-grid/research-map-plus-grid.js",
"embed": {
"iframe": "<iframe src=\"./research-map-plus-grid/research-map-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-map-plus-grid/research-map-plus-grid.js\" integrity=\"sha384-MZuYms3ihyjWC7Qf946V1ykPMoKrvBxanLdNJjadVwZ7jAJNQw5MHXRrKi1WXChm\" crossorigin=\"anonymous\"></script>\n<research-map-plus-grid profile-config='{\"profile\":\"research-map-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-map-plus-grid>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-map-plus-grid/research-map-plus-grid.js\" integrity=\"sha384-mQ5oaa4Ew2CSwX4Jt1zM2M+rLZfRS1TA/0irMWeoJmf+e+0eYIwFTEgUCaYrTN1n\" crossorigin=\"anonymous\"></script>\n<research-map-plus-grid profile-config='{\"profile\":\"research-map-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-map-plus-grid>"
},
"instances": [
{
@ -1058,7 +1058,7 @@
"bundle": "upg-neighbor",
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
"output": "dx-maps/upg-neighbor/upg-neighbor.js",
"integrity": "sha384-yqAjvmkBANT6GFCynk5+2PnAK6JYeS6i2bsSIKdBCI7iIE7BSrQo63F3uGhOMt2P",
"integrity": "sha384-Lu7UYPvGvqKGzv1m97kyJGvHo30NI4kGy3s6GAMQ/IFCUddQlq25OlU10lBA6nFn",
"elements": [
"upg-neighbor"
],
@ -1074,7 +1074,7 @@
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
"embed": {
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/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=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-yqAjvmkBANT6GFCynk5+2PnAK6JYeS6i2bsSIKdBCI7iIE7BSrQo63F3uGhOMt2P\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
"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=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-Lu7UYPvGvqKGzv1m97kyJGvHo30NI4kGy3s6GAMQ/IFCUddQlq25OlU10lBA6nFn\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
},
"instances": [
{
@ -1157,7 +1157,7 @@
"bundle": "wall-of-people-groups",
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"output": "dx-maps/wall-of-people-groups/wall-of-people-groups.js",
"integrity": "sha384-OcQp6Bh/Gmx9jQ6sdud82QlFWKJ8KkWWd3WtMGfulpwTgK7Yco9VaRDC4SmGyC/c",
"integrity": "sha384-H6hiLFIFtwycB2u9AUoqEhJAKw2WUWKXvc0CCyCXuNUXcqCLDNpSeYblTLLnIdAa",
"elements": [
"wall-of-people-groups"
],
@ -1173,7 +1173,7 @@
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"embed": {
"iframe": "<iframe src=\"./wall-of-people-groups/wall-of-people-groups/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=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-OcQp6Bh/Gmx9jQ6sdud82QlFWKJ8KkWWd3WtMGfulpwTgK7Yco9VaRDC4SmGyC/c\" crossorigin=\"anonymous\"></script>\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-H6hiLFIFtwycB2u9AUoqEhJAKw2WUWKXvc0CCyCXuNUXcqCLDNpSeYblTLLnIdAa\" crossorigin=\"anonymous\"></script>\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
},
"bundle": "wall-of-people-groups"
}

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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA" 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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA" 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-S9c6umSb3Sezvx1uLwY8hI81d1eUowmcp0DS7zqRBaTH676mmUK8Jp3+iTrGnTQH" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-0g9R55+dyaikXj9PGGQO2s2HzWDnRYhqEpBDtTnjFK0qlLVzPCgd8GQ+Nff7JVFA" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

Some files were not shown because too many files have changed in this diff Show more