Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
build-gate / build (push) Waiting to run

# Conflicts:
#	public/doxa-maps/index.html
This commit is contained in:
doxa-maps-88655c70 2026-09-24 23:32:16 +00:00
commit b6254567e1
11 changed files with 326 additions and 62 deletions

View file

@ -49,6 +49,55 @@ async function measure(width) {
const page = await browser.newPage({ viewport: { width, height: 1000 }, isMobile: mobile, hasTouch: mobile })
await page.goto(LEAF, { waitUntil: 'load', timeout: 45000 })
await page.waitForTimeout(10000)
// ★ HOVER IS INVISIBLE TO A STATIC PROBE, which is exactly how the blue outline stayed
// unbuilt for so long. Repeated reports asked to "restore the blue outline around the poster";
// measuring the loaded page found `outline:none` on all 136 framed elements and concluded it had
// never existed — true, and useless, because the affordance being asked for only exists WHILE
// THE POINTER IS OVER THE CARD. The pointer has to actually move.
//
// ⚠ AND IT MUST NOT MOVE ANYTHING. The whole rest of this gate asserts where the print row sits.
// A hover that grew a border would re-lay-out the card and push that row, so a cosmetic state
// could break a gate about print controls. Outline paints outside the box; this asserts the box
// WIDTH is identical hovered and not, which is the property that keeps the two independent.
let hover = null
try {
const target = await page.evaluate(`(() => {
const all = (${WALK})(document)
const c = all.filter((e) => (e.className||'').toString().split(' ').includes('card'))
.filter((e) => { const b = e.getBoundingClientRect(); return b.width > 2 && b.height > 2 })[0]
if (!c) return null
c.scrollIntoView({ block: 'center' })
const r = c.getBoundingClientRect()
const cs = getComputedStyle(c)
// ⚠ MEASURE clientWidth, NOT THE BORDER BOX. The card is a GRID ITEM, so its outer width is
// fixed by the track (minmax(210px,1fr)) and CANNOT change no matter what the hover rule
// does — a border grown on hover eats INWARD and leaves getBoundingClientRect().width
// identical. Asserting on the outer width is therefore unfalsifiable here, which is a
// decorative assertion, not a real one. MEASURED: growing border-width to 4px on hover left
// the outer width at 212px and moved nothing this gate was watching. clientWidth is the
// CONTENT box — it shrinks when the border grows, and it is what actually repositions the
// print row, which is the property worth protecting.
return { x: r.x + r.width / 2, y: r.y + r.height / 2, w: Math.round(r.width),
inner: c.clientWidth, outlineStyle: cs.outlineStyle }
})()`)
if (target) {
await page.mouse.move(target.x, target.y)
await page.waitForTimeout(500)
const hovered = await page.evaluate(`(() => {
const all = (${WALK})(document)
const c = all.filter((e) => (e.className||'').toString().split(' ').includes('card'))
.filter((e) => { const b = e.getBoundingClientRect(); return b.width > 2 && b.height > 2 })[0]
if (!c) return null
const cs = getComputedStyle(c)
return { outlineStyle: cs.outlineStyle, outlineColor: cs.outlineColor,
outlineWidth: cs.outlineWidth, w: Math.round(c.getBoundingClientRect().width),
inner: c.clientWidth }
})()`)
if (hovered) hover = { before: target, after: hovered }
}
} catch (_) { /* hover is an enhancement; never let it break the geometry run */ }
const out = await page.evaluate(`(() => {
const all = (${WALK})(document)
const vis = (e) => { const b = e.getBoundingClientRect(); return b.width > 2 && b.height > 2 }
@ -115,7 +164,7 @@ async function measure(width) {
controls: ctrls.length, tiny: tiny.length }
})()`)
await page.close()
return { width, ...out }
return { width, hover, ...out }
}
const results = []
@ -134,7 +183,12 @@ for (const r of results) {
+ ` · over-preview ${r.coveringPreview}${r.worstY ? ` (${r.worstY}px)` : ''}`
+ ` · previews ${r.previewsPainted} painted`
+ `${r.previewsBroken ? `/${r.previewsBroken} BROKEN` : ''}${r.noPreview ? `/${r.noPreview} none` : ''}`
+ ` · ${r.controls} control(s), ${r.tiny} too small, ${r.clipped} clipped`)
+ ` · ${r.controls} control(s), ${r.tiny} too small, ${r.clipped} clipped`
+ (r.hover
? ` · hover ${r.hover.after.outlineStyle === 'none'
? 'no outline' + (r.width < 500 ? ' (touch, correct)' : '')
: `${r.hover.after.outlineWidth} ${r.hover.after.outlineColor}`}`
: ''))
}
const fails = []
@ -185,6 +239,34 @@ for (const r of results) {
+ `(top sits ${r.worstY}px above the image's bottom edge) — growing the row taller to fit it `
+ 'horizontally is not a fix')
}
// HOVER: the card must answer the pointer, and must not move when it does.
// Only asserted where a pointer exists — a touch viewport has `@media (hover:none)` suppressing
// the state deliberately, so demanding an outline there would fail the correct behaviour.
if (r.hover) {
const { before, after } = r.hover
const pointer = r.width >= 500
if (pointer) {
if (after.outlineStyle === 'none') {
fails.push(`${at}: hovering a card paints NO outline — the card does not answer the `
+ 'pointer, so a reader cannot tell which poster their click is about to act on')
} else if (before.outlineStyle !== 'none') {
fails.push(`${at}: the card carries an outline even when NOT hovered `
+ `(${before.outlineStyle}) — the outline is meant to be the hover affordance, not a `
+ 'permanent frame')
}
if (after.w !== before.w || after.inner !== before.inner) {
fails.push(`${at}: hovering RESIZED the card `
+ `(outer ${before.w}->${after.w}px, content ${before.inner}->${after.inner}px). The `
+ 'hover state must not re-lay-out anything: every print-row assertion above is about '
+ 'where that row sits, and a cosmetic hover that moves the box can break them. Use '
+ 'outline (painted OUTSIDE the box and taking no space), never a border change.')
}
} else if (after.outlineStyle !== 'none') {
fails.push(`${at}: a TOUCH viewport painted a hover outline (${after.outlineStyle}) — with `
+ 'no pointer this either never appears or sticks after a tap, leaving a card looking '
+ 'permanently selected')
}
}
}
if (fails.length) {

View file

@ -0,0 +1,158 @@
#!/usr/bin/env node
/**
* check-shared-component-reached-bundles — a shared fix is only as deployed as the last rebuild
* of every bundle that consumes it.
*
* THE DEFECT THIS CATCHES. library/ is the single source of truth, but EVERY BUNDLE SHIPS ITS OWN
* COMPILED COPY of the components it imports. So a one-line fix in a shared component is live in
* whichever bundles happened to be rebuilt afterwards, and silently absent from the rest. Nothing
* detects it: every other gate measures the RENDERED BEHAVIOUR OF ONE LEAF, and a leaf whose
* bundle is stale renders the old behaviour perfectly.
*
* MEASURED 2026-09-24, two independent instances in one week:
* - PosterWall hover outline (landed a7f57c1): present in my-upg-100-list, ABSENT from
* key-cities, country-posters and regions-dashboard — 3 of 4 consumers stale.
* - SemanticTreeLegend legend.a11y keys: present in 4 bundles, ABSENT from research-dashboard —
* so a legend there still ships nameless carets while check:a11y-button-names passes 791/791,
* because that gate only sees the caret on leaves where an expandable branch happens to render.
* The second one was nobody's active work. It had simply been sitting there.
*
* ★ IT ASSERTS A MARKER STRING, NOT A TIMESTAMP OR A HASH. Comparing mtimes says "this bundle is
* older" and produces noise on every unrelated rebuild; comparing hashes cannot work at all,
* because a bundle legitimately differs from the library source it compiled. The question that
* actually matters is narrower and answerable: DOES THE BUILT BUNDLE CONTAIN THIS SPECIFIC CHANGE?
* A distinctive literal from the change answers exactly that, and it is the same technique used to
* prove a bundle is not stale after a rebuild.
*
* ⚠ THE MARKER MUST SURVIVE MINIFICATION. A CSS rule, a translation key or a user-visible string
* does; a variable name, a comment or whitespace does not. Markers are declared in MARKERS below
* with the commit that introduced them, so a reader can tell whether a red line means "not rebuilt"
* or "this marker was legitimately removed and the entry is stale".
*
* Usage: node internal/check-shared-component-reached-bundles.mjs
* SKIPs when the built tree is absent.
*/
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = process.env.DOXA_REPO || resolve(HERE, '..')
const TREE = resolve(REPO, 'public/doxa-maps')
// Each entry: a change that landed in a SHARED component, and a string that proves a built bundle
// contains it. `source` is where the marker must also still exist — if it does not, the marker is
// stale and the gate says so rather than failing every bundle.
const MARKERS = [
{
id: 'posterwall-hover-outline',
component: 'library/components/poster/PosterWall.vue',
marker: 'outline:2px solid #2f6fdb',
landed: 'a7f57c1',
why: 'the blue hover outline on poster cards',
},
{
id: 'legend-a11y-keys',
component: 'library/components/legends/SemanticTreeLegend.vue',
marker: 'legend.a11y',
landed: 'regions, c4c60a4 and earlier',
why: 'accessible names + aria-expanded on the legend row caret and clear chip',
},
]
if (!existsSync(TREE)) {
console.log(`SHARED-REACHED-BUNDLES: SKIP — no built tree at ${TREE}.`)
console.log(' Build first (bun run tree). Absence is not a pass.')
process.exit(0)
}
// WHICH BUNDLES CONSUME A COMPONENT IS DERIVED, NOT LISTED. A hardcoded consumer list goes stale
// the moment someone adopts a shared component, and a gate that silently stops covering a new
// consumer is worse than no gate. Resolve it from the imports instead.
const consumersOf = (componentPath) => {
const name = componentPath.split('/').pop() // PosterWall.vue
const out = new Set()
const designs = resolve(REPO, 'plugin-designs')
if (!existsSync(designs)) return out
const walk = (dir, depth = 0) => {
if (depth > 4) return
for (const e of readdirSync(dir)) {
if (e === 'node_modules' || e.startsWith('.')) continue
const p = join(dir, e)
let st
try { st = statSync(p) } catch { continue }
if (st.isDirectory()) { walk(p, depth + 1); continue }
if (!/\.(vue|js|mjs)$/.test(e)) continue
let text
try { text = readFileSync(p, 'utf8') } catch { continue }
// an import of this component, by path or by the bare stem
if (text.includes(componentPath.replace(/^library\//, '@map/')) || text.includes('/' + name)) {
// the BUNDLE is the first directory under plugin-designs/
const rel = p.slice(designs.length + 1)
out.add(rel.split('/')[0])
}
}
}
walk(designs)
return out
}
console.log('SHARED-REACHED-BUNDLES')
const fails = []
const staleMarkers = []
for (const m of MARKERS) {
const srcPath = resolve(REPO, m.component)
if (!existsSync(srcPath)) {
staleMarkers.push(`${m.id}: source ${m.component} no longer exists`)
continue
}
const src = readFileSync(srcPath, 'utf8')
if (!src.includes(m.marker)) {
// THE MARKER IS GONE FROM SOURCE. That is a stale ENTRY, not a stale bundle — failing every
// consumer here would blame the bundles for a change that was legitimately reverted.
staleMarkers.push(`${m.id}: marker "${m.marker}" is no longer in ${m.component} — `
+ 'the change was reverted or reworded; update or drop this entry')
continue
}
const consumers = [...consumersOf(m.component)].sort()
const have = []
const missing = []
const unbuilt = []
for (const bundle of consumers) {
const built = join(TREE, bundle, `${bundle}.js`)
if (!existsSync(built)) { unbuilt.push(bundle); continue }
const js = readFileSync(built, 'utf8')
;(js.includes(m.marker) ? have : missing).push(bundle)
}
console.log(` ${m.id} — ${m.why}`)
console.log(` landed ${m.landed}; ${have.length} of ${have.length + missing.length} built `
+ `consumer(s) carry it${unbuilt.length ? `, ${unbuilt.length} not built` : ''}`)
if (have.length) console.log(` ✓ ${have.join(', ')}`)
if (missing.length) console.log(` ✗ ${missing.join(', ')}`)
if (unbuilt.length) console.log(` · not built: ${unbuilt.join(', ')}`)
if (missing.length) {
fails.push(`${m.id}: ${missing.length} bundle(s) do NOT carry "${m.why}" — ${missing.join(', ')}`)
}
}
if (staleMarkers.length) {
console.log('\n marker entries needing attention (NOT bundle failures):')
for (const s of staleMarkers) console.log(` · ${s}`)
}
if (fails.length) {
console.error(`\nSHARED-REACHED-BUNDLES: FAIL — ${fails.length} shared change(s) have not reached every consumer.`)
for (const f of fails) console.error(' - ' + f)
console.error('\n THIS IS A REBUILD, NOT A CODE CHANGE. The fix is already in library/; each bundle')
console.error(' ships its own COMPILED copy, so a shared change is only as deployed as the last')
console.error(' rebuild of each consumer. Rebuild the named bundles:')
console.error(' BUNDLE=<name> bun x vite build && bun run tree')
console.error('\n Every other gate measures the rendered behaviour of ONE leaf, and a leaf whose')
console.error(' bundle is stale renders the OLD behaviour perfectly — so they all stay green.')
process.exit(1)
}
console.log('\nSHARED-REACHED-BUNDLES: PASS — every tracked shared change reached every built consumer.')

View file

@ -477,7 +477,30 @@ useShadowStyles(
'.card{position:relative;background:#fff;border:1px solid #e3e6ea;border-radius:3px;' +
'padding:24px 12px 26px;box-shadow:0 1px 3px rgba(0,0,0,.10),0 6px 18px rgba(0,0,0,.07);' +
'transition:transform .16s ease,box-shadow .16s ease}' +
'.card:hover{transform:translateY(-3px);box-shadow:0 3px 8px rgba(0,0,0,.14),0 12px 28px rgba(0,0,0,.10)}' +
// ★ THE BLUE OUTLINE IS A HOVER STATE, NOT A MISSING STATIC BORDER. Several reports have asked
// for "the blue outline around the poster" to be restored, and MEASURED 2026-09-24 there has
// never been one: all 136 elements painting a frame on this wall compute `outline:none`, and
// every border is grey (.card #e3e6ea, .main #dfe3e7). Nothing regressed — the affordance was
// never built. Asked for as Pinterest/Instagram do it: the card answers the pointer.
//
// ⚠ `outline`, NOT `border`, and this is the load-bearing choice. An outline is painted OUTSIDE
// the box and takes no space, so it cannot move anything. Growing the border on hover would
// re-lay-out the card by 2px and push the print row — whose geometry is asserted at five widths
// by check:poster-card-fits (0 overflow / 0 over-preview / 0 neighbour intrusions) — meaning a
// cosmetic hover would break a gate about print controls. outline-offset keeps it clear of the
// rounded corner instead of cutting across it.
//
// ⚠ :focus-visible CARRIES IT TO THE KEYBOARD. A hover-only affordance tells a mouse user which
// card they are about to act on and tells a keyboard user nothing, on a card whose buttons this
// same wall already had to be fixed to name (791/791 accessible names).
'.card:hover,.card:focus-within{transform:translateY(-3px);' +
'box-shadow:0 3px 8px rgba(0,0,0,.14),0 12px 28px rgba(0,0,0,.10);' +
'outline:2px solid #2f6fdb;outline-offset:2px}' +
'.is-dark .card:hover,.is-dark .card:focus-within{outline-color:#5aa0ff}' +
// ⚠ A TOUCH READER NEVER HOVERS. On a phone the lift and the outline would either never appear
// or, worse, stick after a tap and leave a card looking selected. The print row on this wall is
// always visible, so touch loses no affordance by dropping the hover state entirely.
'@media (hover:none){.card:hover,.card:focus-within{transform:none;outline:none}}' +
'.is-dark .card{background:#132019;border-color:rgba(110,231,183,.20)}' +
// box-sizing is NOT inherited into a shadow root from the host page's reset.
'.tape{box-sizing:border-box;position:absolute;left:50%;transform:translateX(-50%);z-index:2;' +

View file

@ -178,7 +178,8 @@
"check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs # the PACKET catalog (packet-strings.json) must be whole in every language: all string keys, all 12 prayer points with every field, all 12 wheel rhythms. check:i18n-parity covers library/i18n/locales only — six gates READ this file and none checked it. Asserts PRESENCE, not translatedness, with point4_passage declared-absent on record.",
"check:poster-degrades": "node internal/check-poster-degrades-honestly.mjs # with NEITHER poster nor source — the DEFAULT drop — nothing must print and the reader must be told why. check:poster-print-fallback SKIPs that case, so the behaviour a reader most often meets had no gate. MUST be served from the TREE ROOT, not public/, or assetsBase resolves outside the tree and the poster loads when it should 404.",
"check:upg-map-data": "node internal/check-upg-map-data-painted.mjs # the UPG-100 map must carry OUR polygons/zones/pins, not just a basemap — reads the counter the profile itself writes. Pass live URLs to audit what is published.",
"check:published": "node internal/check-published-matches-build.mjs # what readers are SERVED must be what we BUILT. Every other gate measures the local tree, so all of them stay green while a stale publish serves something else — and manufactures bug reports that cannot be reproduced. Compares the sha384 per bundle. SKIPs when the published host is unreachable (a network gate that goes red on flaky wifi gets ignored)."
"check:published": "node internal/check-published-matches-build.mjs # what readers are SERVED must be what we BUILT. Every other gate measures the local tree, so all of them stay green while a stale publish serves something else — and manufactures bug reports that cannot be reproduced. Compares the sha384 per bundle. SKIPs when the published host is unreachable (a network gate that goes red on flaky wifi gets ignored).",
"check:shared-reached": "node internal/check-shared-component-reached-bundles.mjs # a shared library fix is only as deployed as the last rebuild of EVERY bundle that consumes it. Each bundle ships its own COMPILED copy, so a one-line fix in library/ is live in whichever bundles happened to be rebuilt after it and silently absent from the rest — and every other gate measures ONE leaf, which renders the old behaviour perfectly. Asserts a distinctive marker string per tracked change; consumers are DERIVED from imports, never hardcoded."
},
"devDependencies": {
"@capacitor/android": "^7.6.9",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -681,7 +681,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "doxa-maps/my-upg-100-list/my-upg-100-list.js",
"integrity": "sha384-p29J0vBYB023wjm27/6NkyLanFQot5B0C48ZZnnyxC9yQTGXva9kRPfjdJeUOj6y",
"integrity": "sha384-NlPYE8Ka9bbUPBeUKZ8tppvBrvF9xdMXZlHtaUym05SxsppfTes/YtnAbr1wq4es",
"elements": [
"my-upg-100-list-map"
],
@ -701,7 +701,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-p29J0vBYB023wjm27/6NkyLanFQot5B0C48ZZnnyxC9yQTGXva9kRPfjdJeUOj6y\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NlPYE8Ka9bbUPBeUKZ8tppvBrvF9xdMXZlHtaUym05SxsppfTes/YtnAbr1wq4es\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -715,7 +715,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<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-p29J0vBYB023wjm27/6NkyLanFQot5B0C48ZZnnyxC9yQTGXva9kRPfjdJeUOj6y\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<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-NlPYE8Ka9bbUPBeUKZ8tppvBrvF9xdMXZlHtaUym05SxsppfTes/YtnAbr1wq4es\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -729,7 +729,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<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-p29J0vBYB023wjm27/6NkyLanFQot5B0C48ZZnnyxC9yQTGXva9kRPfjdJeUOj6y\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<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-NlPYE8Ka9bbUPBeUKZ8tppvBrvF9xdMXZlHtaUym05SxsppfTes/YtnAbr1wq4es\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-p29J0vBYB023wjm27/6NkyLanFQot5B0C48ZZnnyxC9yQTGXva9kRPfjdJeUOj6y" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-NlPYE8Ka9bbUPBeUKZ8tppvBrvF9xdMXZlHtaUym05SxsppfTes/YtnAbr1wq4es" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-p29J0vBYB023wjm27/6NkyLanFQot5B0C48ZZnnyxC9yQTGXva9kRPfjdJeUOj6y" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-NlPYE8Ka9bbUPBeUKZ8tppvBrvF9xdMXZlHtaUym05SxsppfTes/YtnAbr1wq4es" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-p29J0vBYB023wjm27/6NkyLanFQot5B0C48ZZnnyxC9yQTGXva9kRPfjdJeUOj6y" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-NlPYE8Ka9bbUPBeUKZ8tppvBrvF9xdMXZlHtaUym05SxsppfTes/YtnAbr1wq4es" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: