Merge main into bundler-1/work (regenerate the built tree, re-hash)
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
The nine conflicts were all in generated artifacts under public/. Built files are not hand-merged: every bundle whose source either side touched was rebuilt, the tree regenerated against main's new generate-tree, and the hashes re-baked. Source merged clean — main never touched PosterWall.vue or en/regions.json, they only appeared in a two-dot diff because this branch changed them.
This commit is contained in:
commit
61dcd9b28e
99 changed files with 5367 additions and 559 deletions
75
data/legacy/upg-neighbor/index.html
Normal file
75
data/legacy/upg-neighbor/index.html
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>legacy map host</title>
|
||||
<style>html,body{margin:0;height:100%;background:#f3f3f1}.host{position:fixed;inset:0}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="host" id="host"></div>
|
||||
<script>
|
||||
// Sandboxed host for the LEGACY upg-neighbor IIFE (pre-bundler architecture). This page runs
|
||||
// inside an IFRAME so the old engine and its dependencies cannot collide with the modern
|
||||
// bundles -- the isolation pattern the roundup mandates ("add without breaking doxa-maps").
|
||||
var q = new URLSearchParams(location.search);
|
||||
var el = (q.get('el') || 'send-map').replace(/[^a-z0-9-]/g, '');
|
||||
var tk = q.get('tk') || '';
|
||||
// Token globals the legacy build may read.
|
||||
window.MAPBOX_TOKEN = tk; window.MAP_TOKEN = tk; window.UPG_TOKEN = tk;
|
||||
// The legacy code fetches relative "/api/people-groups/..." which only resolved on its old
|
||||
// origin. Shim fetch so those calls reach the live API from ANY host (static tree, iframe).
|
||||
var API = 'https://pray.doxa.life';
|
||||
// The cockpit profile fails closed without a configured API base and names this global in
|
||||
// its own error banner (measured: "No API base configured (apiBase / window.MAP_APP_API_URL
|
||||
// / VITE_API_BASE_URL) -- failing closed").
|
||||
window.MAP_APP_API_URL = API;
|
||||
var origFetch = window.fetch.bind(window);
|
||||
window.fetch = function (input, init) {
|
||||
try {
|
||||
var url = typeof input === 'string' ? input : (input && input.url) || '';
|
||||
if (url.indexOf('/api/') === 0) return origFetch(API + url, init);
|
||||
} catch (e) { /* fall through */ }
|
||||
return origFetch(input, init);
|
||||
};
|
||||
// ── KEYLESS SHIM ──────────────────────────────────────────────────────────
|
||||
// The legacy bundle expects a mapboxgl GLOBAL and requests mapbox://styles/mapbox/light-v11.
|
||||
// MapLibre is API-compatible with the mapbox-gl v1 surface the bundle uses, and OpenFreeMap
|
||||
// Positron is the keyless twin of light-v11 -- so the shim below makes every legacy map run
|
||||
// WITHOUT a Mapbox token: load our self-hosted MapLibre, alias it as mapboxgl, and rewrite
|
||||
// any mapbox:// style to the keyless URL inside a patched Map constructor.
|
||||
var KEYLESS_STYLE = 'https://tiles.openfreemap.org/styles/positron';
|
||||
var lib = document.createElement('link'); lib.rel='stylesheet'; lib.href='./maplibre-gl.css';
|
||||
document.head.appendChild(lib);
|
||||
var ml = document.createElement('script');
|
||||
ml.src = './maplibre-gl.js';
|
||||
ml.onload = function () {
|
||||
var Real = window.maplibregl.Map;
|
||||
function PatchedMap (opts) {
|
||||
if (opts && typeof opts.style === 'string' && opts.style.indexOf('mapbox://') === 0) opts.style = KEYLESS_STYLE;
|
||||
return new Real(opts);
|
||||
}
|
||||
PatchedMap.prototype = Real.prototype;
|
||||
window.mapboxgl = Object.assign({}, window.maplibregl, { Map: PatchedMap, accessToken: '' });
|
||||
loadLegacy();
|
||||
};
|
||||
ml.onerror = function () { loadLegacy(); };
|
||||
document.head.appendChild(ml);
|
||||
|
||||
function loadLegacy () {
|
||||
var s = document.createElement('script');
|
||||
s.src = './upg-neighbor.iife.js';
|
||||
s.onload = function () {
|
||||
var node = document.createElement(el);
|
||||
node.style.cssText = 'display:block;width:100%;height:100%';
|
||||
// The legacy elements use the SAME profile-config attribute contract as the modern
|
||||
// bundles (measured: without it the element renders its own usage banner).
|
||||
node.setAttribute('profile-config', JSON.stringify({ profile: el, tk: tk }));
|
||||
document.getElementById('host').appendChild(node);
|
||||
};
|
||||
s.onerror = function () { document.body.innerHTML = '<p style="font:14px system-ui;padding:20px">legacy bundle failed to load</p>'; };
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
data/legacy/upg-neighbor/maplibre-gl.css
Normal file
1
data/legacy/upg-neighbor/maplibre-gl.css
Normal file
File diff suppressed because one or more lines are too long
59
data/legacy/upg-neighbor/maplibre-gl.js
Normal file
59
data/legacy/upg-neighbor/maplibre-gl.js
Normal file
File diff suppressed because one or more lines are too long
548
data/legacy/upg-neighbor/upg-neighbor.iife.js
Normal file
548
data/legacy/upg-neighbor/upg-neighbor.iife.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -36,7 +36,7 @@ and a change here can break other maps.
|
|||
`import … from '@map/components/legends/LegendProgressBar.vue'`
|
||||
- **LegendRows** — (no description)
|
||||
`import … from '@map/components/legends/LegendRows.vue'`
|
||||
- **LegendTools** — LegendTools.vue — Fly + Clusters floating toolbar
|
||||
- **LegendTools** — Does the Fly + Clusters toolbar render for this legend type?
|
||||
`import … from '@map/components/legends/LegendTools.vue'`
|
||||
- **SemanticTreeLegend** — SemanticTreeLegend — reusable hierarchical legend for any one-to-many semantic tree.
|
||||
`import … from '@map/components/legends/SemanticTreeLegend.vue'`
|
||||
|
|
|
|||
33
documentation/vendored-font-noto-serif-devanagari.md
Normal file
33
documentation/vendored-font-noto-serif-devanagari.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!-- MOVED OUT OF THE SERVED FOLDER, 2026-09-24. This lived at
|
||||
public/assets/fonts/noto-serif-devanagari/README.md, where check:public-hygiene flagged it
|
||||
(twice — the source copy and the copy generate-tree stages into the tree) as "documentation in
|
||||
a served folder", and that finding was red-gating build:unlocked for every builder.
|
||||
The CONTENT is kept verbatim because it answers a question the files cannot: why this font is
|
||||
vendored at all, and why 400 and 700 resolve to one variable file.
|
||||
The font's LICENSE (SIL OFL 1.1) deliberately STAYS beside the woff2 — that one is a
|
||||
distribution obligation, not documentation, and removing it would breach the licence. -->
|
||||
|
||||
# Noto Serif Devanagari — the Devanagari subset only
|
||||
|
||||
**Why vendored:** the prayer packet's font stack is Georgia / Palatino Linotype /
|
||||
serif, none of which carry Devanagari. Measured by rendering Devanagari at 48px
|
||||
and READING THE PIXELS, Hindi came out as **tofu boxes** — this machine has zero
|
||||
Devanagari fonts (`fc-list`). A `hi` translation without this file would ship a
|
||||
page of empty rectangles.
|
||||
|
||||
**Licence:** SIL Open Font License 1.1 (see `LICENSE`). Free to bundle and serve.
|
||||
|
||||
**Subset:** `U+0900-097F` plus the Devanagari extensions Google's own subsetting
|
||||
selects — the *devanagari* range ONLY, matching how `poppins/` ships `latin` and
|
||||
`latin-ext` separately so a page downloads only what it needs. 124 KB, against
|
||||
6-8 KB for a Latin subset; that is the cost of the script's glyph count, and it
|
||||
loads only on a Hindi packet.
|
||||
|
||||
**Source:** the woff2 Google Fonts serves for
|
||||
`Noto+Serif+Devanagari:wght@400;700` with a modern browser UA. Note the 400 and
|
||||
700 faces resolve to the SAME file — it is a variable font, so one file covers
|
||||
both weights and vendoring two would duplicate 124 KB for nothing.
|
||||
|
||||
To refresh: request that CSS with a Chrome user-agent (without one Google serves
|
||||
unsubsetted TTF), take the `@font-face` block whose `unicode-range` contains
|
||||
`U+0900`, and download its woff2.
|
||||
|
|
@ -58,6 +58,24 @@ const lang = await page.evaluate(() => document.documentElement.lang || 'en')
|
|||
// the bug it is, and no other assertion here would notice.
|
||||
const RTL = new Set(['ar', 'he', 'fa', 'ur', 'ps', 'sd', 'yi'])
|
||||
const dir = await page.evaluate(() => document.documentElement.dir || 'ltr')
|
||||
// SCRIPT FONT. Some languages need a face the system stack does not carry — Hindi
|
||||
// renders as tofu boxes in Georgia/Palatino/serif. When the packet declares one,
|
||||
// it must actually LOAD; a declared-but-failed @font-face is silent, and the page
|
||||
// then prints as empty rectangles with every other assertion still green.
|
||||
//
|
||||
// ⚠ This asserts the FONT LOADED, not that glyphs are correct. A general tofu
|
||||
// detector was attempted and abandoned: comparing shaped width against a missing
|
||||
// font false-positives on every system-covered script (Cyrillic, CJK, Arabic all
|
||||
// legitimately measure the same in both stacks), and an ink-profile heuristic
|
||||
// scored CORRECTLY-RENDERED Devanagari the same as tofu, because its baseline bar
|
||||
// gives a uniform column profile. A detector that blocks working packets is worse
|
||||
// than none. Read the pixels for that; this covers the mechanical half.
|
||||
const fontStatus = await page.evaluate(async () => {
|
||||
await document.fonts.ready
|
||||
const faces = [...document.fonts]
|
||||
return { declared: faces.length, loaded: faces.filter((f) => f.status === 'loaded').length,
|
||||
names: faces.map((f) => `${f.family}:${f.status}`) }
|
||||
})
|
||||
// The catalog sits beside the generator that produced this packet, so derive it
|
||||
// from the packet's own path rather than hardcoding a machine-specific location.
|
||||
const catalogPath = process.env.PACKET_STRINGS
|
||||
|
|
@ -145,6 +163,10 @@ if (r.rhythms !== r.rhythmsTotal) fails.push(`only ${r.rhythms}/${r.rhythmsTotal
|
|||
if (!r.hasMark5) fails.push('Mark 5:1-20 is NOT printed in full (Driver requirement for point 4)')
|
||||
if (r.metacamp !== 2) fails.push(`${r.metacamp} metacamp.org attributions, expected exactly 2`)
|
||||
if (r.mapFaults.length) fails.push(`map: ${r.mapFaults.join('; ')}`)
|
||||
if (fontStatus.declared && fontStatus.loaded !== fontStatus.declared) {
|
||||
fails.push(`a declared @font-face did not load (${fontStatus.names.join(', ')}) — `
|
||||
+ 'text in that script will print as empty boxes')
|
||||
}
|
||||
if (RTL.has(lang) && dir !== 'rtl') fails.push(`lang="${lang}" is right-to-left but dir="${dir}"`)
|
||||
if (!RTL.has(lang) && dir === 'rtl') fails.push(`lang="${lang}" is not RTL but dir="rtl"`)
|
||||
if (translated === 0) {
|
||||
|
|
@ -162,6 +184,7 @@ console.log(` Mark 5 in full : ${r.hasMark5}`)
|
|||
console.log(` metacamp.org : ${r.metacamp}`)
|
||||
console.log(` map : ${r.mapFaults.length ? r.mapFaults.join('; ') : 'palette resolves, not black'}`)
|
||||
console.log(` page size : ${boxes[0] ? boxes[0].join(' x ') : '?'} pt`)
|
||||
console.log(` script font : ${fontStatus.declared ? fontStatus.names.join(', ') : 'system stack'}`)
|
||||
console.log(` language : ${lang} (dir=${dir}) (${r.rhythms}/${r.rhythmsTotal} rhythms in ${lang}`
|
||||
+ `${translated === null ? '' : `, ${translated} strings translated`})`)
|
||||
|
||||
|
|
|
|||
115
internal/check-share-policy.mjs
Normal file
115
internal/check-share-policy.mjs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// check-share-policy — one written rule for WHICH MAPS CARRY A SHARE BUTTON.
|
||||
//
|
||||
// Driver: "Some maps need a map-level share button and some do not. The Prayer
|
||||
// Progress map is the one intended for embedding on other people's websites, so it
|
||||
// MUST have a share button. UPG-100 must not (the header already provides it).
|
||||
// Decide + record the per-map rule, then apply it."
|
||||
//
|
||||
// The BEHAVIOUR already existed on both of those. What did not exist was the RULE,
|
||||
// so nothing stopped the next agent adding a share button to a map that should not
|
||||
// have one, or leaving a published map with no way to share it at all. Surveying
|
||||
// all 11 map bundles found FOUR different patterns in use and two published maps
|
||||
// with no sharing path by any mechanism.
|
||||
//
|
||||
// ── THE RULE ────────────────────────────────────────────────────────────────
|
||||
// A map bundle must have EXACTLY ONE share affordance, and it belongs at the
|
||||
// OUTERMOST surface a reader actually sees:
|
||||
//
|
||||
// own-button the map IS the product surface -> <ShareButton> in the map
|
||||
// header a composite wraps it with a header -> the header owns sharing,
|
||||
// the map omits the button
|
||||
// composite a parent stacks it and overrides
|
||||
// shareUrl so the PARENT is shared -> child button is fine,
|
||||
// parent supplies the url
|
||||
// none not published — nothing to share yet
|
||||
//
|
||||
// TWO share buttons on one surface is the defect this prevents (it shipped once:
|
||||
// BUG-UPG100-DOUBLE-SHARE). ZERO on a published map is the opposite defect, and it
|
||||
// is currently REAL on two bundles — recorded below as known debt rather than
|
||||
// silently passing, so the count cannot drift without someone deciding.
|
||||
import { readFileSync, existsSync, readdirSync } from 'node:fs'
|
||||
import { resolve, join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
// Same fix as check-zone-census.mjs, same reason. DOXA_REPO still wins; only the FALLBACK moved
|
||||
// off an absolute path into one author's worktree. check:no-abs-paths rejects those outright — "a
|
||||
// clone must work on any machine" — and it runs inside build:unlocked, so the hardcoded default
|
||||
// turned the whole build red for every builder. This is the idiom that gate names in its own
|
||||
// failure text, and the one every sibling script here uses.
|
||||
const REPO = process.env.DOXA_REPO
|
||||
|| resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const DESIGNS = join(REPO, 'plugin-designs')
|
||||
|
||||
// The decided policy, per bundle. A map bundle MUST appear here — an unlisted one
|
||||
// fails, so adding a map forces the sharing decision rather than deferring it.
|
||||
const POLICY = {
|
||||
'doxa-simple-map': 'own-button', // Prayer Progress — the embed flagship
|
||||
'doxa-research-map': 'own-button',
|
||||
'country-posters': 'own-button',
|
||||
'cities-internet': 'own-button',
|
||||
'gospel-resources': 'own-button',
|
||||
'demo-map': 'own-button',
|
||||
'my-upg-100-list': 'header', // upg-100-mfe.vue header owns it
|
||||
'research-map-plus-grid': 'composite', // overrides child shareUrl to share itself
|
||||
'key-cities': 'none', // not published
|
||||
// KNOWN DEBT — published, and no sharing path by any mechanism. Listed so the
|
||||
// gate passes on today's reality while naming the gap; flipping either to
|
||||
// 'own-button' is a deliberate change, not a drive-by.
|
||||
'all-people-groups': 'debt-no-share',
|
||||
'prayer-walk-planner': 'debt-no-share',
|
||||
}
|
||||
|
||||
const isMap = (src) => /mapboxgl|maplibregl|useMapInstance/.test(src)
|
||||
const manifest = JSON.parse(readFileSync(join(REPO, 'public', 'doxa-maps', 'manifest.json'), 'utf8'))
|
||||
const published = new Set(manifest.bundles.map((b) => b.bundle))
|
||||
|
||||
const fails = []
|
||||
const rows = []
|
||||
for (const name of readdirSync(DESIGNS)) {
|
||||
const dir = join(DESIGNS, name)
|
||||
let vues = []
|
||||
try { vues = readdirSync(dir).filter((f) => f.endsWith('.vue')) } catch { continue }
|
||||
if (!vues.length) continue
|
||||
const src = vues.map((f) => readFileSync(join(dir, f), 'utf8')).join('\n')
|
||||
if (!isMap(src)) continue
|
||||
|
||||
const buttons = (src.match(/<ShareButton/g) || []).length
|
||||
const want = POLICY[name]
|
||||
rows.push({ name, buttons, want, published: published.has(name) })
|
||||
|
||||
if (!want) {
|
||||
fails.push(`${name}: a map bundle with no entry in POLICY — decide whether it shares, and record it`)
|
||||
continue
|
||||
}
|
||||
if (want === 'own-button' && buttons !== 1) {
|
||||
fails.push(`${name}: policy "own-button" but ${buttons} <ShareButton> — `
|
||||
+ (buttons === 0 ? 'the embed surface has no way to be shared' : 'two affordances on one surface'))
|
||||
}
|
||||
if (want === 'header' && buttons !== 1) {
|
||||
// UPG-100's single button lives in the MFE header, not the map — still exactly one.
|
||||
fails.push(`${name}: policy "header" expects exactly one share affordance in the bundle, found ${buttons}`)
|
||||
}
|
||||
if (want === 'debt-no-share' && buttons !== 0) {
|
||||
fails.push(`${name}: listed as known debt (no sharing) but now has ${buttons} — update POLICY`)
|
||||
}
|
||||
if (want === 'none' && published.has(name)) {
|
||||
fails.push(`${name}: policy "none" assumes unpublished, but it IS published — it needs a share path`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' bundle published buttons policy')
|
||||
for (const r of rows.sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
console.log(` ${r.name.padEnd(24)} ${(r.published ? 'yes' : 'no').padEnd(10)} ${String(r.buttons).padEnd(8)} ${r.want || '(UNDECIDED)'}`)
|
||||
}
|
||||
const debt = rows.filter((r) => r.want === 'debt-no-share')
|
||||
if (debt.length) {
|
||||
console.log(`\n known debt: ${debt.length} published map(s) with NO sharing path — `
|
||||
+ debt.map((d) => d.name).join(', '))
|
||||
}
|
||||
|
||||
if (fails.length) {
|
||||
console.error(`\nSHARE-POLICY: FAIL — ${fails.length} problem(s)`)
|
||||
for (const f of fails) console.error(' - ' + f)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`\nSHARE-POLICY: PASS — ${rows.length} map bundles, each with exactly one decided sharing rule.`)
|
||||
66
internal/check-zone-census.mjs
Normal file
66
internal/check-zone-census.mjs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// check-zone-census — every zone a USER CAN SELECT must have a poster.
|
||||
//
|
||||
// THE BUG CLASS THIS EXISTS FOR: a short count with no error. generate.mjs used to
|
||||
// abort on the first bad zone (16 of 29 written, the rest silently unbuilt), and
|
||||
// separately, tooling that enumerated clusters.json rather than the LEGEND warned
|
||||
// that two zones were "MISSING" when nothing was missing — they are superseded and
|
||||
// unreachable. Both look like success from a distance; only a census shows them.
|
||||
//
|
||||
// ★ ASK THE LEGEND, NOT THE DATA FILE. clusters.json carries 30 list definitions;
|
||||
// the legend exposes 28 leaves. deaf-old-world and deaf-new-world-asia-pacific are
|
||||
// collapsed into the unified `deaf` row (affinity bloc A017) — verified against
|
||||
// live records: 63 + 60 = 123 = A017, zero groups on either side of the
|
||||
// difference. They stay in clusters.json deliberately (three isolation-proof
|
||||
// scripts assert deaf_lists.length === 2), but no user can reach them, so a poster
|
||||
// for them is not a gap. Counting the file instead of the legend manufactures two
|
||||
// phantom failures and hides real ones behind them.
|
||||
//
|
||||
// Usage: node internal/check-zone-census.mjs [postersDir]
|
||||
import { readFileSync, existsSync, readdirSync } from 'node:fs'
|
||||
import { resolve, join, dirname } from 'node:path'
|
||||
import { pathToFileURL, fileURLToPath } from 'node:url'
|
||||
|
||||
// DOXA_REPO still wins, unchanged. Only the FALLBACK moved: it was an absolute path to one
|
||||
// author's worktree (/home/…/wt-packet), which check:no-abs-paths rejects outright — "a clone must
|
||||
// work on any machine" — and that gate is inside build:unlocked, so the whole build went red for
|
||||
// every builder, not just for whoever lacked that directory.
|
||||
// The replacement is the idiom check-no-absolute-paths.mjs names in its own failure text, and the
|
||||
// one every sibling script here uses: resolve from this file. The data it reads
|
||||
// (plugin-designs/my-upg-100-list/data) is present in this repo, so the default now resolves for
|
||||
// anyone who clones it, and a caller pointing at a different worktree still exports DOXA_REPO.
|
||||
const REPO = process.env.DOXA_REPO
|
||||
|| resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const DATA = join(REPO, 'plugin-designs', 'my-upg-100-list', 'data')
|
||||
const POSTERS = resolve(process.argv[2]
|
||||
|| join(REPO, 'library', 'scripts', 'posters', 'out', 'posters'))
|
||||
|
||||
const clusters = JSON.parse(readFileSync(join(DATA, 'clusters.json'), 'utf8'))
|
||||
const { buildLegendNodes, leafZones } = await import(pathToFileURL(join(DATA, 'legend-tree.js')).href)
|
||||
const leaves = leafZones(buildLegendNodes(clusters, { records: null }))
|
||||
|
||||
if (!existsSync(POSTERS)) {
|
||||
console.log(`ZONE-CENSUS: SKIP — no posters at ${POSTERS} (run generate.mjs first).`)
|
||||
process.exit(0)
|
||||
}
|
||||
const built = new Set(readdirSync(POSTERS).filter((f) => f.endsWith('.html')).map((f) => f.slice(0, -5)))
|
||||
const missing = leaves.map((z) => z.id).filter((id) => !built.has(id))
|
||||
|
||||
// Definitions that exist in the data but are NOT reachable — reported as context,
|
||||
// never as a failure, so the difference between "unbuilt" and "superseded" stays
|
||||
// visible instead of being argued about every time someone counts.
|
||||
const declared = new Set()
|
||||
for (const k of ['lists', 'deaf_lists', 'asia_lists', 'uncovered_lists']) {
|
||||
for (const z of clusters[k] || []) declared.add(z.id)
|
||||
}
|
||||
const unreachable = [...declared].filter((id) => !leaves.some((z) => z.id === id))
|
||||
|
||||
console.log(` selectable zones : ${leaves.length}`)
|
||||
console.log(` posters built : ${built.size}`)
|
||||
console.log(` unreachable defs : ${unreachable.length}${unreachable.length ? ` (${unreachable.join(', ')}) — superseded, not a gap` : ''}`)
|
||||
|
||||
if (missing.length) {
|
||||
console.error(`\nZONE-CENSUS: FAIL — ${missing.length} selectable zone(s) have NO poster:`)
|
||||
for (const id of missing) console.error(` - ${id}`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`\nZONE-CENSUS: PASS — every one of the ${leaves.length} selectable zones has a poster.`)
|
||||
|
|
@ -1352,6 +1352,10 @@ function main() {
|
|||
['vendor', join(pubAssets, 'vendor')],
|
||||
['posters', join(pubAssets, 'posters')],
|
||||
['tables', join(pubAssets, 'tables')],
|
||||
// legacy — sandboxed pre-bundler maps rescued by the experimental roundup
|
||||
// (EXPERIMENTAL-MAPS-ROUNDUP.md): served as static assets, mounted only inside
|
||||
// wrapper-bundle iframes so the old engines cannot collide with modern bundles.
|
||||
['legacy', join(pubAssets, 'legacy')],
|
||||
]) {
|
||||
const src = join(repoData, srcName)
|
||||
if (!existsSync(src)) continue
|
||||
|
|
@ -1391,6 +1395,11 @@ function main() {
|
|||
// vendor — DataTable/TabulatorTable inject Tabulator from assetsBase at runtime.
|
||||
'vendor/tabulator/tabulator.min.js',
|
||||
'vendor/tabulator/tabulator.min.css',
|
||||
// legacy sandbox — fetched by the upg-neighbor wrapper's iframe at runtime.
|
||||
'legacy/upg-neighbor/index.html',
|
||||
'legacy/upg-neighbor/upg-neighbor.iife.js',
|
||||
'legacy/upg-neighbor/maplibre-gl.js',
|
||||
'legacy/upg-neighbor/maplibre-gl.css',
|
||||
// vendor/vue — the GALLERY SHELL's own Vue (generate-gallery.mjs loadVue()), not a bundle's.
|
||||
//
|
||||
// ★ ADDED 2026-09-22, and it had never been staged. generate-gallery.mjs:542 requests
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
<script>
|
||||
/**
|
||||
* Does the Fly + Clusters toolbar render for this legend type?
|
||||
* Single source of truth — see the note beside showAutoFlyButton.
|
||||
* @param {string} legendType
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function showsFlyTools(legendType) {
|
||||
return legendType === 'doxa-regions'
|
||||
|| legendType === 'language-family'
|
||||
|| legendType === 'language-families'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
/**
|
||||
* LegendTools.vue — Fly + Clusters floating toolbar
|
||||
|
|
@ -117,10 +130,15 @@ const flySpeed = computed(() => mapStore.flySpeed)
|
|||
const showClusters = computed(() => mapStore.showClusters)
|
||||
|
||||
// Predicate: only render the toolbar on tabs that need fly + clusters.
|
||||
const showAutoFlyButton = computed(() => {
|
||||
const lt = props.legendType
|
||||
return lt === 'doxa-regions' || lt === 'language-family' || lt === 'language-families'
|
||||
})
|
||||
// EXPORTED because HOSTS need the same answer. This toolbar is absolutely positioned at top:14px,
|
||||
// centred on the legend column — the SEARCH BAR's slot. When a host drops the search bar (the
|
||||
// maplibre case) and raises its legend into the vacated space, the two land on the same pixels and
|
||||
// the toolbar prints over the legend's header. MEASURED on country-posters: legend raised to 14,
|
||||
// Fly/Clusters drawn across the "WAGF REGIONS" title.
|
||||
// A host must therefore be able to ask "will the toolbar be there?" before deciding how far to
|
||||
// raise. Re-typing this condition at each call site is how two copies of one rule drift apart, so
|
||||
// the rule lives here, once, and showAutoFlyButton uses the same function.
|
||||
const showAutoFlyButton = computed(() => showsFlyTools(props.legendType))
|
||||
|
||||
const speedLabel = computed(() => {
|
||||
const labels = { slow: t('buttons.speedOption.slow'), normal: t('buttons.speedOption.normal'), fast: t('buttons.speedOption.fast') }
|
||||
|
|
|
|||
|
|
@ -67,14 +67,24 @@ const MAPBOX_CSS_URL = ENGINES.mapbox.css
|
|||
// Free basemap for MapLibre (no token). Deliberately NOT the hand-tuned Mapbox
|
||||
// style — see the provider notes; a MapLibre map looks different by design.
|
||||
const OPENFREEMAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty'
|
||||
// The dark counterpart. `fiord` rather than `dark`: side by side at world zoom, `dark` is
|
||||
// near-black land against near-black water while fiord separates them and keeps labels legible
|
||||
// in both Latin and native script — the same reasoning recorded in library/styles/registry.js.
|
||||
const OPENFREEMAP_DARK_STYLE = 'https://tiles.openfreemap.org/styles/fiord'
|
||||
|
||||
// Named styles: the 12-style pack. A profile-config style of 'ink' /
|
||||
// 'midnight-glow' / … resolves per engine here; raw URLs pass through.
|
||||
import { resolveNamedStyle, applyStylePaint } from '../styles/registry.js'
|
||||
import { resolveMapProvider } from './useMapProvider.js'
|
||||
|
||||
/** The live engine global for a provider, or undefined if not yet loaded. */
|
||||
function engineGlobal(provider) {
|
||||
/**
|
||||
* The live engine global for a provider, or undefined if not yet loaded.
|
||||
* EXPORTED for the profiles that build their map RAW (new gl.Map) instead of through this
|
||||
* composable — my-upg-100-list is one. They still need the RIGHT engine for the selected provider,
|
||||
* and the alternative is a second copy of the ENGINES table living in a profile, which is how two
|
||||
* copies of one fact drift apart. Borrow the loader; do not re-declare the registry.
|
||||
*/
|
||||
export function engineGlobal(provider) {
|
||||
const name = ENGINES[provider]?.global || ENGINES.mapbox.global
|
||||
return typeof window !== 'undefined' ? window[name] : undefined
|
||||
}
|
||||
|
|
@ -84,7 +94,7 @@ function engineGlobal(provider) {
|
|||
* Idempotent — resolves immediately if the global already exists (preloaded via
|
||||
* index.html), otherwise injects the script and awaits its load event.
|
||||
*/
|
||||
async function ensureEngineScript(provider) {
|
||||
export async function ensureEngineScript(provider) {
|
||||
const cfg = ENGINES[provider] || ENGINES.mapbox
|
||||
if (engineGlobal(provider)) return // already loaded — no-op
|
||||
if (typeof document === 'undefined') return
|
||||
|
|
@ -166,10 +176,22 @@ async function ensureEngineCSS(element, cssUrl) {
|
|||
/**
|
||||
* Effective style for the MapLibre engine. A mapbox:// style needs a token and
|
||||
* the Mapbox engine, so it is ignored here — an explicit http(s) style URL is
|
||||
* honored, otherwise the free OpenFreeMap basemap is used.
|
||||
* honored, otherwise a free OpenFreeMap basemap is used.
|
||||
*
|
||||
* ★ THE FALLBACK IS THEME-AWARE, AND IT HAS TO BE (2026-09-24).
|
||||
* A Standard-based profile does not name a registry style at all: useMapTheme.bootStyle() hands
|
||||
* back the raw `mapbox://styles/mapbox/standard` URL and the DARKNESS lives in the config
|
||||
* ({ basemap: { lightPreset: 'night' } }), which is a Mapbox-only mechanism. This function used to
|
||||
* collapse every mapbox:// style to ONE light constant, so flipping provider turned the research
|
||||
* map from the dark map the Driver knows into a LIGHT one — MEASURED by screenshotting it, not by
|
||||
* any error: both maps mount, both paint, both report success. Interoperability had "worked" while
|
||||
* the product looked like a different product.
|
||||
* Reading the preset the Mapbox side would have applied keeps the two engines telling the same
|
||||
* story: night/dusk get `fiord` (blue-toned dark), everything else keeps liberty.
|
||||
*/
|
||||
function maplibreStyle(style) {
|
||||
return (typeof style === 'string' && /^https?:\/\//.test(style)) ? style : OPENFREEMAP_STYLE
|
||||
function maplibreStyle(style, lightPreset) {
|
||||
if (typeof style === 'string' && /^https?:\/\//.test(style)) return style
|
||||
return (lightPreset === 'night' || lightPreset === 'dusk') ? OPENFREEMAP_DARK_STYLE : OPENFREEMAP_STYLE
|
||||
}
|
||||
|
||||
// ─── Aspect-aware zoom-out floor (Feedback #2+#4 REDO) ───────────────────────
|
||||
|
|
@ -383,12 +405,22 @@ export function useMapInstance({
|
|||
}
|
||||
const initialMinZoom = floorFor(el.clientWidth, el.clientHeight)
|
||||
|
||||
// Resolve the day/night hint ONCE, before the engine branch, so both engines read the same
|
||||
// decision. `config` may be a function (re-evaluated per init, so a reload boots on the
|
||||
// theme of that moment) — call it the same way the Mapbox branch does.
|
||||
const _bootPreset = _named.lightPreset
|
||||
|| (() => { try { const c = typeof config === 'function' ? config() : config; return c?.basemap?.lightPreset } catch { return undefined } })()
|
||||
|
||||
if (isMaplibre) {
|
||||
// MapLibre branch — free OpenFreeMap basemap, NO access token. Shares the
|
||||
// GL-JS Map option surface; only the token/style differ from Mapbox.
|
||||
map.value = new gl.Map({
|
||||
container: containerRef.value, // real DOM element — never a string ID in shadow DOM
|
||||
style: maplibreStyle(effectiveStyle),
|
||||
// The preset the MAPBOX side would have applied — from the named registry entry, else
|
||||
// from the boot config a Standard profile passes. Either way it is the app's own
|
||||
// day/night decision, not a guess made here. (Computed above the branch: the Mapbox
|
||||
// branch's own `initialConfig` is scoped to that branch and is NOT visible here.)
|
||||
style: maplibreStyle(effectiveStyle, _bootPreset),
|
||||
center,
|
||||
zoom,
|
||||
minZoom: initialMinZoom,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,23 @@
|
|||
// so a new Arabic-script locale is one entry here and not a schema change.
|
||||
const RTL = new Set(['ar', 'he', 'fa', 'ur', 'ps', 'sd', 'yi']);
|
||||
|
||||
export function createPacketRenderer({ strings, lang = 'en' }) {
|
||||
// Scripts with no coverage in the packet's Georgia/Palatino/serif stack, mapped to
|
||||
// the vendored face that does cover them. MEASURED by rendering each script at 48px
|
||||
// and reading the PIXELS: Latin, Cyrillic, Arabic and CJK all shape correctly with
|
||||
// system fonts; DEVANAGARI renders as tofu boxes.
|
||||
// ⚠ A width heuristic got this exactly backwards — it reported Arabic and Chinese
|
||||
// broken and Hindi fine, because Devanagari tofu boxes happen to match real glyph
|
||||
// advance. Never infer glyph coverage from measurements; look at the pixels.
|
||||
const SCRIPT_FONT = {
|
||||
hi: { family: 'Noto Serif Devanagari', dir: 'noto-serif-devanagari',
|
||||
file: 'noto-serif-devanagari-400-devanagari.woff2' },
|
||||
mr: { family: 'Noto Serif Devanagari', dir: 'noto-serif-devanagari',
|
||||
file: 'noto-serif-devanagari-400-devanagari.woff2' }, // Marathi, same script
|
||||
ne: { family: 'Noto Serif Devanagari', dir: 'noto-serif-devanagari',
|
||||
file: 'noto-serif-devanagari-400-devanagari.woff2' }, // Nepali, same script
|
||||
};
|
||||
|
||||
export function createPacketRenderer({ strings, lang = 'en', fontData = null }) {
|
||||
const EN = strings.en;
|
||||
const L = strings[lang] || {};
|
||||
const t = (k) => (L[k] !== undefined ? L[k] : EN[k]);
|
||||
|
|
@ -56,8 +72,28 @@ export function createPacketRenderer({ strings, lang = 'en' }) {
|
|||
const sheet = (inner, cls = '') =>
|
||||
`<div class="sheet ${cls}">${inner}</div>`;
|
||||
|
||||
// The script font for THIS language, if it needs one. `fontData` is a base64
|
||||
// woff2 the caller supplies: the packet must stay a SELF-CONTAINED file that
|
||||
// prints offline (the Node generator writes standalone HTML with no server, and
|
||||
// photos are already inlined for the same reason), so a URL would break exactly
|
||||
// where the packet is most used. Absent fontData, the rule is simply omitted and
|
||||
// the stack falls back to serif — a legible wrong font beats a broken document.
|
||||
const SF = SCRIPT_FONT[lang] || null;
|
||||
const fontFace = (SF && fontData)
|
||||
? `@font-face{font-family:'${SF.family}';font-style:normal;font-weight:400;`
|
||||
+ `font-display:swap;src:url(data:font/woff2;base64,${fontData}) format('woff2')}`
|
||||
: '';
|
||||
// Prepend the script face to EVERY element's stack in one rule rather than
|
||||
// editing 22 hardcoded Georgia declarations. The browser falls through per
|
||||
// CHARACTER, so Latin text in a Hindi packet still renders in Georgia.
|
||||
const scriptStack = SF
|
||||
? `.sheet,.sheet *{font-family:'${SF.family}',Georgia,'Palatino Linotype',serif !important}`
|
||||
: '';
|
||||
|
||||
function css() {
|
||||
return `
|
||||
${fontFace}
|
||||
${scriptStack}
|
||||
@page{size:letter portrait;margin:0}
|
||||
html,body{margin:0;background:#fff;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
||||
.sheet{position:relative;width:${W}px;height:${H}px;background:#f7f4ec;overflow:hidden;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"how_to_translate": "Copy the \"en\" block, change the key to the ISO code, translate the values. Keep {placeholders} exactly as they are — they are substituted at render time. Do not translate scripture REFERENCES (book names are resolved by the reader's own Bible); DO translate the prayer text.",
|
||||
"note_on_scripture": "point4_passage is the ONLY full scripture text in the packet (Mark 5:1-20, printed in full at the Driver's instruction). A translation MUST supply its own rendering in a public-domain or properly-licensed version FOR THAT LANGUAGE. ⚠ Do NOT translate it yourself and do NOT leave a machine rendering in place: this is printed scripture in a prayer packet. A language block that omits point4_passage falls back to the English WEB text per-key, which is honest — an untrusted translation of Mark 5 is not.",
|
||||
"note_on_references": "Scripture REFERENCES (Matthew 27:27, Mark 5:1-20) are deliberately left in English in every language block, so a reader can look them up in their own Bible regardless of the packet's language. ⚠ MEASURED TRADEOFF, not an oversight: on a rendered French page the English book names are visibly foreign next to fully-translated prayer text. If a language wants localised book names, add a `books` map to that block and teach the renderer to use it — do NOT translate the refs inline, because the same strings identify the passage for any future lookup.",
|
||||
"note_on_scripts": "MEASURED 2026-09-24 by rendering each script at 48px and READING THE PIXELS: Latin, Cyrillic (ru), Arabic (ar) and CJK (zh) all shape correctly in the packet's Georgia/serif stack. DEVANAGARI (hi) renders as TOFU BOXES — this box has zero Devanagari fonts installed (fc-list). So hi is BLOCKED on vendoring a Devanagari font (e.g. Noto Serif Devanagari) into public/assets/fonts and adding it to the packet's font stack; adding a hi block before then would ship a page of empty rectangles. ⚠ A WIDTH HEURISTIC GOT THIS BACKWARDS: comparing rendered advance against a missing-font baseline reported ar and zh as broken and hi as fine — the exact opposite of the truth, because Devanagari tofu boxes happen to match real glyph advance. Read the pixels.",
|
||||
"note_on_scripts": "ALL 11 app locales now ship. Script coverage MEASURED by rendering at 48px and READING THE PIXELS: Latin, Cyrillic (ru), Arabic (ar) and CJK (zh) shape correctly with the system Georgia/Palatino/serif stack. DEVANAGARI (hi) does NOT — it renders as tofu boxes — so Noto Serif Devanagari is vendored (OFL) and base64-INLINED into hi packets by the renderer's SCRIPT_FONT map. Inlined, not linked, because a packet is a standalone file that must print offline; ~165KB on a 3.5MB packet, and only for that language. mr and ne map to the same face.\n⚠ TWO MEASUREMENTS LIED ABOUT THIS, so do not automate it cheaply: (1) comparing shaped width against a missing font reported ar and zh BROKEN and hi FINE — exactly backwards, because Devanagari tofu boxes match real glyph advance; (2) an ink-column-profile heuristic scored CORRECTLY-RENDERED Devanagari the same as tofu (3 vs 2 distinct heights), because its baseline bar gives a uniform profile. check:packet-render therefore asserts only that a DECLARED @font-face LOADED — the mechanical half — and leaves glyph correctness to a human reading a screenshot. A detector that blocks working packets is worse than none.",
|
||||
"note_on_rtl": "ar is right-to-left. The renderer emits dir on <html> from an RTL set (packet-render.js), and scripture REFERENCES are wrapped in dir=ltr + unicode-bidi:isolate. Without that isolation the bidi algorithm reorders a reference that STARTS WITH A DIGIT: '1 Peter 5:6-11' rendered as 'Peter 5:6-11 1'. Note the reference LIST then reads right-to-left on an RTL page (first item rightmost) — that is correct for an RTL reader, not a defect."
|
||||
},
|
||||
"en": {
|
||||
|
|
@ -1430,5 +1430,321 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"hi": {
|
||||
"map_page_kicker": "अनछुए जनसमूह",
|
||||
"map_page_sub": "{count} अनछुए जनसमूहों की प्रार्थना सूची",
|
||||
"list_header": "{count} में से {from}–{to} · उनके नाम लेकर प्रार्थना करें",
|
||||
"people_suffix": "लोग",
|
||||
"no_photo": "कोई चित्र उपलब्ध नहीं",
|
||||
"deck_title": "प्रार्थना कार्ड",
|
||||
"card_country": "देश",
|
||||
"card_population": "जनसंख्या",
|
||||
"card_language": "भाषा",
|
||||
"card_religion": "धर्म",
|
||||
"card_cta": "उनके लिए प्रार्थना करें",
|
||||
"points_title": "12 प्रार्थना बिंदु",
|
||||
"points_intro": "परमेश्वर के साथ एक घंटा, हर बिंदु के लिए पाँच मिनट। इस पैकेट के जनसमूहों के लिए ये बारह बिंदु प्रार्थना करें।",
|
||||
"points_attrib": "प्रार्थना लय Metacamp से प्रेरित · metacamp.org",
|
||||
"wheel_title": "प्रार्थना चक्र",
|
||||
"wheel_intro": "परमेश्वर के साथ एक घंटा बिताना? यह आपके सोचने से आसान है। इसे 5 मिनट के हिस्सों में बाँटें और हर हिस्से में इनमें से एक पर ध्यान दें।",
|
||||
"wheel_minutes": "मिनट",
|
||||
"wheel_attrib": "प्रार्थना चक्र · Metacamp मॉड्यूल 1 · metacamp.org",
|
||||
"privacy_note": "चिह्नों की स्थिति अनुमानित है · गोपनीयता के लिए जनसमूहों के स्थान स्रोत पर सामान्यीकृत किए गए हैं",
|
||||
"rhythms": {
|
||||
"praise": "स्तुति",
|
||||
"wait": "प्रतीक्षा",
|
||||
"confess": "अंगीकार",
|
||||
"read": "वचन पढ़ना",
|
||||
"ask": "माँगना",
|
||||
"intercede": "मध्यस्थता",
|
||||
"pray_word": "वचन से प्रार्थना",
|
||||
"thank": "धन्यवाद",
|
||||
"sing": "गायन",
|
||||
"meditate": "मनन",
|
||||
"listen": "सुनना",
|
||||
"praise_end": "स्तुति"
|
||||
},
|
||||
"points": [
|
||||
{
|
||||
"n": 1,
|
||||
"rhythm": "praise",
|
||||
"sub": "दो क्रूसों का सुसमाचार",
|
||||
"text": "कि वे मसीह को राजा मानकर ग्रहण करें, उसके क्रूस से क्षमा पाएँ — और अपना क्रूस उठाएँ, सताव के क्षेत्र में दुख उठाने को तैयार। मेम्ने को उसके दुख का प्रतिफल मिले, मुझसे आरंभ करके।",
|
||||
"refs": [
|
||||
"Matthew 27:27",
|
||||
"Matthew 16:24–25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 2,
|
||||
"rhythm": "wait",
|
||||
"sub": "पवित्र आत्मा और छुटकारा",
|
||||
"text": "कि वे आत्मा से भरें और साहसी गवाह बनें; अकेले न रहें; जल और आत्मा से बपतिस्मा पाएँ; बुराई की हर पकड़ टूट जाए। कि पड़ोसी जातियों के विश्वासी मज़दूरों के रूप में उठ खड़े हों।",
|
||||
"refs": [
|
||||
"Acts 1:8",
|
||||
"John 14:26"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 3,
|
||||
"rhythm": "confess",
|
||||
"sub": "",
|
||||
"text": "हम इस अविश्वास को मानते हैं कि यह परमेश्वर के लिए बहुत बड़ा है। कि वह संसार के अनछुए लोगों के लिए हमारे प्रेम और विश्वास को फिर से जगाए।",
|
||||
"refs": [
|
||||
"John 3:16",
|
||||
"1 John 1:9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 4,
|
||||
"rhythm": "read",
|
||||
"sub": "पिता का प्रेम",
|
||||
"text": "कि वे उद्धार पाएँ और जानें कि उनका स्वर्गीय पिता उनसे प्रेम करता है — और उनके द्वारा वह उनके परिवार, मित्रों, पड़ोसियों, सहकर्मियों, सहपाठियों, उन सबके पास उद्धार पहुँचाए जिन्हें वे व्यक्तिगत रूप से जानते हैं।",
|
||||
"refs": [
|
||||
"Mark 5:1–20",
|
||||
"Acts 16:31"
|
||||
],
|
||||
"print_passage": true
|
||||
},
|
||||
{
|
||||
"n": 5,
|
||||
"rhythm": "ask",
|
||||
"sub": "बपतिस्मा",
|
||||
"text": "स्थानीय लोगों में नए बपतिस्मा हों, और हर बपतिस्मा पाया हुआ व्यक्ति दूसरों को बपतिस्मा देने के लिए प्रशिक्षित हो, और यह बढ़ता जाए।",
|
||||
"refs": [
|
||||
"Acts 16",
|
||||
"Acts 10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 6,
|
||||
"rhythm": "intercede",
|
||||
"sub": "मज़दूर और चरवाहे",
|
||||
"text": "मज़दूर भेजे जाएँ जो उस जाति से गहरा प्रेम करें जिससे वे कभी नहीं मिले; और भीतर से अच्छे चरवाहे उठाए जाएँ।",
|
||||
"refs": [
|
||||
"Luke 10:2",
|
||||
"John 10:11–15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 7,
|
||||
"rhythm": "pray_word",
|
||||
"sub": "शांति के जन और घर",
|
||||
"text": "कि मज़दूर और स्थानीय अगुवे ग्रहणशील व्यक्ति और शांति के घर को पाएँ, और उन्हें भली भाँति चेला बनाएँ।",
|
||||
"refs": [
|
||||
"Luke 10:5–7",
|
||||
"Matthew 10:5–14"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 8,
|
||||
"rhythm": "thank",
|
||||
"sub": "विश्राम",
|
||||
"text": "परमेश्वर का धन्यवाद कि उसने विश्राम दिया है; कि वे मसीह में उस विश्राम में प्रवेश करें, और उनका आत्मिक परिवार एक सुरक्षित स्थान हो।",
|
||||
"refs": [
|
||||
"Mark 4:26–29",
|
||||
"Hebrews 4:9–11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 9,
|
||||
"rhythm": "sing",
|
||||
"sub": "",
|
||||
"text": "कि वह जनसमूह आराधकों से भर जाए — हर जगह आराधना, स्वतंत्रता में और बेड़ियों में भी।",
|
||||
"refs": [
|
||||
"Acts 16:25–26",
|
||||
"Colossians 3:16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 10,
|
||||
"rhythm": "meditate",
|
||||
"sub": "प्रभु भोज",
|
||||
"text": "कि वे मिलकर प्रभु भोज लें और उसकी मृत्यु, गाड़े जाने और पुनरुत्थान के भार को समझें।",
|
||||
"refs": [
|
||||
"Matthew 26:26–30",
|
||||
"1 Corinthians 11:23–26"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 11,
|
||||
"rhythm": "listen",
|
||||
"sub": "जवाबदेही",
|
||||
"text": "केवल यह नहीं कि वे सुनें, परन्तु यह कि उनके जीवन में ऐसे लोग हों जो उन्हें वचन पढ़ने, प्रार्थना करने, संगति में लगे रहने और दुख का सही उत्तर देने में सहायता करें — उस शत्रु का सामना करते हुए जो सारे संसार में विश्वासियों के पीछे पड़ा है।",
|
||||
"refs": [
|
||||
"1 Peter 5:6–11",
|
||||
"Hebrews 10:24–25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 12,
|
||||
"rhythm": "praise_end",
|
||||
"sub": "महान · और महान · सबसे महान",
|
||||
"text": "पहले से स्तुति — उद्धार के लिए; और महान, बढ़ते हुए आत्मिक परिवारों के लिए; सबसे महान, समर्पित जीवनों और उठाए गए स्थानीय कलीसिया-रोपकों के लिए जो पूरे जनसमूह में स्वस्थ कलीसियाएँ बढ़ाते हैं, चाहे मूल्य कुछ भी हो।",
|
||||
"refs": [
|
||||
"Acts 2:42–47",
|
||||
"Matthew 28:19–20"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"zh": {
|
||||
"map_page_kicker": "未得之民",
|
||||
"map_page_sub": "為 {count} 個未得之民的禱告名單",
|
||||
"list_header": "{count} 之中的 {from}–{to} · 按名為他們禱告",
|
||||
"people_suffix": "人",
|
||||
"no_photo": "沒有照片",
|
||||
"deck_title": "禱告卡",
|
||||
"card_country": "國家",
|
||||
"card_population": "人口",
|
||||
"card_language": "語言",
|
||||
"card_religion": "宗教",
|
||||
"card_cta": "為他們禱告",
|
||||
"points_title": "12 個禱告要點",
|
||||
"points_intro": "與神同在一小時,每個要點五分鐘。為這份禱告冊中的族群禱告這十二個要點。",
|
||||
"points_attrib": "禱告節奏取材自 Metacamp · metacamp.org",
|
||||
"wheel_title": "禱告輪",
|
||||
"wheel_intro": "與神同在一小時?比你想的容易。把它分成五分鐘一段,每一段專注其中一個要點。",
|
||||
"wheel_minutes": "分鐘",
|
||||
"wheel_attrib": "禱告輪 · Metacamp 第一單元 · metacamp.org",
|
||||
"privacy_note": "標記位置為概略位置 · 族群所在地已於資料來源處作概化處理以保護隱私",
|
||||
"rhythms": {
|
||||
"praise": "讚美",
|
||||
"wait": "等候",
|
||||
"confess": "認罪",
|
||||
"read": "讀神的話",
|
||||
"ask": "祈求",
|
||||
"intercede": "代禱",
|
||||
"pray_word": "以神的話禱告",
|
||||
"thank": "感謝",
|
||||
"sing": "歌唱",
|
||||
"meditate": "默想",
|
||||
"listen": "聆聽",
|
||||
"praise_end": "讚美"
|
||||
},
|
||||
"points": [
|
||||
{
|
||||
"n": 1,
|
||||
"rhythm": "praise",
|
||||
"sub": "兩個十字架的福音",
|
||||
"text": "願他們接受基督為王,藉著祂的十字架得赦免——也背起自己的十字架,甘願在逼迫之地受苦。願羔羊得著祂受苦的賞賜,從我開始。",
|
||||
"refs": [
|
||||
"Matthew 27:27",
|
||||
"Matthew 16:24–25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 2,
|
||||
"rhythm": "wait",
|
||||
"sub": "聖靈與釋放",
|
||||
"text": "願他們被聖靈充滿,成為剛強的見證人;不至孤單;受水和聖靈的洗;一切邪惡的轄制都被打破。願鄰近族群中的信徒被興起作工人。",
|
||||
"refs": [
|
||||
"Acts 1:8",
|
||||
"John 14:26"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 3,
|
||||
"rhythm": "confess",
|
||||
"sub": "",
|
||||
"text": "我們承認自己的不信,以為這事對神太難。願祂重新點燃我們對普世未得之民的愛與信心。",
|
||||
"refs": [
|
||||
"John 3:16",
|
||||
"1 John 1:9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 4,
|
||||
"rhythm": "read",
|
||||
"sub": "天父的愛",
|
||||
"text": "願他們得救,並知道天父愛他們——也願神藉著他們,把救恩帶給他們的家人、朋友、鄰舍、同事、同學,以及他們所認識的每一個人。",
|
||||
"refs": [
|
||||
"Mark 5:1–20",
|
||||
"Acts 16:31"
|
||||
],
|
||||
"print_passage": true
|
||||
},
|
||||
{
|
||||
"n": 5,
|
||||
"rhythm": "ask",
|
||||
"sub": "洗禮",
|
||||
"text": "願當地有新的受洗者,並且每一位受洗的人都被裝備去為別人施洗,不斷繁增。",
|
||||
"refs": [
|
||||
"Acts 16",
|
||||
"Acts 10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 6,
|
||||
"rhythm": "intercede",
|
||||
"sub": "工人與牧人",
|
||||
"text": "願工人被差派去深愛一群素未謀面的人;也願好牧人從他們中間被興起。",
|
||||
"refs": [
|
||||
"Luke 10:2",
|
||||
"John 10:11–15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 7,
|
||||
"rhythm": "pray_word",
|
||||
"sub": "平安之子與平安之家",
|
||||
"text": "願工人與當地帶領者尋得那樂意接待的人和平安之家,並好好門訓他們。",
|
||||
"refs": [
|
||||
"Luke 10:5–7",
|
||||
"Matthew 10:5–14"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 8,
|
||||
"rhythm": "thank",
|
||||
"sub": "安息",
|
||||
"text": "感謝神所賜的安息;願他們在基督裡進入那安息,也願他們的屬靈家庭是安全的地方。",
|
||||
"refs": [
|
||||
"Mark 4:26–29",
|
||||
"Hebrews 4:9–11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 9,
|
||||
"rhythm": "sing",
|
||||
"sub": "",
|
||||
"text": "願這族群充滿敬拜者——處處敬拜,在自由中,也在捆鎖中。",
|
||||
"refs": [
|
||||
"Acts 16:25–26",
|
||||
"Colossians 3:16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 10,
|
||||
"rhythm": "meditate",
|
||||
"sub": "聖餐",
|
||||
"text": "願他們一同領受聖餐,深深體會祂的死、埋葬與復活的份量。",
|
||||
"refs": [
|
||||
"Matthew 26:26–30",
|
||||
"1 Corinthians 11:23–26"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 11,
|
||||
"rhythm": "listen",
|
||||
"sub": "彼此守望",
|
||||
"text": "不只願他們聆聽,更願他們身邊有人幫助他們讀神的話、禱告、委身群體,並在苦難中正確回應——抵擋那在全世界追逼信徒的仇敵。",
|
||||
"refs": [
|
||||
"1 Peter 5:6–11",
|
||||
"Hebrews 10:24–25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"n": 12,
|
||||
"rhythm": "praise_end",
|
||||
"sub": "大 · 更大 · 最大",
|
||||
"text": "先行讚美——為著得救的人;更大的,為著不斷繁增的屬靈家庭;最大的,為著獻上的生命,以及被興起的本地植堂者,無論代價如何,都在整個族群中繁增健康的教會。",
|
||||
"refs": [
|
||||
"Acts 2:42–47",
|
||||
"Matthew 28:19–20"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +74,30 @@ const fill = (s, vars) => String(s).replace(/\{(\w+)\}/g, (_, k) => (vars[k] ??
|
|||
// the SAME packet from the SAME code. Changing a page here changes it in both.
|
||||
const { createPacketRenderer } = await import(
|
||||
join(__dirname, '..', '..', 'packet', 'packet-render.js'));
|
||||
const R = createPacketRenderer({ strings: STRINGS, lang: LANG });
|
||||
// SCRIPT FONT, inlined. Some languages need a face the system stack does not
|
||||
// carry — Hindi renders as tofu boxes in Georgia/Palatino/serif. The packet is a
|
||||
// standalone file meant to print offline, so the font is base64'd INTO it rather
|
||||
// than linked, exactly as the photos are. Absent, the renderer omits the rule and
|
||||
// falls back to serif rather than failing.
|
||||
const SCRIPT_FONT_FILE = {
|
||||
hi: 'noto-serif-devanagari/noto-serif-devanagari-400-devanagari.woff2',
|
||||
mr: 'noto-serif-devanagari/noto-serif-devanagari-400-devanagari.woff2',
|
||||
ne: 'noto-serif-devanagari/noto-serif-devanagari-400-devanagari.woff2',
|
||||
};
|
||||
let fontData = null;
|
||||
const fontRel = SCRIPT_FONT_FILE[LANG];
|
||||
if (fontRel) {
|
||||
const fp = join(__dirname, '..', '..', '..', 'public', 'assets', 'fonts', fontRel);
|
||||
if (existsSync(fp)) {
|
||||
fontData = readFileSync(fp).toString('base64');
|
||||
} else {
|
||||
// Loud, not silent: a missing font here means the packet prints as empty
|
||||
// rectangles, which looks like a content bug rather than a missing asset.
|
||||
console.warn(`[packet] ⚠ ${LANG} needs ${fontRel} and it is MISSING — `
|
||||
+ 'the packet will render as tofu boxes. Vendor the font first.');
|
||||
}
|
||||
}
|
||||
const R = createPacketRenderer({ strings: STRINGS, lang: LANG, fontData });
|
||||
const { mapPage, listPages, pointsPage, wheelPage, css, mapCss, PER_PAGE, W, H } = R;
|
||||
|
||||
// ── DATA ────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
// ONLY=laos node publish-html.mjs # one
|
||||
|
||||
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -56,13 +56,21 @@ if (!existsSync(SRC)) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
// The zone ids the app can actually reach. A poster for no zone is not publishable.
|
||||
// ★ THE ZONE IDS THE APP CAN ACTUALLY REACH — ask the LEGEND, not clusters.json.
|
||||
// Enumerating the raw cluster keys is what this used to do, and it is wrong:
|
||||
// clusters.json carries 30 list definitions but the legend exposes 28 leaves.
|
||||
// deaf-old-world and deaf-new-world-asia-pacific are SUPERSEDED by the unified
|
||||
// `deaf` row (affinity bloc A017) — verified against live data, 63 + 60 = 123 =
|
||||
// A017 exactly, with zero groups on either side of the difference. They are kept
|
||||
// in clusters.json deliberately (three isolation-proof scripts assert
|
||||
// deaf_lists.length === 2), but no user can select them, so a poster for them is
|
||||
// unreachable. Enumerating them made this script warn that two zones were
|
||||
// "MISSING" when nothing was missing at all.
|
||||
const clusters = JSON.parse(readFileSync(
|
||||
join(REPO, 'plugin-designs', 'my-upg-100-list', 'data', 'clusters.json'), 'utf8'));
|
||||
const zoneIds = new Set();
|
||||
for (const key of ['lists', 'deaf_lists', 'asia_lists', 'uncovered_lists']) {
|
||||
for (const z of clusters[key] || []) zoneIds.add(z.id);
|
||||
}
|
||||
const { buildLegendNodes, leafZones } = await import(
|
||||
pathToFileURL(join(REPO, 'plugin-designs', 'my-upg-100-list', 'data', 'legend-tree.js')).href);
|
||||
const zoneIds = new Set(leafZones(buildLegendNodes(clusters, { records: null })).map((z) => z.id));
|
||||
|
||||
mkdirSync(DEST, { recursive: true });
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,20 @@
|
|||
// day/dawn/dusk/night from ONE style doc — seamless theme flips, see
|
||||
// useMapTheme.js). `lightPreset` here is a HINT profiles may apply on load.
|
||||
// · maplibre side: FREE, no-key styles only — OpenFreeMap (liberty/bright/
|
||||
// positron/dark). No free satellite imagery exists for MapLibre, so imagery
|
||||
// positron/dark/fiord). No free satellite imagery exists for MapLibre, so imagery
|
||||
// styles fall back to the closest free look (noted per entry).
|
||||
//
|
||||
// ★ FIORD ADDED 2026-09-24, after RENDERING the candidates rather than reading a gallery.
|
||||
// OpenFreeMap ships a blue-toned dark style this registry never used. Side by side at the
|
||||
// band this product actually shows (30E 25N, z2.4), `dark` reads flat and muddy — near-black
|
||||
// land against near-black water — while `fiord` separates land from sea, keeps labels legible
|
||||
// in BOTH Latin and native script, and carries the blue cast the galaxy entry below already
|
||||
// asks for in words. It took the three dark entries whose MAPBOX counterpart is also blue-ish
|
||||
// (Standard@night, Standard@dusk, navigation-night); `ink` pairs with near-black dark-v11 and
|
||||
// deliberately did not move — see the note there.
|
||||
// Endpoint verified 200 keyless before the swap, same bar as every other URL here. Note that
|
||||
// OpenFreeMap's documented '3d' style is NOT live at /styles/3d (404) — do not cite it.
|
||||
//
|
||||
// ★ CARTO REMOVED 2026-09-22, and this is a rule, not a preference. Eight of these
|
||||
// twelve entries previously resolved to basemaps.cartocdn.com. Those URLs were keyless
|
||||
// *at the time* — verified live, a real style.json + a 275KB .mvt + a font .pbf all 200
|
||||
|
|
@ -37,7 +48,7 @@
|
|||
export const STYLE_REGISTRY = [
|
||||
{ id: 'midnight-glow', label: 'Midnight Glow', mood: 'the prayer-map dark — pins glow like embers',
|
||||
mapbox: 'mapbox://styles/mapbox/standard', lightPreset: 'night',
|
||||
maplibre: 'https://tiles.openfreemap.org/styles/dark',
|
||||
maplibre: 'https://tiles.openfreemap.org/styles/fiord',
|
||||
bestFor: ['doxa-simple-map', 'my-upg-100-list'] },
|
||||
{ id: 'daylight', label: 'Daylight', mood: 'the same Standard world, lights on',
|
||||
mapbox: 'mapbox://styles/mapbox/standard', lightPreset: 'day',
|
||||
|
|
@ -49,7 +60,7 @@ export const STYLE_REGISTRY = [
|
|||
bestFor: ['doxa-simple-map'] },
|
||||
{ id: 'dusk', label: 'Dusk', mood: 'blue hour — calm contrast for bright pins',
|
||||
mapbox: 'mapbox://styles/mapbox/standard', lightPreset: 'dusk',
|
||||
maplibre: 'https://tiles.openfreemap.org/styles/dark',
|
||||
maplibre: 'https://tiles.openfreemap.org/styles/fiord',
|
||||
bestFor: ['my-upg-100-list'] },
|
||||
{ id: 'paper', label: 'Paper', mood: 'near-white minimal — data reads like print',
|
||||
mapbox: 'mapbox://styles/mapbox/light-v11',
|
||||
|
|
@ -57,6 +68,10 @@ export const STYLE_REGISTRY = [
|
|||
bestFor: ['research-dashboard', 'cities-internet'] },
|
||||
{ id: 'ink', label: 'Ink', mood: 'near-black minimal — maximum pin contrast',
|
||||
mapbox: 'mapbox://styles/mapbox/dark-v11',
|
||||
// KEEPS `dark` ON PURPOSE while the other three dark entries moved to `fiord`. Ink pairs with
|
||||
// mapbox dark-v11, which is NEAR-BLACK, and its whole job is maximum pin contrast. fiord is a
|
||||
// BLUE-toned dark — better looking in isolation and wrong here, because a lighter, bluer ground
|
||||
// eats exactly the contrast this entry exists to provide. Not an oversight; do not "finish" it.
|
||||
maplibre: 'https://tiles.openfreemap.org/styles/dark',
|
||||
bestFor: ['my-upg-100-list', 'cities-internet'] },
|
||||
{ id: 'streetlife', label: 'Street Life', mood: 'full street detail — city-scale work',
|
||||
|
|
@ -81,7 +96,7 @@ export const STYLE_REGISTRY = [
|
|||
bestFor: ['gospel-resources'] },
|
||||
{ id: 'wayfinder-night',label: 'Wayfinder Night',mood: 'dark navigation — dashboards at night',
|
||||
mapbox: 'mapbox://styles/mapbox/navigation-night-v1',
|
||||
maplibre: 'https://tiles.openfreemap.org/styles/dark',
|
||||
maplibre: 'https://tiles.openfreemap.org/styles/fiord',
|
||||
bestFor: ['research-dashboard'] },
|
||||
|
||||
// ── GALAXY — black ground, BLUE water ────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@
|
|||
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
|
||||
"check:card-deck": "node internal/check-card-deck.mjs",
|
||||
"check:poster-raster": "node internal/check-poster-raster.mjs",
|
||||
"check:share-policy": "node internal/check-share-policy.mjs",
|
||||
"check:zone-census": "node internal/check-zone-census.mjs",
|
||||
"check:print-geometry": "node internal/check-print-geometry.mjs",
|
||||
"check:packet-browser": "node internal/check-packet-browser.mjs",
|
||||
"check:packet-render": "node internal/check-packet-render.mjs",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ import LocationButton from '@map/components/map-controls/LocationButton.vue'
|
|||
import FullscreenButton from '@map/components/map-controls/FullscreenButton.vue'
|
||||
import ShareButton from '@map/components/map-controls/ShareButton.vue'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
|
||||
import { ensureEngineScript, engineGlobal } from '@map/composables/useMapInstance.js'
|
||||
import { resolveNamedStyle } from '@map/styles/registry.js'
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
const isDark = computed(() => profileConfig.value?.isDark === true)
|
||||
|
|
@ -127,14 +130,28 @@ function paint(map, adm0) {
|
|||
map.on('mouseleave', 'ci-fill', () => { map.getCanvas().style.cursor = '' })
|
||||
}
|
||||
|
||||
// ── HONOUR THE PROVIDER SWITCH: this profile builds its map RAW ─────────────
|
||||
// Every bundle that goes through useMapInstance gets the switch for free. This one calls
|
||||
// `new gl.Map` directly, so it was invisible to it — MEASURED by sweeping all 16 isMap leaves:
|
||||
// at ?provider=maplibre this leaf still loaded Mapbox and made 18 requests to api.mapbox.com,
|
||||
// identical to its mapbox run, while 12 of 16 siblings swapped. Same shape and same fix as
|
||||
// my-upg-100-list; the engine loader is IMPORTED, never a second copy of the ENGINES table.
|
||||
// No geocoder is mounted here, so the deferred-search-bar rule does not apply to this profile.
|
||||
const ciProvider = resolveMapProvider(inject('profileConfig', null))
|
||||
|
||||
onMounted(async () => {
|
||||
const gl = window.mapboxgl
|
||||
if (!gl || !mapEl.value) { err.value = 'mapbox-gl not loaded'; return }
|
||||
try { await ensureEngineScript(ciProvider) } catch (_) { /* fall through to the notice below */ }
|
||||
const gl = engineGlobal(ciProvider) || window.mapboxgl
|
||||
if (!gl || !mapEl.value) { err.value = 'map engine not loaded'; return }
|
||||
try {
|
||||
const adm0 = await load()
|
||||
const map = new gl.Map({
|
||||
container: mapEl.value,
|
||||
style: isDark.value ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11',
|
||||
// Paired through the registry so both engines show the same intent: 'ink' is the
|
||||
// near-black entry (mapbox dark-v11 / OpenFreeMap dark) and 'paper' the near-white one
|
||||
// (light-v11 / positron). Falls back to the literal if a name ever stops resolving.
|
||||
style: resolveNamedStyle(isDark.value ? 'ink' : 'paper', ciProvider).style
|
||||
|| (isDark.value ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11'),
|
||||
accessToken: mapboxToken.value,
|
||||
center: [42, 18], zoom: 1.3, projection: 'mercator', attributionControl: false,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ import LegendDesktop from '@map/components/legends/LegendDesktop.vue'
|
|||
// legend (not a child) so its CSS lives in its own useShadowStyles namespace
|
||||
// and doesn't fight the legend card's selectors. Per UX 2026-04-27.
|
||||
import LegendTools from '@map/components/legends/LegendTools.vue'
|
||||
import { showsFlyTools } from '@map/components/legends/LegendTools.vue'
|
||||
// PERF: LegendMobile only renders below the @media(max-width:767px) breakpoint.
|
||||
// Defer the import so desktop users never load it.
|
||||
const LegendMobile = retryableAsyncComponent(() => import('@map/components/legends/LegendMobile.vue'))
|
||||
|
|
@ -142,6 +143,7 @@ import FullscreenButton from '@map/components/map-controls/FullscreenButton.vue
|
|||
import ThemeToggleButton from '@map/components/map-controls/ThemeToggleButton.vue'
|
||||
import HamburgerButton from '@map/components/map-controls/HamburgerButton.vue'
|
||||
import GeocoderComponent from '@map/components/map-controls/GeocoderComponent.vue'
|
||||
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
|
||||
import ShareButton from '@map/components/map-controls/ShareButton.vue'
|
||||
|
||||
// PERF: SideMenuDrawer is unused in the current research-map template (the
|
||||
|
|
@ -360,6 +362,32 @@ useShadowStyles(`
|
|||
|
||||
// ─── Config from ProfileLoader (provide/inject — never direct imports) ──────
|
||||
const profileConfig = inject('profileConfig')
|
||||
|
||||
// ── SEARCH BAR IS MAPBOX-ONLY, AND ITS ABSENCE MUST NOT LEAVE A HOLE ─────────
|
||||
// Same contract as doxa-research-map (the reference implementation), deliberately copied rather
|
||||
// than re-invented. Driver 2026-09-24: the geocoder is DEFERRED to Mapbox, because
|
||||
// GeocoderComponent's maplibre path still calls makeMapboxForwardGeocode(accessToken) — the search
|
||||
// REQUEST goes to Mapbox whichever engine draws the tiles, so a keyless map with a search bar is
|
||||
// not keyless and would bill the very key the maplibre option exists to avoid.
|
||||
// MEASURED here before the change, built tree, both engines: the bar rendered at y=12 under mapbox
|
||||
// and STILL rendered (y=0) under maplibre, with the legend pinned at 60 either way.
|
||||
const cpMapProvider = resolveMapProvider(profileConfig)
|
||||
const hasGeocoder = computed(() => {
|
||||
if (profileConfig?.value?.search === false) return false
|
||||
return cpMapProvider === 'mapbox'
|
||||
})
|
||||
// SemanticTreeLegend's --stl-top defaults to 60px, and that 60px exists ONLY to reserve room for a
|
||||
// search bar above it. With no bar the legend must RISE into the vacated space instead of leaving a
|
||||
// dead band — the Driver's explicit requirement. 14px matches the reference.
|
||||
// How far the legend may rise depends on WHAT ELSE is in the strip the search bar vacated.
|
||||
// LegendTools sits at top:14px on the tabs its predicate allows, so raising the legend to 14 there
|
||||
// prints the toolbar across the legend's header — MEASURED on country-posters. Ask the toolbar's own
|
||||
// exported predicate rather than re-typing its condition here, and stop just below it when it shows.
|
||||
// A collision is worse than the gap this is meant to close, and 56px still recovers most of it.
|
||||
const legendTopVar = computed(() => {
|
||||
if (hasGeocoder.value) return null
|
||||
return showsFlyTools(activeLegendType?.value ?? activeLegendType) ? '56px' : '14px'
|
||||
})
|
||||
const mapboxToken = inject('mapboxToken')
|
||||
const dataSource = inject('dataSource')
|
||||
const colorSet = inject('colorSet')
|
||||
|
|
@ -2081,7 +2109,7 @@ onBeforeUnmount(() => {
|
|||
simple-map mfe so tabs 1-3 stay visually identical. -->
|
||||
<GeocoderComponent
|
||||
ref="geocoderRef"
|
||||
v-if="appReady && map"
|
||||
v-if="appReady && map && hasGeocoder"
|
||||
:map-instance="map"
|
||||
:access-token="mapboxToken"
|
||||
:data-source-id="dataSource"
|
||||
|
|
@ -2103,7 +2131,7 @@ onBeforeUnmount(() => {
|
|||
/>
|
||||
|
||||
<!-- Map toolbar (right rail) — canonical button set + ordering. -->
|
||||
<MapToolbar v-if="appReady && map">
|
||||
<MapToolbar v-if="appReady && map" :has-geocoder="hasGeocoder">
|
||||
<ZoomInButton :map="map" :is-dark="isDark" />
|
||||
<ZoomOutButton :map="map" :is-dark="isDark" />
|
||||
<LocationButton :map="map" :is-dark="isDark" />
|
||||
|
|
@ -2139,7 +2167,7 @@ onBeforeUnmount(() => {
|
|||
(prayer/adoption/engagement/religion) pass hideTabs to suppress the
|
||||
inner tab strip. LegendDesktop only renders for the people-group
|
||||
detail panel (pin click → PeopleGroupDetail swap). -->
|
||||
<div class="rm-legend-desktop-slot">
|
||||
<div class="rm-legend-desktop-slot" :style="legendTopVar ? { '--stl-top': legendTopVar } : null">
|
||||
<LegendDesktop
|
||||
v-if="appReady && uiStore.legendMode === 'detail' && uiStore.selectedPeopleGroup"
|
||||
:legend-type="activeLegendType"
|
||||
|
|
|
|||
|
|
@ -121,8 +121,31 @@ function _syncScrollZoomToHost(m) {
|
|||
const docScrolls = doc.scrollHeight > window.innerHeight + 4
|
||||
const host = (m.getContainer && m.getContainer()) || null
|
||||
const ancestorScrolls = host ? !!_findScrollportAncestor(host) : false
|
||||
if (docScrolls || ancestorScrolls) m.scrollZoom.disable()
|
||||
else m.scrollZoom.enable()
|
||||
// ★ THE MAP KEEPS THE GESTURE. THE EDGE STRIPS ARE THE WAY PAST IT.
|
||||
// Driver, 2026-09-24: "two finger scrolling on the map should allow me to interact with the
|
||||
// map, but instead it just scrolls down past the map and does not interact with the map —
|
||||
// that's what the scroll side bars are there for, to let me interact with the map AND have a
|
||||
// way to scroll down past the map."
|
||||
//
|
||||
// This used to disable scroll-zoom whenever the page or an ancestor could scroll, which is
|
||||
// ALWAYS true in the research-map-plus-grid composite (a map with a grid stacked under it).
|
||||
// MEASURED before this change, wheel over the centre of the map in that composite:
|
||||
// scrollY 0 -> 480 and the camera never moved. The map was inert exactly where it is the
|
||||
// main control.
|
||||
//
|
||||
// It was not a careless choice — it fixed BUG-CANNOT-SCROLL-DOWN-TO-THE-GRID, where the map
|
||||
// ate every wheel and the grid below was unreachable. But that bug had already been solved a
|
||||
// second way: the .map-scroll-edge gutters (useEdgeScrollAffordance, pointer-events:auto,
|
||||
// touch-action:pan-y) are a dedicated page-scroll lane down both sides, and the note at the
|
||||
// Map constructor says so outright — "the single-finger page-scroll escape is NOW the gray
|
||||
// side-edge affordance". With the strips in place, releasing the wheel as well made the map
|
||||
// non-interactive and left the strips redundant. Two fixes for one bug, and the second one
|
||||
// silently undid the feature.
|
||||
//
|
||||
// So: keep scroll-zoom ON, and let the strips carry the page. `docScrolls`/`ancestorScrolls`
|
||||
// are still computed because the edge affordance below keys its gray indicator off the same
|
||||
// question — a map with nothing stacked under it should not advertise a scroll lane.
|
||||
m.scrollZoom.enable()
|
||||
} catch (_) { /* an engine without scrollZoom must never break the panel */ }
|
||||
}
|
||||
function loadPoster() {
|
||||
|
|
@ -153,6 +176,7 @@ import LegendDesktop from '@map/components/legends/LegendDesktop.vue'
|
|||
// legend (not a child) so its CSS lives in its own useShadowStyles namespace
|
||||
// and doesn't fight the legend card's selectors. Per UX 2026-04-27.
|
||||
import LegendTools from '@map/components/legends/LegendTools.vue'
|
||||
import { showsFlyTools } from '@map/components/legends/LegendTools.vue'
|
||||
// PERF: LegendMobile only renders below the @media(max-width:767px) breakpoint.
|
||||
// Defer the import so desktop users never load it.
|
||||
const LegendMobile = retryableAsyncComponent(() => import('@map/components/legends/LegendMobile.vue'))
|
||||
|
|
@ -448,7 +472,15 @@ const hasGeocoder = computed(() => {
|
|||
// SemanticTreeLegend's --stl-top defaults to 60px, and that 60px exists ONLY to reserve room for a
|
||||
// search bar above it. Leaving it when the bar is gone is the empty band the Driver called out.
|
||||
// 14px matches .lt-root's own top, so the legend lands where the search bar's top edge was.
|
||||
const legendTopVar = computed(() => (hasGeocoder.value ? null : '14px'))
|
||||
// How far the legend may rise depends on WHAT ELSE is in the strip the search bar vacated.
|
||||
// LegendTools sits at top:14px on the tabs its predicate allows, so raising the legend to 14 there
|
||||
// prints the toolbar across the legend's header — MEASURED on country-posters. Ask the toolbar's own
|
||||
// exported predicate rather than re-typing its condition here, and stop just below it when it shows.
|
||||
// A collision is worse than the gap this is meant to close, and 56px still recovers most of it.
|
||||
const legendTopVar = computed(() => {
|
||||
if (hasGeocoder.value) return null
|
||||
return showsFlyTools(activeLegendType?.value ?? activeLegendType) ? '56px' : '14px'
|
||||
})
|
||||
|
||||
// ★ FLY + CLUSTERS ARE A LEAF-ONLY TOOL. LegendTools renders at top:14px centred on the legend
|
||||
// column — the search bar's exact slot — so inside a COMPOSITE it appears to replace the search
|
||||
|
|
|
|||
|
|
@ -67,10 +67,22 @@ import LocationButton from '@map/components/map-controls/LocationButton.vue'
|
|||
import FullscreenButton from '@map/components/map-controls/FullscreenButton.vue'
|
||||
import ShareButton from '@map/components/map-controls/ShareButton.vue'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
|
||||
import { ensureEngineScript, engineGlobal } from '@map/composables/useMapInstance.js'
|
||||
import { resolveNamedStyle } from '@map/styles/registry.js'
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
const isDark = computed(() => profileConfig.value?.isDark === true)
|
||||
const mapboxToken = computed(() => profileConfig.value?.tk || window.DOXA_TOKEN || '')
|
||||
|
||||
// ── HONOUR THE PROVIDER SWITCH: this profile builds its map RAW ─────────────
|
||||
// Bundles that go through useMapInstance get the switch for free; this one calls `new gl.Map`
|
||||
// directly, so it never saw it. MEASURED by sweeping all 16 isMap leaves: at ?provider=maplibre
|
||||
// this leaf still loaded Mapbox and made 18 requests to api.mapbox.com, identical to its mapbox
|
||||
// run. Same shape and same fix as cities-internet/connectivity-map and my-upg-100-list; the engine
|
||||
// loader is IMPORTED from useMapInstance, never a second copy of the ENGINES table.
|
||||
// No geocoder is mounted here, so the deferred-search-bar rule does not apply to this profile.
|
||||
const grProvider = resolveMapProvider(profileConfig)
|
||||
const assetsBase = computed(() => resolveAssetsBase(profileConfig.value))
|
||||
|
||||
const mapEl = ref(null)
|
||||
|
|
@ -287,13 +299,17 @@ function paint(map, adm0) {
|
|||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const gl = window.mapboxgl
|
||||
if (!gl || !mapEl.value) { err.value = 'mapbox-gl not loaded'; return }
|
||||
try { await ensureEngineScript(grProvider) } catch (_) { /* fall through to the notice below */ }
|
||||
const gl = engineGlobal(grProvider) || window.mapboxgl
|
||||
if (!gl || !mapEl.value) { err.value = 'map engine not loaded'; return }
|
||||
try {
|
||||
const adm0 = await load()
|
||||
const map = new gl.Map({
|
||||
container: mapEl.value,
|
||||
style: isDark.value ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11',
|
||||
// Paired through the registry so both engines show one intent: 'ink' is the near-black entry
|
||||
// (mapbox dark-v11 / OpenFreeMap dark), 'paper' the near-white one (light-v11 / positron).
|
||||
style: resolveNamedStyle(isDark.value ? 'ink' : 'paper', grProvider).style
|
||||
|| (isDark.value ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11'),
|
||||
accessToken: mapboxToken.value,
|
||||
center: [42, 18], zoom: 1.3, projection: 'mercator', attributionControl: false,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ import ThemeToggleButton from '@map/components/map-controls/ThemeToggleButton.vu
|
|||
// `__upgKind` to route PG→detail / zone→fitBounds / territory→fitBounds. Never
|
||||
// edits src/ (zero blast radius — md5 unchanged). (research-map.vue:141,1815.)
|
||||
import GeocoderComponent from '@map/components/map-controls/GeocoderComponent.vue'
|
||||
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
|
||||
import { ensureEngineScript, engineGlobal } from '@map/composables/useMapInstance.js'
|
||||
import { resolveNamedStyle } from '@map/styles/registry.js'
|
||||
// Named imports: only the list arrays ship in the bundle; the file's bookkeeping fields stay out.
|
||||
import { lists as clusterLists, asia_lists as clusterAsiaLists, uncovered_lists as clusterUncoveredLists } from './data/clusters.json'
|
||||
const clusters = { lists: clusterLists, asia_lists: clusterAsiaLists, uncovered_lists: clusterUncoveredLists }
|
||||
|
|
@ -105,6 +108,23 @@ import { buildSearchIndex, buildGeocoderFeed, matchSearch } from './data/search-
|
|||
const mapboxToken = inject('mapboxToken', ref(''))
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
|
||||
// ── THIS PROFILE BUILDS ITS MAP RAW, SO IT HAS TO HONOUR THE SWITCH ITSELF ───
|
||||
// Every other map bundle reaches the provider switch for free because useMapInstance resolves it.
|
||||
// This one calls `new gl.Map` directly (see onMounted), so it was the ONE map the switch could not
|
||||
// move: MEASURED at ?provider=maplibre it still loaded Mapbox and made 19 requests to
|
||||
// api.mapbox.com while five sibling leaves swapped cleanly.
|
||||
// The engine loader is IMPORTED from useMapInstance rather than re-declared here — a second copy of
|
||||
// the ENGINES table in a profile is how two copies of one fact drift apart.
|
||||
const upgProvider = resolveMapProvider(profileConfig)
|
||||
const isMaplibre = upgProvider === 'maplibre'
|
||||
// The geocoder is deferred to Mapbox by decision (Driver 2026-09-24): GeocoderComponent's maplibre
|
||||
// path still calls makeMapboxForwardGeocode(accessToken), so the SEARCH REQUEST goes to Mapbox
|
||||
// whichever engine draws the tiles — a keyless map with a search bar is not keyless.
|
||||
const hasGeocoder = computed(() => {
|
||||
if (profileConfig?.value?.search === false) return false
|
||||
return upgProvider === 'mapbox'
|
||||
})
|
||||
|
||||
// EDGE SCROLL AFFORDANCE — the gray accessibility side bars that tell you the page
|
||||
// scrolls PAST the map. Feature-flagged per embed, because it is a promise: a map
|
||||
// with nothing beneath it must not advertise a scroll escape that goes nowhere.
|
||||
|
|
@ -1383,24 +1403,31 @@ onMounted(async () => {
|
|||
// hidden) to explain it. Sibling poster-wall.vue survives the same early return
|
||||
// precisely because its content does not depend on state onMounted populates.
|
||||
bootstrapLegend()
|
||||
const gl = window.mapboxgl
|
||||
// Load the engine the PROVIDER selects, not the one this file used to assume.
|
||||
try { await ensureEngineScript(upgProvider) } catch (_) { /* fall through to the !gl notice below */ }
|
||||
const gl = engineGlobal(upgProvider) || window.mapboxgl
|
||||
const token = mapboxToken?.value || profileConfig?.value?.tk || ''
|
||||
// Degraded exits: keep the terse telemetry in `status`, put the human-readable
|
||||
// notice in the MAP CELL, and still enrich the legend with live population data
|
||||
// (a plain fetch — it needs no map). The list stays fully usable without tiles.
|
||||
if (!gl) {
|
||||
status.value = '⚠ mapbox-gl not loaded'
|
||||
mapError.value = 'mapbox-gl did not load. The list and legend are unaffected.'
|
||||
status.value = '⚠ map engine not loaded'
|
||||
mapError.value = 'The map engine did not load. The list and legend are unaffected.'
|
||||
enrichLegendFromRecords()
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
status.value = '⚠ no mapbox token'
|
||||
mapError.value = 'No Mapbox token configured. The list and legend are unaffected.'
|
||||
enrichLegendFromRecords()
|
||||
return
|
||||
// ★ A TOKEN IS A MAPBOX REQUIREMENT, NOT A MAP REQUIREMENT. This gate returned early whenever
|
||||
// there was no token, which under maplibre would have refused to draw a map that needs no
|
||||
// token at all — turning the keyless option into a blank cell. MapLibre skips it entirely.
|
||||
if (!isMaplibre) {
|
||||
if (!token) {
|
||||
status.value = '⚠ no mapbox token'
|
||||
mapError.value = 'No Mapbox token configured. The list and legend are unaffected.'
|
||||
enrichLegendFromRecords()
|
||||
return
|
||||
}
|
||||
gl.accessToken = token
|
||||
}
|
||||
gl.accessToken = token
|
||||
// P0 fix (LESSON-03 L4): mapbox-gl.css must live INSIDE the shadow root —
|
||||
// the host page's <link> cannot penetrate it. Without it the geocoder's
|
||||
// .mapboxgl-ctrl-top-left container is position:static and the search pill
|
||||
|
|
@ -1421,7 +1448,11 @@ onMounted(async () => {
|
|||
}
|
||||
} catch (_) { /* CSS injection is best-effort; map still works without it */ }
|
||||
map = new gl.Map({
|
||||
container: mapEl.value, style: 'mapbox://styles/mapbox/light-v11',
|
||||
container: mapEl.value,
|
||||
// 'paper' is the registry's near-white minimal entry — mapbox light-v11 paired with
|
||||
// OpenFreeMap positron — so the two engines show the same intent instead of this file
|
||||
// hardcoding one engine's URL. Falls back to the literal if the name ever stops resolving.
|
||||
style: resolveNamedStyle('paper', upgProvider).style || 'mapbox://styles/mapbox/light-v11',
|
||||
center: [35, 20], zoom: 1.5, projection: 'mercator',
|
||||
})
|
||||
mapRef.value = map // expose the reactive instance to the controls
|
||||
|
|
@ -1860,7 +1891,7 @@ onBeforeUnmount(() => {
|
|||
`.value`; template scope must not. -->
|
||||
|
||||
<GeocoderComponent
|
||||
v-if="mapRef"
|
||||
v-if="mapRef && hasGeocoder"
|
||||
ref="geocoderRef"
|
||||
:map-instance="mapRef"
|
||||
:access-token="mapboxToken || profileConfig?.tk || ''"
|
||||
|
|
@ -1875,7 +1906,7 @@ onBeforeUnmount(() => {
|
|||
<!-- Shared control rail — same set + ordering as research-map. MapToolbar
|
||||
self-positions top-right; the status badge is bottom-right (no collision).
|
||||
Gated on mapRef so the buttons only mount once the map instance exists. -->
|
||||
<MapToolbar v-if="mapRef">
|
||||
<MapToolbar v-if="mapRef" :has-geocoder="hasGeocoder">
|
||||
<ZoomInButton :map="mapRef" :is-dark="isDark" />
|
||||
<ZoomOutButton :map="mapRef" :is-dark="isDark" />
|
||||
<LocationButton :map="mapRef" :is-dark="isDark" />
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import { ref, computed, watch, onMounted, getCurrentInstance } from 'vue'
|
|||
import Fuse from '@map/components/search/fuzzy.js'
|
||||
import SearchBar from '@map/components/search/SearchBar.vue'
|
||||
import FilterDropdown from '@map/components/search/FilterDropdown.vue'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { countrySlug } from './src/countries.js'
|
||||
// ── DERIVED family axes (BUG-SEMANTIC-TREE-SIDEBAR-NOT-INTEROPERABLE-WITH-GRID) ──
|
||||
// The Religion/Language trees' TOP TIERS emit DERIVED family names ("Hinduism",
|
||||
|
|
@ -207,6 +208,30 @@ const cfg = computed(() => {
|
|||
if (!props.profileConfig) return {}
|
||||
try { return JSON.parse(props.profileConfig) || {} } catch { return {} }
|
||||
})
|
||||
// ── THE WORLD-MAP BACKDROP BEHIND THE FILTERS (Driver, 2026-09-24) ──────────
|
||||
// "there is an SVG of a map that shows up under the search bar, where the toggles are."
|
||||
// It is DX-life's own treatment, ported rather than reinvented: UupgsList.vue puts
|
||||
// `background-image: url(<images>/worldmap.svg); background-size: 60%; background-position: top`
|
||||
// on the container that wraps the search box and the toggles — a BACKGROUND, not an icon beside
|
||||
// the input, which is the detail that makes the block read as a map tool instead of a form.
|
||||
//
|
||||
// The file (311KB) is served as a SHARED ASSET rather than inlined: a data: URI would put it in
|
||||
// every bundle that imports this grid, and it is decorative. resolveAssetsBase() is the same
|
||||
// helper the boundary geojson uses, so it resolves wherever the tree is dropped — CDN, sub-path,
|
||||
// or the plugin studio — instead of assuming a root-relative path.
|
||||
//
|
||||
// Only drawn when the block actually exists: with show-search and show-filters both off there is
|
||||
// no filter block to sit behind, and a backdrop with nothing on it is just noise.
|
||||
const filtersBackdrop = computed(() => {
|
||||
if (!useSearch.value && !useFilters.value) return null
|
||||
const base = resolveAssetsBase(cfg.value)
|
||||
return {
|
||||
backgroundImage: `url(${base}/brand/worldmap.svg)`,
|
||||
backgroundSize: '60%',
|
||||
backgroundPosition: 'top center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}
|
||||
})
|
||||
const prayBaseUrl = computed(() => props.prayBaseUrl || cfg.value.prayBaseUrl || 'https://pray.doxa.life')
|
||||
const languageCode = computed(() => props.languageCode || cfg.value.languageCode || '')
|
||||
// ★ ABSOLUTE CANONICAL DEFAULT (Driver 2026-09-23, pasted from his own browser:
|
||||
|
|
@ -681,8 +706,15 @@ onMounted(getUUPGs)
|
|||
<template>
|
||||
<div class="pgg-root">
|
||||
<!-- The element's OWN filters block: shown by default, hidden when driven externally. -->
|
||||
<div v-if="useSearch || useFilters" id="filters" class="filters">
|
||||
<div v-if="useSearch" class="pgg-search">
|
||||
<div v-if="useSearch || useFilters" id="filters" class="filters" :style="filtersBackdrop">
|
||||
<!-- ★ EXACT BELONGS IN THE SEARCH ROW, because it modifies the SEARCH.
|
||||
DX-life's UupgsList puts the input and the Exact button in one `.repel` row inside
|
||||
`.search-box`, so the modifier reads as part of the field it changes. Ours had it in a
|
||||
second `.filters__panel` BELOW the four dropdowns, where it looked like a fifth filter
|
||||
and only appeared once the panel was expanded — so a user searching with the panel
|
||||
collapsed could not reach it at all. Now it sits beside the input whenever search is
|
||||
shown, which is also when it can do anything. -->
|
||||
<div v-if="useSearch" class="pgg-search pgg-search--repel">
|
||||
<SearchBar
|
||||
:model-value="localSearchTerm"
|
||||
:placeholder="t.search || 'Search people groups…'"
|
||||
|
|
@ -690,6 +722,12 @@ onMounted(getUUPGs)
|
|||
max-width="480px"
|
||||
@search="onSearch"
|
||||
/>
|
||||
<button
|
||||
v-if="useFilters"
|
||||
class="filter-toggle pgg-exact" type="button"
|
||||
:data-active="activeFilters.exact ? '' : undefined"
|
||||
@click="toggleExact"
|
||||
>{{ t.exact_filter || 'Exact' }}</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
|
@ -741,13 +779,6 @@ onMounted(getUUPGs)
|
|||
@filter-change="onFilterChange" @filter-clear="onFilterClear"
|
||||
/>
|
||||
</div>
|
||||
<div class="filters__panel">
|
||||
<button
|
||||
class="filter-toggle" type="button"
|
||||
:data-active="activeFilters.exact ? '' : undefined"
|
||||
@click="toggleExact"
|
||||
>{{ t.exact_filter || 'Exact' }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="useFilters && visibleFilterCount > 0" class="filters__active">
|
||||
|
|
@ -865,10 +896,28 @@ onMounted(getUUPGs)
|
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
padding: 16px;
|
||||
}
|
||||
.filters { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; }
|
||||
.filters { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px;
|
||||
/* padding-block, never a `padding` shorthand: the shorthand would zero the side gutters
|
||||
this grid relies on at phone width. min-height gives the 60%-wide backdrop room to read
|
||||
behind the controls, which is what makes it a backdrop and not a stripe. */
|
||||
padding-block: 18px 8px; min-height: 190px; }
|
||||
/* The search box itself is the shared @map SearchBar (self-styling). This wrapper only
|
||||
centres it; the input's look lives in library/components/search/SearchBar.vue. */
|
||||
.pgg-search { display: flex; justify-content: center; }
|
||||
/* ★ THE ROW IS CONSTRAINED, WHICH IS WHAT MAKES `repel` READ AS A PAIR.
|
||||
DX-life's markup is `.search-box | center | max-width-md` wrapping a `.repel` — the
|
||||
repel only looks right because the BOX is narrow. Copying the repel without the width
|
||||
cap put Exact against the far edge of a 1248px row, 390px from the field it modifies,
|
||||
which reads as an unrelated control (measured: input ended at x=843, button at x=1231).
|
||||
So: cap the row near the field's own 480px, centre it, and repel INSIDE that. Wraps on a
|
||||
phone so the button drops under the input instead of squeezing it. */
|
||||
.pgg-search--repel {
|
||||
gap: 10px; align-items: center; flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
width: 100%; max-width: 600px; margin-inline: auto;
|
||||
}
|
||||
.pgg-search--repel > :first-child { flex: 1 1 320px; min-width: 0; }
|
||||
.pgg-exact { flex: none; }
|
||||
/* A CONTROL, not a text link. It was background:none + border:none + text-decoration:underline,
|
||||
which reads as a hyperlink — so the four dropdowns it gates were effectively undiscoverable
|
||||
(Driver-reported). Now a bordered, filled affordance in the material-light palette with a
|
||||
|
|
|
|||
|
|
@ -27,12 +27,15 @@
|
|||
*/
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount, inject } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
// The region palette comes from the shared strategy, never from a table in here:
|
||||
// check:one-palette exists because a private palette in a consumer is what once made two
|
||||
// surfaces disagree about what a colour meant.
|
||||
import { applyColor as regionColorExpression } from '@map/colors/wagf-region.js'
|
||||
|
||||
|
||||
const emit = defineEmits(['pick-country'])
|
||||
const props = defineProps({
|
||||
// ISO-3 codes to highlight, e.g. ['IND'] for a country or every code in a region.
|
||||
|
|
@ -57,6 +60,10 @@ const props = defineProps({
|
|||
|
||||
// The token ladder: ProfileLoader provides it (the same seam every map bundle uses). A nested
|
||||
// element has no leaf page of its own, which is what check:nested-token exists to police.
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
// Which engine is live. Derived ONCE: addLayers() branches on it in three places, and three
|
||||
// separate resolveMapProvider() calls is three chances for them to disagree.
|
||||
const isMaplibreProvider = resolveMapProvider(profileConfig) === 'maplibre'
|
||||
const mapboxToken = inject('mapboxToken', ref(''))
|
||||
const tokenValue = computed(() => (typeof mapboxToken === 'string' ? mapboxToken : mapboxToken?.value) || '')
|
||||
|
||||
|
|
@ -171,15 +178,51 @@ function addLayers() {
|
|||
const { color: pinColor, sortKey: pinSortKey, opacity: pinOpacity, radius: pinRadius } = pinExpressions()
|
||||
|
||||
if (!m.getSource(SRC_COUNTRIES)) {
|
||||
m.addSource(SRC_COUNTRIES, { type: 'vector', url: 'mapbox://mapbox.country-boundaries-v1' })
|
||||
// ── COUNTRY POLYGONS, PER PROVIDER ────────────────────────────────────────────────────────
|
||||
// ★ THE ENGINE SWAP IS NOT ONE CONFIG LINE. This read from
|
||||
// `mapbox://mapbox.country-boundaries-v1`, a Mapbox-hosted VECTOR TILESET that MapLibre
|
||||
// cannot fetch at all — it throws
|
||||
// Fetch API cannot load mapbox://mapbox.country-boundaries-v1. URL scheme "mapbox" is not supported
|
||||
// and then draws the basemap perfectly with the highlighted countries simply ABSENT.
|
||||
// MEASURED, not predicted: the leaf reported canvas=1 and engine=MAPLIBRE — a coarse sweep
|
||||
// called it "swapped" while its primary data layer was missing. Only the console said so.
|
||||
//
|
||||
// The keyless equivalent already ships: assets/boundaries/world-adm0.geojson, staged by
|
||||
// generate-tree and already consumed by upg-100-list, connectivity-map and resources-map
|
||||
// through this same resolveAssetsBase helper. No new asset, no new dependency.
|
||||
//
|
||||
// ★ THE FIELD BRIDGE. The tileset names the ISO field `iso_3166_1_alpha_3`; the geojson names
|
||||
// it `iso3`. countryFilter() reads the tileset name, so the property is normalised ONCE as the
|
||||
// data loads and every expression keeps reading the single name it always read — the Mapbox
|
||||
// path stays byte-for-byte unchanged. Same shape as DashboardMapPanel, deliberately copied.
|
||||
if (isMaplibreProvider) {
|
||||
// Add EMPTY first, then setData after the fetch: the addLayer calls below must run
|
||||
// SYNCHRONOUSLY to keep these fills beneath the pins (layer order is insertion order).
|
||||
m.addSource(SRC_COUNTRIES, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } })
|
||||
fetch(`${resolveAssetsBase(profileConfig?.value)}/boundaries/world-adm0.geojson`)
|
||||
.then((r) => r.json())
|
||||
.then((gj) => {
|
||||
for (const f of gj.features || []) {
|
||||
if (f.properties && f.properties.iso3) f.properties.iso_3166_1_alpha_3 = f.properties.iso3
|
||||
}
|
||||
const src = map.value && map.value.getSource(SRC_COUNTRIES)
|
||||
if (src) src.setData(gj)
|
||||
})
|
||||
.catch((e) => console.warn('[rcm] country boundaries failed to load', e))
|
||||
} else {
|
||||
m.addSource(SRC_COUNTRIES, { type: 'vector', url: 'mapbox://mapbox.country-boundaries-v1' })
|
||||
}
|
||||
// 'source-layer' applies to VECTOR TILE sources only — on a geojson source MapLibre rejects
|
||||
// the layer outright ("does not need a source-layer"), so it is set for Mapbox alone.
|
||||
const srcLayer = isMaplibreProvider ? {} : { 'source-layer': 'country_boundaries' }
|
||||
m.addLayer({
|
||||
id: 'rcm-focus-fill', type: 'fill', source: SRC_COUNTRIES,
|
||||
'source-layer': 'country_boundaries', filter: countryFilter(),
|
||||
...srcLayer, filter: countryFilter(),
|
||||
paint: { 'fill-color': C.brand, 'fill-opacity': 0.12 },
|
||||
})
|
||||
m.addLayer({
|
||||
id: 'rcm-focus-outline', type: 'line', source: SRC_COUNTRIES,
|
||||
'source-layer': 'country_boundaries', filter: countryFilter(),
|
||||
...srcLayer, filter: countryFilter(),
|
||||
paint: { 'line-color': C.brand, 'line-width': 2 },
|
||||
})
|
||||
}
|
||||
|
|
|
|||
18
plugin-designs/upg-neighbor/index.html
Normal file
18
plugin-designs/upg-neighbor/index.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>upg-neighbor (dev)</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
html,body{height:100%;background:#f3f3f1}
|
||||
upg-neighbor{position:fixed;inset:0;display:block}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<upg-neighbor profile-config='{"profile":"upg-neighbor","legacyElement":"send-map","assetsBase":"/public/assets"}'></upg-neighbor>
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
6
plugin-designs/upg-neighbor/index.js
Normal file
6
plugin-designs/upg-neighbor/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { defineCustomElement } from 'vue'
|
||||
import UpgNeighbor from './upg-neighbor.vue'
|
||||
|
||||
// One tag, three preset instances (see upg-neighbor.instances.json) — the standard cap-2 shape.
|
||||
const El = defineCustomElement(UpgNeighbor)
|
||||
if (!customElements.get('upg-neighbor')) customElements.define('upg-neighbor', El)
|
||||
46
plugin-designs/upg-neighbor/upg-neighbor.instances.json
Normal file
46
plugin-designs/upg-neighbor/upg-neighbor.instances.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"//": "Five preset renders of the legacy-wrapper profile -- one per legacy map rescued by the experimental roundup (EXPERIMENTAL-MAPS-ROUNDUP.md). props follow the cap-2 instances contract (props, not config -- measured: config was silently dropped from BASE_CONFIG and every instance page rendered the default send-map).",
|
||||
"profile": "upg-neighbor",
|
||||
"instances": [
|
||||
{
|
||||
"id": "send-map",
|
||||
"title": "Send Map (legacy)",
|
||||
"props": {
|
||||
"legacyElement": "send-map",
|
||||
"legacyTitle": "Send Map"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "upg-neighbors",
|
||||
"title": "UPG Neighbors (legacy)",
|
||||
"props": {
|
||||
"legacyElement": "upg-neighbors",
|
||||
"legacyTitle": "UPG Neighbors"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lang-cluster",
|
||||
"title": "Language Clusters (legacy)",
|
||||
"props": {
|
||||
"legacyElement": "lang-cluster",
|
||||
"legacyTitle": "Language Clusters"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bloom-cockpit",
|
||||
"title": "Bloom Cockpit (legacy)",
|
||||
"props": {
|
||||
"legacyElement": "bloom-cockpit",
|
||||
"legacyTitle": "Bloom Cockpit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flower-mesh",
|
||||
"title": "Flower Mesh (legacy)",
|
||||
"props": {
|
||||
"legacyElement": "flower-mesh",
|
||||
"legacyTitle": "Flower Mesh"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
56
plugin-designs/upg-neighbor/upg-neighbor.vue
Normal file
56
plugin-designs/upg-neighbor/upg-neighbor.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<script setup>
|
||||
/**
|
||||
* upg-neighbor.vue — LEGACY-WRAPPER bundle (experimental roundup).
|
||||
*
|
||||
* @description Rescues the three pre-bundler experimental maps that live inside the legacy
|
||||
* upg-neighbor IIFE — send-map, flower-mesh and lang-cluster — without rebuilding them and
|
||||
* without letting their old engine touch the modern bundles: each profile mounts its element
|
||||
* inside a sandboxed IFRAME whose host page (assets/legacy/upg-neighbor/index.html) loads the
|
||||
* prebuilt script, shims its relative /api fetches to the live API, and passes the token.
|
||||
* The iframe is the whole isolation contract — a legacy crash dies inside its own frame.
|
||||
* Refactor-to-native happens later, per map, only if a map earns it (roundup doc).
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
|
||||
// ★ PROP, NOT INJECT. This bundle defines its element DIRECTLY (index.js), so it never sits
|
||||
// under ProfileLoader and inject('profileConfig') is always null -- measured: the attribute
|
||||
// carried legacyElement:"lang-cluster" while the iframe src said el=send-map (the default),
|
||||
// and assetsBase degraded to a root-relative path that only worked because the sibling
|
||||
// web-root assets copy happened to exist. Direct-defined bundles read the profile-config
|
||||
// ATTRIBUTE as a prop (the people-groups-grid pattern); the send-map default masked the miss
|
||||
// because send-map is also instance #1.
|
||||
const props = defineProps({ profileConfig: { type: String, default: '' } })
|
||||
const cfg = computed(() => {
|
||||
try { return props.profileConfig ? JSON.parse(props.profileConfig) : {} }
|
||||
catch (_) { return {} }
|
||||
})
|
||||
const element = computed(() => String(cfg.value.legacyElement || 'send-map').replace(/[^a-z0-9-]/g, ''))
|
||||
const title = computed(() => cfg.value.legacyTitle || element.value)
|
||||
|
||||
// assetsBase is a RESERVED config key -- raw cfg reads miss it in some hosts; every
|
||||
// data-fetching bundle resolves it through the shared helper (DashboardMapPanel.vue:33),
|
||||
// which also degrades correctly when nothing is provided. Measured before this fix: the
|
||||
// iframe resolved against the LEAF page dir and 404'd in all three instances.
|
||||
const frameSrc = computed(() => {
|
||||
const base = String(resolveAssetsBase({ value: cfg.value }) || cfg.value.assetsBase || './assets').replace(/\/+$/, '')
|
||||
const tk = cfg.value.tk || (typeof window !== 'undefined' && window.MAP_TOKEN) || ''
|
||||
return base + '/legacy/upg-neighbor/index.html?el=' + element.value + (tk ? '&tk=' + encodeURIComponent(tk) : '')
|
||||
})
|
||||
|
||||
useShadowStyles(
|
||||
'.unw{position:relative;width:100%;height:100%;min-height:520px;background:#f3f3f1}' +
|
||||
'.unw iframe{position:absolute;inset:0;width:100%;height:100%;border:0}' +
|
||||
'.unw .tag{position:absolute;top:8px;right:8px;z-index:2;font:600 10px ui-monospace,monospace;' +
|
||||
'letter-spacing:.06em;text-transform:uppercase;color:#6d766e;background:#fff;' +
|
||||
'border:1px solid #d1d4cd;border-radius:100px;padding:3px 8px}',
|
||||
'upg-neighbor-wrapper')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="unw">
|
||||
<span class="tag">experimental · legacy</span>
|
||||
<iframe :src="frameSrc" :title="title" loading="lazy"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
402
public/assets/brand/worldmap.svg
vendored
Normal file
402
public/assets/brand/worldmap.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 304 KiB |
93
public/assets/fonts/noto-serif-devanagari/LICENSE
vendored
Normal file
93
public/assets/fonts/noto-serif-devanagari/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/devanagari)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
public/assets/fonts/noto-serif-devanagari/noto-serif-devanagari-400-devanagari.woff2
vendored
Normal file
BIN
public/assets/fonts/noto-serif-devanagari/noto-serif-devanagari-400-devanagari.woff2
vendored
Normal file
Binary file not shown.
75
public/assets/legacy/upg-neighbor/index.html
vendored
Normal file
75
public/assets/legacy/upg-neighbor/index.html
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>legacy map host</title>
|
||||
<style>html,body{margin:0;height:100%;background:#f3f3f1}.host{position:fixed;inset:0}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="host" id="host"></div>
|
||||
<script>
|
||||
// Sandboxed host for the LEGACY upg-neighbor IIFE (pre-bundler architecture). This page runs
|
||||
// inside an IFRAME so the old engine and its dependencies cannot collide with the modern
|
||||
// bundles -- the isolation pattern the roundup mandates ("add without breaking doxa-maps").
|
||||
var q = new URLSearchParams(location.search);
|
||||
var el = (q.get('el') || 'send-map').replace(/[^a-z0-9-]/g, '');
|
||||
var tk = q.get('tk') || '';
|
||||
// Token globals the legacy build may read.
|
||||
window.MAPBOX_TOKEN = tk; window.MAP_TOKEN = tk; window.UPG_TOKEN = tk;
|
||||
// The legacy code fetches relative "/api/people-groups/..." which only resolved on its old
|
||||
// origin. Shim fetch so those calls reach the live API from ANY host (static tree, iframe).
|
||||
var API = 'https://pray.doxa.life';
|
||||
// The cockpit profile fails closed without a configured API base and names this global in
|
||||
// its own error banner (measured: "No API base configured (apiBase / window.MAP_APP_API_URL
|
||||
// / VITE_API_BASE_URL) -- failing closed").
|
||||
window.MAP_APP_API_URL = API;
|
||||
var origFetch = window.fetch.bind(window);
|
||||
window.fetch = function (input, init) {
|
||||
try {
|
||||
var url = typeof input === 'string' ? input : (input && input.url) || '';
|
||||
if (url.indexOf('/api/') === 0) return origFetch(API + url, init);
|
||||
} catch (e) { /* fall through */ }
|
||||
return origFetch(input, init);
|
||||
};
|
||||
// ── KEYLESS SHIM ──────────────────────────────────────────────────────────
|
||||
// The legacy bundle expects a mapboxgl GLOBAL and requests mapbox://styles/mapbox/light-v11.
|
||||
// MapLibre is API-compatible with the mapbox-gl v1 surface the bundle uses, and OpenFreeMap
|
||||
// Positron is the keyless twin of light-v11 -- so the shim below makes every legacy map run
|
||||
// WITHOUT a Mapbox token: load our self-hosted MapLibre, alias it as mapboxgl, and rewrite
|
||||
// any mapbox:// style to the keyless URL inside a patched Map constructor.
|
||||
var KEYLESS_STYLE = 'https://tiles.openfreemap.org/styles/positron';
|
||||
var lib = document.createElement('link'); lib.rel='stylesheet'; lib.href='./maplibre-gl.css';
|
||||
document.head.appendChild(lib);
|
||||
var ml = document.createElement('script');
|
||||
ml.src = './maplibre-gl.js';
|
||||
ml.onload = function () {
|
||||
var Real = window.maplibregl.Map;
|
||||
function PatchedMap (opts) {
|
||||
if (opts && typeof opts.style === 'string' && opts.style.indexOf('mapbox://') === 0) opts.style = KEYLESS_STYLE;
|
||||
return new Real(opts);
|
||||
}
|
||||
PatchedMap.prototype = Real.prototype;
|
||||
window.mapboxgl = Object.assign({}, window.maplibregl, { Map: PatchedMap, accessToken: '' });
|
||||
loadLegacy();
|
||||
};
|
||||
ml.onerror = function () { loadLegacy(); };
|
||||
document.head.appendChild(ml);
|
||||
|
||||
function loadLegacy () {
|
||||
var s = document.createElement('script');
|
||||
s.src = './upg-neighbor.iife.js';
|
||||
s.onload = function () {
|
||||
var node = document.createElement(el);
|
||||
node.style.cssText = 'display:block;width:100%;height:100%';
|
||||
// The legacy elements use the SAME profile-config attribute contract as the modern
|
||||
// bundles (measured: without it the element renders its own usage banner).
|
||||
node.setAttribute('profile-config', JSON.stringify({ profile: el, tk: tk }));
|
||||
document.getElementById('host').appendChild(node);
|
||||
};
|
||||
s.onerror = function () { document.body.innerHTML = '<p style="font:14px system-ui;padding:20px">legacy bundle failed to load</p>'; };
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
public/assets/legacy/upg-neighbor/maplibre-gl.css
vendored
Normal file
1
public/assets/legacy/upg-neighbor/maplibre-gl.css
vendored
Normal file
File diff suppressed because one or more lines are too long
59
public/assets/legacy/upg-neighbor/maplibre-gl.js
vendored
Normal file
59
public/assets/legacy/upg-neighbor/maplibre-gl.js
vendored
Normal file
File diff suppressed because one or more lines are too long
548
public/assets/legacy/upg-neighbor/upg-neighbor.iife.js
vendored
Normal file
548
public/assets/legacy/upg-neighbor/upg-neighbor.iife.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <all-people-groups> custom element. -->
|
||||
<script src="../all-people-groups.js" integrity="sha384-p/GNZGy24Q6Fcx6en2niqH70w6pgh31gwGQk8BAxuMtLBqbXLl8eIkFeGN6QKHDq" crossorigin="anonymous"></script>
|
||||
<script src="../all-people-groups.js" integrity="sha384-cqk7oshuPW5FdnbZ/DyfBBXN2FObAI+V1jrbxmZR2xzNiBtWG65PETQ5/w5PCDB2" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
93
public/doxa-maps/assets/fonts/noto-serif-devanagari/LICENSE
generated
Normal file
93
public/doxa-maps/assets/fonts/noto-serif-devanagari/LICENSE
generated
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/devanagari)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
public/doxa-maps/assets/fonts/noto-serif-devanagari/noto-serif-devanagari-400-devanagari.woff2
generated
Normal file
BIN
public/doxa-maps/assets/fonts/noto-serif-devanagari/noto-serif-devanagari-400-devanagari.woff2
generated
Normal file
Binary file not shown.
75
public/doxa-maps/assets/legacy/upg-neighbor/index.html
generated
Normal file
75
public/doxa-maps/assets/legacy/upg-neighbor/index.html
generated
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>legacy map host</title>
|
||||
<style>html,body{margin:0;height:100%;background:#f3f3f1}.host{position:fixed;inset:0}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="host" id="host"></div>
|
||||
<script>
|
||||
// Sandboxed host for the LEGACY upg-neighbor IIFE (pre-bundler architecture). This page runs
|
||||
// inside an IFRAME so the old engine and its dependencies cannot collide with the modern
|
||||
// bundles -- the isolation pattern the roundup mandates ("add without breaking doxa-maps").
|
||||
var q = new URLSearchParams(location.search);
|
||||
var el = (q.get('el') || 'send-map').replace(/[^a-z0-9-]/g, '');
|
||||
var tk = q.get('tk') || '';
|
||||
// Token globals the legacy build may read.
|
||||
window.MAPBOX_TOKEN = tk; window.MAP_TOKEN = tk; window.UPG_TOKEN = tk;
|
||||
// The legacy code fetches relative "/api/people-groups/..." which only resolved on its old
|
||||
// origin. Shim fetch so those calls reach the live API from ANY host (static tree, iframe).
|
||||
var API = 'https://pray.doxa.life';
|
||||
// The cockpit profile fails closed without a configured API base and names this global in
|
||||
// its own error banner (measured: "No API base configured (apiBase / window.MAP_APP_API_URL
|
||||
// / VITE_API_BASE_URL) -- failing closed").
|
||||
window.MAP_APP_API_URL = API;
|
||||
var origFetch = window.fetch.bind(window);
|
||||
window.fetch = function (input, init) {
|
||||
try {
|
||||
var url = typeof input === 'string' ? input : (input && input.url) || '';
|
||||
if (url.indexOf('/api/') === 0) return origFetch(API + url, init);
|
||||
} catch (e) { /* fall through */ }
|
||||
return origFetch(input, init);
|
||||
};
|
||||
// ── KEYLESS SHIM ──────────────────────────────────────────────────────────
|
||||
// The legacy bundle expects a mapboxgl GLOBAL and requests mapbox://styles/mapbox/light-v11.
|
||||
// MapLibre is API-compatible with the mapbox-gl v1 surface the bundle uses, and OpenFreeMap
|
||||
// Positron is the keyless twin of light-v11 -- so the shim below makes every legacy map run
|
||||
// WITHOUT a Mapbox token: load our self-hosted MapLibre, alias it as mapboxgl, and rewrite
|
||||
// any mapbox:// style to the keyless URL inside a patched Map constructor.
|
||||
var KEYLESS_STYLE = 'https://tiles.openfreemap.org/styles/positron';
|
||||
var lib = document.createElement('link'); lib.rel='stylesheet'; lib.href='./maplibre-gl.css';
|
||||
document.head.appendChild(lib);
|
||||
var ml = document.createElement('script');
|
||||
ml.src = './maplibre-gl.js';
|
||||
ml.onload = function () {
|
||||
var Real = window.maplibregl.Map;
|
||||
function PatchedMap (opts) {
|
||||
if (opts && typeof opts.style === 'string' && opts.style.indexOf('mapbox://') === 0) opts.style = KEYLESS_STYLE;
|
||||
return new Real(opts);
|
||||
}
|
||||
PatchedMap.prototype = Real.prototype;
|
||||
window.mapboxgl = Object.assign({}, window.maplibregl, { Map: PatchedMap, accessToken: '' });
|
||||
loadLegacy();
|
||||
};
|
||||
ml.onerror = function () { loadLegacy(); };
|
||||
document.head.appendChild(ml);
|
||||
|
||||
function loadLegacy () {
|
||||
var s = document.createElement('script');
|
||||
s.src = './upg-neighbor.iife.js';
|
||||
s.onload = function () {
|
||||
var node = document.createElement(el);
|
||||
node.style.cssText = 'display:block;width:100%;height:100%';
|
||||
// The legacy elements use the SAME profile-config attribute contract as the modern
|
||||
// bundles (measured: without it the element renders its own usage banner).
|
||||
node.setAttribute('profile-config', JSON.stringify({ profile: el, tk: tk }));
|
||||
document.getElementById('host').appendChild(node);
|
||||
};
|
||||
s.onerror = function () { document.body.innerHTML = '<p style="font:14px system-ui;padding:20px">legacy bundle failed to load</p>'; };
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
public/doxa-maps/assets/legacy/upg-neighbor/maplibre-gl.css
generated
Normal file
1
public/doxa-maps/assets/legacy/upg-neighbor/maplibre-gl.css
generated
Normal file
File diff suppressed because one or more lines are too long
59
public/doxa-maps/assets/legacy/upg-neighbor/maplibre-gl.js
generated
Normal file
59
public/doxa-maps/assets/legacy/upg-neighbor/maplibre-gl.js
generated
Normal file
File diff suppressed because one or more lines are too long
548
public/doxa-maps/assets/legacy/upg-neighbor/upg-neighbor.iife.js
generated
Normal file
548
public/doxa-maps/assets/legacy/upg-neighbor/upg-neighbor.iife.js
generated
Normal file
File diff suppressed because one or more lines are too long
38
public/doxa-maps/cities-internet/cities-internet.js
generated
38
public/doxa-maps/cities-internet/cities-internet.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-VfbtDah7CcZdh9U1LNojwqD50sWSZIzYogPohoATgibTn1s00jLOdq4/98uSfsi+" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-tNig8gXdCocWVKD5iXR6iRqiAr7msWFtcFKzYif0L0G5i/RM+HI+SO8oR5c6O+5R" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-VfbtDah7CcZdh9U1LNojwqD50sWSZIzYogPohoATgibTn1s00jLOdq4/98uSfsi+" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-tNig8gXdCocWVKD5iXR6iRqiAr7msWFtcFKzYif0L0G5i/RM+HI+SO8oR5c6O+5R" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-VfbtDah7CcZdh9U1LNojwqD50sWSZIzYogPohoATgibTn1s00jLOdq4/98uSfsi+" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-tNig8gXdCocWVKD5iXR6iRqiAr7msWFtcFKzYif0L0G5i/RM+HI+SO8oR5c6O+5R" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-SEW7qAQI1xONm5JtqYW48DMlv+M36uYUqRMms+simkUtXNLFn36H3r3O2PicVP4F" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-uGk+BENGktebtR4QYc630qqtM8kDPK5uxPo6mwoWMqD/CTrala4WfAA3hNyg9ZuB" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-SEW7qAQI1xONm5JtqYW48DMlv+M36uYUqRMms+simkUtXNLFn36H3r3O2PicVP4F" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-uGk+BENGktebtR4QYc630qqtM8kDPK5uxPo6mwoWMqD/CTrala4WfAA3hNyg9ZuB" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
110
public/doxa-maps/country-posters/country-posters.js
generated
110
public/doxa-maps/country-posters/country-posters.js
generated
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
|
||||
<script src="../../dashboard-plus-grid.js" integrity="sha384-Ba+P2cFNlunEDgs+vOBLJraFBOD+Pw7snZJYzWB1pNErHHapOPZj20/xyRa1omZ6" crossorigin="anonymous"></script>
|
||||
<script src="../../dashboard-plus-grid.js" integrity="sha384-YbJgljynZ4t3/Ro44oHQy9ihl9/5o7j4IKts8MNRfA73Cb5cyD7U52DklXjzpLpG" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dashboard-plus-grid> custom element. -->
|
||||
<script src="../dashboard-plus-grid.js" integrity="sha384-Ba+P2cFNlunEDgs+vOBLJraFBOD+Pw7snZJYzWB1pNErHHapOPZj20/xyRa1omZ6" crossorigin="anonymous"></script>
|
||||
<script src="../dashboard-plus-grid.js" integrity="sha384-YbJgljynZ4t3/Ro44oHQy9ihl9/5o7j4IKts8MNRfA73Cb5cyD7U52DklXjzpLpG" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
44
public/doxa-maps/demo-map/demo-map.js
generated
44
public/doxa-maps/demo-map/demo-map.js
generated
File diff suppressed because one or more lines are too long
2
public/doxa-maps/demo-map/demo-map/index.html
generated
2
public/doxa-maps/demo-map/demo-map/index.html
generated
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <demo-map> custom element. -->
|
||||
<script src="../demo-map.js" integrity="sha384-V2YEXG4I8H76LdmFUKxZexTh5LV+7uPhUsxWqvVcX1Lq8WNbwO/a/E3h7pOlMTMY" crossorigin="anonymous"></script>
|
||||
<script src="../demo-map.js" integrity="sha384-QiUJUgeFn27tiHKVhAYtJuIWo+1z5xsCUP1Nj4m1H4cFUV3vUA7ADTP58Ky3oVtQ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <demo-map> custom element. -->
|
||||
<script src="../../demo-map.js" integrity="sha384-V2YEXG4I8H76LdmFUKxZexTh5LV+7uPhUsxWqvVcX1Lq8WNbwO/a/E3h7pOlMTMY" crossorigin="anonymous"></script>
|
||||
<script src="../../demo-map.js" integrity="sha384-QiUJUgeFn27tiHKVhAYtJuIWo+1z5xsCUP1Nj4m1H4cFUV3vUA7ADTP58Ky3oVtQ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
|
||||
<script src="../doxa-research-map.js" integrity="sha384-wPisP4Zwa8acLC/C/bg6n3TXcsmuL1ct2ovDvXtGUFuDiJWLB4zgXuzQMajKHeoV" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-research-map.js" integrity="sha384-oecoezttPmpRe5GNG/Yko6IkEbtLjCD7PD7qghU1ZigprnfLKhbf0eBEiEbE24iR" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
|
||||
<script src="../../doxa-research-map.js" integrity="sha384-wPisP4Zwa8acLC/C/bg6n3TXcsmuL1ct2ovDvXtGUFuDiJWLB4zgXuzQMajKHeoV" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-research-map.js" integrity="sha384-oecoezttPmpRe5GNG/Yko6IkEbtLjCD7PD7qghU1ZigprnfLKhbf0eBEiEbE24iR" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
62
public/doxa-maps/doxa-simple-map/doxa-simple-map.js
generated
62
public/doxa-maps/doxa-simple-map/doxa-simple-map.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-S0p0NMpR43h6c4L9RvWRr3GqaLSJ3x0q9wGHu2LSWYKY8E9aE/qglSto1L45Q3eC" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-hzEui48BqpW8VhRthz3daMgAdw4w4M3gtBre4ce4sXuAmS+39pMRW9reNAvpFb+W" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-S0p0NMpR43h6c4L9RvWRr3GqaLSJ3x0q9wGHu2LSWYKY8E9aE/qglSto1L45Q3eC" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-hzEui48BqpW8VhRthz3daMgAdw4w4M3gtBre4ce4sXuAmS+39pMRW9reNAvpFb+W" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-S0p0NMpR43h6c4L9RvWRr3GqaLSJ3x0q9wGHu2LSWYKY8E9aE/qglSto1L45Q3eC" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-hzEui48BqpW8VhRthz3daMgAdw4w4M3gtBre4ce4sXuAmS+39pMRW9reNAvpFb+W" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-S0p0NMpR43h6c4L9RvWRr3GqaLSJ3x0q9wGHu2LSWYKY8E9aE/qglSto1L45Q3eC" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-hzEui48BqpW8VhRthz3daMgAdw4w4M3gtBre4ce4sXuAmS+39pMRW9reNAvpFb+W" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
2
public/doxa-maps/key-cities/key-cities.js
generated
2
public/doxa-maps/key-cities/key-cities.js
generated
File diff suppressed because one or more lines are too long
184
public/doxa-maps/manifest.json
generated
184
public/doxa-maps/manifest.json
generated
|
|
@ -2,14 +2,14 @@
|
|||
"schema": "doxa-maps-manifest@1",
|
||||
"generatedFrom": "plugin-designs/*/*.vue",
|
||||
"embedBase": ".",
|
||||
"bundleCount": 21,
|
||||
"profileCount": 32,
|
||||
"bundleCount": 22,
|
||||
"profileCount": 33,
|
||||
"bundles": [
|
||||
{
|
||||
"bundle": "all-people-groups",
|
||||
"scriptUrl": "./all-people-groups/all-people-groups.js",
|
||||
"output": "doxa-maps/all-people-groups/all-people-groups.js",
|
||||
"integrity": "sha384-p/GNZGy24Q6Fcx6en2niqH70w6pgh31gwGQk8BAxuMtLBqbXLl8eIkFeGN6QKHDq",
|
||||
"integrity": "sha384-cqk7oshuPW5FdnbZ/DyfBBXN2FObAI+V1jrbxmZR2xzNiBtWG65PETQ5/w5PCDB2",
|
||||
"elements": [
|
||||
"all-people-groups"
|
||||
],
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
"scriptUrl": "./all-people-groups/all-people-groups.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./all-people-groups/all-people-groups/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-p/GNZGy24Q6Fcx6en2niqH70w6pgh31gwGQk8BAxuMtLBqbXLl8eIkFeGN6QKHDq\" crossorigin=\"anonymous\"></script>\n<all-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></all-people-groups>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./all-people-groups/all-people-groups.js\" integrity=\"sha384-cqk7oshuPW5FdnbZ/DyfBBXN2FObAI+V1jrbxmZR2xzNiBtWG65PETQ5/w5PCDB2\" crossorigin=\"anonymous\"></script>\n<all-people-groups profile-config='{\"profile\":\"all-people-groups\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></all-people-groups>"
|
||||
},
|
||||
"bundle": "all-people-groups"
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
"bundle": "cities-internet",
|
||||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"output": "doxa-maps/cities-internet/cities-internet.js",
|
||||
"integrity": "sha384-VfbtDah7CcZdh9U1LNojwqD50sWSZIzYogPohoATgibTn1s00jLOdq4/98uSfsi+",
|
||||
"integrity": "sha384-tNig8gXdCocWVKD5iXR6iRqiAr7msWFtcFKzYif0L0G5i/RM+HI+SO8oR5c6O+5R",
|
||||
"elements": [
|
||||
"cities-internet-map"
|
||||
],
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-VfbtDah7CcZdh9U1LNojwqD50sWSZIzYogPohoATgibTn1s00jLOdq4/98uSfsi+\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-tNig8gXdCocWVKD5iXR6iRqiAr7msWFtcFKzYif0L0G5i/RM+HI+SO8oR5c6O+5R\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -131,7 +131,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-VfbtDah7CcZdh9U1LNojwqD50sWSZIzYogPohoATgibTn1s00jLOdq4/98uSfsi+\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-tNig8gXdCocWVKD5iXR6iRqiAr7msWFtcFKzYif0L0G5i/RM+HI+SO8oR5c6O+5R\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-VfbtDah7CcZdh9U1LNojwqD50sWSZIzYogPohoATgibTn1s00jLOdq4/98uSfsi+\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-tNig8gXdCocWVKD5iXR6iRqiAr7msWFtcFKzYif0L0G5i/RM+HI+SO8oR5c6O+5R\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
"bundle": "country-posters",
|
||||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"output": "doxa-maps/country-posters/country-posters.js",
|
||||
"integrity": "sha384-SEW7qAQI1xONm5JtqYW48DMlv+M36uYUqRMms+simkUtXNLFn36H3r3O2PicVP4F",
|
||||
"integrity": "sha384-uGk+BENGktebtR4QYc630qqtM8kDPK5uxPo6mwoWMqD/CTrala4WfAA3hNyg9ZuB",
|
||||
"elements": [
|
||||
"country-posters-map"
|
||||
],
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-SEW7qAQI1xONm5JtqYW48DMlv+M36uYUqRMms+simkUtXNLFn36H3r3O2PicVP4F\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-uGk+BENGktebtR4QYc630qqtM8kDPK5uxPo6mwoWMqD/CTrala4WfAA3hNyg9ZuB\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -184,7 +184,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-SEW7qAQI1xONm5JtqYW48DMlv+M36uYUqRMms+simkUtXNLFn36H3r3O2PicVP4F\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-uGk+BENGktebtR4QYc630qqtM8kDPK5uxPo6mwoWMqD/CTrala4WfAA3hNyg9ZuB\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -193,7 +193,7 @@
|
|||
"bundle": "dashboard-plus-grid",
|
||||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"output": "doxa-maps/dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"integrity": "sha384-Ba+P2cFNlunEDgs+vOBLJraFBOD+Pw7snZJYzWB1pNErHHapOPZj20/xyRa1omZ6",
|
||||
"integrity": "sha384-YbJgljynZ4t3/Ro44oHQy9ihl9/5o7j4IKts8MNRfA73Cb5cyD7U52DklXjzpLpG",
|
||||
"elements": [
|
||||
"dashboard-plus-grid"
|
||||
],
|
||||
|
|
@ -213,7 +213,7 @@
|
|||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./dashboard-plus-grid/dashboard-plus-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-Ba+P2cFNlunEDgs+vOBLJraFBOD+Pw7snZJYzWB1pNErHHapOPZj20/xyRa1omZ6\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-YbJgljynZ4t3/Ro44oHQy9ihl9/5o7j4IKts8MNRfA73Cb5cyD7U52DklXjzpLpG\" crossorigin=\"anonymous\"></script>\n<dashboard-plus-grid profile-config='{\"profile\":\"dashboard-plus-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dashboard-plus-grid>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
"bundle": "demo-map",
|
||||
"scriptUrl": "./demo-map/demo-map.js",
|
||||
"output": "doxa-maps/demo-map/demo-map.js",
|
||||
"integrity": "sha384-V2YEXG4I8H76LdmFUKxZexTh5LV+7uPhUsxWqvVcX1Lq8WNbwO/a/E3h7pOlMTMY",
|
||||
"integrity": "sha384-QiUJUgeFn27tiHKVhAYtJuIWo+1z5xsCUP1Nj4m1H4cFUV3vUA7ADTP58Ky3oVtQ",
|
||||
"elements": [
|
||||
"demo-map"
|
||||
],
|
||||
|
|
@ -253,7 +253,7 @@
|
|||
"scriptUrl": "./demo-map/demo-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./demo-map/demo-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./demo-map/demo-map.js\" integrity=\"sha384-V2YEXG4I8H76LdmFUKxZexTh5LV+7uPhUsxWqvVcX1Lq8WNbwO/a/E3h7pOlMTMY\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></demo-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./demo-map/demo-map.js\" integrity=\"sha384-QiUJUgeFn27tiHKVhAYtJuIWo+1z5xsCUP1Nj4m1H4cFUV3vUA7ADTP58Ky3oVtQ\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></demo-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -331,7 +331,7 @@
|
|||
"bundle": "doxa-research-map",
|
||||
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
|
||||
"output": "doxa-maps/doxa-research-map/doxa-research-map.js",
|
||||
"integrity": "sha384-wPisP4Zwa8acLC/C/bg6n3TXcsmuL1ct2ovDvXtGUFuDiJWLB4zgXuzQMajKHeoV",
|
||||
"integrity": "sha384-oecoezttPmpRe5GNG/Yko6IkEbtLjCD7PD7qghU1ZigprnfLKhbf0eBEiEbE24iR",
|
||||
"elements": [
|
||||
"doxa-research-map"
|
||||
],
|
||||
|
|
@ -347,7 +347,7 @@
|
|||
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-wPisP4Zwa8acLC/C/bg6n3TXcsmuL1ct2ovDvXtGUFuDiJWLB4zgXuzQMajKHeoV\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-oecoezttPmpRe5GNG/Yko6IkEbtLjCD7PD7qghU1ZigprnfLKhbf0eBEiEbE24iR\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -373,7 +373,7 @@
|
|||
"bundle": "doxa-simple-map",
|
||||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"output": "doxa-maps/doxa-simple-map/doxa-simple-map.js",
|
||||
"integrity": "sha384-S0p0NMpR43h6c4L9RvWRr3GqaLSJ3x0q9wGHu2LSWYKY8E9aE/qglSto1L45Q3eC",
|
||||
"integrity": "sha384-hzEui48BqpW8VhRthz3daMgAdw4w4M3gtBre4ce4sXuAmS+39pMRW9reNAvpFb+W",
|
||||
"elements": [
|
||||
"doxa-map",
|
||||
"doxa-simple-map"
|
||||
|
|
@ -390,7 +390,7 @@
|
|||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-S0p0NMpR43h6c4L9RvWRr3GqaLSJ3x0q9wGHu2LSWYKY8E9aE/qglSto1L45Q3eC\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-hzEui48BqpW8VhRthz3daMgAdw4w4M3gtBre4ce4sXuAmS+39pMRW9reNAvpFb+W\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -573,7 +573,7 @@
|
|||
"bundle": "gospel-resources",
|
||||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"output": "doxa-maps/gospel-resources/gospel-resources.js",
|
||||
"integrity": "sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj",
|
||||
"integrity": "sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI",
|
||||
"elements": [
|
||||
"gospel-resources-map"
|
||||
],
|
||||
|
|
@ -589,7 +589,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -603,7 +603,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -617,7 +617,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -631,7 +631,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VXjJ4Se+3vtAs3dnqQoWelPf88U32RffjhrVy3usV5m9HHm2fL/CfUq2maQVKhzj\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-4pOAnuuOovcKXSQANV9jnLu1ExxiwQMuC4aU5MtAsd959tYmdUj05u4tT57Z/rKI\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -640,7 +640,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-32Ye4zztZJcuLNXs7frVH4mnI8A9GZXqY3wCq1QmS8k4jbpHqOPU6Dkfemkr+1xk",
|
||||
"integrity": "sha384-cs9Yz6dE0cp2ChVeNB9btNOXrqNk+xl0r4374AkMHRiEW7x+u3wlLDBv9TU469Ra",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -660,7 +660,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-32Ye4zztZJcuLNXs7frVH4mnI8A9GZXqY3wCq1QmS8k4jbpHqOPU6Dkfemkr+1xk\" 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-cs9Yz6dE0cp2ChVeNB9btNOXrqNk+xl0r4374AkMHRiEW7x+u3wlLDBv9TU469Ra\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -674,7 +674,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-32Ye4zztZJcuLNXs7frVH4mnI8A9GZXqY3wCq1QmS8k4jbpHqOPU6Dkfemkr+1xk\" 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-cs9Yz6dE0cp2ChVeNB9btNOXrqNk+xl0r4374AkMHRiEW7x+u3wlLDBv9TU469Ra\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -688,7 +688,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-32Ye4zztZJcuLNXs7frVH4mnI8A9GZXqY3wCq1QmS8k4jbpHqOPU6Dkfemkr+1xk\" 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-cs9Yz6dE0cp2ChVeNB9btNOXrqNk+xl0r4374AkMHRiEW7x+u3wlLDBv9TU469Ra\" 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>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -697,7 +697,7 @@
|
|||
"bundle": "people-groups-grid",
|
||||
"scriptUrl": "./people-groups-grid/people-groups-grid.js",
|
||||
"output": "doxa-maps/people-groups-grid/people-groups-grid.js",
|
||||
"integrity": "sha384-wyfAzVfoufR5FaDoo3DSivtV33qOexc7PhDYZkXlYmru5FqZgncuUos3nN0kkMJL",
|
||||
"integrity": "sha384-ss5PmeSkKuxafBwvxZG3r7roP0OB/oVGwFbfrHnydEhjbtrlB4eisdsPXEyCJF37",
|
||||
"elements": [
|
||||
"people-groups-grid"
|
||||
],
|
||||
|
|
@ -713,7 +713,7 @@
|
|||
"scriptUrl": "./people-groups-grid/people-groups-grid.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./people-groups-grid/people-groups-grid/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./people-groups-grid/people-groups-grid.js\" integrity=\"sha384-wyfAzVfoufR5FaDoo3DSivtV33qOexc7PhDYZkXlYmru5FqZgncuUos3nN0kkMJL\" crossorigin=\"anonymous\"></script>\n<people-groups-grid profile-config='{\"profile\":\"people-groups-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></people-groups-grid>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./people-groups-grid/people-groups-grid.js\" integrity=\"sha384-ss5PmeSkKuxafBwvxZG3r7roP0OB/oVGwFbfrHnydEhjbtrlB4eisdsPXEyCJF37\" crossorigin=\"anonymous\"></script>\n<people-groups-grid profile-config='{\"profile\":\"people-groups-grid\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></people-groups-grid>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -813,7 +813,7 @@
|
|||
"bundle": "prayer-walk-planner",
|
||||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"output": "doxa-maps/prayer-walk-planner/prayer-walk-planner.js",
|
||||
"integrity": "sha384-7jWlo1ERAkJVmIj2zvbUfDXHANMfT5MolW1AV6ex76fxP0rll9PgtoK8CH876uYH",
|
||||
"integrity": "sha384-F0+r4avX8xkXp0hBpU5EBPU0InMRCObIn2Ieh9HJNn1r2amk8/qHmJ68WuNfsirC",
|
||||
"elements": [
|
||||
"prayer-walk-planner"
|
||||
],
|
||||
|
|
@ -829,7 +829,7 @@
|
|||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./prayer-walk-planner/prayer-walk-planner/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=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-7jWlo1ERAkJVmIj2zvbUfDXHANMfT5MolW1AV6ex76fxP0rll9PgtoK8CH876uYH\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-F0+r4avX8xkXp0hBpU5EBPU0InMRCObIn2Ieh9HJNn1r2amk8/qHmJ68WuNfsirC\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -838,7 +838,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-8RyxSBCKq3msHWoz2K5/MMemGhsRoeR+oU8KFnp9aMKVMp6TiGDze4kGRCh7luAD",
|
||||
"integrity": "sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -858,7 +858,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8RyxSBCKq3msHWoz2K5/MMemGhsRoeR+oU8KFnp9aMKVMp6TiGDze4kGRCh7luAD\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -872,7 +872,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-8RyxSBCKq3msHWoz2K5/MMemGhsRoeR+oU8KFnp9aMKVMp6TiGDze4kGRCh7luAD\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -886,7 +886,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-8RyxSBCKq3msHWoz2K5/MMemGhsRoeR+oU8KFnp9aMKVMp6TiGDze4kGRCh7luAD\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -895,7 +895,7 @@
|
|||
"bundle": "research-dashboard",
|
||||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"output": "doxa-maps/research-dashboard/research-dashboard.js",
|
||||
"integrity": "sha384-K8I36S+jJvwOmnJ4Zoulz8T+4fFF1X4hg2Pu8BCNkBjtbgK8eEmIhBsQi7KZQS9V",
|
||||
"integrity": "sha384-VvOYWBcDd7NtnB1eveECKX88ZHIpGnOf79tVdCUqGZzXLgoZPc+oTTPIC3ajaYsn",
|
||||
"elements": [
|
||||
"research-dashboard-map"
|
||||
],
|
||||
|
|
@ -911,7 +911,7 @@
|
|||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-K8I36S+jJvwOmnJ4Zoulz8T+4fFF1X4hg2Pu8BCNkBjtbgK8eEmIhBsQi7KZQS9V\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-VvOYWBcDd7NtnB1eveECKX88ZHIpGnOf79tVdCUqGZzXLgoZPc+oTTPIC3ajaYsn\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></research-dashboard-map>"
|
||||
},
|
||||
"bundle": "research-dashboard"
|
||||
}
|
||||
|
|
@ -1009,7 +1009,7 @@
|
|||
"bundle": "template-bundle",
|
||||
"scriptUrl": "./template-bundle/template-bundle.js",
|
||||
"output": "doxa-maps/template-bundle/template-bundle.js",
|
||||
"integrity": "sha384-L0FkVYzlQATU9b0Ah8f089YyKz29G6Y58/44DkV+TMS5WbpeO2Jq014VzVAmpTMS",
|
||||
"integrity": "sha384-k+G5UlkZ99QmuecCCWUrjz998JBJnQT3hsDG0A+IVmiX9F3CNswmHXXzYhjnUTDX",
|
||||
"elements": [
|
||||
"template-bundle"
|
||||
],
|
||||
|
|
@ -1025,7 +1025,7 @@
|
|||
"scriptUrl": "./template-bundle/template-bundle.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./template-bundle/template-map-a/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=\"./template-bundle/template-bundle.js\" integrity=\"sha384-L0FkVYzlQATU9b0Ah8f089YyKz29G6Y58/44DkV+TMS5WbpeO2Jq014VzVAmpTMS\" crossorigin=\"anonymous\"></script>\n<template-bundle profile-config='{\"profile\":\"template-map-a\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></template-bundle>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-k+G5UlkZ99QmuecCCWUrjz998JBJnQT3hsDG0A+IVmiX9F3CNswmHXXzYhjnUTDX\" crossorigin=\"anonymous\"></script>\n<template-bundle profile-config='{\"profile\":\"template-map-a\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></template-bundle>"
|
||||
},
|
||||
"bundle": "template-bundle"
|
||||
},
|
||||
|
|
@ -1040,11 +1040,113 @@
|
|||
"scriptUrl": "./template-bundle/template-bundle.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./template-bundle/template-map-b/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=\"./template-bundle/template-bundle.js\" integrity=\"sha384-L0FkVYzlQATU9b0Ah8f089YyKz29G6Y58/44DkV+TMS5WbpeO2Jq014VzVAmpTMS\" crossorigin=\"anonymous\"></script>\n<template-bundle profile-config='{\"profile\":\"template-map-b\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></template-bundle>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./template-bundle/template-bundle.js\" integrity=\"sha384-k+G5UlkZ99QmuecCCWUrjz998JBJnQT3hsDG0A+IVmiX9F3CNswmHXXzYhjnUTDX\" crossorigin=\"anonymous\"></script>\n<template-bundle profile-config='{\"profile\":\"template-map-b\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></template-bundle>"
|
||||
},
|
||||
"bundle": "template-bundle"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bundle": "upg-neighbor",
|
||||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"output": "doxa-maps/upg-neighbor/upg-neighbor.js",
|
||||
"integrity": "sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q",
|
||||
"elements": [
|
||||
"upg-neighbor"
|
||||
],
|
||||
"profiles": [
|
||||
{
|
||||
"profile": "upg-neighbor",
|
||||
"file": "upg-neighbor.vue",
|
||||
"description": "Rescues the three pre-bundler experimental maps that live inside the legacy",
|
||||
"element": "upg-neighbor",
|
||||
"type": "parameterized",
|
||||
"isMap": false,
|
||||
"iframeUrl": "./upg-neighbor/upg-neighbor/index.html",
|
||||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
"id": "send-map",
|
||||
"title": "Send Map (legacy)",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "upg-neighbor",
|
||||
"legacyElement": "send-map",
|
||||
"legacyTitle": "Send Map"
|
||||
},
|
||||
"element": "upg-neighbor",
|
||||
"iframeUrl": "./upg-neighbor/upg-neighbor/send-map/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/send-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "upg-neighbors",
|
||||
"title": "UPG Neighbors (legacy)",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "upg-neighbor",
|
||||
"legacyElement": "upg-neighbors",
|
||||
"legacyTitle": "UPG Neighbors"
|
||||
},
|
||||
"element": "upg-neighbor",
|
||||
"iframeUrl": "./upg-neighbor/upg-neighbor/upg-neighbors/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/upg-neighbors/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lang-cluster",
|
||||
"title": "Language Clusters (legacy)",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "upg-neighbor",
|
||||
"legacyElement": "lang-cluster",
|
||||
"legacyTitle": "Language Clusters"
|
||||
},
|
||||
"element": "upg-neighbor",
|
||||
"iframeUrl": "./upg-neighbor/upg-neighbor/lang-cluster/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/lang-cluster/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bloom-cockpit",
|
||||
"title": "Bloom Cockpit (legacy)",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "upg-neighbor",
|
||||
"legacyElement": "bloom-cockpit",
|
||||
"legacyTitle": "Bloom Cockpit"
|
||||
},
|
||||
"element": "upg-neighbor",
|
||||
"iframeUrl": "./upg-neighbor/upg-neighbor/bloom-cockpit/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/bloom-cockpit/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flower-mesh",
|
||||
"title": "Flower Mesh (legacy)",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "upg-neighbor",
|
||||
"legacyElement": "flower-mesh",
|
||||
"legacyTitle": "Flower Mesh"
|
||||
},
|
||||
"element": "upg-neighbor",
|
||||
"iframeUrl": "./upg-neighbor/upg-neighbor/flower-mesh/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/flower-mesh/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
195
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
195
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -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-32Ye4zztZJcuLNXs7frVH4mnI8A9GZXqY3wCq1QmS8k4jbpHqOPU6Dkfemkr+1xk" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-cs9Yz6dE0cp2ChVeNB9btNOXrqNk+xl0r4374AkMHRiEW7x+u3wlLDBv9TU469Ra" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-32Ye4zztZJcuLNXs7frVH4mnI8A9GZXqY3wCq1QmS8k4jbpHqOPU6Dkfemkr+1xk" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-cs9Yz6dE0cp2ChVeNB9btNOXrqNk+xl0r4374AkMHRiEW7x+u3wlLDBv9TU469Ra" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -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-32Ye4zztZJcuLNXs7frVH4mnI8A9GZXqY3wCq1QmS8k4jbpHqOPU6Dkfemkr+1xk" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-cs9Yz6dE0cp2ChVeNB9btNOXrqNk+xl0r4374AkMHRiEW7x+u3wlLDBv9TU469Ra" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <people-groups-grid> custom element. -->
|
||||
<script src="../../people-groups-grid.js" integrity="sha384-wyfAzVfoufR5FaDoo3DSivtV33qOexc7PhDYZkXlYmru5FqZgncuUos3nN0kkMJL" crossorigin="anonymous"></script>
|
||||
<script src="../../people-groups-grid.js" integrity="sha384-ss5PmeSkKuxafBwvxZG3r7roP0OB/oVGwFbfrHnydEhjbtrlB4eisdsPXEyCJF37" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <people-groups-grid> custom element. -->
|
||||
<script src="../people-groups-grid.js" integrity="sha384-wyfAzVfoufR5FaDoo3DSivtV33qOexc7PhDYZkXlYmru5FqZgncuUos3nN0kkMJL" crossorigin="anonymous"></script>
|
||||
<script src="../people-groups-grid.js" integrity="sha384-ss5PmeSkKuxafBwvxZG3r7roP0OB/oVGwFbfrHnydEhjbtrlB4eisdsPXEyCJF37" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <people-groups-grid> custom element. -->
|
||||
<script src="../../people-groups-grid.js" integrity="sha384-wyfAzVfoufR5FaDoo3DSivtV33qOexc7PhDYZkXlYmru5FqZgncuUos3nN0kkMJL" crossorigin="anonymous"></script>
|
||||
<script src="../../people-groups-grid.js" integrity="sha384-ss5PmeSkKuxafBwvxZG3r7roP0OB/oVGwFbfrHnydEhjbtrlB4eisdsPXEyCJF37" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <prayer-walk-planner> custom element. -->
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-7jWlo1ERAkJVmIj2zvbUfDXHANMfT5MolW1AV6ex76fxP0rll9PgtoK8CH876uYH" crossorigin="anonymous"></script>
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-F0+r4avX8xkXp0hBpU5EBPU0InMRCObIn2Ieh9HJNn1r2amk8/qHmJ68WuNfsirC" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-8RyxSBCKq3msHWoz2K5/MMemGhsRoeR+oU8KFnp9aMKVMp6TiGDze4kGRCh7luAD" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-8RyxSBCKq3msHWoz2K5/MMemGhsRoeR+oU8KFnp9aMKVMp6TiGDze4kGRCh7luAD" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-8RyxSBCKq3msHWoz2K5/MMemGhsRoeR+oU8KFnp9aMKVMp6TiGDze4kGRCh7luAD" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <research-dashboard-map> custom element. -->
|
||||
<script src="../research-dashboard.js" integrity="sha384-K8I36S+jJvwOmnJ4Zoulz8T+4fFF1X4hg2Pu8BCNkBjtbgK8eEmIhBsQi7KZQS9V" crossorigin="anonymous"></script>
|
||||
<script src="../research-dashboard.js" integrity="sha384-VvOYWBcDd7NtnB1eveECKX88ZHIpGnOf79tVdCUqGZzXLgoZPc+oTTPIC3ajaYsn" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
30
public/doxa-maps/template-bundle/template-bundle.js
generated
30
public/doxa-maps/template-bundle/template-bundle.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <template-bundle> custom element. -->
|
||||
<script src="../template-bundle.js" integrity="sha384-L0FkVYzlQATU9b0Ah8f089YyKz29G6Y58/44DkV+TMS5WbpeO2Jq014VzVAmpTMS" crossorigin="anonymous"></script>
|
||||
<script src="../template-bundle.js" integrity="sha384-k+G5UlkZ99QmuecCCWUrjz998JBJnQT3hsDG0A+IVmiX9F3CNswmHXXzYhjnUTDX" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <template-bundle> custom element. -->
|
||||
<script src="../template-bundle.js" integrity="sha384-L0FkVYzlQATU9b0Ah8f089YyKz29G6Y58/44DkV+TMS5WbpeO2Jq014VzVAmpTMS" crossorigin="anonymous"></script>
|
||||
<script src="../template-bundle.js" integrity="sha384-k+G5UlkZ99QmuecCCWUrjz998JBJnQT3hsDG0A+IVmiX9F3CNswmHXXzYhjnUTDX" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
20
public/doxa-maps/upg-neighbor/upg-neighbor.js
generated
Normal file
20
public/doxa-maps/upg-neighbor/upg-neighbor.js
generated
Normal file
File diff suppressed because one or more lines are too long
168
public/doxa-maps/upg-neighbor/upg-neighbor/bloom-cockpit/index.html
generated
Normal file
168
public/doxa-maps/upg-neighbor/upg-neighbor/bloom-cockpit/index.html
generated
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bloom Cockpit (legacy) · upg-neighbor</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, upg-neighbor { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
padding: 24px; text-align: center; color: #9aa7b8;
|
||||
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
#status.err { color: #f87171; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"upg-neighbor","legacyElement":"bloom-cockpit","legacyTitle":"Bloom Cockpit"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. The Driver is
|
||||
PHOTOSENSITIVE and a 60fps resize-strobe was already fixed this session; do NOT turn this into
|
||||
a per-frame or per-event handler. */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("upg-neighbor");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
|
||||
if (TOKEN_FREE) { return mount(''); }
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed: say which, and still offer the paste-once box (no dead ends).
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
168
public/doxa-maps/upg-neighbor/upg-neighbor/flower-mesh/index.html
generated
Normal file
168
public/doxa-maps/upg-neighbor/upg-neighbor/flower-mesh/index.html
generated
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Flower Mesh (legacy) · upg-neighbor</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, upg-neighbor { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
padding: 24px; text-align: center; color: #9aa7b8;
|
||||
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
#status.err { color: #f87171; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"upg-neighbor","legacyElement":"flower-mesh","legacyTitle":"Flower Mesh"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. The Driver is
|
||||
PHOTOSENSITIVE and a 60fps resize-strobe was already fixed this session; do NOT turn this into
|
||||
a per-frame or per-event handler. */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("upg-neighbor");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
|
||||
if (TOKEN_FREE) { return mount(''); }
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed: say which, and still offer the paste-once box (no dead ends).
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
168
public/doxa-maps/upg-neighbor/upg-neighbor/index.html
generated
Normal file
168
public/doxa-maps/upg-neighbor/upg-neighbor/index.html
generated
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>upg-neighbor · upg-neighbor</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, upg-neighbor { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
padding: 24px; text-align: center; color: #9aa7b8;
|
||||
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
#status.err { color: #f87171; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../upg-neighbor.js" integrity="sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"profile":"upg-neighbor","dataSource":"pray-tools","instanceId":"upg-neighbor-upg-neighbor","assetsBase":"../../assets"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. The Driver is
|
||||
PHOTOSENSITIVE and a 60fps resize-strobe was already fixed this session; do NOT turn this into
|
||||
a per-frame or per-event handler. */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("upg-neighbor");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
|
||||
if (TOKEN_FREE) { return mount(''); }
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed: say which, and still offer the paste-once box (no dead ends).
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
168
public/doxa-maps/upg-neighbor/upg-neighbor/lang-cluster/index.html
generated
Normal file
168
public/doxa-maps/upg-neighbor/upg-neighbor/lang-cluster/index.html
generated
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Language Clusters (legacy) · upg-neighbor</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, upg-neighbor { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
padding: 24px; text-align: center; color: #9aa7b8;
|
||||
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
#status.err { color: #f87171; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"upg-neighbor","legacyElement":"lang-cluster","legacyTitle":"Language Clusters"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. The Driver is
|
||||
PHOTOSENSITIVE and a 60fps resize-strobe was already fixed this session; do NOT turn this into
|
||||
a per-frame or per-event handler. */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("upg-neighbor");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
|
||||
if (TOKEN_FREE) { return mount(''); }
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed: say which, and still offer the paste-once box (no dead ends).
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
168
public/doxa-maps/upg-neighbor/upg-neighbor/send-map/index.html
generated
Normal file
168
public/doxa-maps/upg-neighbor/upg-neighbor/send-map/index.html
generated
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Send Map (legacy) · upg-neighbor</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, upg-neighbor { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
padding: 24px; text-align: center; color: #9aa7b8;
|
||||
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
#status.err { color: #f87171; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"upg-neighbor","legacyElement":"send-map","legacyTitle":"Send Map"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. The Driver is
|
||||
PHOTOSENSITIVE and a 60fps resize-strobe was already fixed this session; do NOT turn this into
|
||||
a per-frame or per-event handler. */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("upg-neighbor");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
|
||||
if (TOKEN_FREE) { return mount(''); }
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed: say which, and still offer the paste-once box (no dead ends).
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
168
public/doxa-maps/upg-neighbor/upg-neighbor/upg-neighbors/index.html
generated
Normal file
168
public/doxa-maps/upg-neighbor/upg-neighbor/upg-neighbors/index.html
generated
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>UPG Neighbors (legacy) · upg-neighbor</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, upg-neighbor { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
padding: 24px; text-align: center; color: #9aa7b8;
|
||||
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
#status.err { color: #f87171; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-JDJr4qoVZuiSi/rvgdzeuUNrzf7vjPdyh/g8dg7X8RHkZk/Y+tYXIQ3gpEzrU07q" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"upg-neighbor","legacyElement":"upg-neighbors","legacyTitle":"UPG Neighbors"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
var TOKEN_FREE = true;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "pk.eyJ1IjoiZ29zeXN0ZW1zIiwiYSI6ImNtZm9vM291czA3amQya3BzYW42YW9ibTcifQ.52WM0IpxY9FcB0zc9f1zbQ";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. The Driver is
|
||||
PHOTOSENSITIVE and a 60fps resize-strobe was already fixed this session; do NOT turn this into
|
||||
a per-frame or per-event handler. */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("upg-neighbor");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || BAKED_TOKEN;
|
||||
if (TOKEN_FREE) { return mount(''); }
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed: say which, and still offer the paste-once box (no dead ends).
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue