Update from source fc2fa2e4
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 926 KiB |
|
Before Width: | Height: | Size: 827 KiB After Width: | Height: | Size: 872 KiB |
|
Before Width: | Height: | Size: 2 MiB After Width: | Height: | Size: 2 MiB |
|
Before Width: | Height: | Size: 2 MiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 954 KiB After Width: | Height: | Size: 1 MiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 332 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 728 KiB After Width: | Height: | Size: 771 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 678 KiB After Width: | Height: | Size: 761 KiB |
|
Before Width: | Height: | Size: 700 KiB After Width: | Height: | Size: 752 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.7 MiB |
|
|
@ -1,23 +1,23 @@
|
|||
{
|
||||
"cap1-single-element": {
|
||||
"bundle": "all-people-groups",
|
||||
"sha256": "483a04b2ae46e00b0e0530c9291070b30c9baeaddf2940cd61e04cdffb382095"
|
||||
"sha256": "9e94ebdea30cb458a3bf6d28342ecbe7c4f403157b27b3b0bde9543a1882ac53"
|
||||
},
|
||||
"cap2-adoption": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "646efc14670963e1d3e1b1982ff2130562f28183a5908ef3c0c5dbd8d7f9597a"
|
||||
"sha256": "e429739d0fe8ca87943f8e7c1192193d0979a6a7be9d3aada3229833d722a9ec"
|
||||
},
|
||||
"cap2-engagement": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "646efc14670963e1d3e1b1982ff2130562f28183a5908ef3c0c5dbd8d7f9597a"
|
||||
"sha256": "e429739d0fe8ca87943f8e7c1192193d0979a6a7be9d3aada3229833d722a9ec"
|
||||
},
|
||||
"cap2-prayer": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "646efc14670963e1d3e1b1982ff2130562f28183a5908ef3c0c5dbd8d7f9597a"
|
||||
"sha256": "e429739d0fe8ca87943f8e7c1192193d0979a6a7be9d3aada3229833d722a9ec"
|
||||
},
|
||||
"cap3-monolithic-research": {
|
||||
"bundle": "doxa-research-map",
|
||||
"sha256": "9a7161f8337ff9efffcf4c8092157b5a8932de91c69fb541672f92c040033716"
|
||||
"sha256": "11ea5ecc91ecdbfd16e0638cfceecf9c835e0a88f3de2a2aea4fb282828c287f"
|
||||
},
|
||||
"cap4-upg100-list": {
|
||||
"bundle": "my-upg-100-list",
|
||||
|
|
@ -25,78 +25,78 @@
|
|||
},
|
||||
"cap4-upg100-mfe": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "eca013c97650809080806967337b54bbdf5c5817dc318554ccffc00fc66c601f"
|
||||
"sha256": "a7bbbab52d5418e2745215ef204ac548daa551335859b5f58ed81fd611ed850b"
|
||||
},
|
||||
"cap4-upg100-poster-wall": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "eca013c97650809080806967337b54bbdf5c5817dc318554ccffc00fc66c601f"
|
||||
"sha256": "a7bbbab52d5418e2745215ef204ac548daa551335859b5f58ed81fd611ed850b"
|
||||
},
|
||||
"cap5-doxa-maps-spa": {
|
||||
"bundle": "doxa-maps-spa",
|
||||
"sha256": "9093c700a38d42a9444650690d99ce3b8b66f0c73e03e00ac49780c3833807c5"
|
||||
"sha256": "a8b01a95f0362c7a97fa510eb41e668abe0b13671abc3bb174ae2b97dda8280d"
|
||||
},
|
||||
"cap5-plugin-studio": {
|
||||
"bundle": "plugin-studio",
|
||||
"sha256": "0034c901862e25c575966dd48f78d2fa739834db401cf37f1de9653e3034ae05"
|
||||
"sha256": "9108858e3f055a0b09a56206d7f0354e2f1f2b8a5d80d5f6f9fe12eea54ed484"
|
||||
},
|
||||
"deploy2-mobile-upg100": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "eca013c97650809080806967337b54bbdf5c5817dc318554ccffc00fc66c601f"
|
||||
"sha256": "a7bbbab52d5418e2745215ef204ac548daa551335859b5f58ed81fd611ed850b"
|
||||
},
|
||||
"exp-prayer-walk-planner": {
|
||||
"bundle": "prayer-walk-planner",
|
||||
"sha256": "0042dda28574e86cb29543b53ece4abab835bccb995316470e138479e42de2b3"
|
||||
"sha256": "5f0e799cf04331c65e7724820ae34b96ee5a4c0583dd66b5a40c7ea106822acf"
|
||||
},
|
||||
"exp-wall-of-people-groups": {
|
||||
"bundle": "wall-of-people-groups",
|
||||
"sha256": "1ce6d49a495aa794200b97005e620db37fca3ade59325fa359d3a59d200cbcba"
|
||||
"sha256": "9a8fc9c4a39c7113590a9dab8e9e3f5f4ecaa274467ab1d095fb717e60ac5765"
|
||||
},
|
||||
"lang-arabic-upg100": {
|
||||
"bundle": "my-upg-100-list",
|
||||
"sha256": "eca013c97650809080806967337b54bbdf5c5817dc318554ccffc00fc66c601f"
|
||||
"sha256": "a7bbbab52d5418e2745215ef204ac548daa551335859b5f58ed81fd611ed850b"
|
||||
},
|
||||
"prod-big-picture": {
|
||||
"bundle": "dashboard-plus-grid",
|
||||
"sha256": "e8e78401cbc35ab285d6906f7f9ccbe4bc9db7a41c37dd1cde301addda7cd745"
|
||||
"sha256": "fb9da1169851cc2ab49c4c1f289935367841f2367fe071d74c01849f45337ffa"
|
||||
},
|
||||
"prod-cities-internet": {
|
||||
"bundle": "cities-internet",
|
||||
"sha256": "236a1a98ec06562224f77af8ca8290e4381b4df8e81aa143ecb886f5f14f7113"
|
||||
"sha256": "35d8936e0164f94a41d5330f53105dcabf41aefc356090743144c0fce4847bd1"
|
||||
},
|
||||
"prod-cities-internet-phone": {
|
||||
"bundle": "cities-internet",
|
||||
"sha256": "236a1a98ec06562224f77af8ca8290e4381b4df8e81aa143ecb886f5f14f7113"
|
||||
"sha256": "35d8936e0164f94a41d5330f53105dcabf41aefc356090743144c0fce4847bd1"
|
||||
},
|
||||
"prod-gospel-resources": {
|
||||
"bundle": "gospel-resources",
|
||||
"sha256": "88d7705148ff7f48862ef5be164b8dfc5b8d71afa04a9e86bb0fdb499d830109"
|
||||
"sha256": "44e9f631ec66d74b50f142811424583cb4df946c725c3264a02f8306db35096d"
|
||||
},
|
||||
"prod-gospel-resources-phone": {
|
||||
"bundle": "gospel-resources",
|
||||
"sha256": "88d7705148ff7f48862ef5be164b8dfc5b8d71afa04a9e86bb0fdb499d830109"
|
||||
"sha256": "44e9f631ec66d74b50f142811424583cb4df946c725c3264a02f8306db35096d"
|
||||
},
|
||||
"prod-prayer-phone": {
|
||||
"bundle": "doxa-simple-map",
|
||||
"sha256": "646efc14670963e1d3e1b1982ff2130562f28183a5908ef3c0c5dbd8d7f9597a"
|
||||
"sha256": "e429739d0fe8ca87943f8e7c1192193d0979a6a7be9d3aada3229833d722a9ec"
|
||||
},
|
||||
"prod-regions": {
|
||||
"bundle": "regions-dashboard",
|
||||
"sha256": "85a9367643be850d554fbcc704ab57f850e30388ef6cfe5e001032bc3f323f0c"
|
||||
"sha256": "b036622f55012b6917bf691d87f738d9f64c1705c5ee78db2127f57c315e41d1"
|
||||
},
|
||||
"prod-regions-phone": {
|
||||
"bundle": "regions-dashboard",
|
||||
"sha256": "85a9367643be850d554fbcc704ab57f850e30388ef6cfe5e001032bc3f323f0c"
|
||||
"sha256": "b036622f55012b6917bf691d87f738d9f64c1705c5ee78db2127f57c315e41d1"
|
||||
},
|
||||
"prod-research-gallery-phone": {
|
||||
"bundle": "doxa-maps-spa",
|
||||
"sha256": "9093c700a38d42a9444650690d99ce3b8b66f0c73e03e00ac49780c3833807c5"
|
||||
"sha256": "a8b01a95f0362c7a97fa510eb41e668abe0b13671abc3bb174ae2b97dda8280d"
|
||||
},
|
||||
"prod-simple-research": {
|
||||
"bundle": "research-map-plus-grid",
|
||||
"sha256": "e1f8190ed14f461801753c2743d32537fde1094e9a26555b3b40d1e22863bfae"
|
||||
"sha256": "a393659aa63df90410b93cfb1625fcc50cb9fe2b639c4ac286faedbaf658a078"
|
||||
},
|
||||
"prod-simple-research-phone": {
|
||||
"bundle": "research-map-plus-grid",
|
||||
"sha256": "e1f8190ed14f461801753c2743d32537fde1094e9a26555b3b40d1e22863bfae"
|
||||
"sha256": "a393659aa63df90410b93cfb1625fcc50cb9fe2b639c4ac286faedbaf658a078"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,8 +130,6 @@ const NO_LINKS = {
|
|||
|
||||
// ── D: late getCurrentInstance() sites already routed to an owner ─────────────────────────────
|
||||
const LATE_KNOWN = {
|
||||
// emitFilterState runs from a watcher, so `map-filtered` is never dispatched (map -> grid dead).
|
||||
[`${PD}/doxa-research-map/research-map.vue#emitFilterState`]: 'doxa-research-map owner, routed via dx-shepherd 2026-09-26',
|
||||
// reflectDir runs from watch(textDir): a host switching lang to ar AFTER mount gets no dir=rtl.
|
||||
[`${PD}/dashboard-plus-grid/dashboard-plus-grid.vue#reflectDir`]: 'composite owner, routed via dx-shepherd 2026-09-26',
|
||||
[`${PD}/research-map-plus-grid/research-map-plus-grid.vue#reflectDir`]: 'composite owner, routed via dx-shepherd 2026-09-26',
|
||||
|
|
|
|||
|
|
@ -63,17 +63,61 @@ if (!existsSync(BUILT)) {
|
|||
process.exit(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* The hexes of the PALETTE export ONLY: from `export const PALETTE` to its matching closing brace, and
|
||||
* only inside string literals. It used to match every hex from the export to the END OF THE FILE,
|
||||
* comments included, so a later note such as "REJECTED #AF7AC5, scored 2.95, under the 3.0 floor"
|
||||
* (affinity-block.js) obliged every consumer to ship a colour rejected on WCAG grounds.
|
||||
* Returns null when the module has no PALETTE export.
|
||||
*/
|
||||
export function paletteHexes(src) {
|
||||
const at = src.indexOf('export const PALETTE')
|
||||
if (at < 0) return null
|
||||
const open = src.indexOf('{', at), openArr = src.indexOf('[', at)
|
||||
const start = [open, openArr].filter((i) => i >= 0).sort((a, b) => a - b)[0]
|
||||
if (start === undefined) return []
|
||||
const close = { '{': '}', '[': ']' }[src[start]]
|
||||
let depth = 0, i = start, quote = null
|
||||
const strings = []
|
||||
let cur = ''
|
||||
for (; i < src.length; i++) {
|
||||
const c = src[i], n = src[i + 1]
|
||||
if (quote) {
|
||||
if (c === '\\') { cur += c + n; i++; continue }
|
||||
if (c === quote) { strings.push(cur); cur = ''; quote = null; continue }
|
||||
cur += c; continue
|
||||
}
|
||||
if (c === '/' && n === '/') { i = src.indexOf('\n', i); if (i < 0) break; continue }
|
||||
if (c === '/' && n === '*') { i = src.indexOf('*/', i + 2) + 1; if (i <= 0) break; continue }
|
||||
if (c === "'" || c === '"' || c === '`') { quote = c; continue }
|
||||
if (c === src[start]) depth++
|
||||
else if (c === close && --depth === 0) break
|
||||
}
|
||||
return [...new Set(strings.flatMap((t) => [...t.matchAll(/#[0-9a-fA-F]{6}\b/g)].map((m) => m[0].toLowerCase())))]
|
||||
}
|
||||
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const src = [
|
||||
"export const PALETTE = {",
|
||||
" a: '#112233', // not #445566 (a comment inside the export)",
|
||||
" /* nor #778899 */ b: \"#aabbcc\",",
|
||||
"}",
|
||||
"// REJECTED #af7ac5, scored 2.95, under the 3.0 floor",
|
||||
"export const OTHER = { c: '#ddeeff' }",
|
||||
].join('\n')
|
||||
const got = paletteHexes(src).join(',')
|
||||
if (got !== '#112233,#aabbcc') { console.error(`ONE-PALETTE: SELF-TEST FAIL — read ${got}, want #112233,#aabbcc`); process.exit(1) }
|
||||
console.log('ONE-PALETTE: SELF-TEST PASS — only the PALETTE export\'s string values are read; comments and later code are not.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// ── the expected palettes, read from the strategy modules the MAP uses ────────────────────────
|
||||
const strategies = {}
|
||||
for (const f of readdirSync(COLORS)) {
|
||||
if (!f.endsWith('.js') || f.startsWith('_') || f.startsWith('.')) continue
|
||||
const src = readFileSync(join(COLORS, f), 'utf8')
|
||||
// Only the PALETTE export — a module may also contain incidental hexes (a default, a comment)
|
||||
// that no consumer is obliged to ship.
|
||||
const at = src.indexOf('export const PALETTE')
|
||||
if (at < 0) continue
|
||||
const hexes = [...new Set([...src.slice(at).matchAll(/#[0-9a-fA-F]{6}/g)].map((m) => m[0].toLowerCase()))]
|
||||
if (hexes.length) strategies[f.replace(/\.js$/, '')] = hexes
|
||||
const hexes = paletteHexes(src)
|
||||
if (hexes && hexes.length) strategies[f.replace(/\.js$/, '')] = hexes
|
||||
}
|
||||
|
||||
// ── the consumers that must agree ─────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
* Every one of those landings passed its own checks. Nothing refused them.
|
||||
*
|
||||
* PRECONDITIONS, in order (cheap first):
|
||||
* 0. NO MERGE MARKERS no file in the COMMIT (public/ included) holds a conflict marker. d6bf410a put them
|
||||
* into 10 built files under public/ and every other precondition passed; a built bundle with
|
||||
* markers throws on load while its SRI still matches. --markers-self-test proves the scan refuses.
|
||||
* 1. CLEAN the working tree equals HEAD for everything the checks read — otherwise they would judge
|
||||
* files that are not in the commit.
|
||||
* 2. ONE FOLDER public/ holds exactly the configured tree (vite.doxa-maps-build-config.json "name") and
|
||||
|
|
@ -20,6 +23,7 @@
|
|||
* 4. FRESH every composite rebuilds to exactly its committed bytes (check:composite-freshness).
|
||||
*
|
||||
* USAGE node internal/landing-preconditions.mjs exit 0 = may land · 1 = refused
|
||||
* node internal/landing-preconditions.mjs --markers-self-test proves step 0 refuses a conflict
|
||||
* DOXA_SKIP_FRESHNESS=1 skips (4) — for a DRY check only; the merge-queue check never sets it.
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process'
|
||||
|
|
@ -36,6 +40,30 @@ const refuse = (why, detail = '') => {
|
|||
}
|
||||
const run = (cmd, args) => spawnSync(cmd, args, { cwd: ROOT, encoding: 'utf8', env: process.env })
|
||||
|
||||
// 0. NO MERGE MARKERS — scanned in the COMMIT's tree (git grep), so public/ and every file is covered,
|
||||
// whatever the working tree holds. Markers are built from char codes so this file never contains one.
|
||||
const OPEN = String.fromCharCode(60).repeat(7), MID = String.fromCharCode(61).repeat(7), CLOSE = String.fromCharCode(62).repeat(7)
|
||||
function markerFiles(ref) {
|
||||
const g = run('git', ['grep', '-I', '-l', '-E', '-e', `^(${OPEN}( |$)|${MID}$|${CLOSE}( |$))`, ref, '--'])
|
||||
if (g.status > 1) refuse('could not scan the commit for merge markers', g.stderr.trim())
|
||||
return g.stdout.split('\n').filter(Boolean).map((l) => l.replace(new RegExp('^' + ref + ':'), ''))
|
||||
}
|
||||
if (process.argv.includes('--markers-self-test')) {
|
||||
// A throwaway commit object (no branch, no working-tree change) holding one built file with a textbook
|
||||
// conflict; the scan must name it.
|
||||
const blob = spawnSync('git', ['hash-object', '-w', '--stdin'], { cwd: ROOT, encoding: 'utf8', input: `a\n${OPEN} HEAD\nmine\n${MID}\ntheirs\n${CLOSE} other\n` }).stdout.trim()
|
||||
const tree = spawnSync('git', ['mktree'], { cwd: ROOT, encoding: 'utf8', input: `100644 blob ${blob}\tpoison.js\n` }).stdout.trim()
|
||||
const pub = spawnSync('git', ['mktree'], { cwd: ROOT, encoding: 'utf8', input: `040000 tree ${tree}\tpublic\n` }).stdout.trim()
|
||||
const commit = spawnSync('git', ['commit-tree', pub, '-m', 'marker self-test'], { cwd: ROOT, encoding: 'utf8' }).stdout.trim()
|
||||
const hits = markerFiles(commit)
|
||||
if (hits.includes('public/poison.js')) { console.log('LANDING-PRECONDITIONS: markers self-test PASS — a conflict in a public/ file is refused.'); process.exit(0) }
|
||||
console.error('LANDING-PRECONDITIONS: markers self-test FAIL — a textbook conflict under public/ was not found.'); process.exit(1)
|
||||
}
|
||||
const markers = markerFiles('HEAD')
|
||||
if (markers.length) refuse(`${markers.length} file(s) in this commit hold merge-conflict markers`,
|
||||
markers.slice(0, 10).join('\n') + '\nFor a BUILT file: take either side, REBUILD it, commit the rebuild. Never `git add -A public` mid-merge.')
|
||||
console.log('LANDING-PRECONDITIONS: 0 markers — no file in the commit holds a merge-conflict marker')
|
||||
|
||||
// 1. CLEAN
|
||||
const dirty = run('git', ['status', '--porcelain', '--', 'public', 'plugin-designs', 'library', 'internal', 'vite.config.js', 'vite.doxa-maps-build-config.json', 'package.json'])
|
||||
if (dirty.status !== 0) refuse('could not read git status')
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@
|
|||
"check:no-orphan-gates": "node internal/check-no-orphan-gates.mjs",
|
||||
"check:no-public-binds": "node internal/check-no-public-binds.mjs",
|
||||
"check:one-palette": "node internal/check-one-palette.mjs",
|
||||
"check:one-palette:self-test": "node internal/check-one-palette.mjs --self-test",
|
||||
"check:packet-browser": "node internal/check-packet-browser.mjs",
|
||||
"check:packet-fixture": "node internal/generate-packet-fixture.mjs --check",
|
||||
"check:packet-render": "node internal/check-packet-render.mjs",
|
||||
|
|
@ -199,7 +200,7 @@
|
|||
"dev": "bun x vite",
|
||||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
"lists": "node library/scripts/people-groups/build-lists.mjs",
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
* Becomes tab-1 of the Cat-5 SPA.
|
||||
*/
|
||||
import { computed, reactive, ref, watchEffect } from 'vue'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
import { SUPPORTED_LOCALES, setRuntimeLocale } from '@map/i18n/index.js'
|
||||
import BpResearchHeader from './src/components/BpResearchHeader.vue'
|
||||
// The REAL cap-2 grid — same component the standalone <people-groups-grid> ships (no fork).
|
||||
|
|
@ -152,7 +153,7 @@ function onGroupsFiltered(payload) {
|
|||
architecture note (header↔grid via props/events, no shared bus, tab-1 of the SPA)
|
||||
lives in this file's header block, not in the UI where an end user reads it. -->
|
||||
<footer class="bp-foot">
|
||||
Search and filter the world's people groups · least-reached shown first
|
||||
{{ tStatic('dashboard.bp_subtitle') || "Search and filter the world's people groups · least-reached shown first" }}
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -154,6 +154,11 @@ import { useLegendData } from '@map/composables/legends/useLegendData.js'
|
|||
import { mapDefaults } from '@map/constants/mapDefaults.js'
|
||||
import { getColorStrategy } from '@map/colors/_registry.js'
|
||||
import { RELIGION_FAMILIES } from '@map/colors/religion.js'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
|
||||
// This map's own chrome, read out of the catalogue. tStatic and not useI18n: this component is the
|
||||
// root of a custom element, where useI18n() cannot resolve its injection and throws.
|
||||
const T = (key, en) => tStatic(key) || en
|
||||
import { DataSourceManager } from '@map/utils/DataSourceManager.js'
|
||||
import sourcesConfig from '@map/api/sources.json'
|
||||
|
||||
|
|
@ -2136,10 +2141,29 @@ if (typeof window !== 'undefined') {
|
|||
}
|
||||
}
|
||||
|
||||
// ★ THE INSTANCE IS CAPTURED IN SETUP, BECAUSE A WATCHER IS NOT SETUP.
|
||||
// This read getCurrentInstance() INSIDE the function, and the function is only ever called from a
|
||||
// watcher callback (just below). Vue only returns an instance during setup — from a watcher it is
|
||||
// null — so `host` was null on every call, emitFilterState returned at the guard, and the
|
||||
// `map-filtered` event this map owes its composite was NEVER dispatched. The whole map -> grid
|
||||
// direction was dead.
|
||||
//
|
||||
// It looked verified because the check that "proved" it DISPATCHED THE EVENT ITSELF and then
|
||||
// asserted the grid reacted. That tested the composite's listener and the grid, which do work; it
|
||||
// never tested the one line that emits. A synthetic event cannot prove an emitter fires — it
|
||||
// replaces it.
|
||||
//
|
||||
// Captured here, at setup top level, and resolved to the host lazily so it also survives being
|
||||
// called before mount.
|
||||
const _selfInstance = getCurrentInstance()
|
||||
function selfHostElement() {
|
||||
try { return _selfInstance?.proxy?.$el?.getRootNode?.()?.host || null } catch (_) { return null }
|
||||
}
|
||||
|
||||
// Report OUR filter state upward, so a composite can drive its other half with it.
|
||||
function emitFilterState() {
|
||||
if (applyingExternal || typeof window === 'undefined') return
|
||||
const host = getCurrentInstance()?.proxy?.$el?.getRootNode?.()?.host
|
||||
const host = selfHostElement()
|
||||
if (!host) return
|
||||
// Report in the vocabulary a HOST speaks: the family NAME, not the legend's letter key. Same
|
||||
// shared RELIGION_FAMILIES table the inbound direction reads, used the other way round.
|
||||
|
|
@ -2250,18 +2274,36 @@ function applyFitSlugs(slugs) {
|
|||
}
|
||||
if (!n) return // nothing this map holds — say nothing rather than fly nowhere
|
||||
|
||||
const duration = prefersReducedMotion() ? 0 : FLY_DURATION_MS
|
||||
const spanLng = maxLng - minLng
|
||||
const spanLat = maxLat - minLat
|
||||
if (spanLng > WIDE_SPAN_DEG) {
|
||||
mapFly.flyToCoords?.({ longitude: (minLng + maxLng) / 2, latitude: (minLat + maxLat) / 2 }, WORLD_ZOOM, { duration })
|
||||
return
|
||||
const centre = { longitude: (minLng + maxLng) / 2, latitude: (minLat + maxLat) / 2 }
|
||||
const point = spanLng > WIDE_SPAN_DEG ? { c: centre, z: WORLD_ZOOM }
|
||||
: (n === 1 || (spanLng < DEGENERATE_SPAN_DEG && spanLat < DEGENERATE_SPAN_DEG))
|
||||
? { c: { longitude: minLng, latitude: minLat }, z: SINGLE_ROW_ZOOM }
|
||||
: null
|
||||
|
||||
// ★ REDUCED MOTION DRIVES THE MAP DIRECTLY, and that is not belt-and-braces.
|
||||
// MEASURED by rega with the browser's prefers-reduced-motion: reduce set: the framing move still
|
||||
// animated for ~5s on MapLibre and ~6.7s on Mapbox — essentially the same as without it, where a
|
||||
// jump is ~0ms. So the preference was being honoured in THIS file and thrown away downstream:
|
||||
// useMapFly's flyToCoords(coords, zoom) takes NO options argument at all, so the `{ duration: 0 }`
|
||||
// I was passing it went nowhere. (fitBounds does accept one; flyToCoords does not — a difference
|
||||
// between two neighbours in one file, invisible at the call site.)
|
||||
//
|
||||
// A photosensitive reader is the whole reason this branch exists, so it no longer depends on
|
||||
// whether a helper happens to forward an option: it calls the engine's own jump. The eased paths
|
||||
// are unchanged and still go through mapFly, so ordinary motion keeps one owner.
|
||||
const m = map.value
|
||||
if (prefersReducedMotion() && m) {
|
||||
try {
|
||||
if (point) m.jumpTo({ center: [point.c.longitude, point.c.latitude], zoom: point.z })
|
||||
else m.fitBounds([[minLng, minLat], [maxLng, maxLat]], { padding: FIT_PADDING_PX, maxZoom: FIT_MAX_ZOOM, duration: 0 })
|
||||
return
|
||||
} catch (_) { /* fall through to the eased path rather than not moving at all */ }
|
||||
}
|
||||
if (n === 1 || (spanLng < DEGENERATE_SPAN_DEG && spanLat < DEGENERATE_SPAN_DEG)) {
|
||||
mapFly.flyToCoords?.({ longitude: minLng, latitude: minLat }, SINGLE_ROW_ZOOM, { duration })
|
||||
return
|
||||
}
|
||||
mapFly.fitBounds?.([[minLng, minLat], [maxLng, maxLat]], FIT_PADDING_PX, { maxZoom: FIT_MAX_ZOOM, duration })
|
||||
|
||||
if (point) { mapFly.flyToCoords?.(point.c, point.z); return }
|
||||
mapFly.fitBounds?.([[minLng, minLat], [maxLng, maxLat]], FIT_PADDING_PX, { maxZoom: FIT_MAX_ZOOM, duration: FLY_DURATION_MS })
|
||||
}
|
||||
|
||||
// ─── Clusters wiring: TWO CONTROLS, ONE STATE ────────────────────────────────
|
||||
|
|
@ -2538,8 +2580,8 @@ onBeforeUnmount(() => {
|
|||
</div>
|
||||
|
||||
<div class="rm-map-area">
|
||||
<div v-if="!appReady && !mapError" class="rm-loading">Research map loading…</div>
|
||||
<div v-if="mapError" class="rm-error">The map could not be loaded. {{ mapError }}</div>
|
||||
<div v-if="!appReady && !mapError" class="rm-loading">{{ T('dashboard.map_loading', 'Research map loading…') }}</div>
|
||||
<div v-if="mapError" class="rm-error">{{ T('allPeopleGroups.loadError', 'The map could not be loaded.') }} {{ mapError }}</div>
|
||||
<div ref="mapContainer" class="rm-map-canvas" />
|
||||
<div class="map-scroll-edge map-scroll-edge--left" />
|
||||
<div class="map-scroll-edge map-scroll-edge--right" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-reduced-motion-jumps.mjs — with prefers-reduced-motion, the grid→map framing must JUMP.
|
||||
*
|
||||
* ★ THE DRIVER IS PHOTOSENSITIVE. This is not a polish item: a camera that sweeps the globe every
|
||||
* time a list is filtered is the strobe class this repo has already paid for once.
|
||||
*
|
||||
* THE DEFECT, measured by rega in a real browser with the preference set: the framing move still
|
||||
* animated for ~5.0s on MapLibre and ~6.7s on Mapbox — indistinguishable from no preference at all,
|
||||
* where a jump is ~0ms. The preference WAS being read correctly in research-map.vue and then thrown
|
||||
* away one layer down: useMapFly's `flyToCoords(coords, zoom)` takes NO options argument, so the
|
||||
* `{ duration: 0 }` being passed to it went nowhere. Its neighbour `fitBounds(bounds, padding,
|
||||
* options)` does accept one — two functions in one file, different signatures, and nothing at the
|
||||
* call site to show it.
|
||||
*
|
||||
* ★ WHY THIS RUNS WITHOUT A BROWSER, WHICH IS THE POINT. rega found it by watching movestart →
|
||||
* moveend for six seconds; only rega can do that, and only occasionally. What can be checked any
|
||||
* time, in milliseconds, is the DECISION: given the preference, which camera call does the code
|
||||
* make? So this lifts the real camera block out of the SFC, runs it against a mocked matchMedia and
|
||||
* a recording map, and asserts the call that comes out.
|
||||
*
|
||||
* It cannot prove the engine honours duration 0 — that is rega's half. It proves we ASK for a jump,
|
||||
* which is the half that was wrong.
|
||||
*
|
||||
* ASSERTIONS:
|
||||
* reduced motion ON the engine's own jumpTo / fitBounds(duration:0) is called, and the eased
|
||||
* helper (mapFly) is NOT — a single eased call is the whole bug.
|
||||
* reduced motion OFF the eased path is used, so ordinary motion is unchanged.
|
||||
* one row / a tight cluster / a globe-spanning set all take their own branch under both.
|
||||
*
|
||||
* Exit: 0 pass · 1 fail · 2 the camera block could not be found (reported, never silently skipped).
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { parse as parseSFC } from '@vue/compiler-sfc'
|
||||
|
||||
const TAG = 'REDUCED-MOTION-JUMPS'
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
||||
const FILE = join(ROOT, 'plugin-designs', 'doxa-research-map', 'research-map.vue')
|
||||
|
||||
const src = readFileSync(FILE, 'utf8')
|
||||
const { descriptor } = parseSFC(src, { filename: FILE })
|
||||
const script = (descriptor.scriptSetup || descriptor.script || {}).content || ''
|
||||
|
||||
// The block is delimited by its own banner comments, so a refactor that moves it renames the
|
||||
// markers with it rather than leaving this gate quietly reading the wrong lines.
|
||||
const START = 'const FIT_PADDING_PX'
|
||||
const END = '// ─── Clusters wiring: TWO CONTROLS, ONE STATE'
|
||||
// indexOf from the START, not from 0: an identical-looking line can appear earlier in a file this
|
||||
// size, and a block that runs from the right start to the wrong end parses into nonsense.
|
||||
const a = script.indexOf(START)
|
||||
const b = a >= 0 ? script.indexOf(END, a) : -1
|
||||
if (a < 0 || b < 0 || b < a) {
|
||||
console.log(`${TAG}: FAIL — could not find the camera block in research-map.vue (markers moved?).`)
|
||||
process.exit(2)
|
||||
}
|
||||
const block = script.slice(a, b)
|
||||
if (!/function applyFitSlugs/.test(block) || !/function prefersReducedMotion/.test(block)) {
|
||||
console.log(`${TAG}: FAIL — the block found does not contain applyFitSlugs and prefersReducedMotion.`)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
/** Build the block with its collaborators injected, so the REAL code runs against fakes. */
|
||||
function makeCamera({ reduce }) {
|
||||
const calls = []
|
||||
const rec = (name) => (...args) => calls.push({ name, args })
|
||||
const map = { value: { jumpTo: rec('map.jumpTo'), fitBounds: rec('map.fitBounds'), easeTo: rec('map.easeTo'), flyTo: rec('map.flyTo') } }
|
||||
const mapFly = { flyToCoords: rec('mapFly.flyToCoords'), fitBounds: rec('mapFly.fitBounds'), flyTo: rec('mapFly.flyTo') }
|
||||
const rows = []
|
||||
const mapData = { normalizedPeopleGroups: { value: rows } }
|
||||
const win = { matchMedia: (q) => ({ matches: reduce && /prefers-reduced-motion/.test(q) }) }
|
||||
// eslint-disable-next-line no-new-func
|
||||
const factory = new Function('map', 'mapData', 'mapFly', 'window', `${block}\n;return { applyFitSlugs, prefersReducedMotion }`)
|
||||
const api = factory(map, mapData, mapFly, win)
|
||||
return { ...api, calls, rows }
|
||||
}
|
||||
|
||||
const cases = [
|
||||
{ name: 'several peoples', rows: [
|
||||
{ slug: 'a', longitude: 10, latitude: 5 }, { slug: 'b', longitude: 14, latitude: 9 }, { slug: 'c', longitude: 12, latitude: 7 },
|
||||
], slugs: ['a', 'b', 'c'] },
|
||||
{ name: 'one people', rows: [{ slug: 'a', longitude: 10, latitude: 5 }], slugs: ['a'] },
|
||||
{ name: 'tight cluster', rows: [
|
||||
{ slug: 'a', longitude: 10, latitude: 5 }, { slug: 'b', longitude: 10.001, latitude: 5.001 },
|
||||
], slugs: ['a', 'b'] },
|
||||
{ name: 'globe-spanning', rows: [
|
||||
{ slug: 'a', longitude: -170, latitude: -40 }, { slug: 'b', longitude: 170, latitude: 60 },
|
||||
], slugs: ['a', 'b'] },
|
||||
]
|
||||
|
||||
const fails = []
|
||||
const lines = []
|
||||
for (const c of cases) {
|
||||
for (const reduce of [true, false]) {
|
||||
const cam = makeCamera({ reduce })
|
||||
cam.rows.push(...c.rows)
|
||||
cam.applyFitSlugs(c.slugs)
|
||||
const names = cam.calls.map((k) => k.name)
|
||||
const easedUsed = names.some((n) => n.startsWith('mapFly.'))
|
||||
const jumped = names.some((n) => n === 'map.jumpTo')
|
||||
|| cam.calls.some((k) => k.name === 'map.fitBounds' && k.args[1] && k.args[1].duration === 0)
|
||||
lines.push(` · ${c.name.padEnd(16)} reduce=${String(reduce).padEnd(5)} → ${names.join(', ') || '(no camera call)'}`)
|
||||
|
||||
if (!names.length) { fails.push(`${c.name} (reduce=${reduce}) made no camera call at all.`); continue }
|
||||
if (reduce) {
|
||||
if (!jumped) fails.push(`${c.name}: with reduced motion the code did not ask for a jump — it called ${names.join(', ')}.`)
|
||||
if (easedUsed) fails.push(`${c.name}: with reduced motion it still used the EASED helper (${names.filter((n) => n.startsWith('mapFly.')).join(', ')}) — a single eased call is the whole defect.`)
|
||||
} else {
|
||||
if (!easedUsed) fails.push(`${c.name}: without the preference the eased path should be used, got ${names.join(', ')} — ordinary motion must be unchanged.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const l of lines) console.log(l)
|
||||
if (fails.length) {
|
||||
console.log(`\n${TAG}: FAIL`)
|
||||
for (const f of fails) console.log(` ✗ ${f}`)
|
||||
console.log('\n The agent is photosensitive. A camera that sweeps on every filter is the defect.')
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`${TAG}: PASS — reduced motion asks the engine to jump on every branch, and ordinary motion is untouched.`)
|
||||
|
|
@ -23,6 +23,13 @@
|
|||
* DashboardSearchHeader (DOXA-LIB-3) remains available in the kit but is not mounted here.
|
||||
*/
|
||||
import { computed, watch, inject, ref, unref, onMounted } from 'vue'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
|
||||
// This component's chrome, read out of the catalogue. tStatic and not useI18n: these mount inside
|
||||
// custom elements that install no i18n plugin, and an unguarded useI18n() throws on the way in and
|
||||
// takes the subtree with it. The English stays as the fallback, so a missing key degrades to what
|
||||
// shipped rather than to a key name.
|
||||
const T = (key, en) => tStatic(key) || en
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { useDashboardShell, fmt } from './src/composables/useDashboardShell.js'
|
||||
import { useDashboardLayout } from './src/composables/useDashboardLayout.js'
|
||||
|
|
@ -732,12 +739,12 @@ onMounted(() => {
|
|||
owns, so the two input methods can never disagree about the current value. -->
|
||||
<div v-show="!isNarrow && !facetCollapsed" class="rd-rz rd-rz-facet" data-resize="facet"
|
||||
role="separator" aria-orientation="vertical" tabindex="0"
|
||||
title="Drag or use arrow keys to resize the filters rail" aria-label="Resize filters rail"
|
||||
:title="T('aria.resizeFiltersRailHint', 'Drag or use arrow keys to resize the filters rail')" :aria-label="T('aria.resizeFiltersRail', 'Resize filters rail')"
|
||||
:aria-valuenow="facetW" aria-valuemin="160" aria-valuemax="420"
|
||||
@pointerdown="startFacetResize" @keydown="onResizeKeydown('facet', $event)"></div>
|
||||
<div v-show="!isNarrow && !railCollapsed" class="rd-rz rd-rz-rail" data-resize="rail"
|
||||
role="separator" aria-orientation="vertical" tabindex="0"
|
||||
title="Drag or use arrow keys to resize the charts rail" aria-label="Resize charts rail"
|
||||
:title="T('aria.resizeChartsRailHint', 'Drag or use arrow keys to resize the charts rail')" :aria-label="T('aria.resizeChartsRail', 'Resize charts rail')"
|
||||
:aria-valuenow="railW" aria-valuemin="200" aria-valuemax="520"
|
||||
@pointerdown="startRailResize" @keydown="onResizeKeydown('rail', $event)"></div>
|
||||
<!-- ★ FIXED (BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER): this handle is the MAP/TABLE SPLIT
|
||||
|
|
@ -762,8 +769,8 @@ onMounted(() => {
|
|||
band), so there is nothing for this handle to do outside page-scroll. -->
|
||||
<div v-show="!isNarrow && pageScroll" class="rd-rz rd-rz-shell" data-resize="shell"
|
||||
role="separator" aria-orientation="horizontal" tabindex="0"
|
||||
title="Drag or use arrow keys to resize the dashboard's height"
|
||||
aria-label="Resize dashboard height"
|
||||
:title="T('aria.resizeDashboardHeightHint', `Drag or use arrow keys to resize the dashboard's height`)"
|
||||
:aria-label="T('aria.resizeDashboardHeight', 'Resize dashboard height')"
|
||||
:aria-valuenow="shellH" aria-valuemin="400" aria-valuemax="2600"
|
||||
@pointerdown="startShellResize" @keydown="onResizeKeydown('shell', $event)"></div>
|
||||
<div class="rd-seg" data-segmented @click="setView">
|
||||
|
|
@ -771,8 +778,10 @@ onMounted(() => {
|
|||
</div>
|
||||
<div v-if="selCount" class="rd-exportbar" data-export-bar @click="onBarClick">
|
||||
<b>{{ selCount }} selected</b><span class="sp"></span>
|
||||
<button class="x-clr" data-bar-clear>Clear</button>
|
||||
<button class="x-go" data-bar-export>Export CSV ↓</button>
|
||||
<button class="x-clr" data-bar-clear>{{ T('controls.clear', 'Clear') }}</button>
|
||||
<!-- The glyph stays a glyph and the words come from the catalogue — one spelling of this
|
||||
action across the dashboard, not two. -->
|
||||
<button class="x-go" data-bar-export>{{ T('dashboard.export_csv', 'Export CSV') }} ↓</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ useShadowStyles(`
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="rd-breadcrumb" data-breadcrumb @click="onClick" aria-label="Filter breadcrumb">
|
||||
<nav class="rd-breadcrumb" data-breadcrumb @click="onClick" :aria-label="tStatic('aria.filterBreadcrumb') || 'Filter breadcrumb'">
|
||||
<span class="rd-crumb root" data-crumb="__root__">{{ L('world', 'World') }}</span>
|
||||
<template v-for="(c, i) in crumbs" :key="c.k">
|
||||
<span class="rd-sep">▸</span>
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ useShadowStyles(`
|
|||
So the second tab renders DISABLED with an explicit reason rather than being hidden. A hidden
|
||||
tab is indistinguishable from an unbuilt one; a disabled tab that says why is a status report.
|
||||
-->
|
||||
<div class="rd-tabs" role="tablist" aria-label="Rail views">
|
||||
<div class="rd-tabs" role="tablist" :aria-label="tStatic('aria.railViews') || 'Rail views'">
|
||||
<button
|
||||
v-for="t in tabs" :key="t.id"
|
||||
class="rd-tab" :class="{ on: activeTab === t.id, off: t.disabled }"
|
||||
|
|
|
|||
|
|
@ -806,10 +806,10 @@ useShadowStyles(`
|
|||
<template v-if="selCount">
|
||||
<button class="rd-vtog" :class="{ on: onlySelected }" data-only-selected
|
||||
:aria-pressed="onlySelected ? 'true' : 'false'"
|
||||
title="Show only the selected people groups" @click="toggleOnlySelected">Only selected</button>
|
||||
:title="T('dashboard.only_selected_hint', 'Show only the selected people groups')" @click="toggleOnlySelected">{{ T('dashboard.only_selected', 'Only selected') }}</button>
|
||||
<button class="rd-vtog" :class="{ on: selectedFirst }" data-selected-first
|
||||
:aria-pressed="selectedFirst ? 'true' : 'false'"
|
||||
title="Float the selected people groups to the top, keeping your column sort" @click="toggleSelectedFirst">Selected first</button>
|
||||
:title="T('dashboard.selected_first_hint', 'Float the selected people groups to the top, keeping your column sort')" @click="toggleSelectedFirst">{{ T('dashboard.selected_first', 'Selected first') }}</button>
|
||||
</template>
|
||||
<span class="rd-bar-sp"></span>
|
||||
<!--
|
||||
|
|
|
|||
|
|
@ -157,13 +157,13 @@ useShadowStyles(`
|
|||
<section class="rd-detail" data-region="detail-drawer">
|
||||
<div class="rd-dh">
|
||||
<span>{{ T('dashboard.detail', 'Detail') }}</span>
|
||||
<button v-if="record" class="rd-detail-close" type="button" aria-label="Close detail" data-detail-close @click="clearDetail">✕</button>
|
||||
<button v-if="record" class="rd-detail-close" type="button" :aria-label="T('aria.closeDetail', 'Close detail')" data-detail-close @click="clearDetail">✕</button>
|
||||
</div>
|
||||
<div v-if="!record" class="rd-empty" data-detail-empty>{{ T('dashboard.detail_empty', 'Select a people group to see detail') }}</div>
|
||||
<template v-else>
|
||||
<div class="rd-top">
|
||||
<img v-if="showPhoto" class="rd-thumb" :src="record.image_url" :alt="record.name" data-detail-thumb />
|
||||
<div v-else class="rd-ph" data-detail-thumb>No photo</div>
|
||||
<div v-else class="rd-ph" data-detail-thumb>{{ T('dashboard.no_photo', 'No photo') }}</div>
|
||||
<div>
|
||||
<div class="rd-name" data-detail-name>{{ record.name || '—' }}</div>
|
||||
<div class="rd-meta">{{ lbl(record.country_code) || '—' }} · {{ lbl(record.religion) || '—' }}</div>
|
||||
|
|
@ -180,14 +180,14 @@ useShadowStyles(`
|
|||
</a>
|
||||
</div>
|
||||
<div class="rd-rows">
|
||||
<div class="rd-row"><span class="k">Language</span><span class="v">{{ lbl(record.primary_language) || '—' }}<span v-if="record.imb_language_family" class="fam"> ({{ record.imb_language_family }})</span></span></div>
|
||||
<div class="rd-row"><span class="k">Population</span><span class="v">{{ fmt(record.population) }}</span></div>
|
||||
<div class="rd-row"><span class="k">People praying</span><span class="v">{{ fmt(record.people_praying) }}</span></div>
|
||||
<div class="rd-row"><span class="k">People committed</span><span class="v">{{ fmt(record.people_committed) }}</span></div>
|
||||
<div class="rd-row"><span class="k">Adopted by churches</span><span class="v">{{ fmt(record.adopted_by_churches) }}</span></div>
|
||||
<div class="rd-row"><span class="k">Engagement</span><span class="v">{{ lbl(record.engagement_status) || '—' }}</span></div>
|
||||
<div class="rd-row"><span class="k">{{ T('dashboard.language', 'Language') }}</span><span class="v">{{ lbl(record.primary_language) || '—' }}<span v-if="record.imb_language_family" class="fam"> ({{ record.imb_language_family }})</span></span></div>
|
||||
<div class="rd-row"><span class="k">{{ T('dashboard.population', 'Population') }}</span><span class="v">{{ fmt(record.population) }}</span></div>
|
||||
<div class="rd-row"><span class="k">{{ T('dashboard.people_praying', 'People praying') }}</span><span class="v">{{ fmt(record.people_praying) }}</span></div>
|
||||
<div class="rd-row"><span class="k">{{ T('dashboard.people_committed', 'People committed') }}</span><span class="v">{{ fmt(record.people_committed) }}</span></div>
|
||||
<div class="rd-row"><span class="k">{{ T('dashboard.adopted_by_churches', 'Adopted by churches') }}</span><span class="v">{{ fmt(record.adopted_by_churches) }}</span></div>
|
||||
<div class="rd-row"><span class="k">{{ T('dashboard.engagement', 'Engagement') }}</span><span class="v">{{ lbl(record.engagement_status) || '—' }}</span></div>
|
||||
</div>
|
||||
<button class="rd-exp" type="button" data-detail-export @click="exportThis">Export this selection ↓</button>
|
||||
<button class="rd-exp" type="button" data-detail-export @click="exportThis">{{ T('dashboard.export_selection', 'Export this selection') }} ↓</button>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@
|
|||
* and is what CSV export reads later. Click delegation on the bar (defineCustomElement-safe).
|
||||
*/
|
||||
import { inject, computed } from 'vue'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
|
||||
// This component's chrome, read out of the catalogue. tStatic and not useI18n: these mount inside
|
||||
// custom elements that install no i18n plugin, and an unguarded useI18n() throws on the way in and
|
||||
// takes the subtree with it.
|
||||
const T = (key, en) => tStatic(key) || en
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { useTreeLegendLabels } from '../../composables/useTreeLegendLabels.js'
|
||||
|
||||
|
|
@ -82,6 +88,10 @@ useShadowStyles(`
|
|||
:title="c.facet + ': ' + c.text">
|
||||
<span class="fc">{{ c.facet }}:</span><span class="rd-chip-val">{{ c.text }}</span> <i>✕</i>
|
||||
</span>
|
||||
<span class="rd-chip rd-clear" data-chip="__all__">Clear all</span>
|
||||
<!-- ★ REUSES controls.reset_all rather than adding a second key. This chip clears every
|
||||
filter, which is the same action that button names, and two keys for one action is how
|
||||
a control ends up saying two different things in two languages. The English shifts from
|
||||
"Clear all" to "Reset all" — a deliberate consequence of having one name for one thing. -->
|
||||
<span class="rd-chip rd-clear" data-chip="__all__">{{ T('controls.reset_all', 'Reset all') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1679,7 +1679,7 @@ useShadowStyles(`
|
|||
<span class="rd-proj" role="button" tabindex="0" data-proj-toggle @click="toggleProjection">
|
||||
{{ projection === 'globe' ? '🌐 Globe' : '▭ Flat' }}
|
||||
</span>
|
||||
<div class="rd-legend-map" data-map-legend aria-label="UPG count legend">
|
||||
<div class="rd-legend-map" data-map-legend :aria-label="T('aria.upgCountLegend', 'UPG count legend')">
|
||||
<!-- ★ COMPOSED FROM TWO EXISTING CATALOGUE ENTRIES, not from new wording. This legend title
|
||||
was the last piece of English chrome here that could be translated without inventing a
|
||||
phrase in ten languages: legend.columns.upgs and dashboard.country both already ship in
|
||||
|
|
|
|||
|
|
@ -297,8 +297,8 @@ useShadowStyles(`
|
|||
<span class="rd-spacer" />
|
||||
<div v-if="showTopBarSearch" class="rd-search" data-search>
|
||||
<span class="ic">⌕</span>
|
||||
<input type="text" v-model="q" placeholder="Search people groups…" data-search-input
|
||||
aria-label="Search people groups" @input="onInput" @focus="onInput" @keydown="onKeydown" />
|
||||
<input type="text" v-model="q" :placeholder="tStatic('grid.search') || 'Search people groups…'" data-search-input
|
||||
:aria-label="tStatic('aria.searchPeopleGroups') || 'Search people groups'" @input="onInput" @focus="onInput" @keydown="onKeydown" />
|
||||
<div v-if="open && q.trim().length >= 2" class="rd-results" data-search-results @click="onResultsClick">
|
||||
<template v-if="results.length">
|
||||
<div v-for="(r, i) in results" :key="r.slug" class="rd-res" :class="{ on: i === active }" :data-slug="r.slug">
|
||||
|
|
@ -306,7 +306,7 @@ useShadowStyles(`
|
|||
<span class="sub">{{ lbl(r.country_code) }} · {{ lbl(r.religion) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="rd-empty">No people groups match “{{ q.trim() }}”.</div>
|
||||
<div v-else class="rd-empty">{{ (tStatic('dashboard.no_match') || 'No people groups match “{q}”.').replace('{q}', q.trim()) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="rd-align" role="button" tabindex="0" data-align-btn
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <bp-big-picture> custom element. -->
|
||||
<script src="../../big-picture.js" integrity="sha384-Q7nhazsV9dqeNW/ZGwrGXSVInr6Itsf4o98/eKRfJgqVY4jNLu6UkZSkhqtuT7g/" crossorigin="anonymous"></script>
|
||||
<script src="../../big-picture.js" integrity="sha384-f0QwbiCCDjTDqiQVxWY5KwZIGQ/eEcHdrSPLR8GqCZfc4r5PkfObe4GMN4lxK8ki" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <bp-big-picture> custom element. -->
|
||||
<script src="../../big-picture.js" integrity="sha384-Q7nhazsV9dqeNW/ZGwrGXSVInr6Itsf4o98/eKRfJgqVY4jNLu6UkZSkhqtuT7g/" crossorigin="anonymous"></script>
|
||||
<script src="../../big-picture.js" integrity="sha384-f0QwbiCCDjTDqiQVxWY5KwZIGQ/eEcHdrSPLR8GqCZfc4r5PkfObe4GMN4lxK8ki" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <bp-big-picture> custom element. -->
|
||||
<script src="../big-picture.js" integrity="sha384-Q7nhazsV9dqeNW/ZGwrGXSVInr6Itsf4o98/eKRfJgqVY4jNLu6UkZSkhqtuT7g/" crossorigin="anonymous"></script>
|
||||
<script src="../big-picture.js" integrity="sha384-f0QwbiCCDjTDqiQVxWY5KwZIGQ/eEcHdrSPLR8GqCZfc4r5PkfObe4GMN4lxK8ki" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,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-X3RgGbiXGIZfp85WQ5H4/NyF3soIpCRnU4vsNH3+TNJSrBcUMSMn2E3QEAYBBtXN" crossorigin="anonymous"></script>
|
||||
<script src="../../dashboard-plus-grid.js" integrity="sha384-dmlUkR/A54stQ9oHUOIrUbiGInFwp/pn8O6fJhlPKG7gfreqLjIQFTP8xkB1FOz9" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,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-X3RgGbiXGIZfp85WQ5H4/NyF3soIpCRnU4vsNH3+TNJSrBcUMSMn2E3QEAYBBtXN" crossorigin="anonymous"></script>
|
||||
<script src="../dashboard-plus-grid.js" integrity="sha384-dmlUkR/A54stQ9oHUOIrUbiGInFwp/pn8O6fJhlPKG7gfreqLjIQFTP8xkB1FOz9" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"bundle": "big-picture",
|
||||
"scriptUrl": "./big-picture/big-picture.js",
|
||||
"output": "dx-maps/big-picture/big-picture.js",
|
||||
"integrity": "sha384-Q7nhazsV9dqeNW/ZGwrGXSVInr6Itsf4o98/eKRfJgqVY4jNLu6UkZSkhqtuT7g/",
|
||||
"integrity": "sha384-f0QwbiCCDjTDqiQVxWY5KwZIGQ/eEcHdrSPLR8GqCZfc4r5PkfObe4GMN4lxK8ki",
|
||||
"elements": [
|
||||
"bp-big-picture",
|
||||
"bp-research-header",
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
"scriptUrl": "./big-picture/big-picture.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./big-picture/big-picture/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=\"./big-picture/big-picture.js\" integrity=\"sha384-Q7nhazsV9dqeNW/ZGwrGXSVInr6Itsf4o98/eKRfJgqVY4jNLu6UkZSkhqtuT7g/\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./big-picture/big-picture.js\" integrity=\"sha384-f0QwbiCCDjTDqiQVxWY5KwZIGQ/eEcHdrSPLR8GqCZfc4r5PkfObe4GMN4lxK8ki\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -214,7 +214,7 @@
|
|||
"bundle": "dashboard-plus-grid",
|
||||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"output": "dx-maps/dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"integrity": "sha384-X3RgGbiXGIZfp85WQ5H4/NyF3soIpCRnU4vsNH3+TNJSrBcUMSMn2E3QEAYBBtXN",
|
||||
"integrity": "sha384-dmlUkR/A54stQ9oHUOIrUbiGInFwp/pn8O6fJhlPKG7gfreqLjIQFTP8xkB1FOz9",
|
||||
"elements": [
|
||||
"dashboard-plus-grid"
|
||||
],
|
||||
|
|
@ -234,7 +234,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-X3RgGbiXGIZfp85WQ5H4/NyF3soIpCRnU4vsNH3+TNJSrBcUMSMn2E3QEAYBBtXN\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-dmlUkR/A54stQ9oHUOIrUbiGInFwp/pn8O6fJhlPKG7gfreqLjIQFTP8xkB1FOz9\" 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": [
|
||||
{
|
||||
|
|
@ -353,7 +353,7 @@
|
|||
"bundle": "doxa-research-map",
|
||||
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
|
||||
"output": "dx-maps/doxa-research-map/doxa-research-map.js",
|
||||
"integrity": "sha384-rScvP3yQFMFvEQ6SCPE/w1biJ3gJrzsWExhpaJ/L9O9e+9m5OUxUHYKvD45Cy/Z+",
|
||||
"integrity": "sha384-IE7GadcBdYFEn58dO/atf9zTDaFcWkpRWY242m9noDv/Hqh2OiAEZvib3AyvpUbA",
|
||||
"elements": [
|
||||
"doxa-research-map"
|
||||
],
|
||||
|
|
@ -369,7 +369,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-rScvP3yQFMFvEQ6SCPE/w1biJ3gJrzsWExhpaJ/L9O9e+9m5OUxUHYKvD45Cy/Z+\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-IE7GadcBdYFEn58dO/atf9zTDaFcWkpRWY242m9noDv/Hqh2OiAEZvib3AyvpUbA\" 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": [
|
||||
{
|
||||
|
|
@ -944,7 +944,7 @@
|
|||
"bundle": "research-dashboard",
|
||||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"output": "dx-maps/research-dashboard/research-dashboard.js",
|
||||
"integrity": "sha384-iJTJdnn7mwnqLrA6sXQ5QoTx91XkqclHfElKkd7crsKd9WsNjy1or5vnOqWU5LQ+",
|
||||
"integrity": "sha384-ZRsuN2LD7TXf/n/k1P+ivab365wtEfj7NLV2ImVfEW8fmCN++ui1VTXx+qBiulyG",
|
||||
"elements": [
|
||||
"research-dashboard-map"
|
||||
],
|
||||
|
|
@ -960,7 +960,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-iJTJdnn7mwnqLrA6sXQ5QoTx91XkqclHfElKkd7crsKd9WsNjy1or5vnOqWU5LQ+\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-ZRsuN2LD7TXf/n/k1P+ivab365wtEfj7NLV2ImVfEW8fmCN++ui1VTXx+qBiulyG\" 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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,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-rScvP3yQFMFvEQ6SCPE/w1biJ3gJrzsWExhpaJ/L9O9e+9m5OUxUHYKvD45Cy/Z+" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-research-map.js" integrity="sha384-IE7GadcBdYFEn58dO/atf9zTDaFcWkpRWY242m9noDv/Hqh2OiAEZvib3AyvpUbA" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -33,7 +33,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-rScvP3yQFMFvEQ6SCPE/w1biJ3gJrzsWExhpaJ/L9O9e+9m5OUxUHYKvD45Cy/Z+" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-research-map.js" integrity="sha384-IE7GadcBdYFEn58dO/atf9zTDaFcWkpRWY242m9noDv/Hqh2OiAEZvib3AyvpUbA" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"bundle": "big-picture",
|
||||
"scriptUrl": "./big-picture/big-picture.js",
|
||||
"output": "dx-maps/big-picture/big-picture.js",
|
||||
"integrity": "sha384-Q7nhazsV9dqeNW/ZGwrGXSVInr6Itsf4o98/eKRfJgqVY4jNLu6UkZSkhqtuT7g/",
|
||||
"integrity": "sha384-f0QwbiCCDjTDqiQVxWY5KwZIGQ/eEcHdrSPLR8GqCZfc4r5PkfObe4GMN4lxK8ki",
|
||||
"elements": [
|
||||
"bp-big-picture",
|
||||
"bp-research-header",
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
"scriptUrl": "./big-picture/big-picture.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./big-picture/big-picture/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=\"./big-picture/big-picture.js\" integrity=\"sha384-Q7nhazsV9dqeNW/ZGwrGXSVInr6Itsf4o98/eKRfJgqVY4jNLu6UkZSkhqtuT7g/\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./big-picture/big-picture.js\" integrity=\"sha384-f0QwbiCCDjTDqiQVxWY5KwZIGQ/eEcHdrSPLR8GqCZfc4r5PkfObe4GMN4lxK8ki\" crossorigin=\"anonymous\"></script>\n<bp-big-picture profile-config='{\"profile\":\"big-picture\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bp-big-picture>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -214,7 +214,7 @@
|
|||
"bundle": "dashboard-plus-grid",
|
||||
"scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"output": "dx-maps/dashboard-plus-grid/dashboard-plus-grid.js",
|
||||
"integrity": "sha384-X3RgGbiXGIZfp85WQ5H4/NyF3soIpCRnU4vsNH3+TNJSrBcUMSMn2E3QEAYBBtXN",
|
||||
"integrity": "sha384-dmlUkR/A54stQ9oHUOIrUbiGInFwp/pn8O6fJhlPKG7gfreqLjIQFTP8xkB1FOz9",
|
||||
"elements": [
|
||||
"dashboard-plus-grid"
|
||||
],
|
||||
|
|
@ -234,7 +234,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-X3RgGbiXGIZfp85WQ5H4/NyF3soIpCRnU4vsNH3+TNJSrBcUMSMn2E3QEAYBBtXN\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./dashboard-plus-grid/dashboard-plus-grid.js\" integrity=\"sha384-dmlUkR/A54stQ9oHUOIrUbiGInFwp/pn8O6fJhlPKG7gfreqLjIQFTP8xkB1FOz9\" 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": [
|
||||
{
|
||||
|
|
@ -353,7 +353,7 @@
|
|||
"bundle": "doxa-research-map",
|
||||
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
|
||||
"output": "dx-maps/doxa-research-map/doxa-research-map.js",
|
||||
"integrity": "sha384-rScvP3yQFMFvEQ6SCPE/w1biJ3gJrzsWExhpaJ/L9O9e+9m5OUxUHYKvD45Cy/Z+",
|
||||
"integrity": "sha384-IE7GadcBdYFEn58dO/atf9zTDaFcWkpRWY242m9noDv/Hqh2OiAEZvib3AyvpUbA",
|
||||
"elements": [
|
||||
"doxa-research-map"
|
||||
],
|
||||
|
|
@ -369,7 +369,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-rScvP3yQFMFvEQ6SCPE/w1biJ3gJrzsWExhpaJ/L9O9e+9m5OUxUHYKvD45Cy/Z+\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-IE7GadcBdYFEn58dO/atf9zTDaFcWkpRWY242m9noDv/Hqh2OiAEZvib3AyvpUbA\" 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": [
|
||||
{
|
||||
|
|
@ -944,7 +944,7 @@
|
|||
"bundle": "research-dashboard",
|
||||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"output": "dx-maps/research-dashboard/research-dashboard.js",
|
||||
"integrity": "sha384-iJTJdnn7mwnqLrA6sXQ5QoTx91XkqclHfElKkd7crsKd9WsNjy1or5vnOqWU5LQ+",
|
||||
"integrity": "sha384-ZRsuN2LD7TXf/n/k1P+ivab365wtEfj7NLV2ImVfEW8fmCN++ui1VTXx+qBiulyG",
|
||||
"elements": [
|
||||
"research-dashboard-map"
|
||||
],
|
||||
|
|
@ -960,7 +960,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-iJTJdnn7mwnqLrA6sXQ5QoTx91XkqclHfElKkd7crsKd9WsNjy1or5vnOqWU5LQ+\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-ZRsuN2LD7TXf/n/k1P+ivab365wtEfj7NLV2ImVfEW8fmCN++ui1VTXx+qBiulyG\" 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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,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-iJTJdnn7mwnqLrA6sXQ5QoTx91XkqclHfElKkd7crsKd9WsNjy1or5vnOqWU5LQ+" crossorigin="anonymous"></script>
|
||||
<script src="../research-dashboard.js" integrity="sha384-ZRsuN2LD7TXf/n/k1P+ivab365wtEfj7NLV2ImVfEW8fmCN++ui1VTXx+qBiulyG" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||