The map said "someone still needs prayer" and gave you no way to pray

Its toast has always read "Tap a coloured dot — someone still needs prayer". Tapping one
showed Scripture, film, audio, video, stories, tracts and training, and no way to pray.
doxa.life publishes a page per people group and sibling plugins in this repo already link
to it; this map simply never did. 1,723 groups now carry a "Pray for <group>" action with
the number already praying beside it.

THIS IS THE ONE JOIN HERE THAT IS NOT BY LANGUAGE, and it needed justifying rather than
assuming. Every other source keys on ISO 639-3 because a translation serves everyone who
speaks the language; prayer does not work that way — you pray for a people, in a place. So
this keys on (group name, country), which is a NAME join, the thing this map refuses
everywhere else because names collide.

Allowed only because the key was tested first: on the API side (name, country) is
perfectly unique — 2,107 entries, 2,107 distinct keys, 0 collisions — and on the pin side
12,379 pins give 12,369 distinct keys. The ambiguous ones are DROPPED, not guessed. One
entry was lost that way, and 383 more have no pin here at all.

pray.doxa.life is the fourth host in this bundle that answers 200 with identical bytes for
any URL: /abai-sungai and /zzz-not-a-real-group are both 3,903 bytes. Rendered it does
discriminate — a real slug titles the page "Abai Sungai - App" with 1,863 characters, a
bogus one titles it "Doxa.Life" with zero — but existence comes from the API listing,
which is the publisher's own statement and costs one request rather than 1,723 renders.

The lookup is COMPUTED, not baked into groupAt. groupAt runs the instant a pin is clicked,
so a value read there is whatever had arrived by then and never updates when the lazy
fetch lands — the same shape as the link-verification state that used to hang off
openPin's country branch.

Verified with a negative control: a group in the index renders an anchor whose href
matches the expected slug URL exactly, and German, which is not in it, renders nothing.
My probe reported FAIL twice against correct code by waiting a guessed 900ms; it now waits
for the data. Screenshot read — the Lati, 2,900 people with nothing found in any pool, can
now be prayed for.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 16:06:01 +00:00
parent f89e5eb370
commit 22712eb43a
12 changed files with 20881 additions and 34 deletions

10353
data/tables/pray-index.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -154,6 +154,7 @@ const byIso = computed(() => {
/* ══ PEOPLE-GROUP DETAIL — fetched lazily, on the first country tap ═════════════
* people-group-resources.json is 1.4MB. Loading it up-front to populate a sheet
* nobody has opened yet would delay the map's first paint for every visitor. */
const prayIndex = ref({})
const pgReady = ref(false) // link-verification fetched (the group list comes from pins)
const linkVerify = ref(null)
const pgLoading = ref(false)
@ -384,6 +385,11 @@ async function ensurePeopleGroups() {
// 1.4MB file. Only the link verification remains, and it is allowed to fail —
// without it every link falls back to "unchecked", which is the honest state rather
// than a broken UI.
// Prayer links, keyed by (group name, country) rather than by language — see
// scripts/fetch-pray-index.mjs for why this one join is not by ISO code.
const pr = await fetch(`${assetsBase.value}/tables/pray-index.json`)
.then((r) => (r.ok ? r.json() : null)).catch(() => null)
prayIndex.value = pr?.byKey || {}
const lv = await fetch(`${assetsBase.value}/tables/gospel-link-verification.json`)
.then((r) => (r.ok ? r.json() : null)).catch(() => null)
linkVerify.value = lv?.links || {}
@ -421,6 +427,22 @@ const pinsByIso = computed(() => {
// One shape for the sheet, whether a group arrived by pin tap, country list or
// same-language list. Divergent shapes here are how a field renders blank in one path
// and fine in another.
// Same normalisation the index was built with. Kept beside groupAt so the two cannot
// drift: a key built differently here would silently match nothing.
const prayKey = (name, iso3) =>
`${String(name || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()}|${String(iso3 || '').toUpperCase()}`
// COMPUTED, not baked into groupAt. The index is fetched lazily, and groupAt runs the
// moment a pin is clicked — so a value read there is whatever had arrived by then, and
// never updates when the fetch lands. Measured: the control did not render at all, because
// the first click always beats the request. The same shape of bug as the link-verification
// state that hung off openPin's country branch.
const selectedPray = computed(() => {
const g = selectedGroup.value
if (!g) return null
return prayIndex.value[prayKey(g.people_group, g._iso3 || '')] || null
})
function groupAt(i) {
const r = pinRows.value[i]
if (!r) return null
@ -432,6 +454,7 @@ function groupAt(i) {
iso639_3: r[4] || '',
population: r[5],
countries: byIso.value[r[3]]?.country || r[3],
_iso3: r[3] || '',
scripture_url: c.s || '',
film_url: c.f || '',
has_audio: c.a === 1 ? true : c.a === 0 ? false : 'unknown',
@ -862,6 +885,10 @@ useShadowStyles(
'.gm-sign{margin:8px 0 0;padding:8px 10px;border:1px solid #d1d4cd;border-left:3px solid #6d766e;' +
'border-radius:8px;background:#f3f3f1;color:#3b463d;font-size:11.5px;line-height:1.5}' +
'.is-dark .gm-sign{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.18);color:#e5e7eb}' +
'.gm-pray{display:block;margin:8px 0 0;padding:9px 11px;border-radius:8px;text-decoration:none;' +
'background:#3b463d;color:#fff;font:600 12.5px inherit;text-align:center}' +
'.gm-pray:hover,.gm-pray:focus-visible{background:#2f382f}' +
'.gm-pray-n{display:block;margin-top:2px;font-weight:500;font-size:11px;opacity:.85}' +
'.gm-tablink{display:block;margin:10px 0 2px;padding:9px 12px;border:1px solid #d1d4cd;border-left:3px solid #92b195;border-radius:8px;background:#f3f3f1;color:#3b463d;font-weight:600;font-size:12.5px;text-decoration:none;min-height:36px;box-sizing:border-box}' +
'.gm-tablink:hover{background:#e9ebe6}' +
'.is-dark .gm-tablink{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.18);color:#e5e7eb}' +
@ -1267,6 +1294,17 @@ useShadowStyles(
</p>
</div>
<!-- THE MAP'S OWN PROMPT, ANSWERED. Its toast has always said "someone still needs
prayer" and tapping a dot offered no way to pray. doxa.life publishes a page per
group and sibling plugins here already link to it; this map simply did not.
Only shown where the join is unambiguous — 1,723 of 12,379 groups. -->
<a v-if="selectedPray" class="gm-pray" :href="selectedPray.u"
target="_blank" rel="noopener noreferrer"
:title="'Open the prayer page for ' + selectedPray.n + ' on doxa.life.'">
Pray for {{ selectedPray.n }}
<span v-if="selectedPray.praying" class="gm-pray-n">{{ fmt(selectedPray.praying) }} already praying</span>
</a>
<!-- A link when standalone, a filter when composed. Same words either way, because
the promise is the same; only the destination differs. -->
<button v-if="selectedGroup.iso639_3 && composed" type="button" class="gm-tablink"

View file

@ -0,0 +1,103 @@
#!/usr/bin/env node
/**
* fetch-pray-index.mjs — the prayer link for a people group, from doxa.life's own list.
*
* WHY. This map's toast says "Tap a coloured dot — someone still needs prayer", and until
* now tapping one offered no way to pray. Sibling plugins in this repo already link to
* pray.doxa.life per group (see library/components/PeopleGroupDetail.vue), so the house
* pattern and the URL shape already exist:
* https://pray.doxa.life/<slug>?source=doxalife&utm_source=agwm
*
* ═══ THIS IS THE ONE JOIN THAT IS NOT BY LANGUAGE ═══════════════════════════════════════
* Every other source here keys on ISO 639-3, because a translation serves everyone who
* speaks the language. Prayer does not work that way: you pray for a PEOPLE, in a place.
* So this keys on (people-group name, country) — and that is a NAME join, which this map
* refuses everywhere else because language names collide.
*
* It is allowed here only because the key was tested first, not assumed:
* - on the API side (name, country) is PERFECTLY unique — 2,107 entries, 2,107 distinct
* keys, 0 collisions;
* - on the pin side 12,379 pins give 12,369 distinct keys, so 10 pins are ambiguous.
* Those 10 are SKIPPED rather than guessed. An ambiguous key would attach one group's
* prayer page to another group with the same name in the same country, which is exactly
* the failure the no-name-matching rule exists to prevent.
*
* ═══ A 200 FROM pray.doxa.life PROVES NOTHING ═══════════════════════════════════════════
* It is the fourth host in this bundle that answers 200 with identical bytes for any URL:
* /abai-sungai -> 200, 3,903 bytes
* /zzz-not-a-real-group -> 200, 3,903 bytes
* Rendered it does discriminate — a real slug titles the page "Abai Sungai - App" with
* 1,863 characters of content, a bogus one titles it "Doxa.Life" with zero — but the
* existence proof used here is the API listing itself: a slug exists exactly when
* doxa.life publishes it. That is the publisher's own statement, and it costs one request
* rather than 1,725 renders.
*/
import { writeSnapshot } from './_snapshot-guard.mjs'
import { readFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = resolve(HERE, '../../..')
const OUT = resolve(REPO, 'data/tables/pray-index.json')
const API = 'https://pray.doxa.life/api/people-groups/list'
const SITE = 'https://pray.doxa.life'
const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
const key = (name, iso3) => `${String(name || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()}|${String(iso3 || '').toUpperCase()}`
const res = await fetch(API, { headers: { 'user-agent': UA }, signal: AbortSignal.timeout(90000) })
if (!res.ok) throw new Error(`pray list: HTTP ${res.status}`)
const posts = (await res.json()).posts || []
if (posts.length < 500) {
console.error(`PRAY: ABORT — the list returned ${posts.length} entries; refusing to run on a truncated payload.`)
process.exit(1)
}
// The API key must be unique, or the join is not a join. Checked, never assumed.
const seen = new Map()
let apiDup = 0
for (const p of posts) {
const k = key(p.name, p.country_code && p.country_code.value)
if (seen.has(k)) { apiDup++; continue }
seen.set(k, p)
}
if (apiDup) console.warn(`PRAY: ${apiDup} duplicate (name, country) key(s) on the API side — those entries are skipped.`)
// Pin-side ambiguity: any key held by more than one pin is dropped entirely.
const pins = JSON.parse(readFileSync(resolve(REPO, 'data/tables/people-group-pins.json'), 'utf8'))
const pinCount = {}
for (const [name, , , iso3] of pins.pins) {
const k = key(name, iso3)
pinCount[k] = (pinCount[k] || 0) + 1
}
const byKey = {}
let matched = 0, ambiguous = 0, unmatched = 0
for (const [k, p] of seen) {
if (!pinCount[k]) { unmatched++; continue }
if (pinCount[k] > 1) { ambiguous++; continue }
byKey[k] = {
u: `${SITE}/${encodeURIComponent(p.slug)}?source=doxalife&utm_source=agwm`,
n: p.name,
praying: Number(p.people_praying) || 0,
committed: Number(p.people_committed) || 0,
}
matched++
}
writeSnapshot(
OUT,
`${JSON.stringify({
_note: 'Prayer pages from doxa.life, keyed by (people-group name, country) — the ONE join here that is not by language, because you pray for a people in a place, not for a language. Ambiguous keys are dropped, never guessed. pray.doxa.life answers 200 with identical bytes for any slug, so existence comes from this API listing and never from the page.',
source: API,
site: SITE,
generated: new Date().toISOString().slice(0, 10),
counts: { api: posts.length, apiDuplicateKeys: apiDup, matched, ambiguousPinKeysDropped: ambiguous, noPinForEntry: unmatched },
byKey,
}, null, 1)}\n`,
(j) => Object.keys(j.byKey || {}).length,
'prayer links',
)
console.log(`PRAY: ${posts.length} entries -> ${matched} joined to a unique pin`)
console.log(` skipped: ${ambiguous} ambiguous pin key(s), ${unmatched} entr(ies) with no pin here`)

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl" 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

@ -26,7 +26,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl" 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

@ -591,7 +591,7 @@
"bundle": "gospel-resources",
"scriptUrl": "./gospel-resources/gospel-resources.js",
"output": "doxa-maps/gospel-resources/gospel-resources.js",
"integrity": "sha384-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG",
"integrity": "sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl",
"elements": [
"gospel-resources-map"
],
@ -611,7 +611,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG\" 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-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl\" 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>"
}
},
{
@ -625,7 +625,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG\" 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-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl\" 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>"
}
},
{
@ -639,7 +639,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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG\" 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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl\" 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>"
}
},
{
@ -653,7 +653,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-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG\" 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-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl\" 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>"
}
},
{
@ -667,7 +667,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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-CmN5mxV23d/9EjjiFYmjAp3wNs9izSW8X0nwgUPBk19XghuuGD0LnmsmmehdqVdG\" 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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-3KTr1qhgnkpG3VcebvpMExKEBfsMuHLimRhzbv8FBWxHoV+nyVe9vUqI6pcV4gZl\" 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>"
}
}
]