Frame the region map the way their constructor does, not the way fitBounds does

The Africa crops showed theirs tighter than ours, and 107 identical CSS
properties could never have said why. Everything I could think to compare
matched: mapbox-gl 3.24.0 on both sides (their bun.lock, our CDN pin), the
style, the 1200x780 container, the padding of 40, the maxZoom of 6, the box
itself (their viewFor() pin extent is byte-identical to our rollup), and
NEITHER page sets a projection.

The asymmetry was when the fit runs. They frame the box in the constructor
(CountryMap.vue:179); Mapbox v3 resolves a constructor `bounds` while
getProjection() still reports 'mercator', giving zoom 3.210. We had recreated
that as a post-style-load fitBounds, resolved with the globe already active,
giving 2.777 -- a sphere shows more planet at the same zoom, so it pulls back.
0.433 zoom is 1.35x linear scale, which is exactly the tightness in the crops.
Copy don't recreate: I recreated their fit instead of copying their construction.

useMapInstance gains a bounds/fitBoundsOptions pass-through, inert unless a
caller passes it, for the deep-link case -- the only case Mapbox can honour,
since the dashboard mounts its one map before the fetch resolves. The normal
case goes through fitTheirWay(), which asks the engine the same question their
constructor asks: setProjection('mercator'), cameraForBounds, restore the globe
in a `finally` so a throw cannot leave the map flat, then easeTo. Asking the
engine beats reimplementing a Web Mercator fit that would drift from Mapbox's
own padding and worldSize semantics at the next upgrade.

Proof, three independent oracles: the camera moved 2.777 -> 3.210 with the
projection still 'globe' afterwards; the like-for-like tile oracle now reads
ours z3 / 8 tiles / [[-45,-66.51],[90,40.98]] against theirs z3 / 8 tiles /
[[-45,-66.51],[90,40.98]], the same tiles at 1.00x; and the regenerated crops,
looked at, put the same labels and pins on the same pixels.

Two oracles of mine were wrong and are fixed here too. The tile-extent probe
compared a z2-quantized extent against a z3 one and reported a "1.33x framing
gap" twice -- 180/135 is four tiles at z2 against three at z3 of the same box,
and our camera's own visible box was 135.44 deg against their 135 deg of tiles.
It now refuses to compare extents across levels and reports the level instead,
which is the real signal. The framing probe asserted our zoom equalled the
GLOBE's cameraForBounds answer, so it would have failed the correct clone; it
now compares against the mercator-resolved camera and checks that asking the
mercator question left the map on the globe.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 22:31:20 +00:00
parent 77989089e0
commit 770d29fd25
12 changed files with 257 additions and 187 deletions

View file

@ -266,6 +266,21 @@ export function useMapInstance({
provider = undefined,
center = [20, 10],
zoom = 2,
// ★ bounds / fitBoundsOptions: frame a BOX at construction instead of a centre+zoom.
// Mapbox honours these only in the constructor, and that is the whole point of exposing them
// rather than letting a caller do `fitBounds` after style load. MEASURED 2026-09-24 on one page,
// one box, one engine (v3.24.0), via _probe-fit-at-construction-vs-after-load.mjs:
// constructor bounds -> zoom 3.210 (getProjection() reports 'mercator' at t=0)
// fitBounds post-load -> zoom 2.777 (getProjection() reports 'globe')
// Same box, same padding, same maxZoom, same 1200x780 frame: 0.433 zoom apart, which is 1.35x
// linear scale. Mapbox v3 resolves a constructor `bounds` while the camera is still mercator and
// applies the style's globe afterwards, so the camera keeps the tighter mercator fit. A post-load
// fitBounds is resolved with the globe already active and pulls BACK, because a sphere shows more
// planet at a given zoom. Neither is a bug of ours; they are two different framings, and a clone
// has to take the one the reference page takes. Ignored by Mapbox when absent, so this is inert
// for every existing caller.
bounds = undefined,
fitBoundsOptions = undefined,
// minZoom: a profile may PIN a value (e.g. 0.5). It is treated as a LOWER
// bound only — the composable raises it to the aspect-aware floor whenever the
// viewport is wide enough that the pinned value would reveal a duplicate world.
@ -429,6 +444,8 @@ export function useMapInstance({
bearing,
dragRotate: false,
renderWorldCopies,
// bounds OVERRIDE center/zoom in Mapbox's own option handling, so both can be passed.
...(bounds ? { bounds, ...(fitBoundsOptions ? { fitBoundsOptions } : {}) } : {}),
...(maxBounds ? { maxBounds } : {}),
attributionControl: true, // Mapbox's own attribution on every map
// Phones: logo and attribution both bottom-right, so the bottom-left corner stays free
@ -462,6 +479,8 @@ export function useMapInstance({
// The single-finger page-scroll escape is now the gray side-edge affordance
// wired below (showEdgeBorders): the .map-scroll-edge gutters flash gray while
// touching the map and are scrollable to move the page past the map.
// bounds OVERRIDE center/zoom in Mapbox's own option handling, so both can be passed.
...(bounds ? { bounds, ...(fitBoundsOptions ? { fitBoundsOptions } : {}) } : {}),
...(maxBounds ? { maxBounds } : {}), // only if a profile explicitly supplied bounds
attributionControl: true, // Mapbox's own attribution on every map
// Phones: logo and attribution both bottom-right, so the bottom-left corner stays free

View file

@ -118,6 +118,9 @@ const geojson = computed(() => {
}
})
// Their fitBoundsOptions, one constant so the constructor path and fitTheirWay cannot drift apart.
const FIT_OPTS = { padding: 40, maxZoom: 6 } // CountryMap.vue:179, verbatim
const { map, isMapReady, initializeMap, destroy } = useMapInstance({
containerRef: mapContainer,
// A STRING, not the ref -- useMapInstance calls accessToken.startsWith('pk.') directly, so a
@ -130,6 +133,12 @@ const { map, isMapReady, initializeMap, destroy } = useMapInstance({
center: props.center,
zoom: props.zoom,
maxZoom: 12,
// THEIR CONSTRUCTOR FORM (CountryMap.vue:179), for the case where the box is already known at
// mount. Mapbox honours `bounds` only in the constructor, and the dashboard usually mounts its
// one map BEFORE the fetch resolves, so this covers the deep-link case and fitTheirWay() below
// covers the normal one. Both land the same camera on purpose.
bounds: props.bounds || undefined,
fitBoundsOptions: FIT_OPTS,
// THE SHARED EDGE-SCROLL STRIPS, on by Driver decision (2026-09-24, via shepherd card
// BUILD-REGIONS-MAP-RESTORE-EDGE-SCROLL-STRIPS). This was deliberately false, and the reason is
// kept here rather than deleted because it is the thing to re-examine if the result looks wrong:
@ -381,6 +390,48 @@ function chooserZoom(m) {
return Math.max(0, Math.min(2, Math.log2(w / 512)))
}
/**
* Frame `bounds` the way THEIR CONSTRUCTOR frames it, not the way a post-load fitBounds does.
*
* ★ MEASURED 2026-09-24, one page, one box, one engine (mapbox-gl 3.24.0 on both sides), by
* _qa-cycle/02-builder-cycle/_probe-fit-at-construction-vs-after-load.mjs:
*
* new mapboxgl.Map({ bounds, fitBoundsOptions }) -> zoom 3.210, getProjection() 'mercator' at t=0
* map.fitBounds(bounds, sameOptions) -> zoom 2.777, getProjection() 'globe'
*
* Same box [[-16.683,-33.976],[57.497,16.769]], same padding 40, same maxZoom 6, same 1200x780
* frame. 0.433 zoom apart == 1.35x linear scale, which IS the difference the Africa crops showed
* and the reason their frame cuts the Sahel while ours had room for Madagascar and Sri Lanka.
*
* The cause is not the box and not the options: Mapbox v3 resolves a constructor `bounds` while
* the camera is still mercator and applies the style's globe afterwards, so that camera keeps the
* tighter mercator fit forever. A post-load fitBounds is resolved with the globe already active,
* and a sphere shows more planet at the same zoom, so it pulls back.
*
* So the fix is to ask the engine the SAME question their constructor asks -- compute the camera
* with the projection set to mercator, then put the globe back and jump there. Asking the engine
* rather than reimplementing a Web Mercator fit keeps Mapbox's own padding and worldSize
* semantics, which a hand-rolled version would drift from at the next upgrade.
*
* Both setProjection calls land in ONE synchronous tick, so no frame renders in mercator; the
* probe asserts the projection is still 'globe' afterwards rather than trusting that.
*/
function fitTheirWay(m, bounds) {
let cam = null
const proj = (m.getProjection && m.getProjection().name) || null
try {
if (proj && proj !== 'mercator') m.setProjection('mercator')
cam = m.cameraForBounds(bounds, FIT_OPTS)
} catch (e) {
console.warn('[RegionCountryMap] mercator fit unavailable, falling back to fitBounds:', e && e.message)
} finally {
// Restore in `finally` so a throw inside cameraForBounds can never leave the map flat.
try { if (proj && proj !== 'mercator') m.setProjection(proj) } catch (_) {}
}
if (cam && cam.center && typeof cam.zoom === 'number') m.easeTo({ center: cam.center, zoom: cam.zoom, duration: 600 })
else m.fitBounds(bounds, { ...FIT_OPTS, duration: 600 })
}
function applySubject() {
const m = map.value
// Deliberately NOT gated on isMapReady: that flag tracks useMapInstance's own view of the
@ -415,7 +466,7 @@ function applySubject() {
// Expose the box we asked to fit, so a probe can ask the engine what IT would have chosen for
// the same box and tell 'the fit is wrong' apart from 'something overrode the fit'.
if (props.bounds && typeof window !== 'undefined') window.__rcmBounds = props.bounds
if (props.bounds) m.fitBounds(props.bounds, { padding: 40, maxZoom: 6, duration: 600 })
if (props.bounds) fitTheirWay(m, props.bounds)
else m.flyTo({ center: props.center, zoom: chooserZoom(m), duration: 600 })
}

View file

@ -29,7 +29,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-8fQR1xHoPidgeI7VU/Z82bxA303lsM/UZXfsgRu8MZG/Z5ASzgiel/am7dHiO2kH" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-+miFg4jJ9ffz3j/bWOVAozmvoj7APOdJxIl0WmOWrSMDbutivwv8gRBy43qL6Q5b" 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 <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-8fQR1xHoPidgeI7VU/Z82bxA303lsM/UZXfsgRu8MZG/Z5ASzgiel/am7dHiO2kH" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-+miFg4jJ9ffz3j/bWOVAozmvoj7APOdJxIl0WmOWrSMDbutivwv8gRBy43qL6Q5b" 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 <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-8fQR1xHoPidgeI7VU/Z82bxA303lsM/UZXfsgRu8MZG/Z5ASzgiel/am7dHiO2kH" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-+miFg4jJ9ffz3j/bWOVAozmvoj7APOdJxIl0WmOWrSMDbutivwv8gRBy43qL6Q5b" 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

@ -158,7 +158,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "doxa-maps/country-posters/country-posters.js",
"integrity": "sha384-8fQR1xHoPidgeI7VU/Z82bxA303lsM/UZXfsgRu8MZG/Z5ASzgiel/am7dHiO2kH",
"integrity": "sha384-+miFg4jJ9ffz3j/bWOVAozmvoj7APOdJxIl0WmOWrSMDbutivwv8gRBy43qL6Q5b",
"elements": [
"country-posters-map"
],
@ -178,7 +178,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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-8fQR1xHoPidgeI7VU/Z82bxA303lsM/UZXfsgRu8MZG/Z5ASzgiel/am7dHiO2kH\" 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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-+miFg4jJ9ffz3j/bWOVAozmvoj7APOdJxIl0WmOWrSMDbutivwv8gRBy43qL6Q5b\" 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>"
}
},
{
@ -192,7 +192,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-8fQR1xHoPidgeI7VU/Z82bxA303lsM/UZXfsgRu8MZG/Z5ASzgiel/am7dHiO2kH\" 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-+miFg4jJ9ffz3j/bWOVAozmvoj7APOdJxIl0WmOWrSMDbutivwv8gRBy43qL6Q5b\" 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>"
}
},
{
@ -206,7 +206,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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-8fQR1xHoPidgeI7VU/Z82bxA303lsM/UZXfsgRu8MZG/Z5ASzgiel/am7dHiO2kH\" 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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-+miFg4jJ9ffz3j/bWOVAozmvoj7APOdJxIl0WmOWrSMDbutivwv8gRBy43qL6Q5b\" 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>"
}
}
]
@ -879,7 +879,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-Jvx1V3YrtL3L0p6eqByA/1hw4IWz6NpLp7Uza6fbl3HZCVa7JiAR5WMFvYsN6tPP",
"integrity": "sha384-DIkz3aTX+7K5h9hu8OdzGVTQk6ZLOI0CmHPUWsB/teo5kYdFufD/XQnW5E/5z9wy",
"elements": [
"regions-dashboard"
],
@ -899,7 +899,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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Jvx1V3YrtL3L0p6eqByA/1hw4IWz6NpLp7Uza6fbl3HZCVa7JiAR5WMFvYsN6tPP\" 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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-DIkz3aTX+7K5h9hu8OdzGVTQk6ZLOI0CmHPUWsB/teo5kYdFufD/XQnW5E/5z9wy\" 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>"
}
},
{
@ -913,7 +913,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-Jvx1V3YrtL3L0p6eqByA/1hw4IWz6NpLp7Uza6fbl3HZCVa7JiAR5WMFvYsN6tPP\" 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-DIkz3aTX+7K5h9hu8OdzGVTQk6ZLOI0CmHPUWsB/teo5kYdFufD/XQnW5E/5z9wy\" 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>"
}
},
{
@ -927,7 +927,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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Jvx1V3YrtL3L0p6eqByA/1hw4IWz6NpLp7Uza6fbl3HZCVa7JiAR5WMFvYsN6tPP\" 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": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-DIkz3aTX+7K5h9hu8OdzGVTQk6ZLOI0CmHPUWsB/teo5kYdFufD/XQnW5E/5z9wy\" 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>"
}
}
]

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-Jvx1V3YrtL3L0p6eqByA/1hw4IWz6NpLp7Uza6fbl3HZCVa7JiAR5WMFvYsN6tPP" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-DIkz3aTX+7K5h9hu8OdzGVTQk6ZLOI0CmHPUWsB/teo5kYdFufD/XQnW5E/5z9wy" 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

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-Jvx1V3YrtL3L0p6eqByA/1hw4IWz6NpLp7Uza6fbl3HZCVa7JiAR5WMFvYsN6tPP" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-DIkz3aTX+7K5h9hu8OdzGVTQk6ZLOI0CmHPUWsB/teo5kYdFufD/XQnW5E/5z9wy" 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 <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-Jvx1V3YrtL3L0p6eqByA/1hw4IWz6NpLp7Uza6fbl3HZCVa7JiAR5WMFvYsN6tPP" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-DIkz3aTX+7K5h9hu8OdzGVTQk6ZLOI0CmHPUWsB/teo5kYdFufD/XQnW5E/5z9wy" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: