From 28a008331041af4ff4fbd4b64120a01733dc374d Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Mon, 28 Sep 2026 08:59:18 +0000 Subject: [PATCH] Update from source a59db6c4a --- internal/acceptance/interop-harness.mjs | 2 +- internal/backend-dev-plugin.mjs | 8 ++- internal/browser-slot.sh | 3 ++ internal/check-a11y-button-names.mjs | 2 +- internal/check-aria-labels-localized.mjs | 2 +- internal/check-auto-engine.mjs | 2 +- internal/check-basemap-labels-localized.mjs | 2 +- internal/check-browser-gates-guarded.mjs | 42 +++++++++++++++ internal/check-card-deck.mjs | 2 +- internal/check-composite-embed-knobs.mjs | 2 +- internal/check-deck-click.mjs | 2 +- internal/check-element-census-vs-live.mjs | 2 +- internal/check-embed-basemap-paints.mjs | 2 +- internal/check-embedded-rtl.mjs | 2 +- internal/check-facet-changes-map.mjs | 2 +- internal/check-filter-width-stable.mjs | 2 +- internal/check-fits-the-screen.mjs | 2 +- internal/check-fixed-anchoring.mjs | 2 +- internal/check-gospel-composed-filters.mjs | 2 +- internal/check-gospel-embed-contract.mjs | 2 +- internal/check-gospel-engine-contract.mjs | 2 +- internal/check-gospel-english-leaks.mjs | 2 +- internal/check-gospel-leaves-mount.mjs | 2 +- internal/check-gospel-map-paints.mjs | 2 +- internal/check-gospel-rtl-shaping.mjs | 2 +- internal/check-host-locale-contract.mjs | 2 +- internal/check-leaf-engine-per-provider.mjs | 2 +- internal/check-leaf-english-leaks.mjs | 2 +- internal/check-legend-export-reachable.mjs | 2 +- internal/check-legend-info-localized.mjs | 2 +- internal/check-map-data-painted.mjs | 2 +- internal/check-map-embed-cells.mjs | 2 +- internal/check-map-unmount-mid-init.mjs | 2 +- internal/check-maplibre-layer-drops.mjs | 2 +- internal/check-mfe-two-tab.mjs | 2 +- internal/check-nav-reachable-mobile.mjs | 2 +- internal/check-packet-browser.mjs | 2 +- internal/check-packet-render.mjs | 2 +- internal/check-packets-all.mjs | 53 +++++++++++++++++-- internal/check-poster-card-fits.mjs | 2 +- internal/check-poster-degrades-honestly.mjs | 2 +- internal/check-poster-print-fallback.mjs | 2 +- internal/check-poster-prints.mjs | 2 +- internal/check-poster-raster.mjs | 2 +- internal/check-poster-wall-localised.mjs | 2 +- internal/check-prayer-walk-planner.mjs | 2 +- internal/check-print-geometry.mjs | 2 +- internal/check-provider-switch.mjs | 2 +- internal/check-raw-map-rtl-shaping.mjs | 2 +- internal/check-research-gallery-embed.mjs | 2 +- internal/check-scrim-covers-viewport.mjs | 2 +- internal/check-spa-tab-order.mjs | 2 +- internal/check-studio-every-plugin.mjs | 2 +- internal/check-tabs-localized.mjs | 2 +- internal/check-upg-map-data-painted.mjs | 2 +- internal/check-vision-card-numbers.mjs | 2 +- internal/embed-smoke.mjs | 2 +- internal/lib/browser-gates.mjs | 32 +++++++++++ internal/lib/browser.mjs | 2 +- internal/lib/playwright-guarded.mjs | 41 ++++++++++++++ internal/measure-map-load.mjs | 2 +- internal/readme-shots-fix.mjs | 2 +- internal/readme-shots.mjs | 2 +- internal/run-parallel.mjs | 28 +--------- internal/smoke-studio.mjs | 2 +- internal/smoke.mjs | 2 +- internal/verify-5-capabilities.mjs | 2 +- internal/verify-gospel-map.mjs | 2 +- internal/verify-map-paint.mjs | 2 +- internal/verify-shell-scroll.mjs | 2 +- package.json | 6 ++- .../scripts/check-clustering-modes.mjs | 2 +- .../scripts/check-export-poster-prints.mjs | 2 +- .../scripts/check-pin-dim-slider.mjs | 2 +- .../doxa-simple-map/doxa-simple-map.vue | 9 ++-- .../my-upg-100-list/data/palette-preview.mjs | 2 +- .../my-upg-100-list/data/visual-gate.mjs | 2 +- .../check-dashboard-geocoder-provider.mjs | 2 +- .../scripts/check-composite-locale-leaks.mjs | 2 +- .../scripts/check-linked-vocabulary.mjs | 2 +- public/dx-maps/doxa-maps-spa/manifest.json | 4 +- .../doxa-simple-map/doxa-simple-map.js | 12 ++--- .../doxa-simple-map/adoption/index.html | 2 +- .../doxa-simple-map/engagement/index.html | 2 +- .../doxa-simple-map/index.html | 2 +- .../doxa-simple-map/prayer/index.html | 2 +- public/dx-maps/manifest.json | 4 +- 87 files changed, 271 insertions(+), 121 deletions(-) create mode 100644 internal/check-browser-gates-guarded.mjs create mode 100644 internal/lib/browser-gates.mjs create mode 100644 internal/lib/playwright-guarded.mjs diff --git a/internal/acceptance/interop-harness.mjs b/internal/acceptance/interop-harness.mjs index 9f63d840..dac06740 100644 --- a/internal/acceptance/interop-harness.mjs +++ b/internal/acceptance/interop-harness.mjs @@ -62,7 +62,7 @@ * node internal/acceptance/interop-harness.mjs --self-test [--self-test-mode=grid-prop-drop|camera-freeze] * PORT=4180 node internal/acceptance/interop-harness.mjs */ -import { chromium } from 'playwright' +import { chromium } from '../lib/playwright-guarded.mjs' import { spawn } from 'node:child_process' import { setTimeout as sleep } from 'node:timers/promises' import { fileURLToPath } from 'node:url' diff --git a/internal/backend-dev-plugin.mjs b/internal/backend-dev-plugin.mjs index 141b745e..df2ad65d 100644 --- a/internal/backend-dev-plugin.mjs +++ b/internal/backend-dev-plugin.mjs @@ -44,6 +44,8 @@ export function devBackends(env = process.env) { name: 'rudder-engine', bin: 'backend/bin/rudder-engine', build: 'cd backend/rudder-engine && go build -o ../bin/rudder-engine .', + // What a fresh clone is told at startup when this backend is not built: it is OPTIONAL (desktop voice). + optional: 'optional: Rudder (desktop voice-to-text) is not built, which is fine for the maps. It needs Go 1.22+; see documentation/HOWTO-build-the-desktop-app.md.', route: '/api/rudder', port: Number(process.env.RUDDER_PORT || 5193), env: { RUDDER_PORT: String(process.env.RUDDER_PORT || 5193), RUDDER_NEMO_PORT: String(process.env.RUDDER_NEMO_PORT || 5194) }, @@ -238,7 +240,11 @@ export function backendDevPlugin(root, env = process.env) { b.fetch(root, logger).then((ok) => { REG.fetching.delete(b.name); if (ok) launch() }) continue } - logger.warn(`[backend] ${b.name} not built — its plugin will show "engine offline". Build: ${b.build}`) + // An OPTIONAL backend is a note, not a warning: a stranger's first `bun run dev` line said + // "rudder-engine not built … go build …" with no word that it is optional or needs Go (upg100's + // fresh-clone run). A backend without that note keeps the build instruction. + if (b.optional) logger.info(`[backend] ${b.optional}`) + else logger.warn(`[backend] ${b.name} not built — its plugin will show "engine offline". Build: ${b.build}`) continue } launch() diff --git a/internal/browser-slot.sh b/internal/browser-slot.sh index fdf8d804..8ba711cb 100755 --- a/internal/browser-slot.sh +++ b/internal/browser-slot.sh @@ -72,6 +72,9 @@ fi # the command named so its owner can find it. A long gate that legitimately needs more says so: # BROWSER_MAX_HOLD=1800 internal/browser-slot.sh node internal/.mjs run_held() { + # EXPORTED, so the gate's own children (a `bash -c "…"` wrapper, a node grandchild) inherit it: + # internal/lib/playwright-guarded.mjs refuses to launch a browser without it. + export DOXA_BROWSER_SLOT="${DOXA_BROWSER_SLOT:-held}" # The gate's output still flows to the caller untouched (stdout and stderr kept apart); a COPY goes to # RAM (/dev/shm, never the disk that may be full) so a failure can be classified afterwards. local log="" rc diff --git a/internal/check-a11y-button-names.mjs b/internal/check-a11y-button-names.mjs index 0c0dc46e..14833747 100644 --- a/internal/check-a11y-button-names.mjs +++ b/internal/check-a11y-button-names.mjs @@ -33,7 +33,7 @@ import { readFileSync, existsSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { servesThisTree } from './_serves-this-tree.mjs' import { TREE_NAME } from './_tree-name.mjs' diff --git a/internal/check-aria-labels-localized.mjs b/internal/check-aria-labels-localized.mjs index 0ae75d9a..7af708d7 100644 --- a/internal/check-aria-labels-localized.mjs +++ b/internal/check-aria-labels-localized.mjs @@ -26,7 +26,7 @@ // where no English word list could. // // Usage: node internal/check-aria-labels-localized.mjs [locales] -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-auto-engine.mjs b/internal/check-auto-engine.mjs index f71c71be..40b76ab9 100644 --- a/internal/check-auto-engine.mjs +++ b/internal/check-auto-engine.mjs @@ -21,7 +21,7 @@ * Run through internal/browser-slot.sh. * EXIT 0 all three hold · 1 one does not · 75 not measured. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { spawn } from 'node:child_process' import { createServer } from 'node:http' import { existsSync } from 'node:fs' diff --git a/internal/check-basemap-labels-localized.mjs b/internal/check-basemap-labels-localized.mjs index 79ac7eda..442981d1 100644 --- a/internal/check-basemap-labels-localized.mjs +++ b/internal/check-basemap-labels-localized.mjs @@ -25,7 +25,7 @@ * NEGATIVE-TESTED 2026-09-25: the same leaf built from 319064a1's parent (before localize-labels * existed), served with --dir, FAILS on both engines. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { spawn } from 'node:child_process' import { createServer } from 'node:http' import { resolve, dirname } from 'node:path' diff --git a/internal/check-browser-gates-guarded.mjs b/internal/check-browser-gates-guarded.mjs new file mode 100644 index 00000000..aee78d09 --- /dev/null +++ b/internal/check-browser-gates-guarded.mjs @@ -0,0 +1,42 @@ +#!/usr/bin/env node +/** + * check-browser-gates-guarded — no repo gate reaches Playwright except through the slot guard. Headless. + * + * Browsers run only on the browser seat and only inside internal/browser-slot.sh. The rule is enforced + * by internal/lib/playwright-guarded.mjs (a launch refuses, exit 75, without DOXA_BROWSER_SLOT), so a + * file that imports 'playwright' DIRECTLY would bypass it. This fails any such file under internal/ or + * plugin-designs/. `--list` prints every gate script that launches a browser instead. + * EXIT 0 pass · 1 fail + */ +import { readFileSync } from 'node:fs' +import { execFileSync } from 'node:child_process' +import { resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { isBrowserGate } from './lib/browser-gates.mjs' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +if (process.argv.includes('--list')) { + const scripts = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')).scripts || {} + const names = Object.keys(scripts).filter((n) => !n.startsWith('gates:') && isBrowserGate(ROOT, n)).sort() + console.log(names.join('\n')) + console.error(`${names.length} script(s) launch a browser: run each as internal/browser-slot.sh bun run `) + process.exit(0) +} +// A REAL import, not text that names one: a static import statement at the start of a line, or a literal +// dynamic import or require of the playwright package. The detectors (browser-gates.mjs, this file) spell the +// pattern inside regex literals, escaped, which none of these match, so they need no exclusion and a real +// import added to one still fails. Only the guard itself imports playwright, by design. +const DIRECT = /^\s*import\b[^;\n]*\bfrom\s+['"]playwright['"]|\bimport\(\s*['"]playwright['"]\s*\)|\brequire\(\s*['"]playwright['"]\s*\)/m +const files = execFileSync('git', ['ls-files', '--', 'internal', 'plugin-designs'], { cwd: ROOT, encoding: 'utf8' }) + .split('\n').filter((f) => /\.(mjs|js|cjs)$/.test(f) && f !== 'internal/lib/playwright-guarded.mjs') +const bad = [] +for (const f of files) { + const code = readFileSync(resolve(ROOT, f), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '') + if (DIRECT.test(code)) bad.push(f) +} +if (bad.length) { + for (const f of bad) console.log(` FAIL ${f} imports playwright directly; import it from internal/lib/playwright-guarded.mjs`) + console.log(`BROWSER-GATES-GUARDED: FAIL — ${bad.length} file(s) can launch a browser outside the slot.`) + process.exit(1) +} +console.log(`BROWSER-GATES-GUARDED: PASS — no repo file imports playwright except through the slot guard (${files.length} files read).`) diff --git a/internal/check-card-deck.mjs b/internal/check-card-deck.mjs index 1beaf858..0799fd70 100644 --- a/internal/check-card-deck.mjs +++ b/internal/check-card-deck.mjs @@ -12,7 +12,7 @@ // // Usage: node internal/check-card-deck.mjs [lang] [stock] // PACKET_ORIGIN (default http://127.0.0.1:4898) · PACKET_API · PACKET_ISO3 -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-composite-embed-knobs.mjs b/internal/check-composite-embed-knobs.mjs index 6220e29c..d4e347e7 100644 --- a/internal/check-composite-embed-knobs.mjs +++ b/internal/check-composite-embed-knobs.mjs @@ -60,7 +60,7 @@ if (!existsSync(MANIFEST)) { process.exit(0) } let chromium -try { ({ chromium } = await import('playwright')) } catch { +try { ({ chromium } = await import('./lib/playwright-guarded.mjs')) } catch { console.log('COMPOSITE-EMBED-KNOBS: SKIP — playwright is not installed.') process.exit(0) } diff --git a/internal/check-deck-click.mjs b/internal/check-deck-click.mjs index 15363729..f2270de6 100644 --- a/internal/check-deck-click.mjs +++ b/internal/check-deck-click.mjs @@ -13,7 +13,7 @@ // already gone and reports 0 cards for a correct deck. // // Usage: node internal/check-deck-click.mjs [stockId] [expectedPage] -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync, existsSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-element-census-vs-live.mjs b/internal/check-element-census-vs-live.mjs index 9b189a78..1dbc2a96 100644 --- a/internal/check-element-census-vs-live.mjs +++ b/internal/check-element-census-vs-live.mjs @@ -39,7 +39,7 @@ * EXIT 0 no unaccounted addition and no omission · 1 either · 2 bad preset/arguments · * 75 NOT MEASURED (live page unreachable, a side never rendered, or the census is blind). */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { spawn } from 'node:child_process' import { createServer } from 'node:http' import { readFileSync, readdirSync, existsSync } from 'node:fs' diff --git a/internal/check-embed-basemap-paints.mjs b/internal/check-embed-basemap-paints.mjs index 3c15e7b6..abe1b4cf 100644 --- a/internal/check-embed-basemap-paints.mjs +++ b/internal/check-embed-basemap-paints.mjs @@ -26,7 +26,7 @@ * 2. PIXELS — colour variety in a slice of the canvas. A page can 200 on everything and * still paint nothing, and a screenshot is the only witness to that. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { servesThisTree } from './_serves-this-tree.mjs' import { writeFileSync, mkdtempSync, readFileSync } from 'node:fs' diff --git a/internal/check-embedded-rtl.mjs b/internal/check-embedded-rtl.mjs index 7e412077..a83533a8 100644 --- a/internal/check-embedded-rtl.mjs +++ b/internal/check-embedded-rtl.mjs @@ -20,7 +20,7 @@ * * Usage: node internal/check-embedded-rtl.mjs [baseUrl] */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { createServer } from 'node:http' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-facet-changes-map.mjs b/internal/check-facet-changes-map.mjs index 4d600abe..5ffaf1f8 100644 --- a/internal/check-facet-changes-map.mjs +++ b/internal/check-facet-changes-map.mjs @@ -46,7 +46,7 @@ * node internal/check-facet-changes-map.mjs --self-test # NEGATIVE test: the gate must FAIL * BASE_URL=http://127.0.0.1:4173 PROBE_PATH=//... node internal/check-facet-changes-map.mjs */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' // Defaults to the DEV server, because this gate drives a live interaction and the dev surface is // the one that always reflects current source. Point BASE_URL/PROBE_PATH at the built tree diff --git a/internal/check-filter-width-stable.mjs b/internal/check-filter-width-stable.mjs index 03f561a9..bb49f6ef 100644 --- a/internal/check-filter-width-stable.mjs +++ b/internal/check-filter-width-stable.mjs @@ -38,7 +38,7 @@ */ import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { servesThisTree } from './_serves-this-tree.mjs' import { TREE_NAME } from './_tree-name.mjs' diff --git a/internal/check-fits-the-screen.mjs b/internal/check-fits-the-screen.mjs index a84a5fab..5df628b4 100644 --- a/internal/check-fits-the-screen.mjs +++ b/internal/check-fits-the-screen.mjs @@ -58,7 +58,7 @@ * Grades by EXIT CODE. Teaching prose after the verdict — the per-clause table comes first so a * human sees which clause failed without reading the essay. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { spawn, execFileSync } from 'node:child_process' import { fileURLToPath } from 'node:url' import { dirname, resolve } from 'node:path' diff --git a/internal/check-fixed-anchoring.mjs b/internal/check-fixed-anchoring.mjs index 3ecc65e0..a51b99ee 100644 --- a/internal/check-fixed-anchoring.mjs +++ b/internal/check-fixed-anchoring.mjs @@ -33,7 +33,7 @@ * plugin-studio host fail with dLeft=256, while standalone continues to pass — i.e. the gate fails * exactly where the old probe passed, which is the property that makes it worth having. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-gospel-composed-filters.mjs b/internal/check-gospel-composed-filters.mjs index 642fc891..13710b15 100644 --- a/internal/check-gospel-composed-filters.mjs +++ b/internal/check-gospel-composed-filters.mjs @@ -25,7 +25,7 @@ * what makes the pair deadlock-free: * flock -w 1200 flock -w 1200 bun run check:composed-filters */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-gospel-embed-contract.mjs b/internal/check-gospel-embed-contract.mjs index ad408c1a..460568b5 100644 --- a/internal/check-gospel-embed-contract.mjs +++ b/internal/check-gospel-embed-contract.mjs @@ -21,7 +21,7 @@ * Self-test: `--poison` asks for three codes that exist and asserts against a DIFFERENT number, * and asks for a country that does not exist and requires the sheet NOT to open. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-gospel-engine-contract.mjs b/internal/check-gospel-engine-contract.mjs index 59cd364d..050f5db6 100644 --- a/internal/check-gospel-engine-contract.mjs +++ b/internal/check-gospel-engine-contract.mjs @@ -23,7 +23,7 @@ * window.mapboxgl before the bundle runs while maplibre is blocked) and requires the maplibre arm * to FAIL — if it passes, this check cannot see the defect it exists for. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-gospel-english-leaks.mjs b/internal/check-gospel-english-leaks.mjs index bd05512c..06428bf1 100644 --- a/internal/check-gospel-english-leaks.mjs +++ b/internal/check-gospel-english-leaks.mjs @@ -22,7 +22,7 @@ * one, and a check that conflated them would have sent someone to translate strings that are * already translated. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-gospel-leaves-mount.mjs b/internal/check-gospel-leaves-mount.mjs index eb8a1a4e..0e68e0d9 100644 --- a/internal/check-gospel-leaves-mount.mjs +++ b/internal/check-gospel-leaves-mount.mjs @@ -27,7 +27,7 @@ * Run with --poison to prove it can fail: it asks the same questions of a URL that serves no * element at all, and requires every leaf-shaped assertion to go red. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-gospel-map-paints.mjs b/internal/check-gospel-map-paints.mjs index 4501c6d1..cdf67cbc 100644 --- a/internal/check-gospel-map-paints.mjs +++ b/internal/check-gospel-map-paints.mjs @@ -20,7 +20,7 @@ * * Run with --poison to prove it can fail: it hides the canvas and requires a red verdict. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-gospel-rtl-shaping.mjs b/internal/check-gospel-rtl-shaping.mjs index dd757b59..30236083 100644 --- a/internal/check-gospel-rtl-shaping.mjs +++ b/internal/check-gospel-rtl-shaping.mjs @@ -30,7 +30,7 @@ * Run with --poison to prove it can fail: the poison neuters setRTLTextPlugin in the page, which * reproduces the real pre-fix state — the plugin never registered — rather than a synthetic one. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { TREE_NAME } from './_tree-name.mjs' import { resolve, dirname } from 'node:path' diff --git a/internal/check-host-locale-contract.mjs b/internal/check-host-locale-contract.mjs index 94667c2e..2684c1c9 100644 --- a/internal/check-host-locale-contract.mjs +++ b/internal/check-host-locale-contract.mjs @@ -34,7 +34,7 @@ * @browser-slot-max-hold 2700 * EXIT 0 every judged leaf enters the host's locale · 1 one does not · 75 not measured. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { spawn } from 'node:child_process' import { createServer } from 'node:http' import { readFileSync, existsSync } from 'node:fs' diff --git a/internal/check-leaf-engine-per-provider.mjs b/internal/check-leaf-engine-per-provider.mjs index aa04e3d1..ef0efa4e 100644 --- a/internal/check-leaf-engine-per-provider.mjs +++ b/internal/check-leaf-engine-per-provider.mjs @@ -29,7 +29,7 @@ * @browser-slot-max-hold 2700 * EXIT 0 pass · 1 a leaf calls Mapbox under maplibre, or a map stopped drawing · 75 not measured. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { spawn } from 'node:child_process' import { createServer } from 'node:http' import { readFileSync, existsSync } from 'node:fs' diff --git a/internal/check-leaf-english-leaks.mjs b/internal/check-leaf-english-leaks.mjs index 658c37bc..52721596 100644 --- a/internal/check-leaf-english-leaks.mjs +++ b/internal/check-leaf-english-leaks.mjs @@ -33,7 +33,7 @@ * node internal/check-leaf-english-leaks.mjs /: [more...] * LOCALES=ar,zh,ru,hi BASE_URL=http://127.0.0.1:4173 */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync, readdirSync, existsSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-legend-export-reachable.mjs b/internal/check-legend-export-reachable.mjs index b4b0e2bf..d8e2102c 100644 --- a/internal/check-legend-export-reachable.mjs +++ b/internal/check-legend-export-reachable.mjs @@ -27,7 +27,7 @@ // // Usage: node internal/check-legend-export-reachable.mjs [leafUrl] // SKIPs when nothing is served — this needs the built tree. -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { requireThisTree } from './_serves-this-tree.mjs' diff --git a/internal/check-legend-info-localized.mjs b/internal/check-legend-info-localized.mjs index 870adce6..0bfcfc54 100644 --- a/internal/check-legend-info-localized.mjs +++ b/internal/check-legend-info-localized.mjs @@ -28,7 +28,7 @@ // version of this probe reported "(no popover)" in every locale against a working build. // // Usage: node internal/check-legend-info-localized.mjs [locales] -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync, existsSync } from 'node:fs' import { join, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-map-data-painted.mjs b/internal/check-map-data-painted.mjs index f9224be3..c6ea90a0 100644 --- a/internal/check-map-data-painted.mjs +++ b/internal/check-map-data-painted.mjs @@ -81,7 +81,7 @@ * node internal/check-map-data-painted.mjs --poison # must go RED; proves it can fail * Defaults to the LOCAL tree's decoupled leaves. Pass URLs explicitly to audit what is published. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { servesThisTree } from './_serves-this-tree.mjs' import { TREE_NAME } from './_tree-name.mjs' diff --git a/internal/check-map-embed-cells.mjs b/internal/check-map-embed-cells.mjs index 43508ace..8dd41e84 100644 --- a/internal/check-map-embed-cells.mjs +++ b/internal/check-map-embed-cells.mjs @@ -20,7 +20,7 @@ * is then forwarded with the real token so the map can still paint. A map drawn with zero Mapbox * requests is fine for maps that need none; that is reported, not failed. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { spawn } from 'node:child_process' import { createServer } from 'node:http' import { readFileSync, existsSync } from 'node:fs' diff --git a/internal/check-map-unmount-mid-init.mjs b/internal/check-map-unmount-mid-init.mjs index 9b1c2c74..d35f07be 100644 --- a/internal/check-map-unmount-mid-init.mjs +++ b/internal/check-map-unmount-mid-init.mjs @@ -23,7 +23,7 @@ * It refuses to pass vacuously: if the script was never requested (preloaded) or the pane was not * actually removed before release, the race was not staged and it exits 2, not 0. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-maplibre-layer-drops.mjs b/internal/check-maplibre-layer-drops.mjs index d4930928..ef2ec6b6 100644 --- a/internal/check-maplibre-layer-drops.mjs +++ b/internal/check-maplibre-layer-drops.mjs @@ -36,7 +36,7 @@ * node internal/check-maplibre-layer-drops.mjs * BASE_URL=http://127.0.0.1:4399 node internal/check-maplibre-layer-drops.mjs */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync, existsSync } from 'node:fs' import { resolve, join, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-mfe-two-tab.mjs b/internal/check-mfe-two-tab.mjs index 2d4e467b..e4f01b95 100644 --- a/internal/check-mfe-two-tab.mjs +++ b/internal/check-mfe-two-tab.mjs @@ -12,7 +12,7 @@ // tab — a strip that paints but does not switch is the other silent half. // // Usage: node internal/check-mfe-two-tab.mjs [expectedTabCount] -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-nav-reachable-mobile.mjs b/internal/check-nav-reachable-mobile.mjs index 1891294c..fa76b7cc 100644 --- a/internal/check-nav-reachable-mobile.mjs +++ b/internal/check-nav-reachable-mobile.mjs @@ -36,7 +36,7 @@ import { readFileSync, existsSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { TREE_NAME } from './_tree-name.mjs' diff --git a/internal/check-packet-browser.mjs b/internal/check-packet-browser.mjs index 7f91b436..8cbe0c21 100644 --- a/internal/check-packet-browser.mjs +++ b/internal/check-packet-browser.mjs @@ -19,7 +19,7 @@ // PACKET_ISO3 country to build a test packet for (default LAO) // Needs a static server over the repo root, e.g.: python3 -m http.server 4898 -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index 20534176..b33227b4 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -26,7 +26,7 @@ // Usage: node internal/check-packet-render.mjs [path/to/packet.html] // Default path is overridable with PACKET_HTML so this is not machine-specific. -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { existsSync, readFileSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { pathToFileURL, fileURLToPath } from 'node:url' diff --git a/internal/check-packets-all.mjs b/internal/check-packets-all.mjs index 10a110df..a6dcd095 100644 --- a/internal/check-packets-all.mjs +++ b/internal/check-packets-all.mjs @@ -17,7 +17,7 @@ // SKIP (exit 0) only when the directory is genuinely absent or empty: packets are // generated on demand and a clean checkout has none. A skip says so out loud, with the // count, so "0 packets checked" can never read as "all packets passed". -import { existsSync, readdirSync, readFileSync } from 'node:fs' +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' // pathToFileURL, not a bare path: a dynamic import() of an absolute POSIX path works by luck on // linux and throws ERR_UNSUPPORTED_ESM_URL_SCHEME the moment anyone runs this on windows. @@ -38,9 +38,33 @@ if (!existsSync(DIR)) { process.exit(0) } -const files = readdirSync(DIR).filter((f) => f.endsWith('.html')).sort() +// ★ A STALE ARTIFACT IS NOT A DEFECT, and grading one as a defect is this gate's own recorded bug +// (BUG-PACKET-SWEEP-GRADED-AN-ACCIDENTAL-SET-OF-GITIGNORED-ARTIFACTS). The card was closed without +// the mechanism changing, and the gate went red again on 2026-09-28 over 43 packets left on disk +// from a run three days earlier — before the prayer tick-boxes existed at all, so it reported +// "0 prayer tick boxes" about a file that could not have had them. +// +// This whole directory is GITIGNORED (.gitignore:94 `library/scripts/*/out/**`), so nothing here +// ever reaches main or another agent: it is one developer's scratch. The gate already knows that an +// EMPTY directory is not a failure ("a clean checkout has none"); a directory of LEFTOVERS is the +// same fact wearing different clothes. +// +// So a packet older than the renderer that produces it is SKIPPED, loudly, rather than failed. The +// renderer's own mtime is the reference because that is what decides the shape a packet should have +// — comparing against "today" would re-fail every morning, and comparing against nothing is what +// produced this bug twice. +const RENDERER = resolve(HERE, '..', 'library/packet/packet-render.js') +const rendererMtime = existsSync(RENDERER) ? statSync(RENDERER).mtimeMs : 0 +const all = readdirSync(DIR).filter((f) => f.endsWith('.html')).sort() +const stale = all.filter((f) => statSync(join(DIR, f)).mtimeMs < rendererMtime) +const files = all.filter((f) => !stale.includes(f)) +if (stale.length) { + console.log(`PACKETS-ALL: skipping ${stale.length} packet(s) older than the renderer — regenerate`) + console.log(` them (ONLY= node library/scripts/posters/packet.mjs) or delete them; this`) + console.log(` directory is gitignored scratch, so a leftover is not a defect.`) +} if (!files.length) { - console.log(`PACKETS-ALL: SKIP — ${DIR} contains no .html packets`) + console.log(`PACKETS-ALL: SKIP — ${DIR} contains no .html packets newer than the renderer`) process.exit(0) } @@ -67,9 +91,30 @@ if (!EXPECT_FUNNEL) { console.log('PACKETS-ALL: ⚠ PACKETS_ALL_NO_FUNNEL=1 — page 2 is NOT required this run.') } +// ⚠ THE UNCONDITIONAL ABOVE STOPPED BEING TRUE, and this is the correction. The comment says every +// packet comes from packet.mjs "which always passes FUNNEL" — that held until SERIES landed +// (2026-09-28). A `country` packet DROPS the funnel page deliberately: the funnel narrows every +// people group on earth down to THIS UPG-100 list, which is not a description of a country, and a +// page of true numbers about the wrong thing is worse than no page. +// So the expectation follows the SERIES that produced the packet, read from series.json rather than +// hardcoded here — adding a series with funnel:false must not require editing this gate. +// A packet whose series cannot be determined keeps the old unconditional expectation, because the +// safe default for "I do not know" is the stricter check. +const SERIES_PATH = resolve(HERE, '..', 'library/scripts/posters/series.json') +const SERIES_DEFS = existsSync(SERIES_PATH) ? JSON.parse(readFileSync(SERIES_PATH, 'utf8')) : {} +const NO_FUNNEL_MARKERS = Object.entries(SERIES_DEFS) + .filter(([id, def]) => !id.startsWith('_') && def && def.funnel === false) + .flatMap(([, def]) => (def.networks || []).map(([head]) => head).filter(Boolean)) +/** True when this packet's own text identifies it as a series that has no funnel page. */ +function expectsFunnel(html) { + if (!NO_FUNNEL_MARKERS.length) return EXPECT_FUNNEL + return !NO_FUNNEL_MARKERS.some((m) => html.includes(m)) && EXPECT_FUNNEL +} + const failed = [] for (const f of files) { - const args = EXPECT_FUNNEL ? [gate, join(DIR, f), '--expect-funnel'] : [gate, join(DIR, f)] + const wantFunnel = expectsFunnel(readFileSync(join(DIR, f), 'utf8')) + const args = wantFunnel ? [gate, join(DIR, f), '--expect-funnel'] : [gate, join(DIR, f)] const res = spawnSync(process.execPath, args, { encoding: 'utf8' }) const ok = res.status === 0 console.log(` ${ok ? '✓' : '✗'} ${f}`) diff --git a/internal/check-poster-card-fits.mjs b/internal/check-poster-card-fits.mjs index 3f52316c..b74eee29 100644 --- a/internal/check-poster-card-fits.mjs +++ b/internal/check-poster-card-fits.mjs @@ -20,7 +20,7 @@ * * Usage: node internal/check-poster-card-fits.mjs [leafUrl] — SKIPs when nothing is served. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { servesThisTree } from './_serves-this-tree.mjs' diff --git a/internal/check-poster-degrades-honestly.mjs b/internal/check-poster-degrades-honestly.mjs index 0ff59870..5fd08b5d 100644 --- a/internal/check-poster-degrades-honestly.mjs +++ b/internal/check-poster-degrades-honestly.mjs @@ -24,7 +24,7 @@ // `--directory public/doxa-maps`. Serve the TREE. // // Usage: node internal/check-poster-degrades-honestly.mjs [zoneId] -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-poster-print-fallback.mjs b/internal/check-poster-print-fallback.mjs index a9aaadb3..2193adf1 100644 --- a/internal/check-poster-print-fallback.mjs +++ b/internal/check-poster-print-fallback.mjs @@ -23,7 +23,7 @@ // the default-drop condition, and the .html is left reachable. // // Usage: node internal/check-poster-print-fallback.mjs [zoneId] -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-poster-prints.mjs b/internal/check-poster-prints.mjs index 0ef468ef..5c44946f 100644 --- a/internal/check-poster-prints.mjs +++ b/internal/check-poster-prints.mjs @@ -17,7 +17,7 @@ // Runs the composable in a browser page (it calls document/DOM), driving it with a // fake records provider so the assertion is about the DOCUMENTS, not about any // bundle's API being reachable. -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-poster-raster.mjs b/internal/check-poster-raster.mjs index b1f7b057..a1d33fb6 100644 --- a/internal/check-poster-raster.mjs +++ b/internal/check-poster-raster.mjs @@ -16,7 +16,7 @@ // // Usage: node internal/check-poster-raster.mjs [zoneId] // PACKET_ORIGIN (default http://127.0.0.1:4898) -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-poster-wall-localised.mjs b/internal/check-poster-wall-localised.mjs index 72b6dd05..3b947080 100644 --- a/internal/check-poster-wall-localised.mjs +++ b/internal/check-poster-wall-localised.mjs @@ -39,7 +39,7 @@ * Usage: node internal/check-poster-wall-localised.mjs [baseUrl] * SKIPs when nothing is served; exits 2 (NOT MEASURED) when the server is not this tree. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { readFileSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/internal/check-prayer-walk-planner.mjs b/internal/check-prayer-walk-planner.mjs index 8343abaf..b0f3407b 100644 --- a/internal/check-prayer-walk-planner.mjs +++ b/internal/check-prayer-walk-planner.mjs @@ -30,7 +30,7 @@ import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/prayer-walk-planner/prayer-walk-planner.js`))) { console.log('SKIP check-prayer-walk-planner: no built planner'); process.exit(2) } let chromium -try { ({ chromium } = await import('playwright')) } catch { console.log('SKIP check-prayer-walk-planner: playwright not installed'); process.exit(2) } +try { ({ chromium } = await import('./lib/playwright-guarded.mjs')) } catch { console.log('SKIP check-prayer-walk-planner: playwright not installed'); process.exit(2) } const OUT = process.env.OUT || '/tmp' const free = () => new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) }) const TP = await free(), HP = await free(), TREE = `http://127.0.0.1:${TP}/${TREE_NAME}` diff --git a/internal/check-print-geometry.mjs b/internal/check-print-geometry.mjs index fc109f97..fef92a9a 100644 --- a/internal/check-print-geometry.mjs +++ b/internal/check-print-geometry.mjs @@ -13,7 +13,7 @@ // // Usage: node internal/check-print-geometry.mjs // PACKET_ORIGIN (default http://127.0.0.1:4898) — a static server over the repo root -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-provider-switch.mjs b/internal/check-provider-switch.mjs index 270a6294..c8213f3f 100644 --- a/internal/check-provider-switch.mjs +++ b/internal/check-provider-switch.mjs @@ -14,7 +14,7 @@ // Usage: node internal/check-provider-switch.mjs import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { servesThisTree, serveThisTree } from './_serves-this-tree.mjs' import { TREE_NAME } from './_tree-name.mjs' diff --git a/internal/check-raw-map-rtl-shaping.mjs b/internal/check-raw-map-rtl-shaping.mjs index dc0a5092..d596ebce 100644 --- a/internal/check-raw-map-rtl-shaping.mjs +++ b/internal/check-raw-map-rtl-shaping.mjs @@ -25,7 +25,7 @@ * --poison neuters setRTLTextPlugin in the page (the real pre-fix state) and must be caught on * every measured row. Exit: 0 pass · 1 fail · 2 not measured. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { serveReaped } from './_serve-reaped.mjs' import { TREE_NAME } from './_tree-name.mjs' import { readFileSync } from 'node:fs' diff --git a/internal/check-research-gallery-embed.mjs b/internal/check-research-gallery-embed.mjs index 7867b7bb..90b2d7a2 100644 --- a/internal/check-research-gallery-embed.mjs +++ b/internal/check-research-gallery-embed.mjs @@ -120,7 +120,7 @@ if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/doxa-maps-spa/doxa-maps-spa.j process.exit(2) } let chromium -try { ({ chromium } = await import('playwright')) } catch { +try { ({ chromium } = await import('./lib/playwright-guarded.mjs')) } catch { console.log('SKIP check-research-gallery-embed: playwright not installed'); process.exit(2) } diff --git a/internal/check-scrim-covers-viewport.mjs b/internal/check-scrim-covers-viewport.mjs index 24865c1d..be72b6a4 100644 --- a/internal/check-scrim-covers-viewport.mjs +++ b/internal/check-scrim-covers-viewport.mjs @@ -43,7 +43,7 @@ * * Exit: 0 all pass · 1 any fail · 2 dev server unreachable (a precondition, not a defect). */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-spa-tab-order.mjs b/internal/check-spa-tab-order.mjs index 9ad1b6e4..61938e91 100644 --- a/internal/check-spa-tab-order.mjs +++ b/internal/check-spa-tab-order.mjs @@ -31,7 +31,7 @@ * NEGATIVE-TESTED: swapping the first two entries of NAV_MODEL fails by name, printing expected vs * actual side by side. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs' import { fileURLToPath as __f2p } from 'node:url' import { dirname as __dn, resolve as __rs } from 'node:path' diff --git a/internal/check-studio-every-plugin.mjs b/internal/check-studio-every-plugin.mjs index 319ce116..8a8fac47 100644 --- a/internal/check-studio-every-plugin.mjs +++ b/internal/check-studio-every-plugin.mjs @@ -23,7 +23,7 @@ * --url is REQUIRED. Several gates here hardcode :5173, and a drifted dev server made them measure * another worktree's tree — so this one refuses to guess. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' diff --git a/internal/check-tabs-localized.mjs b/internal/check-tabs-localized.mjs index efcee808..78d3f33d 100644 --- a/internal/check-tabs-localized.mjs +++ b/internal/check-tabs-localized.mjs @@ -43,7 +43,7 @@ import { readFileSync, existsSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' const REPO = process.env.DOXA_REPO || resolve(dirname(fileURLToPath(import.meta.url)), '..') diff --git a/internal/check-upg-map-data-painted.mjs b/internal/check-upg-map-data-painted.mjs index 23608e9d..42c9a429 100644 --- a/internal/check-upg-map-data-painted.mjs +++ b/internal/check-upg-map-data-painted.mjs @@ -31,7 +31,7 @@ * Defaults to the LOCAL tree. Pass live URLs explicitly to audit what is published — the two * diverge, and conflating them cost a whole tick of wrong diagnoses. */ -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { servesThisTree } from './_serves-this-tree.mjs' diff --git a/internal/check-vision-card-numbers.mjs b/internal/check-vision-card-numbers.mjs index 7b37d4e7..3f790f10 100644 --- a/internal/check-vision-card-numbers.mjs +++ b/internal/check-vision-card-numbers.mjs @@ -50,7 +50,7 @@ if (i > -1) { // Measure THIS build, or measure nothing (exit 75 = NOT MEASURED; see a9550858 / _serves-this-tree.mjs). const { requireThisTree } = await import('./_serves-this-tree.mjs') await requireThisTree(process.argv[i + 1], ROOT, 'VISION-CARD-NUMBERS') - const { chromium } = await import('playwright') + const { chromium } = await import('./lib/playwright-guarded.mjs') const b = await chromium.launch() const p = await b.newPage({ viewport: { width: 900, height: 1000 } }) await p.goto(process.argv[i + 1], { waitUntil: 'networkidle' }) diff --git a/internal/embed-smoke.mjs b/internal/embed-smoke.mjs index c71d5b69..ee79bd8e 100644 --- a/internal/embed-smoke.mjs +++ b/internal/embed-smoke.mjs @@ -21,7 +21,7 @@ import { createServer } from 'node:http' import { readFileSync, existsSync, statSync, createReadStream } from 'node:fs' import { resolve, join, extname } from 'node:path' import { fileURLToPath } from 'node:url' -import { chromium } from 'playwright' +import { chromium } from './lib/playwright-guarded.mjs' import { TREE_NAME } from './_tree-name.mjs' const __dirname = fileURLToPath(new URL('.', import.meta.url)) diff --git a/internal/lib/browser-gates.mjs b/internal/lib/browser-gates.mjs new file mode 100644 index 00000000..bbb73472 --- /dev/null +++ b/internal/lib/browser-gates.mjs @@ -0,0 +1,32 @@ +/** internal/lib/browser-gates.mjs — which package.json scripts drive a real browser (read from the gate FILE). + * Shared by run-parallel (to route them through browser-slot.sh) and check-browser-gates-guarded (--list). */ +import { readFileSync, existsSync } from 'node:fs' +import { resolve } from 'node:path' + +/** + * Does this step drive a real browser? Asked of the GATE FILE, not of a hand-kept list: + * a list goes stale the first time someone adds a gate, and a browser gate missing from it + * is exactly the one that rejoins the dogpile. package.json names the file; the file says + * whether it imports playwright. + */ +export function isBrowserGate(ROOT, name) { + try { + const scripts = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')).scripts || {} + const m = String(scripts[name] || '').match(/([\w./-]+\.mjs)/) + if (!m) return false + const file = resolve(ROOT, m[1]) + if (!existsSync(file)) return false + const src = readFileSync(file, 'utf8') + // Two ways a step can drive chrome: it launches one itself, or it is a RUNNER that + // launches gates which do. `verify` is the second kind — verify-all.mjs imports no + // playwright and would have been graded static, while three of the gates it spawns + // (posters, app-tables, map-paint) render pages. It already marks those `browser: true` + // for its own reasons, so that declaration is honoured here rather than duplicated. + // …but a runner only drives chrome when ASKED to: verify-all runs its browser guards only with --full, + // so plain `verify` is a static step (it printed "ALL GUARDS PASS (browser guards skipped)" and was + // still routed through the browser slot, where a headless seat could only fail or skip it). + // playwright-guarded.mjs is how every repo gate reaches playwright now (a firefox-only gate names no chromium). + if (/from 'playwright'|require\('playwright'\)|from\s+['"][^'"]*playwright-guarded\.mjs['"]|import\(\s*['"][^'"]*playwright-guarded\.mjs|chromium/.test(src)) return true + return /browser:\s*true/.test(src) && /--full\b/.test(String(scripts[name])) + } catch { return false } +} diff --git a/internal/lib/browser.mjs b/internal/lib/browser.mjs index 41c26284..bbd69c07 100644 --- a/internal/lib/browser.mjs +++ b/internal/lib/browser.mjs @@ -25,7 +25,7 @@ import { readdirSync, existsSync } from 'node:fs' import { join } from 'node:path' import { homedir } from 'node:os' -import * as pw from 'playwright' +import * as pw from './playwright-guarded.mjs' export const TELEMETRY = /events\.mapbox\.com/ diff --git a/internal/lib/playwright-guarded.mjs b/internal/lib/playwright-guarded.mjs new file mode 100644 index 00000000..16d4c0b2 --- /dev/null +++ b/internal/lib/playwright-guarded.mjs @@ -0,0 +1,41 @@ +/** + * internal/lib/playwright-guarded.mjs — the ONE way a repo gate reaches Playwright. + * + * Only the browser seat runs browsers, and only inside the shared slot (internal/browser-slot.sh, a + * memory-floored semaphore). That rule used to be remembered, not enforced: an agent ran a gate it took + * for a headless source check and it launched Chromium outside the slot. Every browser launch now goes + * through this module, and launch()/launchPersistentContext()/launchServer()/connect()/connectOverCDP() + * refuse unless browser-slot.sh has EXPORTED DOXA_BROWSER_SLOT (exported, so a `bash -c "…"` grandchild + * inherits it). Refusal is exit 75, NOT MEASURED, never a failure and never a pass. + * check:browser-gates-guarded fails any repo file that imports playwright directly instead. + */ +import * as pw from 'playwright' + +export const NOT_MEASURED = 75 + +export function requireBrowserSlot(who = process.argv[1] || 'this gate') { + if (process.env.DOXA_BROWSER_SLOT) return + console.error(`${who}: this gate launches a browser. Browsers run only on the browser seat, inside the slot:\n` + + ' internal/browser-slot.sh bun run \n\n" + "script": "\n\n\n\n" }, "instances": [ { diff --git a/public/dx-maps/doxa-simple-map/doxa-simple-map.js b/public/dx-maps/doxa-simple-map/doxa-simple-map.js index 6ed8c6f9..48370386 100644 --- a/public/dx-maps/doxa-simple-map/doxa-simple-map.js +++ b/public/dx-maps/doxa-simple-map/doxa-simple-map.js @@ -1212,7 +1212,7 @@ border-color:rgba(46,164,79,0.30); color:#2ea44f; } -`,"share-button");const{t:o,tm:n,rt:a}=ut(),r=e,s=fe(null),i=fe(!1),l=fe(null),c=fe(null),u=fe("link"),h=fe(!1),d=fe(!1),x=fe("medium"),P={small:{label:"Small",height:400},medium:{label:"Medium",height:600},large:{label:"Large",height:800}},b=Ce("profileConfig",null),w=ne(()=>f1({embedPath:r.embedPath,profileConfig:pe(b)||null})),$=ne(()=>{const j=P[x.value].height;return''});function f(){i.value=!i.value,i.value&&(u.value="link",h.value=!1,d.value=!1)}function m(){i.value=!1}async function v(j){try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(j),!0}catch{}try{const q=document.createElement("textarea");q.value=j,q.setAttribute("readonly",""),q.style.cssText="position:fixed;top:-1000px;opacity:0",document.body.appendChild(q),q.select();const W=document.execCommand("copy");return document.body.removeChild(q),W}catch{return!1}}async function S(){await v(w.value)&&(h.value=!0,setTimeout(()=>{h.value=!1},2e3))}async function A(){await v($.value)&&(d.value=!0,setTimeout(()=>{d.value=!1},2e3))}const _=Ce("mapToolbar",null),C=fe(null);ft("mapToolbar",_||{el:C});const k=!_;function D(j){if(s.value=j||null,!k||!j){j||(C.value=null);return}const q=j.offsetParent||j.parentElement;q&&!q.closest(".dsm-map-area, .rm-map-area, .dm-map-area, [data-map-area]")&&q.setAttribute("data-map-area","share-fallback"),C.value=j}return(j,q)=>(Z(),ue("div",{ref:D,class:"share-wrap","data-share-root":""},[We(en,{ref_key:"btnEl",ref:c,"is-dark":e.isDark,active:i.value,title:pe(o)("buttons.share"),onClick:f},{default:Et(()=>[...q[2]||(q[2]=[F("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[F("path",{d:"M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7"}),F("polyline",{points:"16 6 12 2 8 6"}),F("line",{x1:"12",y1:"2",x2:"12",y2:"15"})],-1)])]),_:1},8,["is-dark","active","title"]),We(xS,{open:i.value,title:pe(o)("buttons.share"),"is-dark":e.isDark,onClose:m},{default:Et(()=>[F("div",{ref_key:"popoverEl",ref:l,class:Fe(["share-pop",{light:!e.isDark}])},[F("ul",PS,[(Z(!0),ue(Me,null,Nt(pe(n)("buttons.shareSteps"),(W,re)=>(Z(),ue("li",{key:re},me(pe(a)(W)),1))),128))]),F("div",AS,[F("button",{class:Fe(["share-tab",{active:u.value==="link"}]),onClick:q[0]||(q[0]=W=>u.value="link")},me(t("share.embed_link","Embed link")),3),F("button",{class:Fe(["share-tab",{active:u.value==="embed"}]),onClick:q[1]||(q[1]=W=>u.value="embed")},me(t("share.add_to_website","Add to Your Website")),3)]),u.value==="link"?(Z(),ue("div",LS,[F("div",CS,[F("div",TS,me(w.value),1)]),F("button",{class:Fe(["share-primary-btn",{copied:h.value}]),onClick:S},[h.value?(Z(),ue("svg",NS,[...q[4]||(q[4]=[F("polyline",{points:"20 6 9 17 4 12"},null,-1)])])):(Z(),ue("svg",ES,[...q[3]||(q[3]=[F("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"},null,-1),F("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"},null,-1)])])),ct(" "+me(h.value?"Copied!":"Copy Link"),1)],2)])):we("",!0),u.value==="embed"?(Z(),ue("div",RS,[F("div",FS,me(t("share.embed_paste_hint","Paste this code into your website builder (WordPress, Squarespace, Wix, or any site that accepts HTML widgets).")),1),F("div",IS,[(Z(),ue(Me,null,Nt(P,(W,re)=>F("button",{key:re,class:Fe(["share-size-pill",{active:x.value===re}]),onClick:N=>x.value=re},me(W.label),11,MS)),64))]),F("div",OS,me($.value),1),F("button",{class:Fe(["share-secondary-btn",{copied:d.value}]),onClick:A},[d.value?(Z(),ue("svg",GS,[...q[6]||(q[6]=[F("polyline",{points:"20 6 9 17 4 12"},null,-1)])])):(Z(),ue("svg",DS,[...q[5]||(q[5]=[F("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"},null,-1),F("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"},null,-1)])])),ct(" "+me(d.value?"Copied!":"Copy Code"),1)],2)])):we("",!0)],2)]),_:1},8,["open","title","is-dark"])],512))}},BS=".dsm-root[data-v-12eee539]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;overflow:hidden}.dsm-tab-bar[data-v-12eee539]{flex:0 0 auto;display:flex;align-items:center;background:#fff;border-bottom:2px solid #e0e0e0;box-shadow:0 2px 4px #00000014;height:48px;padding:0 12px;gap:4px;z-index:10}.dsm-tab[data-v-12eee539]{background:none;border:none;padding:0 20px;height:100%;font-size:14px;font-weight:500;color:#555;cursor:pointer;border-bottom:3px solid transparent;transition:color .15s,border-color .15s,background .15s;white-space:nowrap}.dsm-tab[data-v-12eee539]:hover{background:#3498db0f;color:#3498db}.dsm-tab.active[data-v-12eee539]{color:#3498db;border-bottom-color:#3498db;background:#3498db0f}.dsm-map-area[data-v-12eee539]{flex:1 1 0;position:relative;overflow:hidden}.dsm-map-canvas[data-v-12eee539]{position:absolute;top:0;right:0;bottom:0;left:0}.dsm-loading[data-v-12eee539]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:#f3f3f1;color:#6d766e;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:14px;z-index:5}.dsm-error[data-v-12eee539]{position:absolute;top:50%;left:12px;right:12px;z-index:7;transform:translateY(-50%);text-align:center;font:13px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:#7f1d1d;background:#fffffff0;border:1px solid rgba(127,29,29,.25);border-radius:10px;padding:12px 14px;box-shadow:0 2px 10px #00000029}",HS=["dir"],US={key:0,class:"dsm-tab-bar"},jS=["onClick"],WS={class:"dsm-map-area"},KS={key:0,class:"dsm-loading"},VS={key:1,class:"dsm-error"},qS={class:"dsm-legend-desktop-slot"},YS={class:"dsm-legend-mobile-slot"},ZS={class:"dsm-help-modal"},JS=["aria-label"],Sr="language-family-pins-active",xr="language-family-pins-pulse",zp="language-family-pins-hitbox",XS=Object.freeze(Object.defineProperty({__proto__:null,default:Fo({__name:"doxa-simple-map",setup(e){var I,X,ve,xe;const t=_s(()=>Pr(()=>Promise.resolve().then(()=>Up),void 0));Ot(` +`,"share-button");const{t:o,tm:n,rt:a}=ut(),r=e,s=fe(null),i=fe(!1),l=fe(null),c=fe(null),u=fe("link"),h=fe(!1),d=fe(!1),x=fe("medium"),P={small:{label:"Small",height:400},medium:{label:"Medium",height:600},large:{label:"Large",height:800}},b=Ce("profileConfig",null),w=ne(()=>f1({embedPath:r.embedPath,profileConfig:pe(b)||null})),$=ne(()=>{const j=P[x.value].height;return''});function f(){i.value=!i.value,i.value&&(u.value="link",h.value=!1,d.value=!1)}function m(){i.value=!1}async function v(j){try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(j),!0}catch{}try{const q=document.createElement("textarea");q.value=j,q.setAttribute("readonly",""),q.style.cssText="position:fixed;top:-1000px;opacity:0",document.body.appendChild(q),q.select();const W=document.execCommand("copy");return document.body.removeChild(q),W}catch{return!1}}async function S(){await v(w.value)&&(h.value=!0,setTimeout(()=>{h.value=!1},2e3))}async function A(){await v($.value)&&(d.value=!0,setTimeout(()=>{d.value=!1},2e3))}const _=Ce("mapToolbar",null),C=fe(null);ft("mapToolbar",_||{el:C});const k=!_;function D(j){if(s.value=j||null,!k||!j){j||(C.value=null);return}const q=j.offsetParent||j.parentElement;q&&!q.closest(".dsm-map-area, .rm-map-area, .dm-map-area, [data-map-area]")&&q.setAttribute("data-map-area","share-fallback"),C.value=j}return(j,q)=>(Z(),ue("div",{ref:D,class:"share-wrap","data-share-root":""},[We(en,{ref_key:"btnEl",ref:c,"is-dark":e.isDark,active:i.value,title:pe(o)("buttons.share"),onClick:f},{default:Et(()=>[...q[2]||(q[2]=[F("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[F("path",{d:"M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7"}),F("polyline",{points:"16 6 12 2 8 6"}),F("line",{x1:"12",y1:"2",x2:"12",y2:"15"})],-1)])]),_:1},8,["is-dark","active","title"]),We(xS,{open:i.value,title:pe(o)("buttons.share"),"is-dark":e.isDark,onClose:m},{default:Et(()=>[F("div",{ref_key:"popoverEl",ref:l,class:Fe(["share-pop",{light:!e.isDark}])},[F("ul",PS,[(Z(!0),ue(Me,null,Nt(pe(n)("buttons.shareSteps"),(W,re)=>(Z(),ue("li",{key:re},me(pe(a)(W)),1))),128))]),F("div",AS,[F("button",{class:Fe(["share-tab",{active:u.value==="link"}]),onClick:q[0]||(q[0]=W=>u.value="link")},me(t("share.embed_link","Embed link")),3),F("button",{class:Fe(["share-tab",{active:u.value==="embed"}]),onClick:q[1]||(q[1]=W=>u.value="embed")},me(t("share.add_to_website","Add to Your Website")),3)]),u.value==="link"?(Z(),ue("div",LS,[F("div",CS,[F("div",TS,me(w.value),1)]),F("button",{class:Fe(["share-primary-btn",{copied:h.value}]),onClick:S},[h.value?(Z(),ue("svg",NS,[...q[4]||(q[4]=[F("polyline",{points:"20 6 9 17 4 12"},null,-1)])])):(Z(),ue("svg",ES,[...q[3]||(q[3]=[F("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"},null,-1),F("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"},null,-1)])])),ct(" "+me(h.value?"Copied!":"Copy Link"),1)],2)])):we("",!0),u.value==="embed"?(Z(),ue("div",RS,[F("div",FS,me(t("share.embed_paste_hint","Paste this code into your website builder (WordPress, Squarespace, Wix, or any site that accepts HTML widgets).")),1),F("div",IS,[(Z(),ue(Me,null,Nt(P,(W,re)=>F("button",{key:re,class:Fe(["share-size-pill",{active:x.value===re}]),onClick:N=>x.value=re},me(W.label),11,MS)),64))]),F("div",OS,me($.value),1),F("button",{class:Fe(["share-secondary-btn",{copied:d.value}]),onClick:A},[d.value?(Z(),ue("svg",GS,[...q[6]||(q[6]=[F("polyline",{points:"20 6 9 17 4 12"},null,-1)])])):(Z(),ue("svg",DS,[...q[5]||(q[5]=[F("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"},null,-1),F("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"},null,-1)])])),ct(" "+me(d.value?"Copied!":"Copy Code"),1)],2)])):we("",!0)],2)]),_:1},8,["open","title","is-dark"])],512))}},BS=".dsm-root[data-v-58b6ec72]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;overflow:hidden}.dsm-tab-bar[data-v-58b6ec72]{flex:0 0 auto;display:flex;align-items:center;background:#fff;border-bottom:2px solid #e0e0e0;box-shadow:0 2px 4px #00000014;height:48px;padding:0 12px;gap:4px;z-index:10}.dsm-tab[data-v-58b6ec72]{background:none;border:none;padding:0 20px;height:100%;font-size:14px;font-weight:500;color:#555;cursor:pointer;border-bottom:3px solid transparent;transition:color .15s,border-color .15s,background .15s;white-space:nowrap}.dsm-tab[data-v-58b6ec72]:hover{background:#3498db0f;color:#3498db}.dsm-tab.active[data-v-58b6ec72]{color:#3498db;border-bottom-color:#3498db;background:#3498db0f}.dsm-map-area[data-v-58b6ec72]{flex:1 1 0;position:relative;overflow:hidden}.dsm-map-canvas[data-v-58b6ec72]{position:absolute;top:0;right:0;bottom:0;left:0}.dsm-loading[data-v-58b6ec72]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:#f3f3f1;color:#6d766e;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:14px;z-index:5}.dsm-error[data-v-58b6ec72]{position:absolute;top:50%;left:12px;right:12px;z-index:7;transform:translateY(-50%);text-align:center;font:13px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:#7f1d1d;background:#fffffff0;border:1px solid rgba(127,29,29,.25);border-radius:10px;padding:12px 14px;box-shadow:0 2px 10px #00000029}",HS=["dir"],US={key:0,class:"dsm-tab-bar"},jS=["onClick"],WS={class:"dsm-map-area"},KS={key:0,class:"dsm-loading"},VS={key:1,class:"dsm-error"},qS={class:"dsm-legend-desktop-slot"},YS={class:"dsm-legend-mobile-slot"},ZS={class:"dsm-help-modal"},JS=["aria-label"],Sr="language-family-pins-active",xr="language-family-pins-pulse",zp="language-family-pins-hitbox",XS=Object.freeze(Object.defineProperty({__proto__:null,default:Fo({__name:"doxa-simple-map",setup(e){var I,X,ve,xe;const t=_s(()=>Pr(()=>Promise.resolve().then(()=>Up),void 0));Ot(` .dsm-root { position:absolute;inset:0;display:flex;flex-direction:column;overflow:hidden; } .dsm-tab-bar { flex:0 0 auto;display:flex;align-items:center;background:#fff;border-bottom:2px solid #e0e0e0;box-shadow:0 2px 4px rgba(0,0,0,.08);height:48px;padding:0 12px;gap:4px;z-index:10; } .dsm-tab { background:none;border:none;padding:0 20px;height:100%;font-size:14px;font-weight:500;color:#555;cursor:pointer;border-bottom:3px solid transparent;transition:color .15s,border-color .15s,background .15s;white-space:nowrap; } @@ -1227,8 +1227,8 @@ .dsm-loading { position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#f3f3f1;color:#6d766e;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:14px;z-index:5; } .dsm-error { position:absolute;top:50%;left:12px;right:12px;z-index:7;transform:translateY(-50%);text-align:center;font:13px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#7f1d1d;background:rgba(255,255,255,.94);border:1px solid rgba(127,29,29,.25);border-radius:10px;padding:12px 14px;box-shadow:0 2px 10px rgba(0,0,0,.16); } .dsm-dark .dsm-error { color:#fecaca;background:rgba(19,25,32,.95);border-color:rgba(254,202,202,.28); } - .mapboxgl-ctrl-top-left { top:10px!important;left:10px!important;width:var(--map-legend-width,340px)!important;max-width:var(--map-legend-width,340px)!important;z-index:1200!important; } - .mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:0!important;float:none!important; } + .mapboxgl-ctrl-top-left, .maplibregl-ctrl-top-left { top:10px!important;inset-inline-start:10px!important;width:var(--map-legend-width,340px)!important;max-width:var(--map-legend-width,340px)!important;z-index:1200!important; } + .mapboxgl-ctrl-top-left .mapboxgl-ctrl, .maplibregl-ctrl-top-left .maplibregl-ctrl { margin:0!important;float:none!important; } .mapboxgl-ctrl-geocoder { width:100%!important;max-width:100%!important;min-width:0!important;border-radius:20px!important;box-shadow:0 2px 8px rgba(0,0,0,0.15)!important;background:#fff; } .mapboxgl-ctrl-geocoder--input { border-radius:20px!important; } .mapboxgl-ctrl-geocoder:has(.mapboxgl-ctrl-geocoder--input:not(:placeholder-shown)) .mapboxgl-ctrl-geocoder--button { display:block!important; } @@ -1245,7 +1245,7 @@ .dg-main { font-weight:500;font-size:13px;color:#222;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:6px; } .dg-main strong { font-weight:700;overflow:hidden;text-overflow:ellipsis; } .dg-meta { margin-top:2px;display:flex;flex-wrap:wrap;gap:4px 10px;font-size:11px;color:#555;line-height:1.35; } - .dg-field strong { font-weight:600;color:#333;margin-right:2px; } + .dg-field strong { font-weight:600;color:#333;margin-inline-end:2px; } .dg-tag { flex:none;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;color:#3b463d;background:rgba(59,70,61,0.10);border-radius:4px;padding:1px 5px;line-height:1.5;white-space:nowrap; } .dg-agg-label { overflow:hidden;text-overflow:ellipsis; } .dg-count { flex:none;font-size:11px;font-weight:600;color:#73a17f; } @@ -1295,9 +1295,9 @@ .dsm-help-modal h2 { margin:0 0 12px;font-size:18px;font-weight:600; } .dsm-help-modal p { margin:0 0 10px;font-size:14px;line-height:1.6;color:#ccc; } .dsm-help-modal p:last-of-type { margin-bottom:0; } - .dsm-help-close { position:absolute;top:12px;right:14px;background:none;border:none;color:#fff;font-size:20px;cursor:pointer;line-height:1;padding:4px 6px;border-radius:4px; } + .dsm-help-close { position:absolute;top:12px;inset-inline-end:14px;background:none;border:none;color:#fff;font-size:20px;cursor:pointer;line-height:1;padding:4px 6px;border-radius:4px; } .dsm-help-close:hover { background:rgba(255,255,255,0.12); } -`,"doxa-simple-map");const{t:o,te:n,locale:a}=ut(),r=["country-label","state-label","settlement-major-label","settlement-minor-label","place-label","continent-label","water-point-label","water-line-label"];function s(G){const le=y==null?void 0:y.value;if(le){for(const ae of r)if(le.getLayer(ae))try{le.setLayoutProperty(ae,"text-field",["coalesce",["get","name_"+G],["get","name_en"],["get","name"]])}catch{}}}const i=ne(()=>zs.has(a.value)),l={hamburgerMenu:!1,infoButton:!1,overlapDensity:!1,darkPinBorder:!1,geocoder:!1,activeOverlayEmphasis:!1},c=Ce("profileConfig"),u=Ce("mapboxToken"),h=Ce("dataSource"),d=[{id:"prayer",label:"Prayer",colorStrategy:"prayer",legend:"prayer",popup:"prayer"}],x=ne(()=>{var G;return((G=c==null?void 0:c.value)==null?void 0:G.tabs)??d}),P=Object.fromEntries(d.map(G=>[G.id,G.label])),b=G=>(!G.label||G.label===P[G.id])&&n("tabs."+G.id)?o("tabs."+G.id):G.label,w=ne(()=>x.value.length>1),$=fe(null),f=ne(()=>x.value.find(G=>G.id===$.value)??x.value[0]),m={prayer:"doxa-simple-map/doxa-simple-map/prayer/index.html",engagement:"doxa-simple-map/doxa-simple-map/engagement/index.html",adoption:"doxa-simple-map/doxa-simple-map/adoption/index.html"},v=ne(()=>{var G;return m[(G=f.value)==null?void 0:G.id]||"doxa-simple-map/doxa-simple-map/index.html"}),S={prayer:"prayer",engagement:"engagement",adoption:"adoption"},A=ne(()=>{var G;return S[(G=f.value)==null?void 0:G.legend]??"prayer"}),_=ne(()=>{var G;return((G=f.value)==null?void 0:G.popup)==="adoption"?"adopt":"pray"}),C=Ce("instanceId",null),k=(C==null?void 0:C.value)||C||"doxa-simple-map-"+Math.random().toString(36).slice(2,7);ft("mapId",k),ft("getActivePinColor",G=>{var Ae,De,gt;const le=(Ae=f.value)==null?void 0:Ae.colorStrategy,ae=le==="engagement"?nt.ENGAGEMENT:le==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;return((gt=(De=Xo(ae))==null?void 0:De.getColor)==null?void 0:gt.call(De,G))??"#1a1a1a"});const D=ck();uk(D),D.theme.value="light";const j=Mi(Ur(()=>A.value));function q(G){const le=[];for(const ae of G){le.push({id:ae.key,label:ae.label,color:ae.color,count:ae.count??0,pop:ae.population??0,filter:ae.filterKey?["==",["get","_flat_filter"],ae.filterKey]:null,children:[]});for(const Ae of ae.children||[])le.push({id:Ae.key,label:Ae.label,color:Ae.color,count:Ae.count??0,pop:Ae.population??0,filter:Ae.filterKey?["==",["get","_flat_filter"],Ae.filterKey]:null,children:[]})}return le}const W=ne(()=>q(j.items.value)),re=ne(()=>o("legend.titleBar.legend")),N=j.progress;ft("legendProgress",N);const E=ne(()=>{const G=A.value;return o("legend.colLabel."+(["prayer","engagement","adoption"].includes(G)?G:"status"))}),O=ne(()=>oe.legendState!=="collapsed");function U(G){G?oe.openLegend():oe.collapseLegend()}function Q(G){var Ae;if(!G){const De=A.value;De==="prayer"?oe.setPrayerFilter(null):De==="engagement"?oe.setEngagementFilter(null):De==="adoption"&&oe.setAdoptionFilter(null);return}const le=A.value;le==="prayer"?oe.setPrayerFilter(G.id):le==="engagement"?oe.setEngagementFilter(G.id):le==="adoption"&&oe.setAdoptionFilter(G.id);const ae=At(le,G.id);ae&&((Ae=R.zoomToLegendRow)==null||Ae.call(R,G,{matchExpr:ae}))}ft("onSemanticTreeSelect",G=>Q(G));const Y=fe(null),ce=fe(null);let de=null;function _e(G){const le=ce.value;le&&(getComputedStyle(le).getPropertyValue("--map-radius").trim()||(le.style.setProperty("--map-radius","16px"),le.style.setProperty("--map-radius-sm","8px")),G>=700?(le.style.setProperty("--map-btn-size","40px"),le.style.setProperty("--map-toolbar-gap","8px"),le.style.setProperty("--map-font-md","13px"),le.style.setProperty("--map-font-sm","11px"),le.style.setProperty("--map-legend-width","380px")):G>=500?(le.style.setProperty("--map-btn-size","36px"),le.style.setProperty("--map-toolbar-gap","6px"),le.style.setProperty("--map-font-md","12px"),le.style.setProperty("--map-font-sm","10px"),le.style.setProperty("--map-legend-width","360px")):(le.style.setProperty("--map-btn-size","32px"),le.style.setProperty("--map-toolbar-gap","5px"),le.style.setProperty("--map-font-md","11px"),le.style.setProperty("--map-font-sm","10px"),le.style.setProperty("--map-legend-width","280px")))}const be=fe(!1),Re=fe(null),ke=Ce("mapStore"),Se=Ce("dataStore"),oe=Ce("uiStore"),ge=tv(oe);ge.setLook("dark");const se=(()=>{var le;const G=(le=Ce("lang",null))==null?void 0:le.value;return G&&G!=="en"?G:null})(),he=new kp({locale:se,profileConfig:c}),{map:y,initializeMap:T,destroy:L}=r1({containerRef:Y,accessToken:u.value,style:ge.bootStyle(),config:ge.bootConfig,center:[20,10],zoom:1.8,pitch:((I=c==null?void 0:c.value)==null?void 0:I.pitch)??so.pitch,bearing:((X=c==null?void 0:c.value)==null?void 0:X.bearing)??so.bearing,minZoom:((ve=c==null?void 0:c.value)==null?void 0:ve.minZoom)??so.minZoom,maxZoom:((xe=c==null?void 0:c.value)==null?void 0:xe.maxZoom)??so.maxZoom}),H=p1({mapId:k,dataSourceId:h.value,dataSourceManager:he,dataStore:Se,markRaw:G=>G}),V=P1({getMap:()=>y.value,mapId:k,getLanguageFamilyColor:Dt,getNormalizedPeopleGroups:()=>H.normalizedPeopleGroups.value||[]}),R=oS({getMap:()=>y.value,mapId:k,mapStore:ke,defaultCenter:[20,10],defaultZoom:1.8});iS(()=>y.value);const ie=L1({getMap:()=>y.value,getActivePinColor:G=>{var Ae,De,gt;const le=(Ae=f.value)==null?void 0:Ae.colorStrategy,ae=le==="engagement"?nt.ENGAGEMENT:le==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;return((gt=(De=Xo(ae))==null?void 0:De.getColor)==null?void 0:gt.call(De,G))??"#1a1a1a"}}),J=ne(()=>oe.isDarkTheme),g=ne(()=>ud.lightPreset==="night");ze(J,G=>{D.theme.value=G?"dark":"light"},{immediate:!0});const p=[{layer:"background",prop:"background-color",value:"#323f52"},{layer:"water",prop:"fill-color",value:"#03112d"},{layer:"waterway",prop:"line-color",value:"#0b1a36"},{layer:"landcover_ice_shelf",prop:"fill-color",value:"#3b4a5e"},{layer:"landuse_residential",prop:"fill-color",value:"#3a475b"},{layer:"park",prop:"fill-color",value:"#2f403c"},{layer:"landcover_wood",prop:"fill-color",value:"#2c3e3c"},{layer:"building",prop:"fill-color",value:"hsla(215,30%,15%,0.65)"},{layer:"boundary_country_z0-4",prop:"line-color",value:"#c95d74"},{layer:"boundary_country_z5-",prop:"line-color",value:"#c95d74"},{layer:"boundary_country_z0-4",prop:"line-opacity",value:1},{layer:"boundary_country_z5-",prop:"line-opacity",value:1},{layer:"boundary_country_z0-4",prop:"line-blur",value:0},{layer:"boundary_country_z5-",prop:"line-blur",value:0},...["place_country_major","place_country_minor","place_country_other"].flatMap(G=>[{layer:G,prop:"text-color",value:"#dde1e7"},{layer:G,prop:"text-halo-color",value:"rgba(3,17,45,0.85)"}]),{layer:"boundary_state",prop:"line-color",value:"hsla(345,45%,60%,0.3)"}];function z(G){if(!(!G||typeof G.setConfigProperty=="function"||!g.value)){ep(G,p);for(const le of["place_country_major","place_country_minor","place_country_other"])try{G.getLayer(le)&&G.setLayoutProperty(le,"text-transform","none")}catch{}}}ze(()=>oe.theme,()=>ge.applyTheme(y.value));function B(){var ae;const G=y.value;if(!G||!G.getLayer("language-family-pins"))return;const le=g.value?"#ffffff":["case",[">",["to-number",["get","peoplePraying"],0],0],"#ffffff","#1f2937"];G.setPaintProperty("language-family-pins","circle-stroke-color",le),G.setPaintProperty("language-family-pins","circle-stroke-width",g.value?k1((ae=f.value)==null?void 0:ae.colorStrategy):yp)}ze(g,B,{immediate:!1}),ze(()=>{var G;return(G=f.value)==null?void 0:G.id},B);function ee(){ge.swapTheme(y.value)}function te(){oe.updateBreakpoint(window.innerWidth)}Xt(()=>{oe.updateBreakpoint(window.innerWidth),oe.isMobile?oe.collapseLegend():oe.openLegend(),window.addEventListener("resize",te),ce.value&&(_e(ce.value.offsetWidth),de=new ResizeObserver(G=>{for(const le of G)_e(le.contentRect.width)}),de.observe(ce.value))}),Qt(()=>{window.removeEventListener("resize",te),de==null||de.disconnect(),de=null});const M=fe(!1);function K(){const G=y.value?Ge(y.value):null;!G||G===y.value||(y.value=null,y.value=rn(G))}function $e(G){if(G===$.value)return;$.value=G;const le=x.value.find(De=>De.id===G);if(!le||!y.value)return;ge.applyTheme(y.value);const ae=le.colorStrategy,Ae=ae==="engagement"?nt.ENGAGEMENT:ae==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;if(y.value.getLayer("language-family-pins")){const De=bi(Ae);y.value.setPaintProperty("language-family-pins","circle-color",De),y.value.getLayer(Sr)&&y.value.setPaintProperty(Sr,"circle-color",De),at.forEach(gt=>{y.value.getLayer(gt)&&y.value.setPaintProperty(gt,"circle-color",De)})}B(),le.id==="prayer"||le.colorStrategy==="prayer"?V.startPrayerGlow():V.stopPrayerGlow()}function Pe(G,le){if(G!=null&&G.getLayer(zp))try{G.setFilter(zp,le)}catch{}}let Ie={type:null,key:null};const at=[xr];function qe(){const G=f.value,le=(G==null?void 0:G.colorStrategy)==="engagement"?nt.ENGAGEMENT:(G==null?void 0:G.colorStrategy)==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;return bi(le)}function At(G,le){if(G==="prayer"){const ae=["coalesce",["to-number",["get","peoplePraying"],0],0];if(le==="noPrayer")return["<=",ae,0];if(le==="hasPrayer")return["all",[">",ae,0],["<",ae,Oo]];if(le==="fullPrayer")return[">=",ae,Oo]}else if(G==="engagement"){const ae=["get","engagementStatus"];if(le==="hasEngagement")return["any",["==",ae,!0],["==",ae,1]];if(le==="notEngaged")return["all",["!=",ae,!0],["!=",ae,1]]}else if(G==="adoption"){const ae=["get","adoptionStatus"];if(le==="hasAdoption")return["any",["==",ae,!0],["==",ae,1]];if(le==="notAdopted")return["all",["!=",ae,!0],["!=",ae,1]]}return null}function jt(){const G=y.value;G!=null&&G.getLayer(xr)&&(G.setFilter(xr,["==","uniqueId",""]),G.setPaintProperty(xr,"circle-opacity",0))}function rt(){jt();const G=y.value;G!=null&&G.getLayer(Sr)&&G.setFilter(Sr,["==","uniqueId",""])}function lt(G,le){const ae=y.value;if(!ae||!ae.getLayer("language-family-pins"))return;if(!le){Ie={type:null,key:null},ae.setPaintProperty("language-family-pins","circle-color",qe()),ae.setPaintProperty("language-family-pins","circle-opacity",.9),ae.setPaintProperty("language-family-pins","circle-stroke-opacity",1),Pe(ae,null),V.syncGlowFilter(null),rt();return}const Ae=At(G,le);if(!Ae){Ie={type:null,key:null},ae.setPaintProperty("language-family-pins","circle-color",qe()),ae.setPaintProperty("language-family-pins","circle-opacity",.9),ae.setPaintProperty("language-family-pins","circle-stroke-opacity",1),Pe(ae,null),V.syncGlowFilter(null),rt();return}Ie={type:G,key:le};{ae.setPaintProperty("language-family-pins","circle-color",qe()),ae.setPaintProperty("language-family-pins","circle-opacity",["case",Ae,1,.06]),ae.setPaintProperty("language-family-pins","circle-stroke-opacity",["case",Ae,1,.06]),Pe(ae,Ae),V.syncGlowFilter(Ae);return}}ze(J,()=>{y.value&&Ie.key&<(Ie.type,Ie.key)}),ze(()=>oe.prayerFilter,G=>lt("prayer",G)),ze(()=>oe.engagementFilter,G=>lt("engagement",G)),ze(()=>oe.adoptionFilter,G=>lt("adoption",G));async function An(G){var le,ae;ke.registerMap(k,y.value),ge.applyTheme(y.value);try{jv(y.value,"mercator")}catch{}if(G!=null&&G.length){const Ae=x.value[0],De=(Ae==null?void 0:Ae.colorStrategy)==="engagement"?nt.ENGAGEMENT:(Ae==null?void 0:Ae.colorStrategy)==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;V.addLanguageFamilyLayer(G,De),B()}ie.initialize(),s(a.value),ke.setMapReady(k),be.value=!0,(((le=f.value)==null?void 0:le.id)==="prayer"||((ae=f.value)==null?void 0:ae.colorStrategy)==="prayer")&&V.startPrayerGlow()}return ze(a,G=>s(G)),Xt(async()=>{var le;$.value=((le=x.value[0])==null?void 0:le.id)??"prayer",oe.updateBreakpoint(window.innerWidth);const G=H.loadData().catch(ae=>(console.error("[doxa-simple-map] Data load failed:",ae),{normalizedPeopleGroups:[]}));try{await T()}catch(ae){console.error("[doxa-simple-map] Map failed to initialize —",ae),Re.value=String((ae==null?void 0:ae.message)||ae);return}if(K(),!y.value){console.error("[doxa-simple-map] Map failed to initialize — check token and container"),Re.value=o("simpleMap.initFailed");return}y.value.on("style.load",()=>z(y.value)),y.value.once("style.load",async()=>{try{const{normalizedPeopleGroups:ae}=await G;await An(ae)}catch(ae){console.error("[doxa-simple-map] onMapReady failed:",ae),Re.value=String((ae==null?void 0:ae.message)||ae)}})}),Qt(()=>{V.stopPrayerGlow(),rt(),ke.unregisterMap(k),ie.cleanup(),L()}),(G,le)=>(Z(),ue("div",{ref_key:"dsmRoot",ref:ce,class:Fe(["dsm-root",{"dsm-dark":J.value,"dsm-rtl":i.value,"dsm-no-geocoder":!0}]),dir:i.value?"rtl":"ltr"},[w.value?(Z(),ue("div",US,[(Z(!0),ue(Me,null,Nt(x.value,ae=>(Z(),ue("button",{key:ae.id,class:Fe(["dsm-tab",{active:$.value===ae.id}]),onClick:Ae=>$e(ae.id)},me(b(ae)),11,jS))),128))])):we("",!0),F("div",WS,[!be.value&&!Re.value?(Z(),ue("div",KS,me(pe(o)("simpleMap.loading")),1)):we("",!0),Re.value?(Z(),ue("div",VS,me(pe(o)("allPeopleGroups.loadError"))+" "+me(Re.value),1)):we("",!0),F("div",{ref_key:"mapContainer",ref:Y,class:"dsm-map-canvas"},null,512),we("",!0),we("",!0),we("",!0),F("div",qS,[be.value&&pe(oe).legendMode==="detail"&&pe(oe).selectedPeopleGroup?(Z(),je(lk,{key:0,"legend-type":A.value,"popup-action":_.value,"initially-collapsed":!1,"is-dark":J.value},null,8,["legend-type","popup-action","is-dark"])):we("",!0),Ll(F("div",null,[be.value?(Z(),je(Dp,{key:0,nodes:W.value,tabs:null,title:re.value,hideTabs:!0,columnLabel:E.value,progress:pe(N),open:O.value,onSelect:Q,"onUpdate:open":U},null,8,["nodes","title","columnLabel","progress","open"])):we("",!0)],512),[[Pc,!(pe(oe).legendMode==="detail"&&pe(oe).selectedPeopleGroup)]])]),F("div",YS,[be.value?(Z(),je(pe(t),{key:0,"legend-type":A.value,"popup-action":_.value,"is-dark":J.value},null,8,["legend-type","popup-action","is-dark"])):we("",!0)]),le[7]||(le[7]=F("div",{class:"map-scroll-edge map-scroll-edge--left"},null,-1)),le[8]||(le[8]=F("div",{class:"map-scroll-edge map-scroll-edge--right"},null,-1)),be.value?(Z(),je(lS,{key:5,"has-geocoder":l.geocoder},{default:Et(()=>[We(uS,{map:pe(y),"is-dark":J.value},null,8,["map","is-dark"]),We(dS,{map:pe(y),"is-dark":J.value},null,8,["map","is-dark"]),We(pS,{map:pe(y),"is-dark":J.value},null,8,["map","is-dark"]),We(fS,{"map-container":Y.value,"is-dark":J.value},null,8,["map-container","is-dark"]),We(bS,{"is-dark":J.value,onToggle:ee},null,8,["is-dark"]),We(zS,{"is-dark":J.value,"embed-path":v.value},null,8,["is-dark","embed-path"]),l.infoButton?(Z(),je(yS,{key:0,"is-dark":J.value,onOpenHelp:le[2]||(le[2]=ae=>M.value=!0)},null,8,["is-dark"])):we("",!0)]),_:1},8,["has-geocoder"])):we("",!0),M.value?(Z(),ue("div",{key:6,class:"dsm-help-overlay",onClick:le[4]||(le[4]=mt(ae=>M.value=!1,["self"]))},[F("div",ZS,[F("button",{class:"dsm-help-close","aria-label":pe(o)("buttons.close"),onClick:le[3]||(le[3]=ae=>M.value=!1)},"×",8,JS),F("h2",null,me(pe(o)("buttons.aboutThisMap")),1),F("p",null,me(pe(o)("simpleMap.help.what")),1),F("p",null,me(pe(o)("simpleMap.help.how")),1),F("p",null,[ct(me(pe(o)("simpleMap.help.source"))+" ",1),le[5]||(le[5]=F("strong",{dir:"ltr"},"pray.doxa.life",-1)),le[6]||(le[6]=ct(".",-1))])])])):we("",!0)])],10,HS))}},[["styles",[BS]],["__scopeId","data-v-12eee539"]])},Symbol.toStringTag,{value:"Module"})),QS="#6b7280";function ex(){const e=Xo(nt.AFFINITY_BLOCK);return e&&e.propertyKey==="affinityBlock"&&e.palette?e.palette:{}}function tx(e,t){const o=ex();return e&&o[e]?o[e]:QS}function ox(e){return(e==null?void 0:e.properties)||e||{}}function zi(e){return e&&typeof e=="object"&&"label"in e?e.label||e.value||"":e}function nx(e){var t;return String(e.affinityBlock||zi((t=e._raw)==null?void 0:t.rop1)||"").trim()}function ax(e){var t;return String(e.affinityBlockLabel||((t=e._raw)!=null&&t.rop1&&typeof e._raw.rop1=="object"?e._raw.rop1.label:"")||"").trim()}function rx(e){var t;return String(e.cluster||zi((t=e._raw)==null?void 0:t.imb_reg_of_people_2)||"").trim()}function sx(e){var t;return String(e.clusterLabel||((t=e._raw)!=null&&t.imb_reg_of_people_2&&typeof e._raw.imb_reg_of_people_2=="object"?e._raw.imb_reg_of_people_2.label:"")||"").trim()}function ix(e){var t;return String(e.peopleGroup||zi((t=e._raw)==null?void 0:t.imb_reg_of_people_25)||"").trim()}function lx(e){var t;return String(e.peopleGroupLabel||((t=e._raw)!=null&&t.imb_reg_of_people_25&&typeof e._raw.imb_reg_of_people_25=="object"?e._raw.imb_reg_of_people_25.label:"")||e.name||"").trim()}function cx(e){var t;return String(e.uniqueId||e.slug||((t=e._raw)==null?void 0:t.slug)||"").trim()}function ux(e){var t;return String(e.name||e.displayName||((t=e._raw)==null?void 0:t.name)||"").trim()}function dx(e){var o;const t=Number(e.population??e.populationNum??((o=e._raw)==null?void 0:o.population)??0);return Number.isFinite(t)?t:0}function px(e){return String(e.countryName||e.country||e.countryIsoLabel||e.countryIso||"").trim()}function gx(e){const t=ne(()=>{const r=e&&e.value||[],s=new Map;for(const i of r){const l=ox(i),c=nx(l),u=ax(l)||c||"— Unknown —",h=rx(l),d=sx(l)||h||"— Unknown —",x=ix(l),P=lx(l)||x||"— Unknown —",b=cx(l),w=ux(l)||b,$=dx(l),f=px(l);if(!b)continue;let m=s.get(c||"__unknown__");m||(m={code:c,label:u,count:0,pop:0,memberIds:[],clusters:new Map},s.set(c||"__unknown__",m)),m.count+=1,m.pop+=$,m.memberIds.push(b);let v=m.clusters.get(h||"__unknown__");v||(v={code:h,label:d,count:0,pop:0,memberIds:[],peopleGroups:new Map},m.clusters.set(h||"__unknown__",v)),v.count+=1,v.pop+=$,v.memberIds.push(b);let S=v.peopleGroups.get(x||"__unknown__");S||(S={code:x,label:P,count:0,pop:0,memberIds:[],instances:[]},v.peopleGroups.set(x||"__unknown__",S)),S.count+=1,S.pop+=$,S.memberIds.push(b),S.instances.push({id:b,name:w,country:f,pop:$})}return s}),o=r=>r&&r.length?["in",["get","uniqueId"],["literal",r]]:["==",["get","uniqueId"],"__none__"],n=ne(()=>{const r=t.value,s=[];for(const i of r.values()){const l=tx(i.code,i.label),c={id:`bloc:${i.code||i.label}`,label:i.label,color:l,count:i.count,pop:i.pop,filter:o(i.memberIds),children:[]};for(const u of i.clusters.values()){const h={id:`cluster:${i.code}__${u.code||u.label}`,label:`${u.label} (Cluster)`,color:l,count:u.count,pop:u.pop,filter:o(u.memberIds),children:[]};for(const d of u.peopleGroups.values()){const x={id:`pg:${i.code}__${u.code}__${d.code||d.label}`,label:`${d.label} (PG)`,color:l,count:d.count,pop:d.pop,filter:o(d.memberIds),children:[]};d.instances.sort((P,b)=>b.pop-P.pop||P.country.localeCompare(b.country));for(const P of d.instances)x.children.push({id:`pgic:${P.id}`,label:`${P.country?`${P.name}, ${P.country}`:P.name} (PGIC)`,color:l,count:1,pop:P.pop,filter:["==",["get","uniqueId"],P.id],children:[]});h.children.push(x)}h.children.sort((d,x)=>x.count-d.count||x.pop-d.pop),c.children.push(h)}c.children.sort((u,h)=>h.count-u.count||h.pop-u.pop),s.push(c)}return s.sort((i,l)=>l.count-i.count||l.pop-i.pop),s});function a(r){if(typeof window>"u"||typeof window.CustomEvent!="function")return;if(!r){window.dispatchEvent(new CustomEvent("legend:highlight",{detail:{kind:null}}));return}const s=String(r.id||""),i={kind:null,label:r.label,filterExpr:r.filter};s.startsWith("bloc:")?i.kind="bloc":s.startsWith("cluster:")?i.kind="cluster":s.startsWith("pg:")?i.kind="people-group":s.startsWith("pgic:")&&(i.kind="people-group-in-country"),window.dispatchEvent(new CustomEvent("legend:highlight",{detail:i}))}return{aggregated:t,affinityTree:n,highlight:a}}const hx={africa:"#ff2e2e",asia:"#1a8cff",europe:"#1ee05f","latin_america_&_caribbean":"#FFEB3B",middle_east:"#a83dff","north_america_&_non-spanish_caribbean":"#00e0bd",oceania:"#ff7f11"},fx="#95a5a6";function mx(e){return hx[String(e||"").toLowerCase()]||fx}function bx(e){return(e==null?void 0:e.properties)||e||{}}function yx(e){var o;const t=Number(e.population??e.populationNum??((o=e._raw)==null?void 0:o.population)??0);return Number.isFinite(t)?t:0}function $x(e){var t,o;return String(e.doxaRegion||e.wagfRegion||((o=(t=e._raw)==null?void 0:t.wagf_region)==null?void 0:o.value)||"").trim()}function _x(e){var o;const t=(o=e._raw)==null?void 0:o.wagf_region;return String(e.wagfRegionLabel||e.doxaRegionLabel||(t&&typeof t=="object"?t.label:"")||e.doxaRegion||"").trim()}function vx(e){var t,o;return String(e.wagfBlock||((o=(t=e._raw)==null?void 0:t.wagf_block)==null?void 0:o.value)||"").trim()}function wx(e){var o;const t=(o=e._raw)==null?void 0:o.wagf_block;return String(e.wagfBlockLabel||(t&&typeof t=="object"?t.label:"")||e.wagfBlock||"").trim()}function kx(e){var t,o;return String(e.countryIso||((o=(t=e._raw)==null?void 0:t.country_code)==null?void 0:o.value)||"").trim().toUpperCase()}function Sx(e){var o;const t=(o=e._raw)==null?void 0:o.country_code;return String(e.countryIsoLabel||(t&&typeof t=="object"?t.label:"")||e.countryIso||"").trim()}function xx(e){const t=ne(()=>{const a=e&&e.value||[],r=new Map;for(const s of a){const i=bx(s),l=$x(i),c=_x(i)||l||"— Unknown —",u=vx(i),h=wx(i)||u||"— Unknown —",d=kx(i),x=Sx(i)||d||"— Unknown —",P=yx(i);if(!l||l==="na"||l==="none")continue;let b=r.get(l);if(b||(b={slug:l,label:c,count:0,pop:0,blocks:new Map},r.set(l,b)),b.count+=1,b.pop+=P,!u||u==="na")continue;let w=b.blocks.get(u);if(w||(w={slug:u,label:h,count:0,pop:0,countries:new Map},b.blocks.set(u,w)),w.count+=1,w.pop+=P,!d)continue;let $=w.countries.get(d);$||($={iso:d,name:x,count:0,pop:0},w.countries.set(d,$)),$.count+=1,$.pop+=P}return r}),o=ne(()=>{const a=t.value,r=[];for(const s of a.values()){const i=mx(s.slug),l=[],c=[],u=[...s.blocks.values()].sort((h,d)=>d.pop-h.pop||h.label.localeCompare(d.label));for(const h of u){const d=[],x=[],P=[...h.countries.values()].sort((b,w)=>w.pop-b.pop||b.name.localeCompare(w.name));for(const b of P)d.push(b.iso),l.push(b.iso),x.push({id:`country:${s.slug}__${h.slug}__${b.iso}`,label:b.name,color:i,count:b.count,pop:b.pop,filter:["==",["get","countryIso"],b.iso],isoCodes:[b.iso],children:[]});c.push({id:`block:${s.slug}__${h.slug}`,label:`${h.label} (Bloc)`,color:i,count:h.count,pop:h.pop,filter:["==",["get","wagfBlock"],h.slug],isoCodes:d,children:x})}r.push({id:`region:${s.slug}`,label:`${s.label} (Region)`,color:i,count:s.count,pop:s.pop,filter:["==",["get","doxaRegion"],s.slug],isoCodes:[...new Set(l)],children:c})}return r.sort((s,i)=>i.pop-s.pop||s.label.localeCompare(i.label)),r});function n(a){if(typeof window>"u"||typeof window.CustomEvent!="function")return;if(!a){window.dispatchEvent(new CustomEvent("legend:highlight",{detail:{kind:null}}));return}const r=String(a.id||""),s={kind:null,label:a.label,filterExpr:a.filter};r.startsWith("region:")?s.kind="region":r.startsWith("block:")?s.kind="block":r.startsWith("country:")&&(s.kind="country"),window.dispatchEvent(new CustomEvent("legend:highlight",{detail:s}))}return{aggregated:t,regionsTree:o,highlight:n}}const Px="",Ax={class:"legend-sheet-root"},Lx=["src","alt"],Cx={key:0,class:"pull-tab-handle"},Tx=["aria-label"],Ex=["aria-label"],Nx=["aria-label"],Rx={class:"collapsed-detail-title"},Fx={class:"legend-content"},Ix=["aria-label"],Mx=["aria-label"],Bp=80,Ox=48,Dx=44,Gx=24,Hp="--legend-sheet-h",Up=Object.freeze(Object.defineProperty({__proto__:null,default:Fo({__name:"LegendMobile",props:{legendType:{type:String,required:!0},nodes:{type:Array,default:null},tabs:{type:Array,default:null},defaultTab:{type:Number,default:0},columnLabel:{type:String,default:null},title:{type:String,default:null},columns:{type:Array,default:null},isDark:{type:Boolean,default:!1},popupAction:{type:String,default:"pray",validator:e=>["pray","adopt","none"].includes(e)},disableCollapse:{type:Boolean,default:!1}},setup(e){const{t}=ut();Ot(` +`,"doxa-simple-map");const{t:o,te:n,locale:a}=ut(),r=["country-label","state-label","settlement-major-label","settlement-minor-label","place-label","continent-label","water-point-label","water-line-label"];function s(G){const le=y==null?void 0:y.value;if(le){for(const ae of r)if(le.getLayer(ae))try{le.setLayoutProperty(ae,"text-field",["coalesce",["get","name_"+G],["get","name_en"],["get","name"]])}catch{}}}const i=ne(()=>zs.has(a.value)),l={hamburgerMenu:!1,infoButton:!1,overlapDensity:!1,darkPinBorder:!1,geocoder:!1,activeOverlayEmphasis:!1},c=Ce("profileConfig"),u=Ce("mapboxToken"),h=Ce("dataSource"),d=[{id:"prayer",label:"Prayer",colorStrategy:"prayer",legend:"prayer",popup:"prayer"}],x=ne(()=>{var G;return((G=c==null?void 0:c.value)==null?void 0:G.tabs)??d}),P=Object.fromEntries(d.map(G=>[G.id,G.label])),b=G=>(!G.label||G.label===P[G.id])&&n("tabs."+G.id)?o("tabs."+G.id):G.label,w=ne(()=>x.value.length>1),$=fe(null),f=ne(()=>x.value.find(G=>G.id===$.value)??x.value[0]),m={prayer:"doxa-simple-map/doxa-simple-map/prayer/index.html",engagement:"doxa-simple-map/doxa-simple-map/engagement/index.html",adoption:"doxa-simple-map/doxa-simple-map/adoption/index.html"},v=ne(()=>{var G;return m[(G=f.value)==null?void 0:G.id]||"doxa-simple-map/doxa-simple-map/index.html"}),S={prayer:"prayer",engagement:"engagement",adoption:"adoption"},A=ne(()=>{var G;return S[(G=f.value)==null?void 0:G.legend]??"prayer"}),_=ne(()=>{var G;return((G=f.value)==null?void 0:G.popup)==="adoption"?"adopt":"pray"}),C=Ce("instanceId",null),k=(C==null?void 0:C.value)||C||"doxa-simple-map-"+Math.random().toString(36).slice(2,7);ft("mapId",k),ft("getActivePinColor",G=>{var Ae,De,gt;const le=(Ae=f.value)==null?void 0:Ae.colorStrategy,ae=le==="engagement"?nt.ENGAGEMENT:le==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;return((gt=(De=Xo(ae))==null?void 0:De.getColor)==null?void 0:gt.call(De,G))??"#1a1a1a"});const D=ck();uk(D),D.theme.value="light";const j=Mi(Ur(()=>A.value));function q(G){const le=[];for(const ae of G){le.push({id:ae.key,label:ae.label,color:ae.color,count:ae.count??0,pop:ae.population??0,filter:ae.filterKey?["==",["get","_flat_filter"],ae.filterKey]:null,children:[]});for(const Ae of ae.children||[])le.push({id:Ae.key,label:Ae.label,color:Ae.color,count:Ae.count??0,pop:Ae.population??0,filter:Ae.filterKey?["==",["get","_flat_filter"],Ae.filterKey]:null,children:[]})}return le}const W=ne(()=>q(j.items.value)),re=ne(()=>o("legend.titleBar.legend")),N=j.progress;ft("legendProgress",N);const E=ne(()=>{const G=A.value;return o("legend.colLabel."+(["prayer","engagement","adoption"].includes(G)?G:"status"))}),O=ne(()=>oe.legendState!=="collapsed");function U(G){G?oe.openLegend():oe.collapseLegend()}function Q(G){var Ae;if(!G){const De=A.value;De==="prayer"?oe.setPrayerFilter(null):De==="engagement"?oe.setEngagementFilter(null):De==="adoption"&&oe.setAdoptionFilter(null);return}const le=A.value;le==="prayer"?oe.setPrayerFilter(G.id):le==="engagement"?oe.setEngagementFilter(G.id):le==="adoption"&&oe.setAdoptionFilter(G.id);const ae=At(le,G.id);ae&&((Ae=R.zoomToLegendRow)==null||Ae.call(R,G,{matchExpr:ae}))}ft("onSemanticTreeSelect",G=>Q(G));const Y=fe(null),ce=fe(null);let de=null;function _e(G){const le=ce.value;le&&(getComputedStyle(le).getPropertyValue("--map-radius").trim()||(le.style.setProperty("--map-radius","16px"),le.style.setProperty("--map-radius-sm","8px")),G>=700?(le.style.setProperty("--map-btn-size","40px"),le.style.setProperty("--map-toolbar-gap","8px"),le.style.setProperty("--map-font-md","13px"),le.style.setProperty("--map-font-sm","11px"),le.style.setProperty("--map-legend-width","380px")):G>=500?(le.style.setProperty("--map-btn-size","36px"),le.style.setProperty("--map-toolbar-gap","6px"),le.style.setProperty("--map-font-md","12px"),le.style.setProperty("--map-font-sm","10px"),le.style.setProperty("--map-legend-width","360px")):(le.style.setProperty("--map-btn-size","32px"),le.style.setProperty("--map-toolbar-gap","5px"),le.style.setProperty("--map-font-md","11px"),le.style.setProperty("--map-font-sm","10px"),le.style.setProperty("--map-legend-width","280px")))}const be=fe(!1),Re=fe(null),ke=Ce("mapStore"),Se=Ce("dataStore"),oe=Ce("uiStore"),ge=tv(oe);ge.setLook("dark");const se=(()=>{var le;const G=(le=Ce("lang",null))==null?void 0:le.value;return G&&G!=="en"?G:null})(),he=new kp({locale:se,profileConfig:c}),{map:y,initializeMap:T,destroy:L}=r1({containerRef:Y,accessToken:u.value,style:ge.bootStyle(),config:ge.bootConfig,center:[20,10],zoom:1.8,pitch:((I=c==null?void 0:c.value)==null?void 0:I.pitch)??so.pitch,bearing:((X=c==null?void 0:c.value)==null?void 0:X.bearing)??so.bearing,minZoom:((ve=c==null?void 0:c.value)==null?void 0:ve.minZoom)??so.minZoom,maxZoom:((xe=c==null?void 0:c.value)==null?void 0:xe.maxZoom)??so.maxZoom}),H=p1({mapId:k,dataSourceId:h.value,dataSourceManager:he,dataStore:Se,markRaw:G=>G}),V=P1({getMap:()=>y.value,mapId:k,getLanguageFamilyColor:Dt,getNormalizedPeopleGroups:()=>H.normalizedPeopleGroups.value||[]}),R=oS({getMap:()=>y.value,mapId:k,mapStore:ke,defaultCenter:[20,10],defaultZoom:1.8});iS(()=>y.value);const ie=L1({getMap:()=>y.value,getActivePinColor:G=>{var Ae,De,gt;const le=(Ae=f.value)==null?void 0:Ae.colorStrategy,ae=le==="engagement"?nt.ENGAGEMENT:le==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;return((gt=(De=Xo(ae))==null?void 0:De.getColor)==null?void 0:gt.call(De,G))??"#1a1a1a"}}),J=ne(()=>oe.isDarkTheme),g=ne(()=>ud.lightPreset==="night");ze(J,G=>{D.theme.value=G?"dark":"light"},{immediate:!0});const p=[{layer:"background",prop:"background-color",value:"#323f52"},{layer:"water",prop:"fill-color",value:"#03112d"},{layer:"waterway",prop:"line-color",value:"#0b1a36"},{layer:"landcover_ice_shelf",prop:"fill-color",value:"#3b4a5e"},{layer:"landuse_residential",prop:"fill-color",value:"#3a475b"},{layer:"park",prop:"fill-color",value:"#2f403c"},{layer:"landcover_wood",prop:"fill-color",value:"#2c3e3c"},{layer:"building",prop:"fill-color",value:"hsla(215,30%,15%,0.65)"},{layer:"boundary_country_z0-4",prop:"line-color",value:"#c95d74"},{layer:"boundary_country_z5-",prop:"line-color",value:"#c95d74"},{layer:"boundary_country_z0-4",prop:"line-opacity",value:1},{layer:"boundary_country_z5-",prop:"line-opacity",value:1},{layer:"boundary_country_z0-4",prop:"line-blur",value:0},{layer:"boundary_country_z5-",prop:"line-blur",value:0},...["place_country_major","place_country_minor","place_country_other"].flatMap(G=>[{layer:G,prop:"text-color",value:"#dde1e7"},{layer:G,prop:"text-halo-color",value:"rgba(3,17,45,0.85)"}]),{layer:"boundary_state",prop:"line-color",value:"hsla(345,45%,60%,0.3)"}];function z(G){if(!(!G||typeof G.setConfigProperty=="function"||!g.value)){ep(G,p);for(const le of["place_country_major","place_country_minor","place_country_other"])try{G.getLayer(le)&&G.setLayoutProperty(le,"text-transform","none")}catch{}}}ze(()=>oe.theme,()=>ge.applyTheme(y.value));function B(){var ae;const G=y.value;if(!G||!G.getLayer("language-family-pins"))return;const le=g.value?"#ffffff":["case",[">",["to-number",["get","peoplePraying"],0],0],"#ffffff","#1f2937"];G.setPaintProperty("language-family-pins","circle-stroke-color",le),G.setPaintProperty("language-family-pins","circle-stroke-width",g.value?k1((ae=f.value)==null?void 0:ae.colorStrategy):yp)}ze(g,B,{immediate:!1}),ze(()=>{var G;return(G=f.value)==null?void 0:G.id},B);function ee(){ge.swapTheme(y.value)}function te(){oe.updateBreakpoint(window.innerWidth)}Xt(()=>{oe.updateBreakpoint(window.innerWidth),oe.isMobile?oe.collapseLegend():oe.openLegend(),window.addEventListener("resize",te),ce.value&&(_e(ce.value.offsetWidth),de=new ResizeObserver(G=>{for(const le of G)_e(le.contentRect.width)}),de.observe(ce.value))}),Qt(()=>{window.removeEventListener("resize",te),de==null||de.disconnect(),de=null});const M=fe(!1);function K(){const G=y.value?Ge(y.value):null;!G||G===y.value||(y.value=null,y.value=rn(G))}function $e(G){if(G===$.value)return;$.value=G;const le=x.value.find(De=>De.id===G);if(!le||!y.value)return;ge.applyTheme(y.value);const ae=le.colorStrategy,Ae=ae==="engagement"?nt.ENGAGEMENT:ae==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;if(y.value.getLayer("language-family-pins")){const De=bi(Ae);y.value.setPaintProperty("language-family-pins","circle-color",De),y.value.getLayer(Sr)&&y.value.setPaintProperty(Sr,"circle-color",De),at.forEach(gt=>{y.value.getLayer(gt)&&y.value.setPaintProperty(gt,"circle-color",De)})}B(),le.id==="prayer"||le.colorStrategy==="prayer"?V.startPrayerGlow():V.stopPrayerGlow()}function Pe(G,le){if(G!=null&&G.getLayer(zp))try{G.setFilter(zp,le)}catch{}}let Ie={type:null,key:null};const at=[xr];function qe(){const G=f.value,le=(G==null?void 0:G.colorStrategy)==="engagement"?nt.ENGAGEMENT:(G==null?void 0:G.colorStrategy)==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;return bi(le)}function At(G,le){if(G==="prayer"){const ae=["coalesce",["to-number",["get","peoplePraying"],0],0];if(le==="noPrayer")return["<=",ae,0];if(le==="hasPrayer")return["all",[">",ae,0],["<",ae,Oo]];if(le==="fullPrayer")return[">=",ae,Oo]}else if(G==="engagement"){const ae=["get","engagementStatus"];if(le==="hasEngagement")return["any",["==",ae,!0],["==",ae,1]];if(le==="notEngaged")return["all",["!=",ae,!0],["!=",ae,1]]}else if(G==="adoption"){const ae=["get","adoptionStatus"];if(le==="hasAdoption")return["any",["==",ae,!0],["==",ae,1]];if(le==="notAdopted")return["all",["!=",ae,!0],["!=",ae,1]]}return null}function jt(){const G=y.value;G!=null&&G.getLayer(xr)&&(G.setFilter(xr,["==","uniqueId",""]),G.setPaintProperty(xr,"circle-opacity",0))}function rt(){jt();const G=y.value;G!=null&&G.getLayer(Sr)&&G.setFilter(Sr,["==","uniqueId",""])}function lt(G,le){const ae=y.value;if(!ae||!ae.getLayer("language-family-pins"))return;if(!le){Ie={type:null,key:null},ae.setPaintProperty("language-family-pins","circle-color",qe()),ae.setPaintProperty("language-family-pins","circle-opacity",.9),ae.setPaintProperty("language-family-pins","circle-stroke-opacity",1),Pe(ae,null),V.syncGlowFilter(null),rt();return}const Ae=At(G,le);if(!Ae){Ie={type:null,key:null},ae.setPaintProperty("language-family-pins","circle-color",qe()),ae.setPaintProperty("language-family-pins","circle-opacity",.9),ae.setPaintProperty("language-family-pins","circle-stroke-opacity",1),Pe(ae,null),V.syncGlowFilter(null),rt();return}Ie={type:G,key:le};{ae.setPaintProperty("language-family-pins","circle-color",qe()),ae.setPaintProperty("language-family-pins","circle-opacity",["case",Ae,1,.06]),ae.setPaintProperty("language-family-pins","circle-stroke-opacity",["case",Ae,1,.06]),Pe(ae,Ae),V.syncGlowFilter(Ae);return}}ze(J,()=>{y.value&&Ie.key&<(Ie.type,Ie.key)}),ze(()=>oe.prayerFilter,G=>lt("prayer",G)),ze(()=>oe.engagementFilter,G=>lt("engagement",G)),ze(()=>oe.adoptionFilter,G=>lt("adoption",G));async function An(G){var le,ae;ke.registerMap(k,y.value),ge.applyTheme(y.value);try{jv(y.value,"mercator")}catch{}if(G!=null&&G.length){const Ae=x.value[0],De=(Ae==null?void 0:Ae.colorStrategy)==="engagement"?nt.ENGAGEMENT:(Ae==null?void 0:Ae.colorStrategy)==="adoption"?nt.ADOPTION:nt.PRAYER_PROGRESS;V.addLanguageFamilyLayer(G,De),B()}ie.initialize(),s(a.value),ke.setMapReady(k),be.value=!0,(((le=f.value)==null?void 0:le.id)==="prayer"||((ae=f.value)==null?void 0:ae.colorStrategy)==="prayer")&&V.startPrayerGlow()}return ze(a,G=>s(G)),Xt(async()=>{var le;$.value=((le=x.value[0])==null?void 0:le.id)??"prayer",oe.updateBreakpoint(window.innerWidth);const G=H.loadData().catch(ae=>(console.error("[doxa-simple-map] Data load failed:",ae),{normalizedPeopleGroups:[]}));try{await T()}catch(ae){console.error("[doxa-simple-map] Map failed to initialize —",ae),Re.value=String((ae==null?void 0:ae.message)||ae);return}if(K(),!y.value){console.error("[doxa-simple-map] Map failed to initialize — check token and container"),Re.value=o("simpleMap.initFailed");return}y.value.on("style.load",()=>z(y.value)),y.value.once("style.load",async()=>{try{const{normalizedPeopleGroups:ae}=await G;await An(ae)}catch(ae){console.error("[doxa-simple-map] onMapReady failed:",ae),Re.value=String((ae==null?void 0:ae.message)||ae)}})}),Qt(()=>{V.stopPrayerGlow(),rt(),ke.unregisterMap(k),ie.cleanup(),L()}),(G,le)=>(Z(),ue("div",{ref_key:"dsmRoot",ref:ce,class:Fe(["dsm-root",{"dsm-dark":J.value,"dsm-rtl":i.value,"dsm-no-geocoder":!0}]),dir:i.value?"rtl":"ltr"},[w.value?(Z(),ue("div",US,[(Z(!0),ue(Me,null,Nt(x.value,ae=>(Z(),ue("button",{key:ae.id,class:Fe(["dsm-tab",{active:$.value===ae.id}]),onClick:Ae=>$e(ae.id)},me(b(ae)),11,jS))),128))])):we("",!0),F("div",WS,[!be.value&&!Re.value?(Z(),ue("div",KS,me(pe(o)("simpleMap.loading")),1)):we("",!0),Re.value?(Z(),ue("div",VS,me(pe(o)("allPeopleGroups.loadError"))+" "+me(Re.value),1)):we("",!0),F("div",{ref_key:"mapContainer",ref:Y,class:"dsm-map-canvas"},null,512),we("",!0),we("",!0),we("",!0),F("div",qS,[be.value&&pe(oe).legendMode==="detail"&&pe(oe).selectedPeopleGroup?(Z(),je(lk,{key:0,"legend-type":A.value,"popup-action":_.value,"initially-collapsed":!1,"is-dark":J.value},null,8,["legend-type","popup-action","is-dark"])):we("",!0),Ll(F("div",null,[be.value?(Z(),je(Dp,{key:0,nodes:W.value,tabs:null,title:re.value,hideTabs:!0,columnLabel:E.value,progress:pe(N),open:O.value,onSelect:Q,"onUpdate:open":U},null,8,["nodes","title","columnLabel","progress","open"])):we("",!0)],512),[[Pc,!(pe(oe).legendMode==="detail"&&pe(oe).selectedPeopleGroup)]])]),F("div",YS,[be.value?(Z(),je(pe(t),{key:0,"legend-type":A.value,"popup-action":_.value,"is-dark":J.value},null,8,["legend-type","popup-action","is-dark"])):we("",!0)]),le[7]||(le[7]=F("div",{class:"map-scroll-edge map-scroll-edge--left"},null,-1)),le[8]||(le[8]=F("div",{class:"map-scroll-edge map-scroll-edge--right"},null,-1)),be.value?(Z(),je(lS,{key:5,"has-geocoder":l.geocoder},{default:Et(()=>[We(uS,{map:pe(y),"is-dark":J.value},null,8,["map","is-dark"]),We(dS,{map:pe(y),"is-dark":J.value},null,8,["map","is-dark"]),We(pS,{map:pe(y),"is-dark":J.value},null,8,["map","is-dark"]),We(fS,{"map-container":Y.value,"is-dark":J.value},null,8,["map-container","is-dark"]),We(bS,{"is-dark":J.value,onToggle:ee},null,8,["is-dark"]),We(zS,{"is-dark":J.value,"embed-path":v.value},null,8,["is-dark","embed-path"]),l.infoButton?(Z(),je(yS,{key:0,"is-dark":J.value,onOpenHelp:le[2]||(le[2]=ae=>M.value=!0)},null,8,["is-dark"])):we("",!0)]),_:1},8,["has-geocoder"])):we("",!0),M.value?(Z(),ue("div",{key:6,class:"dsm-help-overlay",onClick:le[4]||(le[4]=mt(ae=>M.value=!1,["self"]))},[F("div",ZS,[F("button",{class:"dsm-help-close","aria-label":pe(o)("buttons.close"),onClick:le[3]||(le[3]=ae=>M.value=!1)},"×",8,JS),F("h2",null,me(pe(o)("buttons.aboutThisMap")),1),F("p",null,me(pe(o)("simpleMap.help.what")),1),F("p",null,me(pe(o)("simpleMap.help.how")),1),F("p",null,[ct(me(pe(o)("simpleMap.help.source"))+" ",1),le[5]||(le[5]=F("strong",{dir:"ltr"},"pray.doxa.life",-1)),le[6]||(le[6]=ct(".",-1))])])])):we("",!0)])],10,HS))}},[["styles",[BS]],["__scopeId","data-v-58b6ec72"]])},Symbol.toStringTag,{value:"Module"})),QS="#6b7280";function ex(){const e=Xo(nt.AFFINITY_BLOCK);return e&&e.propertyKey==="affinityBlock"&&e.palette?e.palette:{}}function tx(e,t){const o=ex();return e&&o[e]?o[e]:QS}function ox(e){return(e==null?void 0:e.properties)||e||{}}function zi(e){return e&&typeof e=="object"&&"label"in e?e.label||e.value||"":e}function nx(e){var t;return String(e.affinityBlock||zi((t=e._raw)==null?void 0:t.rop1)||"").trim()}function ax(e){var t;return String(e.affinityBlockLabel||((t=e._raw)!=null&&t.rop1&&typeof e._raw.rop1=="object"?e._raw.rop1.label:"")||"").trim()}function rx(e){var t;return String(e.cluster||zi((t=e._raw)==null?void 0:t.imb_reg_of_people_2)||"").trim()}function sx(e){var t;return String(e.clusterLabel||((t=e._raw)!=null&&t.imb_reg_of_people_2&&typeof e._raw.imb_reg_of_people_2=="object"?e._raw.imb_reg_of_people_2.label:"")||"").trim()}function ix(e){var t;return String(e.peopleGroup||zi((t=e._raw)==null?void 0:t.imb_reg_of_people_25)||"").trim()}function lx(e){var t;return String(e.peopleGroupLabel||((t=e._raw)!=null&&t.imb_reg_of_people_25&&typeof e._raw.imb_reg_of_people_25=="object"?e._raw.imb_reg_of_people_25.label:"")||e.name||"").trim()}function cx(e){var t;return String(e.uniqueId||e.slug||((t=e._raw)==null?void 0:t.slug)||"").trim()}function ux(e){var t;return String(e.name||e.displayName||((t=e._raw)==null?void 0:t.name)||"").trim()}function dx(e){var o;const t=Number(e.population??e.populationNum??((o=e._raw)==null?void 0:o.population)??0);return Number.isFinite(t)?t:0}function px(e){return String(e.countryName||e.country||e.countryIsoLabel||e.countryIso||"").trim()}function gx(e){const t=ne(()=>{const r=e&&e.value||[],s=new Map;for(const i of r){const l=ox(i),c=nx(l),u=ax(l)||c||"— Unknown —",h=rx(l),d=sx(l)||h||"— Unknown —",x=ix(l),P=lx(l)||x||"— Unknown —",b=cx(l),w=ux(l)||b,$=dx(l),f=px(l);if(!b)continue;let m=s.get(c||"__unknown__");m||(m={code:c,label:u,count:0,pop:0,memberIds:[],clusters:new Map},s.set(c||"__unknown__",m)),m.count+=1,m.pop+=$,m.memberIds.push(b);let v=m.clusters.get(h||"__unknown__");v||(v={code:h,label:d,count:0,pop:0,memberIds:[],peopleGroups:new Map},m.clusters.set(h||"__unknown__",v)),v.count+=1,v.pop+=$,v.memberIds.push(b);let S=v.peopleGroups.get(x||"__unknown__");S||(S={code:x,label:P,count:0,pop:0,memberIds:[],instances:[]},v.peopleGroups.set(x||"__unknown__",S)),S.count+=1,S.pop+=$,S.memberIds.push(b),S.instances.push({id:b,name:w,country:f,pop:$})}return s}),o=r=>r&&r.length?["in",["get","uniqueId"],["literal",r]]:["==",["get","uniqueId"],"__none__"],n=ne(()=>{const r=t.value,s=[];for(const i of r.values()){const l=tx(i.code,i.label),c={id:`bloc:${i.code||i.label}`,label:i.label,color:l,count:i.count,pop:i.pop,filter:o(i.memberIds),children:[]};for(const u of i.clusters.values()){const h={id:`cluster:${i.code}__${u.code||u.label}`,label:`${u.label} (Cluster)`,color:l,count:u.count,pop:u.pop,filter:o(u.memberIds),children:[]};for(const d of u.peopleGroups.values()){const x={id:`pg:${i.code}__${u.code}__${d.code||d.label}`,label:`${d.label} (PG)`,color:l,count:d.count,pop:d.pop,filter:o(d.memberIds),children:[]};d.instances.sort((P,b)=>b.pop-P.pop||P.country.localeCompare(b.country));for(const P of d.instances)x.children.push({id:`pgic:${P.id}`,label:`${P.country?`${P.name}, ${P.country}`:P.name} (PGIC)`,color:l,count:1,pop:P.pop,filter:["==",["get","uniqueId"],P.id],children:[]});h.children.push(x)}h.children.sort((d,x)=>x.count-d.count||x.pop-d.pop),c.children.push(h)}c.children.sort((u,h)=>h.count-u.count||h.pop-u.pop),s.push(c)}return s.sort((i,l)=>l.count-i.count||l.pop-i.pop),s});function a(r){if(typeof window>"u"||typeof window.CustomEvent!="function")return;if(!r){window.dispatchEvent(new CustomEvent("legend:highlight",{detail:{kind:null}}));return}const s=String(r.id||""),i={kind:null,label:r.label,filterExpr:r.filter};s.startsWith("bloc:")?i.kind="bloc":s.startsWith("cluster:")?i.kind="cluster":s.startsWith("pg:")?i.kind="people-group":s.startsWith("pgic:")&&(i.kind="people-group-in-country"),window.dispatchEvent(new CustomEvent("legend:highlight",{detail:i}))}return{aggregated:t,affinityTree:n,highlight:a}}const hx={africa:"#ff2e2e",asia:"#1a8cff",europe:"#1ee05f","latin_america_&_caribbean":"#FFEB3B",middle_east:"#a83dff","north_america_&_non-spanish_caribbean":"#00e0bd",oceania:"#ff7f11"},fx="#95a5a6";function mx(e){return hx[String(e||"").toLowerCase()]||fx}function bx(e){return(e==null?void 0:e.properties)||e||{}}function yx(e){var o;const t=Number(e.population??e.populationNum??((o=e._raw)==null?void 0:o.population)??0);return Number.isFinite(t)?t:0}function $x(e){var t,o;return String(e.doxaRegion||e.wagfRegion||((o=(t=e._raw)==null?void 0:t.wagf_region)==null?void 0:o.value)||"").trim()}function _x(e){var o;const t=(o=e._raw)==null?void 0:o.wagf_region;return String(e.wagfRegionLabel||e.doxaRegionLabel||(t&&typeof t=="object"?t.label:"")||e.doxaRegion||"").trim()}function vx(e){var t,o;return String(e.wagfBlock||((o=(t=e._raw)==null?void 0:t.wagf_block)==null?void 0:o.value)||"").trim()}function wx(e){var o;const t=(o=e._raw)==null?void 0:o.wagf_block;return String(e.wagfBlockLabel||(t&&typeof t=="object"?t.label:"")||e.wagfBlock||"").trim()}function kx(e){var t,o;return String(e.countryIso||((o=(t=e._raw)==null?void 0:t.country_code)==null?void 0:o.value)||"").trim().toUpperCase()}function Sx(e){var o;const t=(o=e._raw)==null?void 0:o.country_code;return String(e.countryIsoLabel||(t&&typeof t=="object"?t.label:"")||e.countryIso||"").trim()}function xx(e){const t=ne(()=>{const a=e&&e.value||[],r=new Map;for(const s of a){const i=bx(s),l=$x(i),c=_x(i)||l||"— Unknown —",u=vx(i),h=wx(i)||u||"— Unknown —",d=kx(i),x=Sx(i)||d||"— Unknown —",P=yx(i);if(!l||l==="na"||l==="none")continue;let b=r.get(l);if(b||(b={slug:l,label:c,count:0,pop:0,blocks:new Map},r.set(l,b)),b.count+=1,b.pop+=P,!u||u==="na")continue;let w=b.blocks.get(u);if(w||(w={slug:u,label:h,count:0,pop:0,countries:new Map},b.blocks.set(u,w)),w.count+=1,w.pop+=P,!d)continue;let $=w.countries.get(d);$||($={iso:d,name:x,count:0,pop:0},w.countries.set(d,$)),$.count+=1,$.pop+=P}return r}),o=ne(()=>{const a=t.value,r=[];for(const s of a.values()){const i=mx(s.slug),l=[],c=[],u=[...s.blocks.values()].sort((h,d)=>d.pop-h.pop||h.label.localeCompare(d.label));for(const h of u){const d=[],x=[],P=[...h.countries.values()].sort((b,w)=>w.pop-b.pop||b.name.localeCompare(w.name));for(const b of P)d.push(b.iso),l.push(b.iso),x.push({id:`country:${s.slug}__${h.slug}__${b.iso}`,label:b.name,color:i,count:b.count,pop:b.pop,filter:["==",["get","countryIso"],b.iso],isoCodes:[b.iso],children:[]});c.push({id:`block:${s.slug}__${h.slug}`,label:`${h.label} (Bloc)`,color:i,count:h.count,pop:h.pop,filter:["==",["get","wagfBlock"],h.slug],isoCodes:d,children:x})}r.push({id:`region:${s.slug}`,label:`${s.label} (Region)`,color:i,count:s.count,pop:s.pop,filter:["==",["get","doxaRegion"],s.slug],isoCodes:[...new Set(l)],children:c})}return r.sort((s,i)=>i.pop-s.pop||s.label.localeCompare(i.label)),r});function n(a){if(typeof window>"u"||typeof window.CustomEvent!="function")return;if(!a){window.dispatchEvent(new CustomEvent("legend:highlight",{detail:{kind:null}}));return}const r=String(a.id||""),s={kind:null,label:a.label,filterExpr:a.filter};r.startsWith("region:")?s.kind="region":r.startsWith("block:")?s.kind="block":r.startsWith("country:")&&(s.kind="country"),window.dispatchEvent(new CustomEvent("legend:highlight",{detail:s}))}return{aggregated:t,regionsTree:o,highlight:n}}const Px="",Ax={class:"legend-sheet-root"},Lx=["src","alt"],Cx={key:0,class:"pull-tab-handle"},Tx=["aria-label"],Ex=["aria-label"],Nx=["aria-label"],Rx={class:"collapsed-detail-title"},Fx={class:"legend-content"},Ix=["aria-label"],Mx=["aria-label"],Bp=80,Ox=48,Dx=44,Gx=24,Hp="--legend-sheet-h",Up=Object.freeze(Object.defineProperty({__proto__:null,default:Fo({__name:"LegendMobile",props:{legendType:{type:String,required:!0},nodes:{type:Array,default:null},tabs:{type:Array,default:null},defaultTab:{type:Number,default:0},columnLabel:{type:String,default:null},title:{type:String,default:null},columns:{type:Array,default:null},isDark:{type:Boolean,default:!1},popupAction:{type:String,default:"pray",validator:e=>["pray","adopt","none"].includes(e)},disableCollapse:{type:Boolean,default:!1}},setup(e){const{t}=ut();Ot(` .floating-mini-image{position:absolute;inset-inline-start:16px;bottom:calc(30% + 16px);z-index:999;pointer-events:auto;cursor:pointer;} .mini-image{width:120px;height:150px;object-fit:cover;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.25);display:block;} .legend-sheet-root{position:absolute;inset:0;pointer-events:none;overflow:hidden;} diff --git a/public/dx-maps/doxa-simple-map/doxa-simple-map/adoption/index.html b/public/dx-maps/doxa-simple-map/doxa-simple-map/adoption/index.html index 9c4d8fe5..3b1b550a 100644 --- a/public/dx-maps/doxa-simple-map/doxa-simple-map/adoption/index.html +++ b/public/dx-maps/doxa-simple-map/doxa-simple-map/adoption/index.html @@ -36,7 +36,7 @@ - + - + - + - + \n\n" + "script": "\n\n\n\n" }, "instances": [ {