Compare commits

...

25 commits

Author SHA1 Message Date
Doxa Maps
bcbaa816ae Merge remote-tracking branch 'origin/development' into regions-plugin/view-address-p2 2026-10-03 07:48:30 +00:00
Doxa Maps
04057b9177 Merge remote-tracking branch 'origin/development' into regions-plugin/view-address-p2 2026-10-03 07:38:38 +00:00
Doxa Maps
3be82cb23f Shareable pages P2: rebuild the reached bundles from the merged source, then the tree and SRI 2026-10-03 07:34:06 +00:00
Doxa Maps
ef49411405 Merge remote-tracking branch 'origin/development' into regions-plugin/view-address-p2 2026-10-03 07:23:34 +00:00
Doxa Maps
0b066ba1ae Merge the Research Gallery's dev manifest fallback (shareable pages P2 assembly) 2026-10-03 07:19:40 +00:00
Doxa Maps
edbd280d4d Research Gallery dev fallback: the manifest at the public root first, so the dev server serves children as the raw build
Reached as /public/<tree>/, the dev server treats a built bundle as source and
injects an import into its IIFE, so a child loaded as a classic script threw
"Cannot use import statement outside a module" on the staging page. The root
path is tried first; /public/<tree>/ stays last, for a plain static server.
Built trees resolve the manifest before these fallbacks and are unaffected.
2026-10-03 07:19:22 +00:00
Doxa Maps
49b0fff62b Merge Posterverse's dev manifest fallback (shareable pages P2 assembly) 2026-10-03 07:18:40 +00:00
Doxa Maps
09d884ce3a Posterverse: on the dev server, read the built tree from its root, not /public/
On a staging page the manifest fallback was /public/<tree>/manifest.json, which
made /public/<tree>/ the base for every child script. The dev server treats a
/public/… request as source and injects an import into the built IIFE, so the
child failed as a classic script ("Cannot use import statement outside a
module"). The fallback is now /<tree>/manifest.json, where the dev server serves
the built tree unchanged. Built trees are unaffected (they resolve the manifest
before this fallback).
2026-10-03 07:18:00 +00:00
Doxa Maps
dacb9a0762 useViewAddress: unchanged means the same view-key VALUES, not the same string
Measured in a browser on a fresh load: ?owner=x&pane=map came back as ?pane=map&owner=x through one replaceState.
viewSearch puts the view keys first, and writeViewKeys compared strings. The link meant the same thing and no Back
step was added, but it was a needless write, and on a host it would be a needless router.replace. writeViewKeys now
compares the parsed tab/pane/place. check:view-address B covers it, with a poison (44); the real-file revert fails.
2026-10-03 06:45:39 +00:00
Doxa Maps
6f94ff0e30 pane-wrapper: a pending link is also retried by a bounded rAF with an observer present, and by every settled scroll
From the 5B: an observer fires only when the COLUMN resizes. A pane whose height arrives later (styles adopted late
in a shadow root) would have left the link pending, and a pending link blocks the settle's write. check:view-address
covers it: a fresh load whose observer never fires again still opens the link (43 poisons).
2026-10-03 06:42:17 +00:00
Doxa Maps
2b62cc0120 Merge Country Posters' pane address (shareable pages P2) 2026-10-03 06:39:09 +00:00
Doxa Maps
7ad84e5610 pane-wrapper: a fresh load with ?pane= opens that pane once the column has its layout
Measured in a browser on Country Posters and on Regions: a fresh load with ?pane=<second pane> stayed on the first
pane, with the column's scrollTop 0 at 6 s. The column is built in the composite's onMounted, before it has a
layout, so the pane's offsetTop was 0 when the link was shown. And a ResizeObserver's first callback, which comes
after layout, only recorded the height.

Now the link waits until the column has a height and the pane a top, then shows it once. The wait is the observer's
callback, or a bounded rAF without one. A scroll that settles while the link waits does not overwrite it. The
composite's view readout follows from the scroll event it already listens to.

check:view-address F models a column with no layout at build, whose observer first fires after layout. There are 2
new poisons (42 in all), and restoring the old immediate show in the real file fails the gate.
2026-10-03 06:39:09 +00:00
Doxa Maps
4428156bfe Country Posters' panes are in the page address: on its own leaf the open pane is ?pane= (Map; the first pane is the default), and nested it announces to the shell, the same opt-in as Regions and UPG100 2026-10-03 06:36:09 +00:00
Doxa Maps
54fdead5e1 check:view-address: every composite that opts into the address also owns its leaf (a census from the tree, 40 poisons) 2026-10-03 06:32:22 +00:00
Doxa Maps
19bf214650 Merge UPG100's pane address (shareable pages P2) 2026-10-03 06:31:53 +00:00
Doxa Maps
08d236e0df check:view-address: each shell's ownAddress wiring (G), with a poison per shell (38 poisons) 2026-10-03 06:31:40 +00:00
Doxa Maps
ce15aba511 UPG100: the composite's pane in view is part of the page address (?pane=) 2026-10-03 06:31:14 +00:00
Doxa Maps
68842185ea Merge Plugin Studio's ownAddress (shareable pages P2) 2026-10-03 06:30:42 +00:00
Doxa Maps
00577668d6 Merge the Research Gallery shell's ownAddress (shareable pages P2) 2026-10-03 06:30:32 +00:00
Doxa Maps
86a601b3b2 Merge the Posterverse shell's ownAddress (shareable pages P2) 2026-10-03 06:30:18 +00:00
Doxa Maps
e4c31b6583 Research Gallery: a nested composite's pane and place reach the page address (shareable pages P2)
ownAddress on the shell's element, while the page owns its address: a nested
composite's open pane or selected place is written by this shell, the page's
one writer, and a nested element can never switch the shell's tab. Released
in beforeUnmount.
2026-10-03 06:30:10 +00:00
Doxa Maps
5a3194bd08 Plugin Studio: a nested composite's pane and place reach the address
Studio owns the page address and writes ?tab=. A composite nested in a
tab announces its own choice (its open pane, its selected place); Studio
now records that beside ?tab=, while it owns the address, so a reload
or a sent link opens the same pane and place. Only pane and place: the
tab stays Studio's own. The listener is released on unmount, before the
hot-update return.
2026-10-03 06:30:07 +00:00
Doxa Maps
8c05f6207a Posterverse: a nested plugin's view inside a tab reaches the address
The shell owns the address for the views inside its tabs too: a nested
composite's ?pane= / ?place= choice is recorded through the shared helper, only
while this page owns its address, never as a tab switch, and the event stops at
the shell.
2026-10-03 06:29:51 +00:00
Doxa Maps
c963a2a9fa P2: studio's 5B notes. A nested element cannot switch the owner's tab, a kept-alive pane or place is claimed back when its tab is shown again, and scrollTo takes null
- ownAddress records only pane and place. tab is the owner's own key, so a nested announcement cannot switch it.
- A shell keeps Regions (and UPG100) alive when another tab is open. Switching back dropped ?pane= and ?place=
  (the tab change), while the screen still showed them. pane-wrapper and region-dashboards now watch for being
  shown again (a ResizeObserver: no height, then height) and claim their pane / place back with a replace. An
  address that names one wins, because Back to an entry of this tab is applied while the column has no layout.
- scrollTo(id, null) behaves as scrollTo(id).

check:view-address covers each of these (34 poisons, each caught). The slow pane-wrapper arm runs only for
pane-wrapper poisons, so the self-test takes 16 s.
2026-10-03 06:26:53 +00:00
Doxa Maps
003af1caa8 Shareable pages P2: a composite's open pane and Regions' subject are in the page address
The address gets its second and third keys:
- ?pane= is the open view of a composite;
- ?place= is Regions' subject.
Both keep the ONE-writer rule:
- On its own leaf the composite owns the address and writes.
- Nested in a shell it announces, and the shell writes.
- On a partner's page nothing is written; a link is still read.

useViewAddress:
- claimView() is the one call for a reader's choice: the owner writes it, any other element announces it.
- ownAddress() lets an owner record its nested elements' announcements, and stops them there.
- An announcement carries only the view keys, one event per action.
- pane and place are siblings: a tab change drops both, but a pane change keeps the place, because Regions keeps
  its region while the reader is on the list.

pane-wrapper (buildPaneColumn) takes an opt-in `address`:
- a link opens its pane without writing;
- a click is a push, and a selection made by the same click rides in the same push (one Back step);
- where the reader's scroll settles is a replace;
- Back/Forward scroll and never write;
- the first pane is the default and is never written.

Regions:
- regions-mfe opts its column in and records its panes' announcements on its own leaf. A list click opens the
  dashboard with its place in one write.
- region-dashboards claims ?place= instead of writing its #slug with its own pushState (replace at first paint,
  push on a choice, re-applied on Back). An old #slug link still opens, and on its own leaf it is rewritten to
  ?place=.
- urlSync:false now guards only the tab title.

check:view-address adds:
- E: nested claims and the owner;
- F: pane-wrapper on a simulated column;
- G: the Regions wiring.
The census drops region-dashboards (5 writers now). Self-test: 29 poisons, each caught. Real-source mutations are
caught too.
2026-10-03 06:09:51 +00:00
40 changed files with 1135 additions and 747 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env node
// check:view-address — the page's address (library/composables/useViewAddress.js) and its ONE history write.
// A. the pure helpers: the id whitelist + 64 cap; an unknown or malformed value reads as absent (the default opens);
// the level rule (a tab change drops pane and place, a pane change drops place, unless the change sets them);
// the level rule (a tab change drops pane and place unless the change sets them; pane and place are siblings);
// foreign params keep their exact text.
// B. the writes, on a simulated browser history: an 'app' owner pushes once and keeps history.state with `current`
// and the hash; an unchanged address writes nothing; a partner page (no owner) writes nothing; a 'host' owner
@ -13,9 +13,22 @@
// writes nothing, so Forward still exists.
// D. THE CENSUS of history writers, enumerated from the tree: every history.pushState/replaceState call in library/
// and plugin-designs/ is the helper's or a listed writer. A new one fails until it is listed here with its reason.
// E. ONE WRITER, NESTED: claimView writes for an owner and announces for anyone else (one event per action, view keys
// only); ownAddress records an announcement only while it owns (and stops it there), so a pane and its selection
// clicked together are ONE Back step.
// F. pane-wrapper's opt-in address, on a simulated column: a link opens its pane without writing, also on a FRESH
// load where the column is built before its layout (it waits for a height, measured in a browser); a click is a push
// (the first pane is the default and is never written); a click's selection rides in the same push; where the
// reader's scroll settles is a replace; Back/Forward scroll and never write; nested it announces; without the
// opt-in it never writes; a kept-alive column shown again claims its pane back (a replace), unless the address
// names one.
// G. THE WIRING in Regions: regions-mfe opts its column in, owns its leaf, and carries the place with its pane;
// region-dashboards claims ?place= (replace at first paint, push on a choice) and re-applies it on Back. And
// each shell (Plugin Studio, the Research Gallery, Posterverse) records its nested composites' choices
// (ownAddress) and releases the listener before any hot-update return.
// --self-test: each poison below must FAIL its arm. Pure node: no Vite, no build, no browser.
import { execFileSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { readFileSync, writeFileSync, unlinkSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
@ -27,7 +40,6 @@ const KNOWN = {
'library/layouts/app-wrapper-material/index.html': [1, "the layout page's own #/<map> hash; a fragment-only URL keeps the query"],
'plugin-designs/gospel-internet/src/useBoard.js': [1, 'its own keys on its own leaves (writeUrl); keeps state, foreign params and the hash'],
'plugin-designs/gospel-resources/data-tables.vue': [1, "clearFilter: replaceState({}, '', pathname) wipes the whole query and hash; reported to its owner"],
'plugin-designs/regions-dashboard/region-dashboards.vue': [2, '#<slug> on its own leaf (URL_SYNC); P2 moves it to `place`'],
}
// SOURCE files only: gates and node scripts may simulate history. A write is `.pushState(` / `.replaceState(` (an aliased
// history included) or a computed `history[… 'pushState' …]`.
@ -76,7 +88,24 @@ function browser(href, state = null) {
}
const unset = () => { for (const k of ['location', 'history', 'addEventListener', 'removeEventListener']) delete globalThis[k] }
async function arms({ va, sv, counts }) {
// A column of panes for pane-wrapper, simulated: each pane is 100 px tall, scrolling dispatches 'scroll'.
class FakeEl extends EventTarget {
constructor() { super(); this.className = ''; this.dataset = {}; this.children = []; this.parent = null; this.scrollTop = 0; this.clientHeight = FakeEl.h0 ?? 100; this.innerHTML = '' }
appendChild(c) { c.parent = this; this.children.push(c); return c }
remove() { if (this.parent) this.parent.children = this.parent.children.filter((x) => x !== this) }
// A pane's top is its index times the column's height: 0 while the column has no layout.
get offsetTop() { return this.parent ? this.parent.children.filter((c) => c.className === 'pane').indexOf(this) * this.parent.clientHeight : 0 }
querySelector(sel) {
const id = /data-pane-id="([^"]+)"/.exec(sel)?.[1]
const walk = (n) => { for (const c of n.children) { if (c.className === 'pane' && c.dataset.paneId === id) return c; const r = walk(c); if (r) return r } return null }
return walk(this)
}
scrollTo({ top }) { this.scrollTop = top; this.dispatchEvent(new Event('scroll')) }
}
const SPEC = { paneHint: false, panes: [{ id: 'list', label: 'List', at: [0, 0] }, { id: 'dashboard', label: 'Dashboard', at: [1, 0] }] }
const settle = () => new Promise((r) => setTimeout(r, 125)) // just past the 120 ms debounce; an earlier-due timer always fires first
async function arms({ va, sv, counts, pw, src }, { skipF = false } = {}) {
const fails = []
const eq = (tag, got, want) => { if (JSON.stringify(got) !== JSON.stringify(want)) fails.push(`${tag}: got ${JSON.stringify(got)}, want ${JSON.stringify(want)}`) }
try {
@ -88,7 +117,7 @@ async function arms({ va, sv, counts }) {
}
eq('A: an unknown or malformed value reads as absent', va.readViewKeys('?tab=a%20b&pane=%E0%A4%A&place=angola&tab=second'), { tab: '', pane: '', place: 'angola' })
eq('A: a tab change drops pane and place', va.viewSearch('?tab=a&pane=b&place=c&lang=ar', { tab: 'd' }), '?tab=d&lang=ar')
eq('A: a pane change drops place, keeps tab', va.viewSearch('?tab=a&pane=b&place=c', { pane: 'e' }), '?tab=a&pane=e')
eq('A: a pane change keeps tab and place (siblings)', va.viewSearch('?tab=a&pane=b&place=c', { pane: 'e' }), '?tab=a&pane=e&place=c')
eq('A: a place change keeps tab and pane', va.viewSearch('?tab=a&pane=b&place=c', { place: 'f' }), '?tab=a&pane=b&place=f')
eq('A: a change that sets the deeper level keeps it', va.viewSearch('?tab=a&pane=b&place=c', { tab: 'd', pane: 'p' }), '?tab=d&pane=p')
eq('A: the same tab changes nothing', va.viewSearch('?tab=a&pane=b&place=c', { tab: 'a' }), '?tab=a&pane=b&place=c')
@ -108,6 +137,11 @@ async function arms({ va, sv, counts }) {
const r2 = va.writeViewKeys({ tab: 'regions' }, { owner: 'app', push: true })
if (r2 !== 'unchanged' || b.writes.push + b.writes.replace !== 1) fails.push(`B: an unchanged address wrote (${r2})`)
if (va.commitUrl({}) !== false || b.writes.push + b.writes.replace !== 1) fails.push('B: commitUrl wrote an identical URL')
// the same values in another order is unchanged too (a link written by someone else, a host's own params first)
va.commitUrl({ search: '?owner=x&pane=map&tab=regions' })
const n = b.writes.push + b.writes.replace
const r3 = va.writeViewKeys({ pane: 'map' }, { owner: 'app', push: false })
if (r3 !== 'unchanged' || b.writes.push + b.writes.replace !== n) fails.push(`B: the same keys in another order were rewritten (${r3}): ${b.at()}`)
}
{
const b = browser('https://partner.test/page?tab=theirs')
@ -135,9 +169,9 @@ async function arms({ va, sv, counts }) {
browser('https://x.test/p')
const el = new EventTarget(); const got = []
el.addEventListener(va.VIEW_EVENT, (e) => got.push(e))
va.announceView(el, 'pane', 'dashboard')
if (va.announceView(el, 'filters', 'x') !== false) fails.push('B: announceView accepts a key that is not tab/pane/place')
if (got.length !== 1 || !got[0].bubbles || !got[0].composed || got[0].detail?.key !== 'pane' || got[0].detail?.value !== 'dashboard') fails.push(`B: a nested element's ${va.VIEW_EVENT} is wrong or cannot leave its shadow root`)
va.announceView(el, { pane: 'dashboard' })
if (va.announceView(el, { filters: 'x' }) !== false) fails.push('B: announceView accepts a key that is not tab/pane/place')
if (got.length !== 1 || !got[0].bubbles || !got[0].composed || got[0].detail?.changes?.pane !== 'dashboard') fails.push(`B: a nested element's ${va.VIEW_EVENT} is wrong or cannot leave its shadow root`)
}
// ── C. coexistence with the filters, and Back/Forward ──
@ -184,7 +218,182 @@ async function arms({ va, sv, counts }) {
off()
if (b.listeners.size) fails.push('C: onAddressChange off() left its popstate listener')
}
} finally { unset() }
// ── E. one writer, nested ──
{
const b = browser('https://x.test/p?tab=regions')
const el = new EventTarget(); const got = []
el.addEventListener(va.VIEW_EVENT, (e) => got.push(e))
const r = va.claimView({ pane: 'dashboard', place: 'angola', filters: 'x' }, { owner: '', el, push: true })
if (b.writes.push + b.writes.replace) fails.push('E: a claim by an element that does not own the address WROTE history')
if (r !== 'announced' || got.length !== 1) fails.push(`E: a non-owner's claim is not ONE announcement (${r}, ${got.length})`)
else if (JSON.stringify(got[0].detail?.changes) !== JSON.stringify({ pane: 'dashboard', place: 'angola' }) || got[0].detail?.push !== true) fails.push(`E: an announcement carries more or less than the view keys: ${JSON.stringify(got[0].detail)}`)
const r2 = va.claimView({ pane: 'dashboard' }, { owner: 'app', el })
if (r2 !== 'written' || got.length !== 1 || b.writes.push !== 1) fails.push(`E: an owner's claim did not write it itself (${r2}, ${got.length} events)`)
}
{
const b = browser('https://x.test/shell?tab=regions&lang=ar')
const el = new EventTarget(); let owner = 'app'
const off = va.ownAddress(el, () => owner)
const fire = (changes, push = true) => { const e = new CustomEvent(va.VIEW_EVENT, { bubbles: true, composed: true, detail: { changes, push } }); el.dispatchEvent(e); return e }
const e1 = fire({ pane: 'dashboard', place: 'angola' })
if (b.writes.push !== 1 || b.at() !== '/shell?tab=regions&pane=dashboard&place=angola&lang=ar') fails.push(`E: an owner did not record a nested pane + place as ONE push: ${b.at()} (${b.writes.push} pushes)`)
if (!e1.cancelBubble) fails.push('E: a recorded announcement went on bubbling (an outer owner could write it twice)')
fire({ place: 'angola' })
if (b.writes.push !== 1) fails.push('E: the selection claimed again after its pane click was a second Back step')
owner = ''
const e2 = fire({ place: 'benin' })
if (b.writes.push !== 1 || b.writes.replace) fails.push('E: ownAddress wrote while the page has no owner')
if (e2.cancelBubble) fails.push('E: an announcement an element does not own was stopped (the real owner further out never hears it)')
owner = 'app'
const before = b.at()
fire({ tab: 'posters' })
if (b.at() !== before) fails.push(`E: a nested announcement switched the OWNER's tab: ${b.at()}`)
off(); fire({ place: 'benin' })
if (b.writes.push !== 1) fails.push('E: ownAddress off() left its listener')
b.hist.back()
if (b.at() !== '/shell?tab=regions&lang=ar') fails.push(`E: Back after a pane + place click did not return in one step: ${b.at()}`)
}
// ── F. pane-wrapper's address, on a simulated column (the slow arm: it waits out the scroll debounce) ──
globalThis.document = { createElement: () => new FakeEl() }
if (!skipF) {
const b = browser('https://x.test/regions-mfe?pane=dashboard&lang=ar')
const host = new FakeEl(); const got = []
host.addEventListener(va.VIEW_EVENT, (e) => got.push(e))
const c = pw.buildPaneColumn(SPEC, { host, address: { owner: () => 'app' } })
const col = host.children[0]
await settle()
if (col.scrollTop !== 100) fails.push(`F: a link to ?pane=dashboard did not open that pane (scrollTop ${col.scrollTop})`)
if (b.writes.push + b.writes.replace) fails.push('F: opening a link wrote the address')
c.scrollTo('list'); await settle()
if (b.writes.push !== 1 || b.at() !== '/regions-mfe?lang=ar') fails.push(`F: a click on the first pane is not ONE push that removes ?pane= (the default is never written): ${b.at()}`)
c.scrollTo('dashboard', { changes: { place: 'angola' } }); await settle()
if (b.writes.push !== 2 || b.at() !== '/regions-mfe?pane=dashboard&place=angola&lang=ar') fails.push(`F: a click's pane and its selection are not ONE push: ${b.at()} (${b.writes.push} pushes)`)
const w2 = b.writes.push + b.writes.replace
b.hist.back(); await settle()
if (col.scrollTop !== 0 || b.writes.push + b.writes.replace !== w2) fails.push(`F: Back did not show the address's pane, or it WROTE (scrollTop ${col.scrollTop}, ${b.at()})`)
b.hist.forward(); await settle()
if (col.scrollTop !== 100 || b.writes.push + b.writes.replace !== w2) fails.push(`F: Forward wrote, or showed the wrong pane (scrollTop ${col.scrollTop})`)
const w = b.writes.push + b.writes.replace
col.scrollTo({ top: 0 }); await settle()
if (b.writes.push + b.writes.replace !== w + 1 || b.writes.push !== 2 || b.at() !== '/regions-mfe?place=angola&lang=ar') fails.push(`F: where the reader's scroll settles is not ONE replace (place kept): ${b.at()}`)
c.destroy()
if (b.listeners.size) fails.push('F: destroy() left its popstate listener')
if (got.length) fails.push('F: an owner composite announced instead of writing')
}
if (!skipF) {
// A FRESH LOAD: the column is built before it has a layout (measured in a browser), and a ResizeObserver's
// FIRST callback comes after layout, already with the full height. The link must still open.
const ros = []
globalThis.ResizeObserver = class { constructor(cb) { this.cb = cb; ros.push(this) } observe() {} disconnect() { this.off = true } }
const b = browser('https://x.test/country-posters-mfe?pane=dashboard&lang=ar')
const host = new FakeEl()
FakeEl.h0 = 0
const c = pw.buildPaneColumn(SPEC, { host, address: { owner: () => 'app' } })
FakeEl.h0 = undefined
const col = host.children[0]
col.dispatchEvent(new Event('scroll')); await settle()
if (b.at() !== '/country-posters-mfe?pane=dashboard&lang=ar') fails.push(`F: before its layout, the column overwrote the link it has not opened yet: ${b.at()}`)
col.clientHeight = 100; ros.forEach((r) => !r.off && r.cb([])); await settle()
if (col.scrollTop !== 100) fails.push(`F: a FRESH LOAD with ?pane=dashboard did not open that pane once the column had its layout (scrollTop ${col.scrollTop})`)
if (b.writes.push + b.writes.replace) fails.push('F: opening a link on a fresh load wrote the address')
c.destroy()
delete globalThis.ResizeObserver
}
if (!skipF) {
// The same fresh load, where the observer NEVER fires again after layout (a pane's height arrived after the
// column's last resize): the bounded rAF loop must still open the link.
const ros = []
globalThis.ResizeObserver = class { constructor(cb) { this.cb = cb; ros.push(this) } observe() {} disconnect() { this.off = true } }
globalThis.requestAnimationFrame = (cb) => setTimeout(cb, 5)
const b = browser('https://x.test/upg-100-mfe?pane=dashboard')
const host = new FakeEl()
FakeEl.h0 = 0
const c = pw.buildPaneColumn(SPEC, { host, address: { owner: () => 'app' } })
FakeEl.h0 = undefined
const col = host.children[0]
col.clientHeight = 100; await settle()
if (col.scrollTop !== 100) fails.push(`F: with an observer that never fires again, the bounded rAF did not open the link (scrollTop ${col.scrollTop})`)
if (b.writes.push + b.writes.replace) fails.push('F: the rAF-opened link wrote the address')
c.destroy()
delete globalThis.ResizeObserver; delete globalThis.requestAnimationFrame
}
if (!skipF) {
// A KEPT-ALIVE column whose tab is hidden and shown again (ResizeObserver: no height, then height).
const ros = []
globalThis.ResizeObserver = class { constructor(cb) { this.cb = cb; ros.push(this) } observe() { this.cb([]) } disconnect() { this.off = true } }
const b = browser('https://x.test/shell?tab=regions')
const host = new FakeEl()
const c = pw.buildPaneColumn(SPEC, { host, address: { owner: () => 'app' } })
const col = host.children[0]
const resize = async (hgt) => { col.clientHeight = hgt; ros.forEach((r) => !r.off && r.cb([])); await settle() }
c.scrollTo('dashboard', null); await settle()
if (b.at() !== '/shell?tab=regions&pane=dashboard') fails.push(`F: scrollTo(id, null) did not behave as scrollTo(id): ${b.at()}`)
await resize(0)
va.writeViewKeys({ tab: 'posters' }, { owner: 'app' }); va.writeViewKeys({ tab: 'regions' }, { owner: 'app' })
const w = b.writes.push + b.writes.replace
await resize(100)
if (b.at() !== '/shell?tab=regions&pane=dashboard' || b.writes.push + b.writes.replace !== w + 1 || b.writes.replace < 1) fails.push(`F: a kept-alive column shown again did not claim its pane back with ONE replace: ${b.at()}`)
await resize(0)
col.scrollTop = 0
va.commitUrl({ search: '?tab=regions&pane=dashboard' })
const w2 = b.writes.push + b.writes.replace
await resize(100)
if (col.scrollTop !== 100 || b.writes.push + b.writes.replace !== w2) fails.push(`F: shown again with an address that names a pane, the column did not show it (or wrote): scrollTop ${col.scrollTop}`)
c.destroy()
if (!ros.every((r) => r.off)) fails.push('F: destroy() left its ResizeObserver')
delete globalThis.ResizeObserver
}
if (!skipF) {
const b = browser('https://x.test/shell?tab=regions')
const host = new FakeEl(); const got = []
host.addEventListener(va.VIEW_EVENT, (e) => got.push(e))
const c = pw.buildPaneColumn(SPEC, { host, address: { owner: () => '' } })
c.scrollTo('dashboard'); await settle()
if (b.writes.push + b.writes.replace) fails.push('F: a NESTED composite wrote history')
if (!got.length || got[0].detail?.changes?.pane !== 'dashboard' || got[0].detail?.push !== true) fails.push('F: a nested composite did not announce its pane to the owner')
c.destroy()
}
if (!skipF) {
const b = browser('https://x.test/plain?pane=dashboard')
const host = new FakeEl(); const got = []
host.addEventListener(va.VIEW_EVENT, (e) => got.push(e))
const c = pw.buildPaneColumn(SPEC, { host })
c.scrollTo('dashboard'); await settle(); c.scrollTo('list'); await settle()
if (b.writes.push + b.writes.replace || got.length || host.children[0].scrollTop !== 0) fails.push('F: a column WITHOUT the address opt-in read or wrote the address')
c.destroy()
}
delete globalThis.document
} finally { unset(); delete globalThis.document }
// ── G. the wiring in Regions ──
{
const m = src.mfe, d = src.dash
if (!/buildPaneColumn\(spec\.value, \{ host: stage\.value, address: \{ owner \} \}\)/.test(m)) fails.push('G: regions-mfe does not opt its column into the address')
if (!/ownAddress\(stage\.value, owner\)/.test(m)) fails.push("G: regions-mfe does not record its panes' announcements on its own leaf (ownAddress)")
if (!/go\('dashboard', \{ place: detail\.countrySlug \|\| detail\.regionSlug/.test(m)) fails.push('G: regions-mfe opens the dashboard without its place (two Back steps for one click)')
if (!/claimView\(\{ place: currentSlug\(\) \}/.test(d)) fails.push('G: region-dashboards does not claim ?place=')
if (!/claimPlace\(false\)/.test(d) || !/claimPlace\(true\)/.test(d)) fails.push('G: region-dashboards does not replace at first paint and push on a choice')
if (!/onAddressChange\(onAddress\)/.test(d)) fails.push('G: region-dashboards does not re-apply ?place= on Back/Forward')
if (!/new ResizeObserver\([\s\S]{0,400}else if \(!place\) claimPlace\(false\)/.test(d)) fails.push('G: a kept-alive region-dashboards shown again does not claim its place back')
// A composite that opts its column in also owns its own leaf: it records its panes' choices (ownAddress) and
// releases that listener on unmount, or a choice made inside it on its own leaf is never written.
if (!Object.keys(src.opted || {}).length) fails.push('G: no composite opts its pane column into the address (the census of opted composites is empty)')
for (const [f, t] of Object.entries(src.opted || {})) {
if (!/ownAddress\(stage\.value, owner\)/.test(t)) fails.push(`G: ${f} opts its column in but does not record its panes' choices (ownAddress)`)
else if (!/offOwn\?\.\(\)/.test(t)) fails.push(`G: ${f} never releases ownAddress`)
}
// Each shell that owns a page records its nested composites' choices, and releases the listener on unmount
// BEFORE any hot-update early return (or a hot remount stacks a second one).
for (const [f, t] of Object.entries(src.shells || {})) {
if (!/this\._offOwn = ownAddress\(this\.\$el, \(\) => urlOwnerOf\(this\.cfg\)\)/.test(t)) { fails.push(`G: the shell ${f} does not record nested choices (ownAddress)`); continue }
const bu = t.indexOf('beforeUnmount()')
const rel = bu < 0 ? -1 : t.indexOf('this._offOwn()', bu)
const hot = bu < 0 ? -1 : t.indexOf('if (import.meta.hot) return', bu)
if (rel < 0 || (hot >= 0 && hot < rel)) fails.push(`G: the shell ${f} does not release ownAddress in beforeUnmount before any hot-update return`)
}
}
// ── D. the census ──
for (const [file, n] of Object.entries(counts)) {
@ -197,8 +406,28 @@ async function arms({ va, sv, counts }) {
const va = await import(join(ROOT, 'library/composables/useViewAddress.js'))
const sv = await import(join(ROOT, 'library/composables/useShareableView.js'))
const PW = 'library/layouts/pane-wrapper/pane-wrapper.js'
const pw = await import(join(ROOT, PW))
const SHELLS = ['plugin-studio/plugin-studio.vue', 'doxa-maps-spa/doxa-maps-spa.vue', 'posterverse/posterverse.vue']
// Every composite that opts its pane column into the address, enumerated from the tree.
const OPTED = execFileSync('git', ['grep', '--untracked', '-lE', 'buildPaneColumn\\([^)]*address:', '--', 'plugin-designs', 'library', ':!*.md'], { cwd: ROOT, encoding: 'utf8' }).trim().split('\n').filter(Boolean)
const src = {
mfe: readFileSync(join(ROOT, 'plugin-designs/regions-dashboard/regions-mfe.vue'), 'utf8'),
dash: readFileSync(join(ROOT, 'plugin-designs/regions-dashboard/region-dashboards.vue'), 'utf8'),
shells: Object.fromEntries(SHELLS.map((f) => [f, readFileSync(join(ROOT, 'plugin-designs', f), 'utf8')])),
opted: Object.fromEntries(OPTED.map((f) => [f, readFileSync(join(ROOT, f), 'utf8')])),
}
const counts = census()
const real = { va, sv, counts }
const real = { va, sv, counts, pw, src }
// A pane-wrapper with one line changed, loaded from a sibling file so its relative import still resolves.
let nth = 0
async function pwVariant(from, to) {
const s = readFileSync(join(ROOT, PW), 'utf8')
if (!s.includes(from)) throw new Error('poison anchor not found in pane-wrapper.js: ' + from)
const f = join(ROOT, `library/layouts/pane-wrapper/.tmp-check-view-address-${process.pid}-${nth++}.js`)
writeFileSync(f, s.replace(from, to))
try { return await import(f) } finally { unlinkSync(f) }
}
let code = 0
if (process.argv.includes('--self-test')) {
// The old filter write, as it was: its own replaceState(null, …) of the whole URL.
@ -211,6 +440,7 @@ if (process.argv.includes('--self-test')) {
['a write when not the owner', { ...real, va: { ...va, writeViewKeys: (c, o) => va.writeViewKeys(c, { ...o, owner: 'app' }) } }, /^B: a partner page/],
['a tab change that keeps a stale place', { ...real, va: { ...va, viewSearch: (s, c) => { const k = { ...va.readViewKeys(s), ...Object.fromEntries(Object.entries(c).map(([a, b]) => [a, va.cleanKey(b)])) }; const f = s.replace(/^\?/, '').split('&').filter((p) => p && !va.VIEW_KEYS.includes(p.split('=')[0])); const all = [...va.VIEW_KEYS.filter((x) => k[x]).map((x) => `${x}=${k[x]}`), ...f]; return all.length ? '?' + all.join('&') : '' } } }, /^A: a tab change drops pane and place/],
['a whitelist with no length cap', { ...real, va: { ...va, cleanKey: (v) => (/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(String(v ?? '')) ? String(v) : '') } }, /^A: the whitelist lets/],
['unchanged judged by the string, not the values', { ...real, va: { ...va, writeViewKeys: (c, o) => { if (o.owner !== 'app') return va.writeViewKeys(c, o); const s = va.viewSearch(location.search, c); if (s === location.search) return 'unchanged'; return va.commitUrl({ search: s }, { push: o.push }) ? 'written' : 'unchanged' } } }, /^B: the same keys in another order were rewritten/],
['a write when the address is unchanged', { ...real, va: { ...va, writeViewKeys: (c, o) => { const s = va.viewSearch(location.search, c); history[o.push ? 'pushState' : 'replaceState']({ ...(history.state || {}), current: location.pathname + s + location.hash }, '', location.pathname + s + location.hash); return 'written' } } }, /^B: an unchanged address wrote|^C: re-applying the tab after Back WROTE/],
["a 'host' owner that writes history itself", { ...real, va: { ...va, writeViewKeys: (c, o) => va.writeViewKeys(c, { ...o, owner: o.owner === 'host' ? 'app' : o.owner }) } }, /^B: a 'host' owner wrote/],
['a dxm-address that cannot leave a shadow root', { ...real, va: { ...va, writeViewKeys: (c, o) => { if (o.owner !== 'host') return va.writeViewKeys(c, o); const s = va.viewSearch(location.search, c); o.el.dispatchEvent(new CustomEvent(va.ADDRESS_EVENT, { bubbles: true, detail: { keys: va.readViewKeys(s), href: location.pathname + s, push: o.push } })); return 'announced' } } }, /^B: dxm-address cannot leave/],
@ -223,15 +453,48 @@ if (process.argv.includes('--self-test')) {
// the census
['a new history writer in the tree', { ...real, counts: { ...counts, 'plugin-designs/some-plugin/page.vue': 1 } }, /^D: a new history writer/],
['useShareableView writing history itself again', { ...real, counts: { ...counts, 'library/composables/useShareableView.js': 1 } }, /^D: a new history writer: library\/composables\/useShareableView\.js/],
['region-dashboards writing its #slug itself again', { ...real, counts: { ...counts, 'plugin-designs/regions-dashboard/region-dashboards.vue': 2 } }, /^D: a new history writer: plugin-designs\/regions-dashboard/],
// P2: nested writes, pane-wrapper, the Regions wiring
['the old level rule: a pane change drops place', { ...real, va: { ...va, viewSearch: (s, c) => va.viewSearch(s, 'pane' in c && !('place' in c) ? { ...c, place: '' } : c) } }, /^A: a pane change keeps tab and place/],
['a claim by a non-owner that writes', { ...real, va: { ...va, claimView: (c, o) => va.writeViewKeys(c, { ...o, owner: 'app' }) } }, /^E: a claim by an element that does not own/],
['an announcement that carries any key', { ...real, va: { ...va, claimView: (c, o) => (o.owner ? va.claimView(c, o) : (o.el.dispatchEvent(new CustomEvent(va.VIEW_EVENT, { bubbles: true, composed: true, detail: { changes: c, push: o.push } })), 'announced')) } }, /^E: an announcement carries more or less/],
['ownAddress that writes with no owner', { ...real, va: { ...va, ownAddress: (el, g) => va.ownAddress(el, () => g() || 'app') } }, /^E: ownAddress wrote while the page has no owner/],
['ownAddress that lets a recorded announcement bubble on', { ...real, va: { ...va, ownAddress: (el, g) => { const h = (e) => { const o = g(); if (o) va.writeViewKeys(e.detail.changes, { owner: o, el, push: e.detail.push }) }; el.addEventListener(va.VIEW_EVENT, h); return () => el.removeEventListener(va.VIEW_EVENT, h) } } }, /^E: a recorded announcement went on bubbling/],
['pane-wrapper writing the default pane', { ...real, pw: await pwVariant("pane: id === column[0].id ? '' : id", 'pane: id') }, /^F: a click on the first pane/],
["pane-wrapper writing a click's selection separately", { ...real, pw: await pwVariant('if (write && known(paneId)) claimPane(paneId, push, changes)', 'if (write && known(paneId)) { claimPane(paneId, push); if (Object.keys(changes).length) claimView(changes, { owner: address.owner(), el: host, push }) }') }, /^F: a click's pane and its selection are not ONE push/],
['pane-wrapper pushing where a scroll settles', { ...real, pw: await pwVariant('claimPane(current(), false)', 'claimPane(current(), true)') }, /^F: where the reader's scroll settles/],
['pane-wrapper writing on Back (the pane it is leaving)', { ...real, pw: await pwVariant("show(known(pane) ? pane : column[0].id, 'smooth')", "(claimPane(current(), true), show(known(pane) ? pane : column[0].id, 'smooth'))") }, /^F: Back did not show the address's pane, or it WROTE/],
['pane-wrapper opening a link at build time only (before the column has a layout)', { ...real, pw: await pwVariant("let pending = known(linked) && linked !== column[0].id ? linked : ''", "let pending = ''; if (known(linked)) show(linked, 'instant')") }, /^F: a FRESH LOAD with \?pane=dashboard did not open/],
['pane-wrapper whose settle overwrites a link still waiting to open', { ...real, pw: await pwVariant('if (col.clientHeight && !pending) claimPane(current(), false)', 'if (!pending || true) claimPane(current(), false)') }, /^F: before its layout, the column overwrote the link/],
['pane-wrapper retrying a pending link by rAF only when there is no observer', { ...real, pw: await pwVariant("if (pending && typeof requestAnimationFrame !== 'undefined') {", "if (pending && typeof ResizeObserver === 'undefined' && typeof requestAnimationFrame !== 'undefined') {") }, /^F: with an observer that never fires again/],
['pane-wrapper ignoring a link', { ...real, pw: await pwVariant("let pending = known(linked) && linked !== column[0].id ? linked : ''", "let pending = ''") }, /^F: a link to \?pane=dashboard did not open/],
['pane-wrapper writing without the opt-in', { ...real, pw: await pwVariant("=> address && claimView({ pane: id === column[0].id ? '' : id, ...changes }, { owner: address.owner(),", "=> claimView({ pane: id === column[0].id ? '' : id, ...changes }, { owner: address ? address.owner() : 'app',") }, /^F: a column WITHOUT the address opt-in/],
['ownAddress that lets a nested element switch the tab', { ...real, va: { ...va, ownAddress: (el, g) => { const h = (e) => { const o = g(); if (o) { e.stopPropagation(); va.writeViewKeys(e.detail.changes, { owner: o, el, push: e.detail.push }) } }; el.addEventListener(va.VIEW_EVENT, h); return () => el.removeEventListener(va.VIEW_EVENT, h) } } }, /^E: a nested announcement switched the OWNER's tab/],
['pane-wrapper not claiming its pane when shown again', { ...real, pw: await pwVariant("if (typeof ResizeObserver !== 'undefined') {", 'if (false) {') }, /^F: a kept-alive column shown again/],
['pane-wrapper letting the screen beat an address that names a pane', { ...real, pw: await pwVariant('if (known(asked) && asked !== current()) show(asked, \'instant\')\n else if (!asked) claimPane', 'if (false) show(asked, \'instant\')\n else claimPane') }, /^F: shown again with an address that names a pane/],
['pane-wrapper whose scrollTo(id, null) throws (the old default)', { ...real, pw: await pwVariant('const { write = true, push = true, changes = {} } = o || {}', 'const { write = true, push = true, changes = {} } = o === undefined ? {} : o') }, /^F: scrollTo\(id, null\)|^THREW: .*null/],
['region-dashboards not claiming its place when shown again', { ...real, src: { ...src, dash: src.dash.replace('else if (!place) claimPlace(false)', 'else if (!place) void 0') } }, /^G: a kept-alive region-dashboards shown again/],
['regions-mfe opening the dashboard without its place', { ...real, src: { ...src, mfe: src.mfe.replace("go('dashboard', { place: detail.countrySlug || detail.regionSlug || '' })", "go('dashboard')") } }, /^G: regions-mfe opens the dashboard without its place/],
['regions-mfe without the address opt-in', { ...real, src: { ...src, mfe: src.mfe.replace('{ host: stage.value, address: { owner } }', '{ host: stage.value }') } }, /^G: regions-mfe does not opt its column in/],
['a composite that opts in without ownAddress', { ...real, src: { ...src, opted: { ...src.opted, 'plugin-designs/x/x-mfe.vue': "column = buildPaneColumn(spec.value, { host: stage.value, address: { owner } })" } } }, /^G: plugin-designs\/x\/x-mfe\.vue opts its column in but does not record/],
['no composite opted in at all', { ...real, src: { ...src, opted: {} } }, /^G: no composite opts its pane column/],
['Plugin Studio without ownAddress', { ...real, src: { ...src, shells: { ...src.shells, 'plugin-studio/plugin-studio.vue': src.shells['plugin-studio/plugin-studio.vue'].replace('this._offOwn = ownAddress(this.$el, () => urlOwnerOf(this.cfg))', 'this._offOwn = null') } } }, /^G: the shell plugin-studio\/plugin-studio\.vue does not record/],
['Plugin Studio releasing ownAddress after its hot-update return', { ...real, src: { ...src, shells: { ...src.shells, 'plugin-studio/plugin-studio.vue': src.shells['plugin-studio/plugin-studio.vue'].replace(' if (this._offOwn) { this._offOwn(); this._offOwn = null }\n if (import.meta.hot) return', ' if (import.meta.hot) return\n if (this._offOwn) { this._offOwn(); this._offOwn = null }') } } }, /^G: the shell plugin-studio\/plugin-studio\.vue does not release/],
['the Research Gallery without ownAddress', { ...real, src: { ...src, shells: { ...src.shells, 'doxa-maps-spa/doxa-maps-spa.vue': src.shells['doxa-maps-spa/doxa-maps-spa.vue'].replace('this._offOwn = ownAddress(', 'this._offOwn = (') } } }, /^G: the shell doxa-maps-spa/],
['Posterverse never releasing ownAddress', { ...real, src: { ...src, shells: { ...src.shells, 'posterverse/posterverse.vue': src.shells['posterverse/posterverse.vue'].replace('if (this._offOwn) { this._offOwn(); this._offOwn = null }', 'void 0') } } }, /^G: the shell posterverse\/posterverse\.vue does not release/],
['region-dashboards not re-applying ?place= on Back', { ...real, src: { ...src, dash: src.dash.replace('onAddressChange(onAddress)', 'null') } }, /^G: region-dashboards does not re-apply/],
]
for (const [name, inp, rx] of poisons) {
const f = await arms(inp); const ok = f.some((x) => rx.test(x))
let f
// Only a pane-wrapper poison needs the slow arm F; every other poison must fail its own, faster arm.
try { f = await arms(inp, { skipF: !/^\/\^F|THREW/.test(String(rx)) }) } catch (e) { f = ['THREW: ' + e.message] }
const ok = f.some((x) => rx.test(x))
console.log(ok ? `✓ caught: ${name} -> ${f.find((x) => rx.test(x))}` : `✗ MISSED: ${name}${f.length ? ' (failed instead: ' + f[0] + ')' : ''}`)
if (!ok) code = 1
}
} else {
const f = await arms(real)
if (f.length) { f.forEach((x) => console.log('FAIL ' + x)); code = 1 }
else console.log(`PASS check:view-address — the id whitelist and the level rule; one push per tab change with history.state + current kept; nothing written when unchanged, on a partner page, or by a 'host' owner (one dxm-address instead); filters and tab/pane/place keep each other through writes, Back and Forward; ${Object.keys(counts).length} history writers in the tree, all accounted for`)
else console.log(`PASS check:view-address — the id whitelist and the level rule; one push per tab change with history.state + current kept; nothing written when unchanged, on a partner page, or by a 'host' owner (one dxm-address instead); filters and tab/pane/place keep each other through writes, Back and Forward; nested choices announced and recorded by the one owner, a pane and its selection as one Back step; pane-wrapper's opt-in ?pane= (link, click, scroll, Back) and the Regions wiring; ${Object.keys(counts).length} history writers in the tree, all accounted for`)
}
process.exit(code)

View file

@ -5,7 +5,9 @@
*
* ★ THE SCHEME (query, not hash: a hash is a host page's anchor, and filters already live there).
* ?tab=<app tab>&pane=<view inside the tab>&place=<selection>
* Levels: changing `tab` drops `pane` and `place`; changing `pane` drops `place`. Every other parameter (a host's
* Levels: changing `tab` drops `pane` and `place`, which belong to the tab. `pane` and `place` are siblings: a
* composite keeps its selection while the reader looks at another of its panes (Regions keeps the region while the
* reader is on the list), so the address keeps it too. Every other parameter (a host's
* own, the filters useShareableView writes) and the hash are kept as they are. A value that is not a plain id
* (letters, digits, `-`, `_`, at most 64) reads as absent, so a stale or hand-edited link falls back to the default.
* `pane`, not `view`: `?view=` is already published by four of our own pages (gospel-internet's board/map, Gospel
@ -16,7 +18,8 @@
* 'app' our own pages (a generated leaf, Plugin Studio): writeViewKeys() writes history itself;
* 'host' a host with its own router (DX Life): writeViewKeys() dispatches ADDRESS_EVENT and the HOST writes;
* absent a partner's page: nothing is written; links are still READ (readViewKeys works everywhere).
* A nested element never writes: it announces its choice upward (announceView) and the owner records it.
* A nested element never writes: it announces its choice upward (announceView), and the owner records it (ownAddress).
* claimView() is the one call for a reader's choice: the owner writes it, any other element announces it.
*
* ★ THE ONE HISTORY WRITE: commitUrl(). The view keys and useShareableView's filters both write through it, so those
* two can never undo each other. The tree's other history writers are pinned by check:view-address's census and move
@ -32,7 +35,8 @@
*/
export const VIEW_KEYS = ['tab', 'pane', 'place']
/** nested element → the address owner: { key, value, push }. Composed + bubbling, so it leaves shadow roots. */
/** nested element → the address owner: { changes: { tab?, pane?, place? }, push }. Composed + bubbling, so it leaves
* shadow roots. One event per reader action, so one action is one Back step. */
export const VIEW_EVENT = 'dxm-view'
/** an owner with urlOwner 'host' → the host's router: { keys, href, push }; the host does router[push ? 'push' : 'replace'](href). */
export const ADDRESS_EVENT = 'dxm-address'
@ -52,7 +56,7 @@ const send = (el, type, detail) => !!el?.dispatchEvent && (el.dispatchEvent(new
/**
* The search string after `changes` ({ tab?, pane?, place? }; '' or null removes a key), with the level rule: a
* changed level drops the deeper levels that `changes` does not set itself. Foreign parameters keep their place and
* changed `tab` drops `pane` and `place` unless `changes` sets them itself. Foreign parameters keep their place and
* their exact text (useShareableView's legible `,` and unicode are not re-encoded). Returns '' or '?…'.
*/
export function viewSearch(search, changes = {}) {
@ -60,7 +64,7 @@ export function viewSearch(search, changes = {}) {
VIEW_KEYS.forEach((k, level) => {
if (!(k in changes) || cleanKey(changes[k]) === next[k]) return
next[k] = cleanKey(changes[k])
VIEW_KEYS.slice(level + 1).forEach((d) => { if (!(d in changes)) next[d] = '' })
if (!level) VIEW_KEYS.slice(1).forEach((d) => { if (!(d in changes)) next[d] = '' })
})
const all = VIEW_KEYS.filter((k) => next[k]).map((k) => k + '=' + next[k])
.concat((search || '').replace(/^\?/, '').split('&').filter((p) => p && !VIEW_KEYS.includes(p.split('=')[0])))
@ -94,14 +98,43 @@ export const urlOwnerOf = (cfg) => (cfg && (cfg.urlOwner === 'app' || cfg.urlOwn
export function writeViewKeys(changes, { owner = '', push = true, el = null } = {}) {
if ((owner !== 'app' && owner !== 'host') || !globalThis.location) return 'none'
const search = viewSearch(location.search, changes)
if (search === location.search) return 'unchanged'
// Unchanged means the same VALUES, not the same string: viewSearch puts the view keys first, so comparing strings
// rewrote a link like ?owner=x&pane=map as ?pane=map&owner=x on every fresh load (measured in a browser), a
// needless replace (and on a host, a needless router.replace).
const now = readViewKeys(location.search), next = readViewKeys(search)
if (VIEW_KEYS.every((k) => now[k] === next[k])) return 'unchanged'
if (owner === 'app') return commitUrl({ search }, { push }) ? 'written' : 'unchanged'
return send(el, ADDRESS_EVENT, { keys: readViewKeys(search), href: location.pathname + search + location.hash, push }) ? 'announced' : 'none'
}
// Only the view keys, each value cleaned: what an announcement may carry.
const viewChanges = (changes) => Object.fromEntries(Object.entries(changes || {}).filter(([k]) => VIEW_KEYS.includes(k)).map(([k, v]) => [k, cleanKey(v)]))
/** A nested element reports a reader's choice to whoever owns the address (it never writes itself). */
export const announceView = (el, key, value, { push = true } = {}) =>
VIEW_KEYS.includes(key) && send(el, VIEW_EVENT, { key, value: cleanKey(value), push })
export function announceView(el, changes, { push = true } = {}) {
const c = viewChanges(changes)
return Object.keys(c).length > 0 && send(el, VIEW_EVENT, { changes: c, push })
}
/** THE call for a reader's choice: the owner writes it ('written' | 'announced' | 'unchanged' | 'none'); any other
* element announces it to the owner above it ('announced' | 'none'). */
export const claimView = (changes, { owner = '', el = null, push = true } = {}) =>
owner ? writeViewKeys(changes, { owner, el, push }) : (announceView(el, changes, { push }) ? 'announced' : 'none')
/** An owner records what the elements inside it announce: their pane and place. `tab` is the owner's own key, so a
* nested element cannot switch it. getOwner() is read per event (a host may set profile-config after mount); while
* it is '' the event passes on to an owner further out. Returns off(). */
export function ownAddress(el, getOwner) {
const h = (e) => {
const owner = getOwner()
if (!owner || !e.detail) return
e.stopPropagation()
const { tab, ...changes } = viewChanges(e.detail.changes)
if (Object.keys(changes).length) writeViewKeys(changes, { owner, el, push: e.detail.push !== false })
}
el?.addEventListener?.(VIEW_EVENT, h)
return () => el?.removeEventListener?.(VIEW_EVENT, h)
}
/** Back / Forward: cb(readViewKeys(location.search)). Re-apply state only; never write from here. Returns off(). */
export function onAddressChange(cb) {

View file

@ -34,6 +34,8 @@
* see every pane laid out like posters on a wall.
*/
import { readViewKeys, claimView, onAddressChange } from '../../composables/useViewAddress.js'
export const PANE_GRID_STYLES = `
/* The column that scrolls. Vertical neighbours stack here and the user scrolls
between them; scroll-snap makes each pane settle cleanly instead of stopping
@ -112,9 +114,14 @@ export const PANE_GRID_STYLES = `
* @param {(pane:Object, mountEl:HTMLElement) => Promise<void>} opts.mountPane
* called for each pane that should be LIVE; the caller owns element creation
* so this layout never needs to know about bundles, tags or tokens.
* @returns {{ panes:Object[], scrollTo:(paneId:string)=>void, destroy:()=>void }}
* @param {{ owner: () => string }} [opts.address] OPT-IN: the open pane is in the page address as ?pane=
* (library/composables/useViewAddress.js). owner() is the composite's urlOwner, read per write: on its own
* leaf the composite writes; nested in a shell it announces, and the shell writes. The first pane is the
* default and is never written. A click (scrollTo) is a push; where the reader's own scroll settles is a
* replace; a link opens its pane; Back / Forward scroll to the address's pane and never write.
* @returns {{ panes:Object[], scrollTo:(paneId:string, o?:{write?:boolean, push?:boolean, changes?:Object})=>void, destroy:()=>void }}
*/
export function buildPaneColumn(spec, { host, mountPane }) {
export function buildPaneColumn(spec, { host, mountPane, address }) {
const panes = Array.isArray(spec?.panes) ? spec.panes : []
// No pane declaration → not a pane profile. The caller falls back to its
// original single-mount path, so nothing changes for existing profiles.
@ -169,6 +176,66 @@ export function buildPaneColumn(spec, { host, mountPane }) {
host.appendChild(col)
const paneEl = (id) => col.querySelector(`.pane[data-pane-id="${id}"]`)
const known = (id) => column.some((p) => p.id === id)
const current = () => {
const tops = column.map(p => ({ id: p.id, d: Math.abs((paneEl(p.id)?.offsetTop ?? 0) - col.scrollTop) }))
tops.sort((a, b) => a.d - b.d)
return tops[0]?.id || column[0].id
}
// The first pane is the default, so it is never written: '' removes ?pane=.
const claimPane = (id, push, changes) => address && claimView({ pane: id === column[0].id ? '' : id, ...changes }, { owner: address.owner(), el: host, push })
const show = (id, behavior) => { const t = paneEl(id); if (t) col.scrollTo({ top: t.offsetTop, behavior }) }
const offs = []
if (address) {
// A link opens its pane. Reading writes nothing, and the default needs no write. ★ NOT AT ONCE. MEASURED in a
// browser (a fresh load with ?pane=<second pane>, on Country Posters and Regions): when the column is built it
// has no layout yet, so the pane's offsetTop is 0 and the column stayed on its first pane. The link waits until
// the column has a height and the pane a top, then shows it, once. 'instant': 'auto' would take the column's CSS
// smooth scrolling.
const linked = readViewKeys(typeof location !== 'undefined' ? location.search : '').pane
let pending = known(linked) && linked !== column[0].id ? linked : ''
const openLink = () => {
if (!pending || !col.clientHeight || !(paneEl(pending)?.offsetTop > 0)) return
show(pending, 'instant')
pending = ''
}
openLink()
// Retried by the ResizeObserver below, by every settled scroll, and by a bounded rAF loop that runs even WITH an
// observer: the observer fires only when the COLUMN resizes, and a pane's height can arrive later (styles adopted
// late in a shadow root).
if (pending && typeof requestAnimationFrame !== 'undefined') {
let n = 0
const tick = () => { openLink(); if (pending && ++n < 120) requestAnimationFrame(tick) }
requestAnimationFrame(tick)
}
// Where the reader's own scroll settles is a REPLACE, so a copied link opens what is on screen without a Back
// step per scroll. A column with no height (a hidden tab) has no "where", and a link still waiting to open must
// not be overwritten, so neither writes.
let t = null
const settle = () => { if (t) clearTimeout(t); t = setTimeout(() => { openLink(); if (col.clientHeight && !pending) claimPane(current(), false) }, 120) }
col.addEventListener('scroll', settle, { passive: true })
offs.push(() => { if (t) clearTimeout(t); col.removeEventListener('scroll', settle) })
offs.push(onAddressChange(({ pane }) => show(known(pane) ? pane : column[0].id, 'smooth')))
// A KEPT-ALIVE column shown again (a shell switched back to its tab: no height, then height). The tab change
// dropped ?pane= while the column kept its pane, so the screen claims it again (a replace). An address that names
// a pane wins: Back to an entry of this tab is applied while the column had no layout to scroll.
if (typeof ResizeObserver !== 'undefined') {
let h = null
const ro = new ResizeObserver(() => {
openLink()
const was = h
h = col.clientHeight
if (was !== 0 || !h) return
const asked = readViewKeys(location.search).pane
if (known(asked) && asked !== current()) show(asked, 'instant')
else if (!asked) claimPane(current(), false)
})
ro.observe(col)
offs.push(() => ro.disconnect())
}
}
return {
panes: column,
/** Animate to a pane by id — this is what the View fieldset tabs call. */
@ -177,19 +244,15 @@ export function buildPaneColumn(spec, { host, mountPane }) {
// window's top, and a host shorter than the window then showed its white space under the map. The column is
// the scroller (position:absolute; overflow-y:auto) and each pane's offsetParent, so its offsetTop is exactly
// the scroll position that puts the pane at the top.
scrollTo(paneId) {
const target = col.querySelector(`.pane[data-pane-id="${paneId}"]`)
if (target) col.scrollTo({ top: target.offsetTop, behavior: 'smooth' })
// With `address`, the reader's choice is also written (a push). `changes` rides along in the SAME write, so a
// click that opens a pane AND selects in it is one Back step. write:false is for a caller re-showing a pane.
scrollTo(paneId, o) {
const { write = true, push = true, changes = {} } = o || {}
show(paneId, 'smooth')
if (write && known(paneId)) claimPane(paneId, push, changes)
},
/** Which pane is currently filling the viewport (for URL sync). */
current() {
const tops = column.map(p => {
const el = col.querySelector(`.pane[data-pane-id="${p.id}"]`)
return { id: p.id, d: Math.abs((el?.offsetTop ?? 0) - col.scrollTop) }
})
tops.sort((a, b) => a.d - b.d)
return tops[0]?.id || column[0].id
},
current,
onScroll(cb) {
let t = null
const h = () => { if (t) clearTimeout(t); t = setTimeout(cb, 120) }
@ -197,6 +260,6 @@ export function buildPaneColumn(spec, { host, mountPane }) {
return () => { if (t) clearTimeout(t); col.removeEventListener('scroll', h) }
},
mountEl: (paneId) => mounts.get(paneId) || null,
destroy() { col.remove(); mounts.clear() },
destroy() { offs.forEach((off) => off()); col.remove(); mounts.clear() },
}
}

View file

@ -725,8 +725,8 @@
"check:side-lanes:self-test": "a poster-render.js copy WITHOUT the lane clamp must make check:side-lanes' crossing arm FAIL on the same seeded data (it does: 36 crossings), so the arm is shown able to fail.",
"check:poster-section": "the reusable poster section (<dxm-poster-section>): the type registry has 'country'; DX Life's /regions slugs (and every other spelling of a region) each show their countries, every country once; a withheld whole-country poster stays withheld through the type; the element's attributes map to its config; PosterSection.vue injects every name its template uses.",
"check:poster-section:self-test": "a registry without 'country', a region key without \"&\" → \"and\", and a section that uses a name it does not inject must each make check:poster-section fail.",
"check:view-address": "the page's address (library/composables/useViewAddress.js: ?tab= &pane= &place=) and its ONE history write, on a simulated browser history: the id whitelist + 64 cap and the level rule; an app owner's tab change is one push that keeps history.state with current (Vue Router's field) and the hash; nothing is written when unchanged, on a partner page (no urlOwner), or by a host owner (it hands the host one composed dxm-address). Coexistence with the filters (useShareableView writes through the same commitUrl): a filter write keeps tab/pane/place, a tab/pane/place write keeps the filters, both survive Back and Forward, and re-applying the tab after Back writes nothing. Plus the census: every history.pushState/replaceState in library/ and plugin-designs/ source is the helper or a listed writer. Pure node, no build.",
"check:view-address:self-test": "14 poisons must each make check:view-address fail: a write without history.state.current, a write when not the owner, a tab change that keeps a stale place, no length cap, a write when unchanged, a host owner writing history, a dxm-address that cannot leave a shadow root, a filter write that drops tab/pane/place, the old replaceState(null) filter write, a tab write that drops the filters, a tab change written as a replace, a popstate handler that writes, a new history writer in the tree, useShareableView writing history itself again.",
"check:view-address": "the page's address (library/composables/useViewAddress.js: ?tab= &pane= &place=) and its ONE history write, on a simulated browser history: the id whitelist + 64 cap and the level rule; an app owner's tab change is one push that keeps history.state with current (Vue Router's field) and the hash; nothing is written when unchanged, on a partner page (no urlOwner), or by a host owner (it hands the host one composed dxm-address). Coexistence with the filters (useShareableView writes through the same commitUrl): a filter write keeps tab/pane/place, a tab/pane/place write keeps the filters, both survive Back and Forward, and re-applying the tab after Back writes nothing. Nested choices (P2): claimView writes for the owner and announces for anyone else; ownAddress records announcements only while it owns, so a pane and its selection are one Back step; pane-wrapper's opt-in ?pane= (a link opens it, a click pushes, a settled scroll replaces, Back never writes; the default pane is never written); the Regions wiring (?pane= on regions-mfe, ?place= on region-dashboards instead of its #slug). Plus the census: every history.pushState/replaceState in library/ and plugin-designs/ source is the helper or a listed writer. Pure node, no build.",
"check:view-address:self-test": "29 poisons must each make check:view-address fail. P1: a write without history.state.current, a write when not the owner, a tab change that keeps a stale place, no length cap, a write when unchanged, a host owner writing history, a dxm-address that cannot leave a shadow root, a filter write that drops tab/pane/place, the old replaceState(null) filter write, a tab write that drops the filters, a tab change written as a replace, a popstate handler that writes, a new history writer, useShareableView or region-dashboards writing history itself again. P2: a pane change that drops place, a non-owner claim that writes, an announcement carrying any key, ownAddress writing with no owner or letting a recorded announcement bubble on, pane-wrapper writing the default pane / a click's selection separately / a push where a scroll settles / on Back / ignoring a link / without the opt-in, and regions-mfe or region-dashboards wired without the address.",
"check:poster-sections": "the Poster Sections page (plugin-designs/poster-sections): every panel choice (each region or All regions, each language, open or collapsed, 1-5 per row) builds <dxm-poster-section> tags that the element's own attribute reader turns back into exactly those choices; All regions is one section per region; Copy embed is the bundle script plus those same tags. The presets file holds, per poster type, exactly an all-regions preset plus one per region its adapter's scopes() returns, so a region the data drops (or gains) cannot drift from the presets; a poster type not on the tree has no presets (its pages could show no posters); and the built preset pages are exactly the presets (no stale page).",
"check:poster-sections:self-test": "a tag builder that drops columns, one that drops collapsed, one that repeats a region under All regions, a preset for a region the type does not have, and a built page for a preset that no longer exists must each make check:poster-sections fail.",
"gen:poster-sections-presets": "writes plugin-designs/poster-sections/poster-sections.instances.json from each poster type's own scopes() (all regions + one preset per region, titled by the type's own section title, then configure); the list check:poster-sections holds the file to.",

View file

@ -32,6 +32,7 @@
*/
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue'
import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js'
import { urlOwnerOf, ownAddress } from '@map/composables/useViewAddress.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import ViewSelect from '@map/components/ViewSelect.vue'
import { countryName } from '@map/utils/countryName.js'
@ -104,6 +105,11 @@ const view = computed({
})
let column = null
let offScroll = null
let offOwn = null
// The page address (useViewAddress): ?pane= is the open view ('map'; the first pane is the default and never written).
// On this composite's own leaf it owns the address (ownAddress); nested in a shell it announces and the shell writes.
// The India pane exists only in an opted-in preview, so ?pane=india names nothing anywhere else.
const owner = () => urlOwnerOf(cfg.value)
// PAGE-LEVEL config keys a pane INHERITS from the host page. A cap-4 composite
// nests children as custom elements in THIS document, so anything the generator
@ -209,8 +215,9 @@ function onShowOnMap(e) {
onMounted(() => {
if (!stage.value) return
column = buildPaneColumn(spec.value, { host: stage.value })
column = buildPaneColumn(spec.value, { host: stage.value, address: { owner } })
if (!column) return
offOwn = ownAddress(stage.value, owner)
for (const pane of column.panes) {
if (!pane.config) continue
// ★ KEEP THE ELEMENT. It used to be created and dropped on the same line, so there was no
@ -236,6 +243,7 @@ onMounted(() => {
onBeforeUnmount(() => {
window.removeEventListener(SHOW_ON_MAP, onShowOnMap)
offScroll?.()
offOwn?.()
column?.destroy()
column = null
})

View file

@ -216,7 +216,7 @@ import { helpFor } from './src/experimentalHelp.js'
import { TAB_INFO } from './src/tabInfo.js'
import MapInfo from '@map/components/info/MapInfo.vue'
import { loadScriptOnce, whenTagDefined } from '@map/utils/embedScript.js'
import { readViewKeys, writeViewKeys, onAddressChange, urlOwnerOf } from '@map/composables/useViewAddress.js'
import { readViewKeys, writeViewKeys, onAddressChange, urlOwnerOf, ownAddress } from '@map/composables/useViewAddress.js'
// THE HOST PAGE'S LANGUAGE, by the same ladder every nested map already uses (library/i18n
// detectLocale): ?lang= / ?locale= on the page URL, then <html lang>. '' when the page says nothing.
@ -1136,6 +1136,9 @@ export default defineComponent({
// would snap a host's fixed `tab` attribute back to the default. The owner is read per event: a host may set
// profile-config after mount.
this._offAddress = onAddressChange(({ tab }) => { if (urlOwnerOf(this.cfg)) this.openFromAddress(tab) })
// A nested composite's choice (its open pane, a selected place) reaches the address through this shell, the
// page's one writer, while the page owns its address; a nested element can never switch this shell's tab.
this._offOwn = ownAddress(this.$el, () => urlOwnerOf(this.cfg))
},
// Release the media-query listener. This element is a custom element a host can add and REMOVE at
// will (a cap-5 SPA is mounted and unmounted by shells, tabs and the studio), and a live listener
@ -1144,6 +1147,7 @@ export default defineComponent({
if (this._langObs) { this._langObs.disconnect(); this._langObs = null }
if (this._onHostedHelp) { this.$el.removeEventListener('doxa:help', this._onHostedHelp); this._onHostedHelp = null }
if (this._offAddress) { this._offAddress(); this._offAddress = null }
if (this._offOwn) { this._offOwn(); this._offOwn = null }
if (!this._mql || !this._onMedia) return
if (this._mql.removeEventListener) this._mql.removeEventListener('change', this._onMedia)
else if (this._mql.removeListener) this._mql.removeListener(this._onMedia)
@ -1350,8 +1354,13 @@ export default defineComponent({
const candidates = crossOrigin ? [copy, root] : [root, copy]
// The tree's name comes from the build config (vite define), never a literal here: this was the
// one tree path compiled into any bundle, so a renamed tree would have needed a gallery edit.
const devFallback = '/public/' + __DOXA_TREE_NAME__ + '/manifest.json'
if (!candidates.some(c => c.url === devFallback)) candidates.push({ url: devFallback, root: dirOf(devFallback) })
// ★ THE DEV SERVER SERVES public/ AT THE ROOT. Reached as /public/<tree>/…, Vite treats a built bundle as SOURCE
// and injects an import into its IIFE, so a child loaded as a classic script throws "Cannot use import statement
// outside a module" (measured on the staging page, 2026-10-03). So the root path comes first, served as the raw
// build; /public/<tree>/ stays last, for a plain static server of the repository, which transforms nothing.
for (const devFallback of ['/' + __DOXA_TREE_NAME__ + '/manifest.json', '/public/' + __DOXA_TREE_NAME__ + '/manifest.json']) {
if (!candidates.some(c => c.url === devFallback)) candidates.push({ url: devFallback, root: dirOf(devFallback) })
}
const tried = []
// Every mount waits for this gate (see FRESH BUNDLES above): it opens once the manifest's bundles are known
// to be current, when no manifest can be read (fail-soft, as before), or after 8 s whatever happens.

View file

@ -36,6 +36,7 @@
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue'
import { useOptionalT } from '@map/composables/useOptionalT.js'
import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js'
import { urlOwnerOf, ownAddress } from '@map/composables/useViewAddress.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import ViewSelect from '@map/components/ViewSelect.vue'
import ShareButton from '@map/components/map-controls/ShareButton.vue'
@ -71,6 +72,8 @@ import MapInfoButton from '@map/components/map-controls/MapInfoButton.vue'
const t = useOptionalT('')
const cfg = inject('profileConfig', ref(null))
// P2: the pane in view is part of the address (?pane=), written by whoever owns this page (urlOwnerOf).
const owner = () => urlOwnerOf(cfg.value)
const tk = inject('mapboxToken', ref(''))
const uiStore = inject('uiStore', null)
const isDark = computed(() => !!uiStore?.isDark)
@ -150,6 +153,7 @@ const view = computed({
})
let column = null
let offScroll = null
let offOwn = null // P2: ownAddress's undo, called on unmount
// The mounted custom element per pane id. Kept because a cross-pane action has to reach the
// TARGET PANE'S ELEMENT: upg-100-list listens for 'doxa:select-zone' on itself rather than on
// window, precisely so two embeds on one page do not cross-talk — which means the shell has to
@ -300,8 +304,9 @@ function onShowOnMap(e) {
onMounted(() => {
if (!stage.value) return
column = buildPaneColumn(spec.value, { host: stage.value })
column = buildPaneColumn(spec.value, { host: stage.value, address: { owner } })
if (!column) return
offOwn = ownAddress(stage.value, owner)
for (const pane of column.panes) {
if (!pane.config) continue
const el = paneElement(pane)
@ -330,6 +335,8 @@ onMounted(() => {
onBeforeUnmount(() => {
window.removeEventListener('doxa-upg-100-list:show-on-map', onShowOnMap)
offScroll?.()
offOwn?.()
offOwn = null
column?.destroy()
column = null
// Drop the element references with the column that owned them — holding a detached custom

View file

@ -225,7 +225,7 @@ import StudioTree from './src/StudioTree.vue'
import { LANGUAGES, loadLocale, saveLocale, getLanguage, normaliseLocale } from '@map/layouts/app-wrapper-material/languages.js'
import { publishMapToken, clearMapToken } from '@map/composables/useSharedMapToken.js'
import { loadScriptOnce, whenTagDefined } from '@map/utils/embedScript.js'
import { readViewKeys, writeViewKeys, onAddressChange, urlOwnerOf, commitUrl } from '@map/composables/useViewAddress.js'
import { readViewKeys, writeViewKeys, onAddressChange, urlOwnerOf, commitUrl, ownAddress } from '@map/composables/useViewAddress.js'
// The reader's own map token / token-service URL. localStorage only: never written into the build,
// never sent anywhere but Mapbox (the token) or the URL the reader typed (the service).
@ -769,6 +769,9 @@ export default defineComponent({
if (key) this.select(key, { fromHistory: true })
else if (this.rows.length) this.select(this.rows[0].key, { fromHistory: true })
})
// A nested composite's own choice (its open pane, its place) is announced up to here and written beside ?tab=,
// while this page owns its address. Only pane and place: the tab is Studio's own.
this._offOwn = ownAddress(this.$el, () => urlOwnerOf(this.cfg))
},
// The corner-widget hosts live on window.__doxaStudioWidgets so a HOT UPDATE remounts Studio onto
// the same hosts and a live widget (Rudder) keeps its state. A real unmount must release them, or
@ -777,6 +780,7 @@ export default defineComponent({
// FIRST, before the hot-update return below: a remounted Studio adds its own listener, and a kept one would
// re-apply every Back twice.
if (this._offAddress) { this._offAddress(); this._offAddress = null }
if (this._offOwn) { this._offOwn(); this._offOwn = null }
if (import.meta.hot) return
const W = window.__doxaStudioWidgets
if (W && W.hosts) for (const h of Object.values(W.hosts)) { try { h.remove() } catch (_) {} }

View file

@ -74,7 +74,7 @@ import BackToTopButton from '@map/components/BackToTopButton.vue'
import ShellLanguageMenu from '@map/components/ShellLanguageMenu.vue'
import { saveLocale } from '@map/layouts/app-wrapper-material/languages.js'
import { loadScriptOnce, whenTagDefined } from '@map/utils/embedScript.js'
import { readViewKeys, writeViewKeys, onAddressChange, urlOwnerOf } from '@map/composables/useViewAddress.js'
import { readViewKeys, writeViewKeys, onAddressChange, urlOwnerOf, ownAddress } from '@map/composables/useViewAddress.js'
import { translate, LOCALES } from './src/strings.js'
import { SECTIONS, TABS, resolveTabs } from './src/tabs.js'
@ -233,6 +233,9 @@ export default defineComponent({
const want = this.requestedTab()
const known = !want || this.tabs.some((t) => t.id === want)
this.select(this.initialIndex(), { firstPaint: true, fromHistory: !known })
// A nested plugin's choice inside a tab (?pane=, ?place=) reaches the address through the owner: recorded
// only while this page owns its address, never a tab switch, and the event stops here.
this._offOwn = ownAddress(this.$el, () => urlOwnerOf(this.cfg))
// Back / Forward re-apply the tab from the address and never write (writing would lose Forward).
this._offAddress = onAddressChange(({ tab }) => {
const i = this.tabs.findIndex((t) => t.id === tab)
@ -243,6 +246,7 @@ export default defineComponent({
beforeUnmount() {
if (this._langObs) { this._langObs.disconnect(); this._langObs = null }
if (this._offAddress) { this._offAddress(); this._offAddress = null }
if (this._offOwn) { this._offOwn(); this._offOwn = null }
},
methods: {
tr(key, params) { return translate(this.uiLocale, key, params) },
@ -277,7 +281,9 @@ export default defineComponent({
// 2. <tree>/doxa-maps-spa/manifest.json, the byte-identical copy generate-tree writes for the
// Research Gallery. The live static layer sends CORS headers only inside a tree folder, not
// on the root manifest, so a partner page's script embed reads this copy FIRST;
// 3. /public/<tree>/manifest.json, where the dev server keeps the built manifest.
// 3. /<tree>/manifest.json, the built tree as the dev server serves it (at the ROOT). NOT /public/<tree>/:
// the dev server treats a /public/… request as SOURCE and injects an import into each built IIFE, which
// then fails as a classic script ("Cannot use import statement outside a module").
// Script URLs always resolve against the tree ROOT, never the copy's folder.
loadManifest() {
const dirOf = (u) => { try { return new URL(u, location.href).href.replace(/[^/]*$/, '') } catch { return '' } }
@ -287,7 +293,7 @@ export default defineComponent({
let crossOrigin = false
try { crossOrigin = new URL(rootUrl, location.href).origin !== location.origin } catch { /* relative = same */ }
const candidates = crossOrigin ? [copy, root] : [root, copy]
const devFallback = '/public/' + __DOXA_TREE_NAME__ + '/manifest.json'
const devFallback = '/' + __DOXA_TREE_NAME__ + '/manifest.json'
if (!candidates.some((c) => c.url === devFallback)) candidates.push({ url: devFallback, root: dirOf(devFallback) })
const tried = []
const attempt = (i) => {

View file

@ -25,6 +25,7 @@
* rather than imported, which was true before the port and false afterwards.
*/
import { inject, ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
import { readViewKeys, claimView, urlOwnerOf, commitUrl, onAddressChange } from '@map/composables/useViewAddress.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js'
// ar renders right-to-left. RTL_LOCALES is the shared set and :dir on our own root is the
@ -578,41 +579,22 @@ const mapNote = computed(() => {
// of ours. It fetches and filters the people groups itself, exactly as theirs does, so there is
// no countryTable computed here any more.
// ── URL <-> SELECTION: what makes this a standalone app rather than a widget ─
// ── ADDRESS <-> SELECTION: what makes this a standalone app rather than a widget ─
// MEASURED before this existed: 4 of 10 app properties OK. Five of the six failures were ONE missing thing — the URL never reflected
// the selection, so nothing was linkable, a reload lost the subject, Back unmounted the element,
// a deep link did nothing, and Share shared the index. DX-life gets all of that free from Nuxt
// routing (/regions/oceania IS the state); a custom element has to do it deliberately.
//
// THE HASH, NOT THE PATH. A leaf page is a static file at a fixed URL and there is no server to
// route /regions/oceania, so the subject lives in the hash — and the hash is also the only part
// an EMBEDDED element may touch without fighting its host page. Slugs are DX-life's own
// (#oceania, #cameroon), so a link reads the way theirs does.
// replaceState on the first paint, pushState on a user choice: that is what makes Back mean
// "the previous subject" instead of "leave the page".
const URL_SYNC = typeof window !== 'undefined' && typeof history !== 'undefined'
// ★ URL SYNC IS THE HOST'S TO REFUSE. The two capabilities above are ours when we ARE the page,
// and NOT ours when we are one tab inside someone else's. Nested in the Research Gallery this
// element rewrote the HOST page's #anchor through writeUrl() and retitled the host's tab through
// syncTitle() -- reported by an embed sweep, and carried as two routed entries in
// check:gallery-embed's NEIGHBOUR_KNOWN (regions-dashboard:url and :title), deleted in this same
// commit now that it is honoured.
// The gallery passes profile-config.urlSync:false for exactly this. It is a COMPUTED rather than a
// second const because the injected profile-config arrives after this module evaluates, so a
// snapshot taken here would read `undefined` and sync anyway -- the same late-injection trap that
// made every value read off the string prop fall back to its default.
// DELIBERATELY NOT GUARDED: slugFromHash(), the boot-time READ. The complaint is about WRITING to a
// host we do not own, and refusing to read would change which subject a standalone deep link opens.
// The residual is narrow and worth stating: on a host whose own #anchor happens to equal one of
// their slugs, a nested Regions would open that subject instead of the chooser.
const urlSyncEnabled = computed(() => URL_SYNC && cfg.value.urlSync !== false)
function slugFromHash() {
if (!URL_SYNC) return ''
return decodeURIComponent(String(window.location.hash || '').replace(/^#/, '')).trim()
}
// THE SUBJECT IS ?place=<slug> (library/composables/useViewAddress.js, the page address every shell and composite
// shares). Slugs are DX-life's own (oceania, cameroon), so a link reads the way theirs does. It used to be the HASH,
// written here with this element's own pushState; one writer per page replaces that:
// on its OWN leaf (urlOwner 'app') this element writes ?place= itself;
// nested (in Regions, a shell, DX Life) it ANNOUNCES the choice, and the page's owner writes it with its own keys,
// so a nested Regions no longer touches a host's #anchor at all, and Regions inside the Research Gallery gets
// its own link (?tab=…&pane=dashboard&place=angola);
// on a partner's page nothing is written; a link is still READ.
// Replace on the first paint, push on a reader's choice, nothing on Back/Forward: Back means "the previous subject".
// An old #<slug> link still opens its subject; on our own leaf the first paint rewrites it to ?place=.
// A slug may name a region OR a country — DX-life's [slug].vue matches region slugs first, then
// country slugs, and 404s on neither. Same order here; "neither" lands on the chooser rather
// than an error, because the chooser is a legitimate state for this element.
@ -631,38 +613,27 @@ function currentSlug() {
return sel.value.countrySlug || sel.value.regionSlug || ''
}
// Write the subject into the URL. push:true for a user choice (Back should undo it), false for
// the initial read (Back should leave the page, not replay the boot).
function writeUrl(push) {
if (!urlSyncEnabled.value) return
const slug = currentSlug()
const url = window.location.pathname + window.location.search + (slug ? '#' + encodeURIComponent(slug) : '')
// WRITE ONLY WHEN THE URL WOULD ACTUALLY CHANGE. This is what makes Back and Forward work, and
// it replaces a flag that could not do the job. MEASURED before the fix, drilling
// All Regions -> Africa -> Angola and then pressing Back three times and Forward twice:
// back 1 -> Africa (correct)
// back 2 -> Africa (WRONG, should be All Regions)
// back 3 -> Africa
// forward 1 -> Africa (WRONG, should be Angola)
// history.length stayed 4 throughout: every Back pushed a REPLACEMENT entry, which discards the
// forward stack and pins the position — the classic loop the note below warns about.
// The old guard set applyingHistory=true, assigned sel, then cleared it SYNCHRONOUSLY, while
// the watcher that calls this is { flush: 'post' } and therefore runs after the DOM update,
// by which time the flag was already false. A scheduling-dependent flag cannot guarantee this;
// comparing the URL can. After a popstate the address bar already holds the target, so there is
// nothing to write and no entry is created.
const current = window.location.pathname + window.location.search + window.location.hash
if (url === current) { syncTitle(); return }
try { push ? history.pushState({ slug }, '', url) : history.replaceState({ slug }, '', url) } catch (_) {}
syncTitle()
// The OLD form of a link, #<slug>. Read only when the address has no place.
function slugFromHash() {
if (typeof location === 'undefined') return ''
try { return decodeURIComponent(String(location.hash || '').replace(/^#/, '')).trim() } catch (_) { return '' }
}
const placeInAddress = () => readViewKeys(typeof location !== 'undefined' ? location.search : '').place
function claimPlace(push) {
claimView({ place: currentSlug() }, { owner: urlOwnerOf(cfg.value), el: scrollEl.value, push })
}
// ★ urlSync:false NOW GUARDS THE TAB TITLE ONLY. A shell passes it so a nested Regions does not retitle the host's
// tab; the address no longer needs it, since only the page's owner writes the address.
const titleSyncEnabled = computed(() => typeof document !== 'undefined' && cfg.value.urlSync !== false)
// Their page sets the document title per subject (useSeoMeta in [slug].vue: `${title} — Doxa`).
// A standalone app should too: it is what a bookmark, a tab strip and a shared link all show.
function syncTitle() {
if (typeof document === 'undefined') return
// The host owns its tab title when we are nested; see urlSyncEnabled.
if (!urlSyncEnabled.value) return
// The host owns its tab title when we are nested; see titleSyncEnabled.
if (!titleSyncEnabled.value) return
const t = subject.value?.title
// COMPARE AGAINST THE TRANSLATED VALUE, not the English literal. The subject title is now
// t('All Regions'), so at ar it is 'جميع المناطق' and a comparison with the English string
@ -674,18 +645,13 @@ function syncTitle() {
try { document.title = t && t !== allLabel ? t + ' — ' + suffix : suffix } catch (_) {}
}
// popstate WRITES the selection, and the watcher below then runs — but it can no longer create an
// entry for it, because writeUrl() refuses to write a URL the address bar already holds. The flag
// that used to guard this is GONE rather than patched: it was cleared synchronously while the
// watcher it guarded is { flush: 'post' }, so it was never in effect when it mattered.
function onPopState() {
// A host that refused URL sync also owns its own history entries.
if (!urlSyncEnabled.value) return
sel.value = selectionForSlug(slugFromHash())
syncTitle()
// Back / Forward: re-apply the address's subject. Never writes: the watcher below then claims the same subject, which
// the address already holds, so nothing is written and the forward stack survives.
function onAddress({ place }) {
sel.value = selectionForSlug(place || slugFromHash())
}
// One watcher rather than a writeUrl() call at each of the five places a selection is made
// One watcher rather than a claim at each of the five places a selection is made
// (tab-1 event, table row, region dropdown, country dropdown, back-link) — those would drift.
// ★ A NEW SUBJECT OPENS AT ITS TOP. Picking a region from the all-regions table, or a country from a region,
// kept the old scroll height, so the reader landed at the bottom of the new page (reported on a phone). The
@ -698,12 +664,8 @@ watch(() => `${sel.value.regionSlug}|${sel.value.countrySlug}`, () => {
}, { flush: 'post' })
watch(() => [sel.value.regionSlug, sel.value.countrySlug], () => {
// No guard needed: writeUrl() is idempotent, so the call this watcher makes after a popstate
// writes nothing. (Deleting the declaration while leaving `if (applyingHistory) return` here
// threw a ReferenceError inside the watcher, which silently stopped the URL being written at
// all — the hash never changed and Back left the page. Caught by re-running the measurement
// rather than by trusting the fix.)
writeUrl(true)
claimPlace(true)
syncTitle()
}, { flush: 'post' })
// ── in-dashboard navigation ─────────────────────────────────────────────────
@ -763,22 +725,45 @@ async function loadRegions() {
// first paint.
watch(languageCode, () => { loadRegions() })
let offAddress = null
let reshown = null
onMounted(async () => {
window.addEventListener(SELECT_EVENT, onSelect)
window.addEventListener(POSTER_WALL_EVENT, onPosterShowOnMap)
if (URL_SYNC) window.addEventListener('popstate', onPopState)
// The hash can only be resolved to a subject once the regions are known, so it is applied
offAddress = onAddressChange(onAddress)
// The address can only be resolved to a subject once the regions are known, so it is applied
// after the fetch rather than on mount — otherwise a deep link silently lands on the chooser.
await loadRegions()
const slug = slugFromHash()
if (slug) sel.value = selectionForSlug(slug)
writeUrl(false)
const place = placeInAddress()
const legacy = place ? '' : slugFromHash()
if (place || legacy) sel.value = selectionForSlug(place || legacy)
// First paint is a REPLACE. An old #<slug> link becomes ?place= on our own leaf, and its hash goes, so the address
// has ONE form. Nested, the hash is the host's and stays.
claimPlace(false)
if (legacy && currentSlug() && urlOwnerOf(cfg.value) === 'app') commitUrl({ hash: '' })
syncTitle()
// A KEPT-ALIVE Regions shown again (a shell switched back to its tab: no height, then height). The tab change
// dropped ?place= while this element kept its subject, so the screen claims it again (a replace). An address that
// names a subject wins (Back already applied it).
if (typeof ResizeObserver !== 'undefined' && scrollEl.value) {
let h = null
reshown = new ResizeObserver(() => {
const was = h
h = scrollEl.value?.clientHeight || 0
if (was !== 0 || !h) return
const place = placeInAddress()
if (place && place !== currentSlug()) sel.value = selectionForSlug(place)
else if (!place) claimPlace(false)
})
reshown.observe(scrollEl.value)
}
})
onBeforeUnmount(() => {
window.removeEventListener(SELECT_EVENT, onSelect)
window.removeEventListener(POSTER_WALL_EVENT, onPosterShowOnMap)
if (URL_SYNC) window.removeEventListener('popstate', onPopState)
offAddress?.()
reshown?.disconnect()
})
// Palette is DX-life material light, matching regions-list.vue tab-for-tab so swapping between

View file

@ -27,6 +27,7 @@
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue'
import { usePaneConfig } from '@map/composables/usePaneConfig.js'
import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js'
import { urlOwnerOf, ownAddress } from '@map/composables/useViewAddress.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js'
import ViewSelect from '@map/components/ViewSelect.vue'
@ -80,6 +81,11 @@ const stage = ref(null)
const view = ref('list')
let column = null
let offScroll = null
let offOwn = null
// The page address (useViewAddress): ?pane= is the open view, ?place= the region or country. On this composite's own
// leaf it owns the address and writes both, the dashboard's choice included (ownAddress). Nested in a shell it owns
// nothing; its writes go up as announcements and the shell writes them.
const owner = () => urlOwnerOf(cfg.value)
// PAGE-LEVEL config keys a pane INHERITS from the host page — see the BUG-UPG100MFE-ASSET-PATH
// note in upg-100-mfe.vue: a cap-4 composite nests children in THIS document, so anything the
@ -141,9 +147,9 @@ function paneElement(pane) {
return el
}
function go(id) {
function go(id, changes) {
view.value = id
column?.scrollTo(id)
column?.scrollTo(id, { changes })
}
// ── tab 1 -> tab 2: forward the selection, then swap the visible pane ───────
@ -153,7 +159,9 @@ function forwardSelection(detail) {
window.dispatchEvent(new CustomEvent(SELECT_EVENT, {
detail: { ...detail, instanceId: instanceId.value },
}))
go('dashboard')
// ONE address write for the click: the dashboard pane AND its subject, so Back returns to the list in one step.
// The dashboard's own write of the same subject then finds it already there and writes nothing.
go('dashboard', { place: detail.countrySlug || detail.regionSlug || '' })
}
// A region heading (or its "See X dashboard" link) was clicked on tab 1.
@ -171,8 +179,9 @@ function onCountrySelected(e) {
onMounted(() => {
if (!stage.value) return
column = buildPaneColumn(spec.value, { host: stage.value })
column = buildPaneColumn(spec.value, { host: stage.value, address: { owner } })
if (!column) return
offOwn = ownAddress(stage.value, owner)
for (const pane of column.panes) {
if (!pane.config) continue
const el = paneElement(pane)
@ -195,6 +204,7 @@ onBeforeUnmount(() => {
stage.value?.removeEventListener('region-selected', onRegionSelected)
stage.value?.removeEventListener('country-selected', onCountrySelected)
offScroll?.()
offOwn?.()
column?.destroy()
column = null
})

View file

@ -76,7 +76,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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -67,7 +67,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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -82,7 +82,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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE" 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

@ -67,7 +67,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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE" 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

@ -67,7 +67,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-JbdbnT273exxkEbpgtOE3DxfYeQ1iPRockZeONzgkP1TJ3dITNCl4yRJ5KMXiXQb" crossorigin="anonymous"></script>
<script src="../../doxa-maps-spa.js" integrity="sha384-Wop35cwyXSmt+i3+2mobkc03oOirRiyyL4nx22fiPqN5uo0uQpegH4hE9PQnpHx7" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -67,7 +67,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-JbdbnT273exxkEbpgtOE3DxfYeQ1iPRockZeONzgkP1TJ3dITNCl4yRJ5KMXiXQb" crossorigin="anonymous"></script>
<script src="../doxa-maps-spa.js" integrity="sha384-Wop35cwyXSmt+i3+2mobkc03oOirRiyyL4nx22fiPqN5uo0uQpegH4hE9PQnpHx7" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -306,7 +306,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj",
"integrity": "sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE",
"elements": [
"country-posters-map"
],
@ -329,8 +329,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -368,7 +368,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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"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-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -384,8 +384,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -401,7 +401,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/poster-section/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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"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-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -505,7 +505,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-JbdbnT273exxkEbpgtOE3DxfYeQ1iPRockZeONzgkP1TJ3dITNCl4yRJ5KMXiXQb",
"integrity": "sha384-Wop35cwyXSmt+i3+2mobkc03oOirRiyyL4nx22fiPqN5uo0uQpegH4hE9PQnpHx7",
"elements": [
"doxa-maps-spa"
],
@ -535,7 +535,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-JbdbnT273exxkEbpgtOE3DxfYeQ1iPRockZeONzgkP1TJ3dITNCl4yRJ5KMXiXQb\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"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-Wop35cwyXSmt+i3+2mobkc03oOirRiyyL4nx22fiPqN5uo0uQpegH4hE9PQnpHx7\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
},
"instances": [
{
@ -1025,7 +1025,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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI",
"integrity": "sha384-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G",
"elements": [
"my-upg-100-list-map"
],
@ -1112,7 +1112,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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -1128,8 +1128,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -1145,8 +1145,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]
@ -1219,7 +1219,7 @@
"bundle": "plugin-studio",
"scriptUrl": "./plugin-studio/plugin-studio.js",
"output": "dx-maps/plugin-studio/plugin-studio.js",
"integrity": "sha384-fUBGsSNbCdWEJ6QNmeOJf78N88LmjL7b5ktyt11xm5iSZxM/u2xwNdPzHaT2A6Dz",
"integrity": "sha384-KH5r1cFKUtfzOiE5iRdTbaoHYP27HquWyUNX0gjnVai5MfNzwUiJI2kBaStmGuIY",
"elements": [
"plugin-studio"
],
@ -1250,7 +1250,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-fUBGsSNbCdWEJ6QNmeOJf78N88LmjL7b5ktyt11xm5iSZxM/u2xwNdPzHaT2A6Dz\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"provider\":\"maplibre\",\"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-KH5r1cFKUtfzOiE5iRdTbaoHYP27HquWyUNX0gjnVai5MfNzwUiJI2kBaStmGuIY\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
},
"instances": [
{
@ -1277,7 +1277,7 @@
"bundle": "poster-design",
"scriptUrl": "./poster-design/poster-design.js",
"output": "dx-maps/poster-design/poster-design.js",
"integrity": "sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm",
"integrity": "sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI",
"elements": [
"poster-design"
],
@ -1300,7 +1300,7 @@
"scriptUrl": "./poster-design/poster-design.js",
"embed": {
"iframe": "<iframe src=\"./poster-design/poster-design-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=\"./poster-design/poster-design.js\" integrity=\"sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
}
},
{
@ -1316,7 +1316,7 @@
"scriptUrl": "./poster-design/poster-design.js",
"embed": {
"iframe": "<iframe src=\"./poster-design/poster-sizes/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=\"./poster-design/poster-design.js\" integrity=\"sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
}
},
{
@ -1332,7 +1332,7 @@
"scriptUrl": "./poster-design/poster-design.js",
"embed": {
"iframe": "<iframe src=\"./poster-design/poster-styles/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=\"./poster-design/poster-design.js\" integrity=\"sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
}
}
]
@ -1505,7 +1505,7 @@
"bundle": "posterverse",
"scriptUrl": "./posterverse/posterverse.js",
"output": "dx-maps/posterverse/posterverse.js",
"integrity": "sha384-1jvCHQrT11Mixml/KvdnT44djTWvIe6TjNk2vU66hwtF4mefgVCSIRhMwXukYAqZ",
"integrity": "sha384-SdRnjbGFsb/972hqwXtINbk7EFAAC++BUwdTv+qfQyBupa87B/ofj5uDzys/Rvi+",
"elements": [
"posterverse-app"
],
@ -1532,7 +1532,7 @@
"scriptUrl": "./posterverse/posterverse.js",
"embed": {
"iframe": "<iframe src=\"./posterverse/posterverse/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=\"./posterverse/posterverse.js\" integrity=\"sha384-1jvCHQrT11Mixml/KvdnT44djTWvIe6TjNk2vU66hwtF4mefgVCSIRhMwXukYAqZ\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-SdRnjbGFsb/972hqwXtINbk7EFAAC++BUwdTv+qfQyBupa87B/ofj5uDzys/Rvi+\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
},
"instances": [
{
@ -1617,7 +1617,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH",
"integrity": "sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb",
"elements": [
"regions-dashboard"
],
@ -1640,8 +1640,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1657,7 +1657,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-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"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-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1673,8 +1673,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -24,6 +24,6 @@
</head>
<body>
<plugin-studio tree-base="."></plugin-studio>
<script src="./plugin-studio/plugin-studio.js" integrity="sha384-fUBGsSNbCdWEJ6QNmeOJf78N88LmjL7b5ktyt11xm5iSZxM/u2xwNdPzHaT2A6Dz" crossorigin="anonymous"></script>
<script src="./plugin-studio/plugin-studio.js" integrity="sha384-KH5r1cFKUtfzOiE5iRdTbaoHYP27HquWyUNX0gjnVai5MfNzwUiJI2kBaStmGuIY" crossorigin="anonymous"></script>
</body>
</html>

View file

@ -306,7 +306,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj",
"integrity": "sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE",
"elements": [
"country-posters-map"
],
@ -329,8 +329,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -368,7 +368,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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"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-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -384,8 +384,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -401,7 +401,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/poster-section/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-XHZcAHcXeGm5RuVBdu6++sNnf/EYwqXFiEUIlam5JNMUHwSCuIjNTRiI5Ric0sYj\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"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-qqQFkG1sE7jcJgSvy3UTqStm7XXPomROEaDydN2SuK3G3im9wzg5fPpcBtoli6CE\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -505,7 +505,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-JbdbnT273exxkEbpgtOE3DxfYeQ1iPRockZeONzgkP1TJ3dITNCl4yRJ5KMXiXQb",
"integrity": "sha384-Wop35cwyXSmt+i3+2mobkc03oOirRiyyL4nx22fiPqN5uo0uQpegH4hE9PQnpHx7",
"elements": [
"doxa-maps-spa"
],
@ -535,7 +535,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-JbdbnT273exxkEbpgtOE3DxfYeQ1iPRockZeONzgkP1TJ3dITNCl4yRJ5KMXiXQb\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"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-Wop35cwyXSmt+i3+2mobkc03oOirRiyyL4nx22fiPqN5uo0uQpegH4hE9PQnpHx7\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
},
"instances": [
{
@ -1025,7 +1025,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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI",
"integrity": "sha384-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G",
"elements": [
"my-upg-100-list-map"
],
@ -1112,7 +1112,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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -1128,8 +1128,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -1145,8 +1145,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" 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-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]
@ -1219,7 +1219,7 @@
"bundle": "plugin-studio",
"scriptUrl": "./plugin-studio/plugin-studio.js",
"output": "dx-maps/plugin-studio/plugin-studio.js",
"integrity": "sha384-fUBGsSNbCdWEJ6QNmeOJf78N88LmjL7b5ktyt11xm5iSZxM/u2xwNdPzHaT2A6Dz",
"integrity": "sha384-KH5r1cFKUtfzOiE5iRdTbaoHYP27HquWyUNX0gjnVai5MfNzwUiJI2kBaStmGuIY",
"elements": [
"plugin-studio"
],
@ -1250,7 +1250,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-fUBGsSNbCdWEJ6QNmeOJf78N88LmjL7b5ktyt11xm5iSZxM/u2xwNdPzHaT2A6Dz\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"provider\":\"maplibre\",\"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-KH5r1cFKUtfzOiE5iRdTbaoHYP27HquWyUNX0gjnVai5MfNzwUiJI2kBaStmGuIY\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
},
"instances": [
{
@ -1277,7 +1277,7 @@
"bundle": "poster-design",
"scriptUrl": "./poster-design/poster-design.js",
"output": "dx-maps/poster-design/poster-design.js",
"integrity": "sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm",
"integrity": "sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI",
"elements": [
"poster-design"
],
@ -1300,7 +1300,7 @@
"scriptUrl": "./poster-design/poster-design.js",
"embed": {
"iframe": "<iframe src=\"./poster-design/poster-design-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=\"./poster-design/poster-design.js\" integrity=\"sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
}
},
{
@ -1316,7 +1316,7 @@
"scriptUrl": "./poster-design/poster-design.js",
"embed": {
"iframe": "<iframe src=\"./poster-design/poster-sizes/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=\"./poster-design/poster-design.js\" integrity=\"sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
}
},
{
@ -1332,7 +1332,7 @@
"scriptUrl": "./poster-design/poster-design.js",
"embed": {
"iframe": "<iframe src=\"./poster-design/poster-styles/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=\"./poster-design/poster-design.js\" integrity=\"sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
}
}
]
@ -1505,7 +1505,7 @@
"bundle": "posterverse",
"scriptUrl": "./posterverse/posterverse.js",
"output": "dx-maps/posterverse/posterverse.js",
"integrity": "sha384-1jvCHQrT11Mixml/KvdnT44djTWvIe6TjNk2vU66hwtF4mefgVCSIRhMwXukYAqZ",
"integrity": "sha384-SdRnjbGFsb/972hqwXtINbk7EFAAC++BUwdTv+qfQyBupa87B/ofj5uDzys/Rvi+",
"elements": [
"posterverse-app"
],
@ -1532,7 +1532,7 @@
"scriptUrl": "./posterverse/posterverse.js",
"embed": {
"iframe": "<iframe src=\"./posterverse/posterverse/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=\"./posterverse/posterverse.js\" integrity=\"sha384-1jvCHQrT11Mixml/KvdnT44djTWvIe6TjNk2vU66hwtF4mefgVCSIRhMwXukYAqZ\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-SdRnjbGFsb/972hqwXtINbk7EFAAC++BUwdTv+qfQyBupa87B/ofj5uDzys/Rvi+\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
},
"instances": [
{
@ -1617,7 +1617,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH",
"integrity": "sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb",
"elements": [
"regions-dashboard"
],
@ -1640,8 +1640,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1657,7 +1657,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-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"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-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1673,8 +1673,8 @@
"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": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\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<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -67,7 +67,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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -76,7 +76,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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -82,7 +82,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-kiObByXpzWi7T4m9M3zg9p21Ykm051Juxs5prWqiXFSmC0gYl/CXppWeE8OK+scI" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-WdfjKpu2QooaCQl6+/aV+/UO3J/FlaK/d9J6XhOxy9ej8r/eCl9MY9Kg/FvwXU4G" 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

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
<script src="../plugin-studio.js" integrity="sha384-fUBGsSNbCdWEJ6QNmeOJf78N88LmjL7b5ktyt11xm5iSZxM/u2xwNdPzHaT2A6Dz" crossorigin="anonymous"></script>
<script src="../plugin-studio.js" integrity="sha384-KH5r1cFKUtfzOiE5iRdTbaoHYP27HquWyUNX0gjnVai5MfNzwUiJI2kBaStmGuIY" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
<script src="../../plugin-studio.js" integrity="sha384-fUBGsSNbCdWEJ6QNmeOJf78N88LmjL7b5ktyt11xm5iSZxM/u2xwNdPzHaT2A6Dz" crossorigin="anonymous"></script>
<script src="../../plugin-studio.js" integrity="sha384-KH5r1cFKUtfzOiE5iRdTbaoHYP27HquWyUNX0gjnVai5MfNzwUiJI2kBaStmGuIY" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -73,7 +73,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <poster-design> custom element. -->
<script src="../poster-design.js" integrity="sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm" crossorigin="anonymous"></script>
<script src="../poster-design.js" integrity="sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI" 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

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <poster-design> custom element. -->
<script src="../poster-design.js" integrity="sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm" crossorigin="anonymous"></script>
<script src="../poster-design.js" integrity="sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <poster-design> custom element. -->
<script src="../poster-design.js" integrity="sha384-uvtlx1oHJ+2WUpBJG0UVWrXaWW7Ay3p23pn2SZGVwPAAh/QtCHF9cDKKn/t8+rTm" crossorigin="anonymous"></script>
<script src="../poster-design.js" integrity="sha384-+awg8qsCdVYBdgB1Ec63JiZ0c2706CsHG6N6yF75nighyxRkPd4U9Mt4lrVHJvXI" 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

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <posterverse-app> custom element. -->
<script src="../../posterverse.js" integrity="sha384-1jvCHQrT11Mixml/KvdnT44djTWvIe6TjNk2vU66hwtF4mefgVCSIRhMwXukYAqZ" crossorigin="anonymous"></script>
<script src="../../posterverse.js" integrity="sha384-SdRnjbGFsb/972hqwXtINbk7EFAAC++BUwdTv+qfQyBupa87B/ofj5uDzys/Rvi+" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <posterverse-app> custom element. -->
<script src="../posterverse.js" integrity="sha384-1jvCHQrT11Mixml/KvdnT44djTWvIe6TjNk2vU66hwtF4mefgVCSIRhMwXukYAqZ" crossorigin="anonymous"></script>
<script src="../posterverse.js" integrity="sha384-SdRnjbGFsb/972hqwXtINbk7EFAAC++BUwdTv+qfQyBupa87B/ofj5uDzys/Rvi+" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -76,7 +76,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-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb" 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

@ -67,7 +67,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-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -78,7 +78,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-6HQ0iqiKSPXGEMz9Gaodcy/a4EuOodmCy132i67qRqshEV/97lFvS4RrOJQymntH" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-jo4fOr0Hs5kNzNKaecPNSZdfBF+UbqRAX6pdYAaJQIUOFdduFDJezcUGNqroThmb" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: