diff --git a/documentation/PRESET-PLUGIN-STANDARD.md b/documentation/PRESET-PLUGIN-STANDARD.md
new file mode 100644
index 00000000..3efa63e6
--- /dev/null
+++ b/documentation/PRESET-PLUGIN-STANDARD.md
@@ -0,0 +1,55 @@
+# The preset plugin standard — how to ship named variants that appear by themselves
+
+A **preset** is one profile rendered N ways by different props: doxa-simple-map ships `prayer`,
+`engagement` and `adoption` from a single `` element. Presets are **discovered, never
+registered** — nothing in Plugin Studio, the gallery or the manifest generator holds a list of them.
+Verified on the tree as it stands: the only match for "prayer|engagement|adoption" anywhere in
+`plugin-studio.vue` is an unrelated comment about a layout bug.
+
+## Declare one file
+
+Put `.instances.json` beside your profile in `plugin-designs//`. That file's PRESENCE is
+the whole registration: `discover.mjs` (`detectProfileType`) types a bundle **parameterized** — capability
+2 — because it ships one, and `null` otherwise. Nothing else is consulted.
+
+```
+plugin-designs/doxa-simple-map/
+ doxa-simple-map.vue the profile
+ simple-map.instances.json the presets <- this is the registration
+```
+
+Two accepted shapes, both read by `discover.mjs`: a bare array `[{ id, title, props }]`, or an object
+`{ profile, apiBaseUrl, instances: [...] }` when the presets need a base URL or name their profile
+explicitly. Each instance needs `id` (url-safe, becomes the folder), `title` (what a reader sees) and
+`props` (exactly what you would pass the element).
+
+## What you get without asking
+
+- **A standalone embeddable page per preset** at `///index.html`, nested under
+ the profile, each with its own SRI-pinned script tag.
+- **A manifest entry per preset** under the profile's `instances`, so every consumer sees them at once.
+- **A rail row and a gallery card per preset**, from that same manifest.
+- **Capability 2**, derived. Do not hand-declare `"type": "parameterized"` to get it.
+
+## The rules that keep it automatic
+
+1. **Never add your preset to a list elsewhere.** If a preset needs a change in Plugin Studio to show
+ up, that is a defect in the discovery path, not something to work around. The one bundle-name list
+ that used to exist is exactly the thing this standard replaced.
+2. **The base profile still stands on its own.** Presets SPLIT one profile into named render targets;
+ they do not replace it. `doxa-simple-map/doxa-simple-map/` still accepts live props.
+3. **`props` is the whole difference between presets.** If two presets differ by anything the element
+ cannot be told through props, they are two profiles, not two presets.
+4. **Presets are not tabs.** A tab nests several components inside one element; a preset renders the
+ same element several ways. Choosing the wrong one shows up later as a capability that will not
+ derive: tabs make a bundle a composite (cap 4+), presets keep it parameterized (cap 2).
+5. **An `id` is a URL.** It becomes a folder under the tree and is embedded in partner pages, so
+ renaming one breaks links already shared. Treat it like the share-link vocabulary: additive.
+
+## How to check it worked
+
+Build the tree, then read the manifest rather than the screen: your bundle's profile should carry an
+`instances` array with one entry per preset, and `check:capability-derived` should report the profile as
+a real tier. A preset that does not appear is almost always a filename that does not end
+`.instances.json`, or an `instances` key that is not an array — `discover.mjs` treats both as "no
+presets" and types the bundle `single`, silently.
diff --git a/internal/backend-dev-plugin.mjs b/internal/backend-dev-plugin.mjs
index 2bf49074..80f4e9a4 100644
--- a/internal/backend-dev-plugin.mjs
+++ b/internal/backend-dev-plugin.mjs
@@ -30,6 +30,9 @@ export const DEV_BACKENDS = [
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) },
+ // This server is SUPPOSED to run the engine (a server deployment with the engine switched on): while it
+ // is down, the plugin says "not running yet" instead of "runs on your own computer".
+ expected: () => process.env.RUDDER_ENGINE === '1' || process.env.RUDDER_AUDIO === 'browser',
},
]
@@ -85,11 +88,18 @@ export function backendDevPlugin(root) {
for (const b of DEV_BACKENDS) {
server.middlewares.use(b.route, (req, res, next) => {
if (isAlive(b.name)) return next()
+ const status = { engine: 'offline', expected: !!(b.expected && b.expected()), error: 'The desktop engine is not running.' }
if ((req.url || '').startsWith('/events')) {
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' })
- res.write(`data: ${JSON.stringify({ type: 'status', status: { engine: 'offline', error: 'The desktop engine is not running.' } })}\n\n`)
+ res.write(`data: ${JSON.stringify({ type: 'status', status })}\n\n`)
return // held open, so the EventSource does not reconnect in a loop
}
+ // /status answers the same, for a page whose event stream cannot get through (a proxy that buffers).
+ if ((req.url || '').startsWith('/status')) {
+ res.writeHead(200, { 'Content-Type': 'application/json' })
+ res.end(JSON.stringify(status))
+ return
+ }
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end('[]')
})
diff --git a/internal/check-capability-derived.mjs b/internal/check-capability-derived.mjs
new file mode 100644
index 00000000..8cd796f0
--- /dev/null
+++ b/internal/check-capability-derived.mjs
@@ -0,0 +1,92 @@
+#!/usr/bin/env node
+/**
+ * check-capability-derived — every bundle lands on a real capability, 1-5, never 0.
+ *
+ * ★ THE DEFECT. Tiers 3-5 used to be AUTHORED ONLY: discover.mjs infers `single` and `parameterized`
+ * and nothing else, so a composite whose author forgot a .meta.json shipped as capability 1.
+ * research-dashboard did exactly that while nesting ten views. A missing file is indistinguishable
+ * from a deliberate choice, so nothing failed and the understatement propagated into the manifest,
+ * the gallery and the studio rail — every surface that claims a capability reads this one field.
+ *
+ * WHAT THIS ASSERTS, and why each half is here:
+ * 1. Every profile resolves to a capability 1-5. A 0 or a null means some surface will render a
+ * tier that does not exist; the agent saw exactly that as "the Research Gallery shows as
+ * capability 0".
+ * 2. A profile that composes anything derives cap-4, or cap-5 if it composes a cap-4 — and an
+ * EXPLICIT type must AGREE with that. The declaration stays authoritative; it is not
+ * overridden, it is checked. A meta that contradicts the structure means the meta or the
+ * composes list is stale, and choosing silently between them is how a tier rots.
+ *
+ * ★ TWO TRAPS THIS GATE EXISTS BECAUSE OF, both found by running the rule before writing it:
+ * - `composes` is PER-PROFILE at source and HOISTED TO THE BUNDLE in the manifest. Deriving from
+ * the bundle row applies a composite's edges to its plain sibling leaves: gospel-resources has one
+ * composite and four single leaves, and all four derived as cap-4. 11 false failures.
+ * - refs appear in TWO FORMS in the same field: 'bundle/profile' and a bare 'bundle'. Resolving
+ * only the first demoted plugin-studio from 5 to 4.
+ * With both handled: 10 composing profiles, 10 agree, 0 disagree, on the tree as it stands.
+ *
+ * An UNRESOLVED ref is REPORTED, never counted as "not cap-4": a typo and a genuine non-composite
+ * child look identical from here, and quietly treating one as the other is how a tier silently drops.
+ *
+ * Headless, no browser. Exit 0 pass · 1 fail.
+ */
+import { readFileSync, existsSync } from 'node:fs'
+import { resolve, dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { TREE_NAME } from './_tree-name.mjs'
+
+const TAG = 'CAPABILITY-DERIVED'
+const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
+const PT = { 'nested-mfe-spa': 5, 'nested-elements-mfe': 4, monolithic: 3, parameterized: 2, single: 1 }
+const fails = []
+
+const mfPath = join(ROOT, 'public', TREE_NAME, 'manifest.json')
+if (!existsSync(mfPath)) { console.log(`${TAG}: SKIP — no built tree at ${TREE_NAME}.`); process.exit(0) }
+const manifest = JSON.parse(readFileSync(mfPath, 'utf8'))
+const bundles = manifest.bundles || []
+
+// ── 1. every profile has a real capability ─────────────────────────────────────────────────────
+let checked = 0
+for (const b of bundles) for (const p of b.profiles || []) {
+ checked++
+ const cap = PT[p.type]
+ if (!cap) fails.push(`${b.bundle}/${p.profile}: type ${JSON.stringify(p.type)} is not one of the five tiers — every surface that shows a capability reads this.`)
+}
+
+// ── 2. a declared tier must agree with the edges it declares ────────────────────────────────────
+const readMeta = (bundle, profile) => {
+ const f = join(ROOT, 'plugin-designs', bundle, `${profile}.meta.json`)
+ try { return existsSync(f) ? JSON.parse(readFileSync(f, 'utf8')) : null } catch { return null }
+}
+const byKey = new Map(), byName = new Map()
+for (const b of bundles) for (const p of b.profiles || []) {
+ byKey.set(`${b.bundle}/${p.profile}`, p.type || null)
+ if (!byName.has(b.bundle)) byName.set(b.bundle, p.type || null)
+ if (!byName.has(p.profile)) byName.set(p.profile, p.type || null)
+}
+const resolveRef = (ref) => (ref.includes('/') ? byKey.get(ref) : byName.get(ref))
+const unresolved = []
+let composing = 0
+for (const b of bundles) for (const p of b.profiles || []) {
+ const meta = readMeta(b.bundle, p.profile)
+ const own = Array.isArray(meta?.composes) ? meta.composes.map(String).filter(Boolean) : []
+ if (!own.length) continue
+ composing++
+ const kinds = own.map((ref) => { const k = resolveRef(ref); if (k === undefined) unresolved.push(`${b.bundle}/${p.profile} -> ${ref}`); return k })
+ const derived = kinds.some((k) => k === 'nested-elements-mfe') ? 'nested-mfe-spa' : 'nested-elements-mfe'
+ if (p.type !== derived) {
+ fails.push(`${b.bundle}/${p.profile}: declares "${p.type}" but its own composes edges derive "${derived}" — one of the two is stale.`)
+ }
+}
+
+console.log(` · ${checked} profile(s) across ${bundles.length} bundle(s); ${composing} declare composes edges`)
+if (unresolved.length) {
+ console.log(` · ${unresolved.length} composes ref(s) resolve to no known bundle/profile. Reported, NOT treated as "not a composite" — a typo and a plain child look the same from here:`)
+ for (const u of unresolved) console.log(` ${u}`)
+}
+if (fails.length) {
+ console.error(`\n${TAG}: FAIL`)
+ for (const f of fails) console.error(` ✗ ${f}`)
+ process.exit(1)
+}
+console.log(`${TAG}: PASS — every profile lands on a real tier 1-5, and every declared tier agrees with its own composes edges.`)
diff --git a/internal/check-studio-rail.mjs b/internal/check-studio-rail.mjs
index c3f640ff..22d0312d 100644
--- a/internal/check-studio-rail.mjs
+++ b/internal/check-studio-rail.mjs
@@ -5,9 +5,9 @@
* The agent's ask (2026-09-26): one FOLDER per BUNDLE with its ELEMENTS inside, bundles sorted by
* capability, highest first. Runs the studio's REAL treeSections / compositionTrees / rowsFromManifest
* (loaded through Vite, no browser) against the BUILT manifest and asserts:
- * - PRODUCTION bundles (manifest bundle.stage, from their .meta.json) sit in capability sections that run
- * CAP 5 → 1, each in the section of its HIGHEST capability, once; every EXPERIMENTAL bundle sits in one
- * final Experimental section, highest capability first (the agent, 2026-09-27);
+ * - EVERY bundle sits in a capability section (they run CAP 5 → 1), in the section of its HIGHEST
+ * capability, once; there is no Experimental section: every plugin is capability 1-5 (the agent,
+ * 2026-09-27), and a non-production bundle says so on its own rows;
* - bundles within a section are in name order;
* - a bundle with several elements is ONE folder holding all of them; a single-element bundle is its element;
* - every studio row has exactly ONE canonical node (one identity per plugin; parts only target it).
@@ -39,10 +39,8 @@ try {
ctx.compositionTrees = comp.computed.compositionTrees.call(ctx)
const secs = comp.computed.treeSections.call(ctx)
const stageOf = new Map(manifest.bundles.map((b) => [b.bundle, b.stage === 'production' ? 'production' : 'experimental']))
- const expIdx = secs.findIndex((s) => s.id === 'stage-experimental')
- if (expIdx !== -1 && expIdx !== secs.length - 1) fail('the Experimental section is not last')
- const capSecs = secs.filter((s) => s.id !== 'stage-experimental')
- const caps = capSecs.map((s) => s.cap)
+ for (const s of secs) if (!(s.cap >= 1 && s.cap <= 5)) fail(`section ${s.id} is not a capability 1-5 section (cap ${s.cap})`)
+ const caps = secs.map((s) => s.cap)
if (caps.join() !== [...caps].sort((a, b) => b - a).join()) fail(`sections not CAP 5 → 1: ${caps.join(',')}`)
const byBundle = new Map()
for (const r of ctx.rows) { if (!byBundle.has(r.bundle)) byBundle.set(r.bundle, []); byBundle.get(r.bundle).push(r) }
@@ -50,21 +48,14 @@ try {
for (const s of secs) {
const tops = s.nodes.filter((n) => n.depth === 0)
const names = tops.map((n) => (n.folder ? n.label : ctx.rows.find((r) => r.key === n.target)?.bundle))
- const isExp = s.id === 'stage-experimental'
- const topCap = (b) => Math.max(...(byBundle.get(b) || []).map((r) => r.cap))
- const want = isExp ? [...names].sort((a, b) => topCap(b) - topCap(a) || a.localeCompare(b)) : [...names].sort((a, b) => a.localeCompare(b))
- if (names.join() !== want.join()) fail(`${isExp ? 'Experimental' : 'CAP ' + s.cap}: bundles not in order (${names.join(', ')})`)
- for (const b of names) {
- const st = stageOf.get(b)
- if (isExp && st === 'production') fail(`production bundle ${b} is filed under Experimental`)
- if (!isExp && st !== 'production') fail(`experimental bundle ${b} is filed under CAP ${s.cap}, not Experimental`)
- }
+ const want = [...names].sort((a, b) => a.localeCompare(b))
+ if (names.join() !== want.join()) fail(`CAP ${s.cap}: bundles not in order (${names.join(', ')})`)
for (const [i, n] of tops.entries()) {
const b = names[i]; const rows = byBundle.get(b) || []
if (seen.has(b)) fail(`bundle ${b} appears twice (CAP ${seen.get(b)} and CAP ${s.cap})`)
seen.set(b, s.cap)
const top = Math.max(...rows.map((r) => r.cap))
- if (!isExp && top !== s.cap) fail(`bundle ${b} (highest CAP ${top}) filed under CAP ${s.cap}`)
+ if (top !== s.cap) fail(`bundle ${b} (highest CAP ${top}) filed under CAP ${s.cap}`)
if (rows.length > 1) {
if (!n.folder) fail(`bundle ${b} has ${rows.length} elements but is not a folder`)
const kids = s.nodes.filter((k) => k.parent === n.id && k.canonical).map((k) => k.target).sort()
@@ -79,4 +70,4 @@ try {
console.log(`STUDIO-RAIL: ${byBundle.size} bundle(s) (${nProd} production, ${byBundle.size - nProd} experimental), ${ctx.rows.length} element(s), ${secs.length} section(s); folders: ${secs.flatMap((s) => s.nodes.filter((n) => n.folder).map((n) => n.label + '(' + n.kids + ')')).join(', ')}`)
} finally { await server.close() }
if (bad) { console.log(`STUDIO-RAIL: FAIL — ${bad} problem(s).`); process.exit(1) }
-console.log('STUDIO-RAIL: PASS — one folder per bundle with its elements inside; production bundles grouped by their highest capability (5 → 1) in name order, experimental bundles in one final Experimental section; one identity per plugin.')
+console.log('STUDIO-RAIL: PASS — one folder per bundle with its elements inside; every bundle grouped by its highest capability (5 → 1) in name order, no Experimental section; one identity per plugin.')
diff --git a/internal/check-universal-props.mjs b/internal/check-universal-props.mjs
index a767b9bf..f7ef8837 100644
--- a/internal/check-universal-props.mjs
+++ b/internal/check-universal-props.mjs
@@ -58,6 +58,14 @@ const BUNDLES = [
]
const ALLOW = {
'prayer-walk-planner:ENG-PIN': 'keyless by design: always MapLibre, needs no token (index.js header)',
+ 'regions-dashboard:LOC-ENUS:plugin-designs/regions-dashboard/src/RegionCountryMap.vue':
+ 'HELD DECISION, not an oversight: the map popup formats population as fixed en-US on purpose. '
+ + 'Their own site renders en-US numerals, which is the evidence on the card '
+ + 'NUMERALS-REVERTED-POPUP-TO-FIXED-EN-US-AND-THEIR-OWN-CONVENTION-IS-EVIDENCE, and '
+ + '_chk-numerals-held-format-does-not-vary-by-locale ASSERTS it does not vary by locale -- so '
+ + 'making it locale-derived would break a gate that exists to keep it fixed. Scoped to this FILE: '
+ + 'the rest of regions-dashboard is still answerable for a new en-US. Revisit only if the agent '
+ + 'rules the numerals policy.',
'all-people-groups:LIST-DSM': 'draws the IMB dataset (all 12,379 groups), not the DOXA list: a list there is Tier B (SPEC-PEOPLE-GROUP-LIST-PROP.md)',
}
@@ -237,7 +245,13 @@ for (const b of BUNDLES.filter((x) => !ONLY || ONLY.includes(x))) {
// bundles when the sweep first reached it, which is a gate telling four owners that the library
// is broken because it read the definition as a use.
if (r.id === 'LIST-DSM' && /library\/api\/DataSourceManager\.js$/.test(f.replace(/\\/g, '/'))) continue
- const allowed = ALLOW[`${b}:${r.id}`]
+ // ★ ALLOW IS NOW SITE-SCOPABLE, because bundle-wide was the wrong granularity for a HELD
+ // decision. regions-dashboard's one LOC-ENUS is a deliberate fixed en-US in the map popup, with
+ // a card and DX-life's own convention as evidence. Allowing it at `bundle:RULE` would also have
+ // silenced any FUTURE operative en-US anywhere in that bundle -- clearing a column by going
+ // blind. A `bundle:RULE:file` key exempts the one file that earned it and leaves the rest of the
+ // bundle answerable.
+ const allowed = ALLOW[`${b}:${r.id}:${relative(ROOT, f)}`] || ALLOW[`${b}:${r.id}`]
if (allowed) { if (matrix[b][r.prop] === 'ok') matrix[b][r.prop] = 'allowed'; continue }
findings.push({ b, rule: r.id, prop: r.prop, warn: !!r.warn, where: `${relative(ROOT, f)}:${lineOf(src, idx)}`, text: src.slice(idx, idx + 70).replace(/\s+/g, ' ').trim() })
matrix[b][r.prop] = r.warn ? (matrix[b][r.prop] === 'BYPASS' ? 'BYPASS' : 'warn') : 'BYPASS'
diff --git a/internal/generate-tree.mjs b/internal/generate-tree.mjs
index 2d950d0f..0fef8c81 100644
--- a/internal/generate-tree.mjs
+++ b/internal/generate-tree.mjs
@@ -396,6 +396,48 @@ function composesAMapLeaf(dir, profileName, file) {
* composesAMapLeaf reads (see its point 1) but returns the author's data itself rather than
* a map-ness boolean derived from it — the manifest needs the edges, not just the verdict.
*/
+/**
+ * Derive the composition tier from the `composes` edges, and refuse to disagree with an author.
+ *
+ * ★ WHY DERIVE AT ALL (the agent, 2026-09-27). Tiers 3-5 were AUTHORED ONLY — discover.mjs infers just
+ * `single` and `parameterized`, so a composite with no meta.json shipped as capability 1. That is not
+ * hypothetical: research-dashboard did exactly that while nesting ten views, and the warning in
+ * detectProfileType exists because of it. A missing file is indistinguishable from a deliberate choice.
+ *
+ * THE RULE, measured against all 25 bundles before it was written: a profile that composes anything is
+ * cap-4 (nested-elements-mfe); if any of the things it composes is itself cap-4, it is cap-5
+ * (nested-mfe-spa). Keyed per PROFILE, and refs are accepted in BOTH published forms — 'bundle/profile'
+ * (doxa-maps-spa) and a bare 'bundle' (plugin-studio). Getting either of those wrong is not academic:
+ * keying on the bundle derived 11 false tiers, and resolving only the slashed form demoted plugin-studio
+ * from 5 to 4. With both handled: 10 composing profiles, 10 agree, 0 disagree.
+ *
+ * AN EXPLICIT type STAYS AUTHORITATIVE AND MUST AGREE. It is not overridden — it is CHECKED. A
+ * declaration that contradicts the structure is the interesting case: either the meta is stale or the
+ * composes list is, and both are worth a loud build failure rather than a silent pick between them.
+ *
+ * An UNRESOLVED ref is reported separately and never silently treated as "not cap-4", because a typo in
+ * composes and a genuinely non-composite child look identical from here.
+ */
+export function deriveComposedTiers(bundles) {
+ const byKey = new Map(), byBundle = new Map()
+ for (const b of bundles) for (const p of b.profiles || []) {
+ byKey.set(`${b.bundle}/${p.profile}`, p.type || null)
+ if (!byBundle.has(b.bundle)) byBundle.set(b.bundle, p.type || null)
+ if (!byBundle.has(p.profile)) byBundle.set(p.profile, p.type || null)
+ }
+ const resolve = (ref) => (ref.includes('/') ? byKey.get(ref) : byBundle.get(ref))
+ const conflicts = [], unresolved = []
+ for (const b of bundles) for (const p of b.profiles || []) {
+ const own = p.composesOwn
+ if (!own || !own.length) continue
+ const kinds = own.map((ref) => { const k = resolve(ref); if (k === undefined) unresolved.push(`${b.bundle}/${p.profile} -> ${ref}`); return k })
+ const derived = kinds.some((k) => k === 'nested-elements-mfe') ? 'nested-mfe-spa' : 'nested-elements-mfe'
+ if (!p.type) { p.type = derived; continue }
+ if (p.type !== derived) conflicts.push(`${b.bundle}/${p.profile}: meta.json declares "${p.type}" but its composes edges derive "${derived}"`)
+ }
+ return { conflicts, unresolved }
+}
+
function readComposes(dir, profileName) {
try {
const metaPath = join(dir, `${profileName}.meta.json`)
@@ -1316,6 +1358,11 @@ function scanBundles() {
// GENERATOR-INTERNAL: stripped before the manifest is written (see writeManifestAndHtml)
// so the published manifest shape is unchanged.
extraConfig: readProfileConfig(dir, profileName),
+ // Generator-internal, stripped with extraConfig at manifest assembly: the profile's OWN
+ // composes. The published manifest hoists composes to the BUNDLE, which is right for its
+ // consumers but useless for deriving a tier — a bundle's composite and its plain leaves would
+ // all inherit the same edges. The tier is a property of the profile that declares them.
+ composesOwn: readComposes(dir, profileName),
...rest,
}
})
@@ -1362,6 +1409,24 @@ function readDestinations() {
}
function writeManifestAndHtml(root, bundles, tokenUrlExpr, bakedToken) {
+ // ── DERIVE THE COMPOSITION TIER FROM THE EDGES, BEFORE ANYTHING READS THE MANIFEST ──────────
+ // Every consumer downstream — the gallery's capFor, the studio rail, check:composite-embed-knobs —
+ // trusts profile.type. Deriving here means a composite can no longer ship understated because its
+ // author forgot a meta.json, and a stale meta can no longer disagree with the structure in silence.
+ {
+ const { conflicts, unresolved } = deriveComposedTiers(bundles)
+ if (unresolved.length) {
+ console.warn(`[tree] ${unresolved.length} composes ref(s) resolve to no known bundle/profile — a typo here silently weakens a tier:`)
+ for (const u of unresolved) console.warn(` ${u}`)
+ }
+ if (conflicts.length) {
+ console.error('[tree] composition tier CONFLICT — a profile\'s meta.json disagrees with its own composes edges:')
+ for (const c of conflicts) console.error(` ${c}`)
+ console.error(' Fix the meta.json or the composes list; the generator will not choose between them.')
+ process.exit(1)
+ }
+ }
+
const manifest = {
schema: 'doxa-maps-manifest@1',
generatedFrom: 'plugin-designs/*/*.vue',
@@ -1386,7 +1451,7 @@ function writeManifestAndHtml(root, bundles, tokenUrlExpr, bakedToken) {
// Consumers read the manifest as a contract; a new key would be a silent schema change.
bundles: bundles.map((b) => ({
...b,
- profiles: b.profiles.map(({ extraConfig, ...p }) => p),
+ profiles: b.profiles.map(({ extraConfig, composesOwn, ...p }) => p),
})),
}
mkdirSync(root, { recursive: true })
diff --git a/library/components/legends/LegendProgressBar.vue b/library/components/legends/LegendProgressBar.vue
index 9e18e487..4beec56e 100644
--- a/library/components/legends/LegendProgressBar.vue
+++ b/library/components/legends/LegendProgressBar.vue
@@ -67,9 +67,19 @@ useShadowStyles(`
part of the legend chrome. Dark is the STL default; .lpb-light mirrors the
.stl-panel[data-theme="light"] palette. ── */
.lpb{position:relative;display:flex;align-items:center;gap:8px;min-width:0;width:100%;box-sizing:border-box;}
-.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:10px;border-radius:5px;overflow:hidden;background:rgba(110,118,129,0.25);}
+/* Hit area vs paint (WCAG 2.5.8, rega at 390: every segment was a 10px-tall button). The track is 24px
+ tall so each segment button is a 24px target, but it paints only a 10px band: the track's background is
+ a centred 10px strip and each segment clips its colour to its content box inside 7px of padding. The
+ -7px margins keep the legend's layout exactly 10px tall, as before. */
+.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:24px;margin:-7px 0;overflow:hidden;}
+/* The grey band, rounded, under the segments; the first and last segment round the bar's two ends (an
+ elliptical radius 5px/12px leaves a 5px curve on the 10px painted band inside the 7px padding). */
+.lpb-track::before{content:'';position:absolute;left:0;right:0;top:7px;height:10px;border-radius:5px;background:rgba(110,118,129,0.25);}
+.lpb-seg{position:relative;}
+.lpb-span:first-child .lpb-seg:first-child{border-top-left-radius:5px 12px;border-bottom-left-radius:5px 12px;}
+.lpb-seg-rest,.lpb-span:last-child .lpb-seg:last-child{border-top-right-radius:5px 12px;border-bottom-right-radius:5px 12px;}
.lpb-span{display:flex;height:100%;min-width:0;flex:0 0 auto;}
-.lpb-seg{appearance:none;-webkit-appearance:none;border:none;margin:0;padding:0;height:100%;display:block;flex:0 0 auto;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;transition:filter 0.12s,box-shadow 0.12s;}
+.lpb-seg{appearance:none;-webkit-appearance:none;border:none;margin:0;padding:7px 0;box-sizing:border-box;background-clip:content-box !important;height:100%;display:block;flex:0 0 auto;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;transition:filter 0.12s,box-shadow 0.12s;}
.lpb-seg-rest{flex:1 1 auto;}
.lpb-seg:focus{outline:none;}
/* Hover / keyboard-focus / active highlight — brighter + a 1px inset ring (inset
@@ -180,8 +190,10 @@ function hide() { activeKey.value = null }
segments are drawn left→right, so for prayer the full-coverage colour
fills the span from the left and the has-prayer colour completes it. -->
+
for a negative control. Negative-tested: a bundle built before 319064a1 FAILS on both engines.",
"check:capability-citations": "a doc that names a bundle as \"capability N\" must match manifest.json, which is what the BUILD computed. README captioned doxa-vision-card as cap-1; it ships 2 instances so it is parameterized (cap-2). The SAME error was fixed once in verify-5-capabilities.mjs and never reached the README — two copies of one fact, nothing forcing agreement.",
+ "check:capability-derived": "every bundle lands on a real capability 1-5, never 0, and a DECLARED tier must agree with the composes edges it declares. Tiers 3-5 used to be authored only - discover.mjs infers single and parameterized and nothing else - so a composite whose author forgot a meta.json shipped as capability 1; research-dashboard did exactly that while nesting ten views, and a missing file is indistinguishable from a deliberate choice, so nothing failed. The declaration stays authoritative: it is CHECKED, not overridden, because a meta that contradicts the structure means one of the two is stale and picking silently is how a tier rots. Two traps found by running the rule BEFORE writing it: composes is per-PROFILE at source but hoisted to the BUNDLE in the manifest, so deriving from the bundle row gave a composite's edges to its plain sibling leaves (11 false failures on correct data); and refs come in two forms in one field, 'bundle/profile' and a bare 'bundle', so resolving only the first demoted plugin-studio from 5 to 4. With both handled: 10 composing profiles, 10 agree, 0 disagree. An unresolved ref is REPORTED, never counted as 'not a composite' - a typo and a genuine plain child look identical from here. Negative-tested on the research-dashboard shape (a composite declared single).",
"check:capability-exemplars": "REGENERATES and content-compares (not mtimes: a full build rewrites manifest.json every time, so an mtime rule would cry wolf after every build and get switched off).",
"check:catalog-identity": "proves the LIVE server serves the CATALOG’s exact bytes, by hashing each live bundle and comparing sha384 against the manifest’s own integrity field. An HTTP 200 cannot do this: /app/doxa-maps/ returns 200 with a 2872-byte HTML SIGN-IN PAGE. Network-dependent, so NOT in build:unlocked and NOT in CI; SKIPs cleanly when unreachable. CATALOG= also requires the live manifest to match a catalog checkout.",
"check:cdn-pins": "every runtime third-party script/stylesheet in shipped code is an exact version listed in library/constants/cdn.js with its SRI hash (no floating ranges). --online re-fetches each and compares the hash.",
diff --git a/plugin-designs/doxa-maps-spa/doxa-maps-spa.meta.json b/plugin-designs/doxa-maps-spa/doxa-maps-spa.meta.json
index 864a240f..a247593e 100644
--- a/plugin-designs/doxa-maps-spa/doxa-maps-spa.meta.json
+++ b/plugin-designs/doxa-maps-spa/doxa-maps-spa.meta.json
@@ -1,5 +1,5 @@
{
- "_comment": "Declares the composability tier. 'nested-mfe-spa' (display alias 'app-shell') = Capability-5: this plugin is the OUTER app shell of the dx-maps app, the Research Gallery. Its single element renders a vertical-tab rail whose tabs each mount a REAL, independently embeddable plugin: the shell loads that bundle's script (URL from manifest.json) and creates its registered custom element in the pane. There is no iframe mode. Tab order and grouping live in NAV_MODEL/TAB_DEFS in doxa-maps-spa.vue; `composes` below lists the seven curated tabs as bundle/profile and must be kept in step with TAB_DEFS (check:composite reads it; the studio's \"Inside doxa-maps-spa\" tree shows it). Bundles not placed in a section appear under Experimental automatically. This explicit meta is the ONLY way the Cat-5 tier is declared (discover.mjs never infers it).",
+ "_comment": "Declares the composability tier. 'nested-mfe-spa' (display alias 'app-shell') = Capability-5: this plugin is the OUTER app shell of the dx-maps app, the Research Gallery. Its single element renders a vertical-tab rail whose tabs each mount a REAL, independently embeddable plugin: the shell loads that bundle's script (URL from manifest.json) and creates its registered custom element in the pane. There is no iframe mode. Tab order and grouping live in NAV_MODEL/TAB_DEFS in doxa-maps-spa.vue; `composes` below lists the seven curated tabs as bundle/profile and must be kept in step with TAB_DEFS (check:composite reads it; the studio's \"Inside doxa-maps-spa\" tree shows it). Bundles not placed in a section appear under Experimental automatically. The tier is now DERIVED from the `composes` edges below at tree generation (a profile that composes anything is cap-4; one that composes a cap-4 is cap-5), and this explicit `type` is CHECKED against that derivation rather than being the only source: if the two disagree the build fails, because one of them is stale. discover.mjs still never infers tiers 3-5 on its own.",
"type": "nested-mfe-spa",
"composes": [
"dashboard-plus-grid/dashboard-plus-grid",
diff --git a/plugin-designs/plugin-studio/plugin-studio.vue b/plugin-designs/plugin-studio/plugin-studio.vue
index 41c3fc3a..2382e795 100644
--- a/plugin-designs/plugin-studio/plugin-studio.vue
+++ b/plugin-designs/plugin-studio/plugin-studio.vue
@@ -616,23 +616,19 @@ export default defineComponent({
const treeOf = new Map(this.compositionTrees.map(t => [t.nodes[0].selects, t]))
const byBundle = new Map()
for (const r of this.rows) { if (!byBundle.has(r.bundle)) byBundle.set(r.bundle, []); byBundle.get(r.bundle).push(r) }
- // ★ PRODUCTION vs EXPERIMENTAL (the agent, 2026-09-27): a public plugin that does not yet work as a
- // production map is listed under Experimental until it does. The stage is the bundle's own
- // .meta.json "stage", carried by the manifest, so this file keeps no list and the gallery reads the
- // same field. Production bundles keep their capability sections; every experimental bundle goes in
- // one Experimental section at the end, highest capability first.
- const bundles = [...byBundle.entries()].map(([bundle, rows]) => ({ bundle, rows, cap: Math.max(...rows.map(r => r.cap)), stage: rows[0].stage }))
- const experimental = bundles.filter(b => b.stage !== 'production').sort((x, y) => y.cap - x.cap || x.bundle.localeCompare(y.bundle))
- const production = bundles.filter(b => b.stage === 'production')
- const groups = [5, 4, 3, 2, 1].map(cap => ({ cap, group: production.filter(b => b.cap === cap).sort((x, y) => x.bundle.localeCompare(y.bundle)) }))
- groups.push({ cap: 'experimental', group: experimental })
+ // ★ EVERY PLUGIN IS CAPABILITY 1-5 (the agent, 2026-09-27, replacing the Experimental section he
+ // asked for earlier the same day): each bundle sits in the section of its highest capability. A bundle
+ // whose .meta.json "stage" is not production says so on its own rows instead; the gallery reads the
+ // same field.
+ const bundles = [...byBundle.entries()].map(([bundle, rows]) => ({ bundle, rows, cap: Math.max(...rows.map(r => r.cap)) }))
+ const groups = [5, 4, 3, 2, 1].map(cap => ({ cap, group: bundles.filter(b => b.cap === cap).sort((x, y) => x.bundle.localeCompare(y.bundle)) }))
return groups.map(({ cap, group }) => {
if (!group.length) return null
const nodes = []
const pushElement = (r, depth, parent) => {
const t = treeOf.get(r.key)
nodes.push({
- id: r.key, parent, depth, label: r.label, desc: r.tag, icon: r.icon, target: r.key, canonical: true,
+ id: r.key, parent, depth, label: r.label, desc: (r.stage === 'production' ? '' : this.tr('stageExperimental') + ' · ') + 'cap ' + r.cap + ' · ' + r.tag, icon: r.icon, target: r.key, canonical: true,
kids: t ? t.nodes[0].kids : 0, title: r.label + ' · <' + r.tag + '> · CAP ' + r.cap + ' ' + this.tr('cap' + r.cap)
})
if (!t) return
@@ -660,7 +656,6 @@ export default defineComponent({
})
for (const r of rows) pushElement(r, 1, fid)
}
- if (cap === 'experimental') return { id: 'stage-experimental', cap: 0, label: this.tr('stageExperimental'), title: this.tr('stageExperimental'), icon: 'layers', nodes }
return { id: 'cap-' + cap, cap, label: this.tr('cap' + cap), title: this.tr('cap' + cap) + ' · ' + (MACHINE_TYPE[cap] || ''), icon: CAP_ICON[cap] || 'embed', nodes }
}).filter(Boolean)
},
diff --git a/plugin-designs/plugin-studio/src/StudioTree.vue b/plugin-designs/plugin-studio/src/StudioTree.vue
index 36c9c90e..ddbaac9e 100644
--- a/plugin-designs/plugin-studio/src/StudioTree.vue
+++ b/plugin-designs/plugin-studio/src/StudioTree.vue
@@ -36,10 +36,10 @@
- {{ s.cap }}
+ {{ s.short || s.cap }}
@@ -70,7 +70,7 @@
@focus="focusId = item.id">
- CAP {{ item.cap }}
+ {{ item.badge }}
{{ item.label }}
{{ item.count }}
@@ -209,7 +209,7 @@ export default defineComponent({
if (this.kept && !nodes.length) continue
out.push({
id: 'sec:' + s.id, section: true, sectionId: s.id, level: 1, depth: 0, expandable: true,
- cap: s.cap, label: s.label, title: s.title || '', count: s.nodes.filter(n => n.depth === 0).length
+ cap: s.cap, badge: s.badge || 'CAP ' + s.cap, label: s.label, title: s.title || '', count: s.nodes.filter(n => n.depth === 0).length
})
if (!this.kept && this.secClosed[s.id]) continue
for (const n of nodes) {
@@ -273,11 +273,11 @@ export default defineComponent({
},
setRow(id, el) { if (!this._rows) this._rows = {}; if (el) this._rows[id] = el; else delete this._rows[id] },
// Choosing a plugin opens it in the pane (through the layout's pick, which also closes the mobile
- // drawer) and unfolds its parts — you asked for that plugin, so show what it is made of. Only the
- // twisty or ← folds. A part with no page of its own opens the composite it lives in.
+ // drawer). It does NOT unfold the row: the agent clicks UPG 100 List to see UPG 100 List, not every
+ // element inside it. Only the twisty (or → / ←) folds and unfolds. A part with no page of its own
+ // opens the composite it lives in.
activate(item) {
if (item.section) { this.toggle(item); return }
- if (item.expandable && !this.isOpen(item)) this.setOpen(item, true)
if (!item.target) return
if (this.pick) this.pick(item.target); else this.$emit('select', item.target)
},
@@ -333,7 +333,16 @@ export default defineComponent({
row = s.nodes.find(n => isCanonical(n) && n.target === this.active)
if (row) { if (this.secClosed[s.id]) { this.secClosed = { ...this.secClosed, [s.id]: false }; this.saveState() } break }
}
- // A plugin inside a BUNDLE FOLDER: open the folder(s) above it, or it cannot be seen.
+ // A closed bundle folder whose own target IS this plugin already stands for it: highlight the folder,
+ // don't unfold it. Otherwise open the folder(s) above the plugin, or it cannot be seen.
+ if (row) {
+ for (let p = row.parent; p;) {
+ const pn = this.byId.get(p)
+ if (!pn) break
+ if (pn.target === this.active && !this.nodeOpen[p]) { row = pn; break }
+ p = pn.parent
+ }
+ }
if (row) {
let p = row.parent, opened = false
while (p) { if (!this.nodeOpen[p]) { this.nodeOpen = { ...this.nodeOpen, [p]: true }; opened = true } p = this.byId.get(p) && this.byId.get(p).parent }
diff --git a/plugin-designs/rudder-voice/rudder-voice.vue b/plugin-designs/rudder-voice/rudder-voice.vue
index 41f1538f..1d477ab9 100644
--- a/plugin-designs/rudder-voice/rudder-voice.vue
+++ b/plugin-designs/rudder-voice/rudder-voice.vue
@@ -287,6 +287,7 @@ function applyStatus(s) {
if (s.runtime) runtime.value = s.runtime
if (s.model) model.value = s.model
if (s.audio) audioMode.value = s.audio
+ if (typeof s.expected === 'boolean') engineExpected.value = s.expected
// A browser session's state is this page's own; the server's shared status never reports it.
if (typeof s.recording === 'boolean' && audioMode.value !== 'browser') recording.value = s.recording
}
@@ -307,6 +308,7 @@ function connect() {
engine.value = 'offline'
if (audioMode.value !== 'browser') recording.value = false
logc('events disconnected; retrying in 3s')
+ fetchStatus()
try { es.close() } catch {}
retry = setTimeout(connect, 3000)
}
@@ -377,6 +379,9 @@ const WORKLET = `class RudderPcm16k extends AudioWorkletProcessor {
}
registerProcessor('rudder-pcm16k', RudderPcm16k)`
const audioMode = ref('local')
+// The server is set up to run the engine (it reports expected=true while the engine is down): an absent
+// engine there means "starting or failed to set up", not "this only runs on your computer".
+const engineExpected = ref(false)
let sock = null, media = null, actx = null
async function startBrowser() {
@@ -540,7 +545,13 @@ function onKey(ev) {
if (ev.ctrlKey && ev.shiftKey && ev.code === 'Space') { ev.preventDefault(); toggle() }
}
+// The status also by plain request: a proxy that buffers the event stream (rega's harness did) must not
+// leave the page guessing whether this server is meant to have an engine.
+async function fetchStatus() {
+ try { const r = await fetch(apiBase.value + '/status'); if (r.ok) applyStatus(await r.json()) } catch {}
+}
onMounted(() => {
+ fetchStatus()
connect()
refreshDevices()
window.addEventListener('keydown', onKey)
@@ -577,7 +588,7 @@ const statusText = computed(() => ({
starting: 'Loading speech model… (about 20 s the first time)',
ready: recording.value ? 'Listening' : 'Ready',
error: 'Engine error',
- offline: onOwnComputer.value ? 'Engine offline' : 'Runs on your computer',
+ offline: onOwnComputer.value ? 'Engine offline' : engineExpected.value ? 'Not running yet' : 'Runs on your computer',
}[engine.value] || engine.value))
@@ -671,10 +682,13 @@ const statusText = computed(() => ({
“{{ chosen.label }}” is muted in your system sound settings — Rudder won't hear anything until it's unmuted.
{{ deviceError }}
- No microphone found. Connect a headset or turn on your computer's microphone in Sound settings.
+ No microphone found. Connect a headset or turn on your computer's microphone in Sound settings.
Also available but switched off in Sound settings: {{ offCards.join(', ') }}. Turn it on there to use it here.
{{ notice }}
+
+ The voice engine on this server isn't running yet (it is starting, or it failed to set up). Try again in a minute.
+
Voice to text runs on your own computer, not on this server: it listens to the microphone of the machine
the engine runs on. Open the Plugin Studio on your computer (bun run dev) or use the Rudder desktop app.
diff --git a/public/dx-maps/cities-internet/cities-internet.js b/public/dx-maps/cities-internet/cities-internet.js
index a896782d..26ee5193 100644
--- a/public/dx-maps/cities-internet/cities-internet.js
+++ b/public/dx-maps/cities-internet/cities-internet.js
@@ -464,9 +464,13 @@
`,t}function o1(e={}){const{getMap:t=()=>null,getActivePinColor:o=null}=e,n=Ce("uiStore");let a=null,r=null;const i=o??Ce("getActivePinColor",null);let s=or;function l(){const p=t();p&&(p.getLayer(ho)||p.getSource("language-families")&&p.addLayer({id:ho,type:"circle",source:"language-families",paint:{"circle-radius":["interpolate",["linear"],["zoom"],0,3,2,3.5,4,4,5,5,6,6.5,7,8,8,10,10,14,12,18,14,22],"circle-color":s,...B$(p),"circle-opacity":1,"circle-stroke-width":2,"circle-stroke-color":"#FFFFFF","circle-stroke-opacity":1},filter:["==",["get","uniqueId"],"__none__"]}))}function u(){a||(a=t1(or))}function c(p,d){const v=t();v&&(u(),r&&r.remove(),r=new(D$(v)).Marker({element:a,anchor:"bottom",offset:[0,0]}).setLngLat([p,d]).addTo(v))}function g(){r&&(r.remove(),r=null)}function h(p){var S,L;const d=t();if(!d)return;if(p&&(s=(i==null?void 0:i(p.properties))??or),l(),d.getLayer(ho)&&(d.setPaintProperty(ho,"circle-color",s),Yu(d)&&d.setPaintProperty(ho,"circle-emissive-strength",1)),!p){d.getLayer(ho)&&d.setFilter(ho,["==",["get","uniqueId"],"__none__"]),g();return}const v=((S=p.properties)==null?void 0:S.uniqueId)||"__none__",C=(L=p.geometry)==null?void 0:L.coordinates;if(!C)return;d.getLayer(ho)&&d.setFilter(ho,["==",["get","uniqueId"],v]);const[x,k]=C;c(x,k)}qe(()=>n.selectedPeopleGroup,p=>{h(p)});function y(){l()}function _(){g(),a=null}return{initialize:y,cleanup:_,updateSelectedPin:h}}const op="peoplePraying",nr=144,No={noPrayer:"#f03b2d",hasPrayer:"#f0c64a",fullPrayer:"#22c55e"},Ko=No,Di={noPrayer:"Needs Prayer",hasPrayer:"Has Prayer",fullPrayer:"Has Full Prayer Coverage"},n1=Di;function ar(e){var n;const t=(e==null?void 0:e.peoplePraying)??((n=e==null?void 0:e._raw)==null?void 0:n.people_praying)??null,o=Number(t)||0;return o>=nr?"fullPrayer":o>0?"hasPrayer":"noPrayer"}function Bi(e){return No[ar(e)]}function np(e){var o;const t=(e==null?void 0:e.peoplePraying)??((o=e==null?void 0:e._raw)==null?void 0:o.people_praying)??null;return t!==null&&t>0}function ap(e){return ar(e)==="fullPrayer"}function rp(e){return Bi(e)}function Gi({colorSource:e="properties"}={}){const t=e==="feature-state"?["feature-state","peoplePraying"]:["get","peoplePraying"];return["case",[">=",t,nr],No.fullPrayer,[">",t,0],No.hasPrayer,No.noPrayer]}const ip=Gi,a1=Object.freeze(Object.defineProperty({__proto__:null,FULL_PRAYER_THRESHOLD:nr,LABELS:Di,PALETTE:No,PRAYER_COLORS:Ko,PRAYER_LABELS:n1,PROPERTY_KEY:op,applyColor:Gi,buildColorExpression:ip,checkHasFullPrayer:ap,checkHasPrayer:np,default:{name:"Prayer Progress",propertyKey:op,palette:No,colors:No,labels:Di,threshold:nr,getColor:rp,applyColor:Gi,buildColorExpression:ip,getPrayerLevel:ar,getPrayerColor:Bi,checkHasPrayer:np,checkHasFullPrayer:ap},getColor:rp,getPrayerColor:Bi,getPrayerLevel:ar},Symbol.toStringTag,{value:"Module"})),zi=ue(0),rr=ue(0),ir=ue(0),Hi=ue(!1),Ui=ue(!1),ji=ue(null);let Qn=null;async function r1(){if(!Hi.value)return Qn||(Ui.value=!0,ji.value=null,Qn=(async()=>{var e,t;try{const o=((e=dn==null?void 0:dn.sources)==null?void 0:e[dn.activeSource])||{},n=Fi(),a=((t=o.endpoints)==null?void 0:t.statistics)||"/api/people-groups/statistics",r=await fetch(`${n}${a}`,{cache:"no-store"});if(!r.ok)throw new Error(`statistics HTTP ${r.status}`);const i=await r.json();zi.value=Number(i.total||0),rr.value=Number(i.total_with_prayer_committed??i.total_with_prayer??0),ir.value=Number(i.total_with_100_committed??i.total_with_full_prayer??0),Hi.value=!0}catch(o){ji.value=o&&o.message?o.message:String(o)}finally{Ui.value=!1,Qn=null}})(),Qn)}function i1(){return{total:zi,withPrayer:rr,withFullPrayer:ir,loaded:Hi,loading:Ui,error:ji,load:r1,noPrayerCount:()=>Math.max(0,zi.value-rr.value),hasPrayerCount:()=>Math.max(0,rr.value-ir.value),fullPrayerCount:()=>ir.value}}const sp="engagementStatus",mo={notEngaged:"#f03b2d",hasEngagement:"#22c55e"},Wi=mo,Ki={notEngaged:"Unengaged",hasEngagement:"Engaged"},s1=Ki;function lp(e){var n;const t=e.engagementStatus??((n=e._raw)==null?void 0:n.people_committed);return t===!0||t===1||typeof t=="number"&&t>0||typeof t=="object"&&(t==null?void 0:t.value)?mo.hasEngagement:mo.notEngaged}function Vi({colorSource:e="properties"}={}){const t=e==="feature-state"?["feature-state","engagementStatus"]:["get","engagementStatus"];return["case",["==",t,!0],mo.hasEngagement,["==",t,1],mo.hasEngagement,mo.notEngaged]}const cp=Vi,l1=Object.freeze(Object.defineProperty({__proto__:null,ENGAGEMENT_COLORS:Wi,ENGAGEMENT_LABELS:s1,LABELS:Ki,PALETTE:mo,PROPERTY_KEY:sp,applyColor:Vi,buildColorExpression:cp,default:{name:"Engagement Progress",propertyKey:sp,palette:mo,colors:mo,labels:Ki,getColor:lp,applyColor:Vi,buildColorExpression:cp},getColor:lp},Symbol.toStringTag,{value:"Module"})),up="adoptionStatus",fo={notAdopted:"#f03b2d",hasAdoption:"#22c55e"},qi=fo,Yi={notAdopted:"Needs Adoption",hasAdopted:"Has Adoption"},c1=Yi;function pp(e){var n;const t=e.adoptionStatus??((n=e._raw)==null?void 0:n.adopted_by_churches);return t===!0||t===1||typeof t=="number"&&t>0||typeof t=="object"&&(t==null?void 0:t.value)?fo.hasAdoption:fo.notAdopted}function Ji({colorSource:e="properties"}={}){const t=e==="feature-state"?["feature-state","adoptionStatus"]:["get","adoptionStatus"];return["case",["==",t,!0],fo.hasAdoption,["==",t,1],fo.hasAdoption,fo.notAdopted]}const dp=Ji,u1=Object.freeze(Object.defineProperty({__proto__:null,ADOPTION_COLORS:qi,ADOPTION_LABELS:c1,LABELS:Yi,PALETTE:fo,PROPERTY_KEY:up,applyColor:Ji,buildColorExpression:dp,default:{name:"Adoption Progress",propertyKey:up,palette:fo,colors:fo,labels:Yi,getColor:pp,applyColor:Ji,buildColorExpression:dp},getColor:pp},Symbol.toStringTag,{value:"Module"})),Zi="languageFamily",p1="#000000",To={"Indo-European":"#e74c3c",Dravidian:"#ff9800","Afro-Asiatic":"#27ae60","Sino-Tibetan":"#3498db","Niger-Congo":"#9b59b6",Unclassified:"#95a5a6","Nilo-Saharan":"#e91e63","Khoe-Kwadi":"#00bcd4","Austro-Asiatic":"#00bcd4","Kra-Dai":"#8e44ad",Austronesian:"#ff69b4","Hmong-Mien":"#f44336",Turkic:"#ffc107","Nakh-Daghestanian":"#009688",Mongolic:"#795548",Uralic:"#607d8b",Tungusic:"#4caf50",Japonic:"#ff5722",Cariban:"#673ab7",Tupian:"#03a9f4",Tucanoan:"#8bc34a",Maipurean:"#ffc107",Panoan:"#cddc39",Guajiboan:"#9e9e9e",Chibchan:"#ff7043",Quechuan:"#ab47bc",Jean:"#26a69a",Harákmbut:"#7e57c2",Nambikwara:"#42a5f5",Muran:"#66bb6a",Karajá:"#ffa726",Bororoan:"#ef5350",Arauan:"#5c6bc0",Yanomaman:"#26c6da",Yaguan:"#d4e157",Witotoan:"#78909c",Zamucoan:"#8d6e63",Puinavean:"#aed581",Barbacoan:"#4db6ac","Eyak-Athabaskan":"#7986cb","Trans-New Guinea":"#ba68c8",Andamanese:"#4dd0e1","Atlantic-Congo":"#7D3C98",Mande:"#B9770E","Sign Language":"#212121",Creole:"#9575cd","Mixed language":"#a1887f","Language isolate":"#90a4ae",Unknown:"#000000"},gp=To;function Rt(e){return To[e]||p1}const d1=(()=>{const e=o=>String(o||"").toLowerCase().trim(),t={};for(const o of Object.keys(To))t[e(o)]=o;return t})(),g1={"tai-kadai":"Kra-Dai",none:"Unknown"};function Xi(e){if(!e)return"Unknown";const t=String(e).trim(),o=t.toLowerCase();return g1[o]||d1[o]||t}function hp(e){var t;return Rt(e.languageFamily||((t=e._normalized)==null?void 0:t.languageFamily))}function Qi(){const e=["match",["get",Zi]];return Object.entries(To).forEach(([t,o])=>{e.push(t,o)}),e.push("#95a5a6"),e}const mp=Qi,h1=Object.freeze(Object.defineProperty({__proto__:null,LANGUAGE_FAMILY_COLORS:gp,PALETTE:To,PROPERTY_KEY:Zi,applyColor:Qi,buildColorExpression:mp,canonicalFamilyName:Xi,default:{name:"Language Family",propertyKey:Zi,palette:To,colors:To,getColor:hp,applyColor:Qi,buildColorExpression:mp,getLanguageFamilyColor:Rt,canonicalFamilyName:Xi},defaultFamilyColor:"#95a5a6",getColor:hp,getLanguageFamilyColor:Rt,languageFamilyColors:To},Symbol.toStringTag,{value:"Module"})),es="affinityBlock",mn="#95a5a6",bo={A001:"Arab World",A002:"East Asian Peoples",A003:"Eurasian Peoples",A004:"Horn of Africa Peoples",A005:"Jewish",A006:"Malay Peoples",A007:"Persian-Median Peoples",A008:"South Asian Peoples",A009:"North American Peoples",A010:"Pacific Islanders",A011:"Southeast Asian Peoples",A012:"Sub-Saharan Peoples",A013:"Tibetan-Himalayan Peoples",A014:"Turkic Peoples",A015:"Latin-Caribbean Americans",A017:"Deaf",A018:"Tibetan Peoples",A019:"Himalayan Peoples"},sr={A001:"#E74C3C",A002:"#3498DB",A003:"#2ECC71",A004:"#F39C12",A005:"#9B59B6",A006:"#1ABC9C",A007:"#E67E22",A008:"#34495E",A009:"#95A5A6",A010:"#5DADE2",A011:"#16A085",A012:"#C0392B",A013:"#8E44AD",A014:"#D35400",A015:"#27AE60",A017:"#000000",A018:"#6C3483",A019:"#A569BD"},Oo=sr,fp=Object.entries(bo).reduce((e,[t,o])=>(e[o]=t,e),{}),ts=Object.entries(bo).reduce((e,[t,o])=>(e[o]=Oo[t],e),{}),bp=bo;function os(e){return bo[e]||"Unknown"}function ns(e){return Oo[e]||mn}function _p(e){return e?typeof e=="string"&&/^A\d{3}$/.test(e)?Oo[e]||mn:ts[e]||mn:mn}function yp(){return Object.entries(bo).map(([e,t])=>({code:e,name:t,color:Oo[e]}))}function $p(e){return{code:e,name:bo[e]||"Unknown",color:Oo[e]||mn}}function vp(e){var o;const t=e.affinityBlock||((o=e._raw)==null?void 0:o.ROP1);return ns(t)}function wp(e){var o;const t=e.affinityBlock||((o=e._raw)==null?void 0:o.ROP1);return os(t)}function as(){const e=["match",["get",es]];return Object.entries(Oo).forEach(([t,o])=>{e.push(t,o)}),e.push(mn),e}const kp=as,m1=Object.freeze(Object.defineProperty({__proto__:null,AFFINITY_BLOCK_COLORS:Oo,AFFINITY_BLOCK_COLORS_BY_NAME:ts,AFFINITY_BLOCK_NAMES:bo,AFFINITY_BLOCK_NAME_TO_CODE:fp,NAMES:bp,PALETTE:sr,PROPERTY_KEY:es,affinityBlockCodeLookup:bo,affinityBlockCodeToColor:Oo,affinityBlockCodeToName:bo,affinityBlockColors:ts,affinityBlockNameToCode:fp,applyColor:as,buildColorExpression:kp,default:{name:"Affinity Block",propertyKey:es,palette:sr,colors:sr,names:bp,getColor:vp,applyColor:as,buildColorExpression:kp,getName:wp,getAffinityBlockName:os,getAffinityBlockColor:_p,getAffinityBlockColorByCode:ns,getAffinityBlocksArray:yp,getAffinityBlockData:$p},getAffinityBlockColor:_p,getAffinityBlockColorByCode:ns,getAffinityBlockData:$p,getAffinityBlockName:os,getAffinityBlocksArray:yp,getColor:vp,getName:wp},Symbol.toStringTag,{value:"Module"}));function f1(e){if(!e)return"hsl(0, 60%, 50%)";let t=0;for(let n=0;n{e.push(t,o)}),e.push(is),e}const Cp=cs,_1=Object.freeze(Object.defineProperty({__proto__:null,DOXA_REGION_COLORS:b1,PALETTE:lr,PROPERTY_KEY:rs,REGION_COLOR_ALIASES:ls,SLUG_PALETTE:cr,applyColor:cs,buildColorExpression:Cp,default:{name:"DOXA Region",propertyKey:rs,palette:lr,colors:lr,slugColors:cr,aliases:ls,fallback:is,getColor:Ap,applyColor:cs,buildColorExpression:Cp,getRegionColor:Sp,getRegionColorBySlug:ss},getColor:Ap,getRegionColor:Sp,getRegionColorBySlug:ss},Symbol.toStringTag,{value:"Module"})),Pp="religion",us="#94a3b8",ur={C:"Christianity",M:"Islam",H:"Hinduism",B:"Buddhism",E:"Ethnoreligion",J:"Judaism",S:"Sikhism",N:"Unaffiliated",O:"Other",U:"Unknown"},St={C:"#3b82f6",M:"#ef4444",H:"#f59e0b",B:"#facc15",E:"#84cc16",J:"#a855f7",S:"#ec4899",N:"#71717a",O:"#ff6b2b",U:"#94a3b8"};function ps(e){if(!e||typeof e!="string")return"U";const t=e.charAt(0).toUpperCase();return ur[t]?t:"U"}function Lp(e){var o,n,a;const t=(e==null?void 0:e.religion)||((n=(o=e==null?void 0:e._raw)==null?void 0:o.religion)==null?void 0:n.value)||((a=e==null?void 0:e._raw)==null?void 0:a.religion);return St[ps(t)]||us}function ds(){return["match",["slice",["coalesce",["get","religion"],""],0,1],"C",St.C,"M",St.M,"H",St.H,"B",St.B,"E",St.E,"J",St.J,"S",St.S,"N",St.N,"O",St.O,us]}const Np=ds,y1={name:"Religion",propertyKey:Pp,palette:St,families:ur,getColor:Lp,applyColor:ds,buildColorExpression:Np,fallback:us};function Tp(e){const t=String(e||""),o=ur[t]||t;return o?Xt("religion.family."+o)||o:""}const $1=Object.freeze(Object.defineProperty({__proto__:null,PALETTE:St,PROPERTY_KEY:Pp,RELIGION_FAMILIES:ur,applyColor:ds,buildColorExpression:Np,default:y1,getColor:Lp,getReligionFamily:ps,religionFamilyLabel:Tp},Symbol.toStringTag,{value:"Module"})),Op="resourceType",Eo={bible:"#3498db",jesusFilm:"#20c997",radio:"#9b59b6",gospel:"#f39c12",audio:"#1abc9c",stories:"#e74c3c",hasResources:"#27ae60",noResources:"#2c3e50"},v1=Eo,ea=["bible","jesusFilm","radio","gospel","audio","stories"],gs={bible:"Bible",jesusFilm:"Jesus Film",radio:"Radio",gospel:"Gospel",audio:"Audio",stories:"Stories",hasResources:"Has Resources",noResources:"No Resources"},w1=gs;function ta(e){return{bible:"Bible",jesusFilm:"Jesus",radio:"Radio",gospel:"Gospel",audio:"Audio",stories:"Stories"}[e]}function Ep(e){var t;for(const o of ea)if((e[o]||((t=e._raw)==null?void 0:t[ta(o)]))==="Available")return Eo.hasResources;return Eo.noResources}function Ip(e){var t;for(const o of ea)if((e[o]||((t=e._raw)==null?void 0:t[ta(o)]))==="Available")return!0;return!1}function Fp(e){var t;for(const o of ea)if((e[o]||((t=e._raw)==null?void 0:t[ta(o)]))==="Available")return o;return"noResources"}function hs(){return["case",["any",["==",["get","bible"],"Available"],["==",["get","jesusFilm"],"Available"],["==",["get","radio"],"Available"],["==",["get","gospel"],"Available"],["==",["get","audio"],"Available"],["==",["get","stories"],"Available"]],Eo.hasResources,Eo.noResources]}const Mp=hs,k1=Object.freeze(Object.defineProperty({__proto__:null,NAMES:gs,PALETTE:Eo,PROPERTY_KEY:Op,RESOURCE_COLORS:v1,RESOURCE_NAMES:w1,RESOURCE_PRIORITY:ea,applyColor:hs,buildColorExpression:Mp,default:{name:"Gospel Resources",propertyKey:Op,palette:Eo,colors:Eo,names:gs,priority:ea,getColor:Ep,applyColor:hs,buildColorExpression:Mp,hasAnyResource:Ip,getResourceType:Fp,getCSVColumn:ta},getCSVColumn:ta,getColor:Ep,getResourceType:Fp,hasAnyResource:Ip},Symbol.toStringTag,{value:"Module"})),Rp="region",ms="#6d766e",Dp={africa:"Africa",asia:"Asia",europe:"Europe","latin_america_&_caribbean":"Latin America & Caribbean",middle_east:"Middle East","north_america_&_non-spanish_caribbean":"North America & Non-Spanish Caribbean",oceania:"Oceania"},oa={africa:"#ff2e2e",asia:"#1a8cff",europe:"#1ee05f","latin_america_&_caribbean":"#FFEB3B",middle_east:"#a83dff","north_america_&_non-spanish_caribbean":"#00e0bd",oceania:"#ff7f11"},fs=Object.keys(oa);function Bp(e){var o,n,a;const t=(e==null?void 0:e.region)||((n=(o=e==null?void 0:e._raw)==null?void 0:o.wagf_region)==null?void 0:n.value)||((a=e==null?void 0:e._raw)==null?void 0:a.wagf_region);return oa[t]||ms}function bs(){const e=["match",["coalesce",["get","region"],""]];for(const t of fs)e.push(t,oa[t]);return e.push(ms),e}const Gp=bs,x1=Object.assign({"./adoption.js":u1,"./affinity-block.js":m1,"./doxa-region.js":_1,"./engagement.js":l1,"./language-family.js":h1,"./prayer-progress.js":a1,"./religion.js":$1,"./resource.js":k1,"./wagf-region.js":Object.freeze(Object.defineProperty({__proto__:null,PALETTE:oa,PROPERTY_KEY:Rp,REGION_ORDER:fs,WAGF_REGIONS:Dp,applyColor:bs,buildColorExpression:Gp,default:{name:"WAGF Region",propertyKey:Rp,palette:oa,regions:Dp,order:fs,getColor:Bp,applyColor:bs,buildColorExpression:Gp,fallback:ms},getColor:Bp},Symbol.toStringTag,{value:"Module"}))}),S1=e=>e.replace(/-([a-z0-9])/g,(t,o)=>o.toUpperCase()),A1=e=>e.replace(/-/g,"_").toUpperCase(),fn={...{STATUS:"status",CUSTOM:"custom"}},_s={};function C1(e){for(const t of Object.keys(e).sort()){const o=t.split("/").pop().replace(/\.js$/,"");if(o.startsWith("_")||o.startsWith("."))continue;const n=e[t].default;if(!n)continue;const a=S1(o);fn[A1(o)]=a,_s[a]=n}}C1(x1);function pr(e){return _s[e]||_s[fn.LANGUAGE_FAMILY]}const Io={Chamacoco:"Zamucoan",Ninam:"Yanomaman",Yagua:"Yaguan",Muinane:"Witotoan",Finnish:"Uralic","Hill Mari":"Uralic",Mansi:"Uralic","Meadow Mari":"Uralic","North Saami":"Uralic",Carabayo:"Unclassified",Kujarge:"Unclassified",Shabo:"Unclassified",Ainu:"Turkic",Kazakh:"Turkic","Khorasani Turkish":"Turkic",Kyrgyz:"Turkic","Northern Uzbek":"Turkic","South Azerbaijani":"Turkic",Turkish:"Turkic",Tuvan:"Turkic",Uyghur:"Turkic",Amundava:"Tupian",Araweté:"Tupian","Asurini of Xingú":"Tupian","Avá-Canoeiro":"Tupian",Awetí:"Tupian",Kamayurá:"Tupian",Karo:"Tupian",Morerebi:"Tupian",Tenharim:"Tupian","Uru-Eu-Wau-Wau":"Tupian","Uru-Pa-In":"Tupian",Wayampi:"Tupian","Zo’é":"Tupian",Evenki:"Tungusic",Xibe:"Tungusic",Carapana:"Tucanoan",Cubeo:"Tucanoan",Desano:"Tucanoan",Macuna:"Tucanoan",Piratapuyo:"Tucanoan",Siona:"Tucanoan",Siriano:"Tucanoan",Tucano:"Tucanoan",Tuyuca:"Tucanoan",Waimaha:"Tucanoan",Tay:"Trans-New Guinea",Adi:"Sino-Tibetan",Akha:"Sino-Tibetan","Amdo Tibetan":"Sino-Tibetan",Axi:"Sino-Tibetan",Biyo:"Sino-Tibetan","Boro (India)":"Sino-Tibetan",Brokkat:"Sino-Tibetan",Bugun:"Sino-Tibetan",Byangsi:"Sino-Tibetan","Central Lalo":"Sino-Tibetan","Central Tibetan":"Sino-Tibetan",Chak:"Sino-Tibetan",Chalikha:"Sino-Tibetan",Chaudangsi:"Sino-Tibetan",Chocangacakha:"Sino-Tibetan",Darlong:"Sino-Tibetan",Dhimal:"Sino-Tibetan","Digaro-Mishmi":"Sino-Tibetan",Dzalakha:"Sino-Tibetan","Eastern Nisu":"Sino-Tibetan",Ersu:"Sino-Tibetan",Gahri:"Sino-Tibetan",Gongduk:"Sino-Tibetan",Gyalsumdo:"Sino-Tibetan",Hani:"Sino-Tibetan","Idu-Mishmi":"Sino-Tibetan",Kaike:"Sino-Tibetan",Kanashi:"Sino-Tibetan",Katso:"Sino-Tibetan",Khamba:"Sino-Tibetan",Koireng:"Sino-Tibetan",Kucong:"Sino-Tibetan",Kurtokha:"Sino-Tibetan",Ladakhi:"Sino-Tibetan",Lakha:"Sino-Tibetan","Lang’e":"Sino-Tibetan",Lhokpu:"Sino-Tibetan",Lolopo:"Sino-Tibetan",Lunanakha:"Sino-Tibetan","Mandarin Chinese":"Sino-Tibetan",Meitei:"Sino-Tibetan","Miju-Mishmi":"Sino-Tibetan",Mpi:"Sino-Tibetan",Na:"Sino-Tibetan",Namuyi:"Sino-Tibetan",Narua:"Sino-Tibetan",Naxi:"Sino-Tibetan","Northern Muji":"Sino-Tibetan",Nuosu:"Sino-Tibetan",Nupbikha:"Sino-Tibetan",Nyenkha:"Sino-Tibetan",Olekha:"Sino-Tibetan",Pahari:"Sino-Tibetan",Pattani:"Sino-Tibetan","Phana’":"Sino-Tibetan",Phula:"Sino-Tibetan",Phunoi:"Sino-Tibetan",Puma:"Sino-Tibetan",Puroik:"Sino-Tibetan",Raji:"Sino-Tibetan",Rawat:"Sino-Tibetan",Sani:"Sino-Tibetan",Sherdukpen:"Sino-Tibetan",Sherpa:"Sino-Tibetan",Shuhi:"Sino-Tibetan",Sila:"Sino-Tibetan",Singpho:"Sino-Tibetan",Sunwar:"Sino-Tibetan","Tawang Monpa":"Sino-Tibetan",Tseku:"Sino-Tibetan",Tshangla:"Sino-Tibetan",Usoi:"Sino-Tibetan","Western Minyag":"Sino-Tibetan","Wusa Nasu":"Sino-Tibetan","Xiang Chinese":"Sino-Tibetan",Yakkha:"Sino-Tibetan","Youle Jinuo":"Sino-Tibetan","Yue Chinese":"Sino-Tibetan",Zakhring:"Sino-Tibetan","Afghan Sign Language":"Sign Language","Albanian Sign Language":"Sign Language","Algerian Sign Language":"Sign Language","American Sign Language":"Sign Language","Armenian Sign Language":"Sign Language","British Sign Language":"Sign Language","Bulgarian Sign Language":"Sign Language","Cambodian Sign Language":"Sign language","Cuban Sign Language":"Sign Language","Egyptian Sign Language":"Sign Language","Greek Sign Language":"Sign Language","Guinean Sign Language":"Sign Language","Hong Kong Sign Language":"Sign Language","Icelandic Sign Language":"Sign Language","Iranian Sign Language":"Sign Language","Israeli Sign Language":"Sign Language","Jamaican Sign Language":"Sign Language","Jordanian Sign Language":"Sign Language","Laos Sign Language":"Sign Language","Libyan Sign Language":"Sign Language","Maltese Sign Language":"Sign Language","Mauritian Sign Language":"Sign Language","Moroccan Sign Language":"Sign Language","Mozambican Sign Language":"Sign Language","Myanmar Sign Language":"Sign language","Natural Sign Language":"Sign Language","Pakistan Sign Language":"Sign Language","Papua New Guinean Sign Language":"Sign language","Russian Sign Language":"Sign Language","Saudi Arabian Sign Language":"Sign Language","Sign Language of the Netherlands":"Sign Language","Solomon Islands Sign Language":"Sign Language","Sri Lankan Sign Language":"Sign Language","Swedish Sign Language":"Sign Language","Turkish Sign Language":"Sign Language","Northern Conchucos Ancash Quechua":"Quechuan","Northern Pastaza Quichua":"Quechuan",Hupdë:"Puinavean",Amahuaca:"Panoan",Korubo:"Panoan",Sharanahua:"Panoan","Shipibo-Conibo":"Panoan",Yaminahua:"Panoan",Yawanawa:"Panoan",Afitti:"Nilo-Saharan",Assangori:"Nilo-Saharan","Beli (South Sudan)":"Nilo-Saharan",Berta:"Nilo-Saharan",Burun:"Nilo-Saharan","Dar Daju Daju":"Nilo-Saharan","Dar Fur Daju":"Nilo-Saharan","Dar Sila Daju":"Nilo-Saharan",Dazaga:"Nilo-Saharan","Dendi (Benin)":"Nilo-Saharan",Dilling:"Nilo-Saharan",Fur:"Nilo-Saharan",Gbaya:"Nilo-Saharan",Ghulfan:"Nilo-Saharan",Gwama:"Nilo-Saharan","Humburi Senni Songhay":"Nilo-Saharan",Jopadhola:"Nilo-Saharan",Kadaru:"Nilo-Saharan",Kanga:"Nilo-Saharan",Karanga:"Nilo-Saharan",Karko:"Nilo-Saharan",Kebu:"Nilo-Saharan",Keiga:"Nilo-Saharan",Keliko:"Nilo-Saharan",Kendeje:"Nilo-Saharan",Kibet:"Nilo-Saharan","Komo (Sudan)":"Nilo-Saharan",Korandje:"Nilo-Saharan",Kulfa:"Nilo-Saharan",Kwegu:"Nilo-Saharan",Laarim:"Nilo-Saharan",Logorik:"Nilo-Saharan",Majang:"Nilo-Saharan","Manga Kanuri":"Nilo-Saharan",Mango:"Nilo-Saharan",Mararit:"Nilo-Saharan",Marfa:"Nilo-Saharan",Midob:"Nilo-Saharan","Mo’da":"Nilo-Saharan",Morom:"Nilo-Saharan",Moru:"Nilo-Saharan",Naba:"Nilo-Saharan",Nara:"Nilo-Saharan",Njalgulgule:"Nilo-Saharan",Nuer:"Nilo-Saharan","Nyamusa-Molo":"Nilo-Saharan",Nyangatom:"Nilo-Saharan","Olu’bo":"Nilo-Saharan",Opo:"Nilo-Saharan",Päri:"Nilo-Saharan","Sara Kaba Naa":"Nilo-Saharan",Surbakhal:"Nilo-Saharan",Tadaksahak:"Nilo-Saharan","Tama (Chad)":"Nilo-Saharan",Temein:"Nilo-Saharan",Thuri:"Nilo-Saharan","Tirmaga-Chai Suri":"Nilo-Saharan",Toposa:"Nilo-Saharan",Tumtum:"Nilo-Saharan","Wali (Sudan)":"Nilo-Saharan","Yerwa Kanuri":"Nilo-Saharan",Yulu:"Nilo-Saharan",Zaghawa:"Nilo-Saharan","Adamawa Fulfulde":"Niger-Congo",Akaselem:"Niger-Congo","Amba (Uganda)":"Niger-Congo",Anii:"Niger-Congo",Baangi:"Niger-Congo",Badyara:"Niger-Congo","Bago-Kusuntu":"Niger-Congo",Bai:"Niger-Congo","Bainouk-Gunyaamolo":"Niger-Congo",Bamanankan:"Niger-Congo","Banda-Mbrès":"Niger-Congo",Bandial:"Niger-Congo",Bankagooma:"Niger-Congo","Belanda Viri":"Niger-Congo",Bemba:"Niger-Congo",Bolgo:"Niger-Congo","Bon Gula":"Niger-Congo",Bua:"Niger-Congo",Chichewa:"Niger-Congo","Chung (Cameroon)":"Niger-Congo","Ci Gbe":"Niger-Congo",Degema:"Niger-Congo",Dek:"Niger-Congo",Dhimba:"Niger-Congo",Dogoso:"Niger-Congo","East Limba":"Niger-Congo",Engenni:"Niger-Congo",Epie:"Niger-Congo",Éwé:"Niger-Congo",Fania:"Niger-Congo",Foodo:"Niger-Congo",Fwe:"Niger-Congo",Gciriku:"Niger-Congo",Gwa:"Niger-Congo",Herero:"Niger-Congo",Ibuoro:"Niger-Congo","Jenaama Bozo":"Niger-Congo",Jita:"Niger-Congo",Jula:"Niger-Congo",Kabiyè:"Niger-Congo",Kadung:"Niger-Congo",Kakabe:"Niger-Congo",Kalamsé:"Niger-Congo","Kami (Tanzania)":"Niger-Congo","Kango (Bas-Uélé District)":"Niger-Congo",Kasem:"Niger-Congo","Kelengaxo Bozo":"Niger-Congo",Khe:"Niger-Congo",Khisa:"Niger-Congo",Kim:"Niger-Congo",Kinyarwanda:"Niger-Congo",Kipfokomu:"Niger-Congo",Kitharaka:"Niger-Congo",Kiyombe:"Niger-Congo",Ko:"Niger-Congo",Koalib:"Niger-Congo",Koke:"Niger-Congo",Konongo:"Niger-Congo","Koro Nulu":"Niger-Congo","Koro Zuba":"Niger-Congo",Kuhane:"Niger-Congo",Kunda:"Niger-Congo",Kuranko:"Niger-Congo",Kuria:"Niger-Congo",Kwangali:"Niger-Congo",Lame:"Niger-Congo",Laru:"Niger-Congo",Lopa:"Niger-Congo","Makhuwa-Meetto":"Niger-Congo","Makhuwa-Shirima":"Niger-Congo","Malawi Nyiha":"Niger-Congo",Mandinka:"Niger-Congo",Mankanya:"Niger-Congo",Mansoanka:"Niger-Congo",Mbukushu:"Niger-Congo","Mende (Sierra Leone)":"Niger-Congo",Miyobe:"Niger-Congo",Mushungulu:"Niger-Congo","Mwali Comorian":"Niger-Congo","Mwera (Chimwera)":"Niger-Congo",Ndemli:"Niger-Congo",Ndengereko:"Niger-Congo",Ndumu:"Niger-Congo",Ndunga:"Niger-Congo","Ndzwani Comorian":"Niger-Congo",Ngile:"Niger-Congo","Northwest Gbaya":"Niger-Congo",Notre:"Niger-Congo",Nyamwanga:"Niger-Congo",Nyankore:"Niger-Congo",Nyengo:"Niger-Congo",Okpamheri:"Niger-Congo",Osatu:"Niger-Congo","Pana (Burkina Faso)":"Niger-Congo",Pulaar:"Niger-Congo",Pular:"Niger-Congo",Rufiji:"Niger-Congo","Sankaran Maninka":"Niger-Congo",Sapo:"Niger-Congo","Senara Sénoufo":"Niger-Congo",Shanga:"Niger-Congo",Sininkere:"Niger-Congo","Southern Ngbandi":"Niger-Congo",Swahili:"Niger-Congo","Syenara Sénoufo":"Niger-Congo",Tagbu:"Niger-Congo",Tagoi:"Niger-Congo",Tegali:"Niger-Congo",Tem:"Niger-Congo","Tiéyaxo Bozo":"Niger-Congo",Tiyaa:"Niger-Congo",Tocho:"Niger-Congo","Tonga (Zambia)":"Niger-Congo",Totela:"Niger-Congo","Toura (Côte d’Ivoire)":"Niger-Congo",Tswa:"Niger-Congo",Tumbuka:"Niger-Congo",Ukaan:"Niger-Congo",Uneme:"Niger-Congo",Vai:"Niger-Congo",Vute:"Niger-Congo",Waama:"Niger-Congo",Warnang:"Niger-Congo",Wolof:"Niger-Congo",Yao:"Niger-Congo",Yauma:"Niger-Congo",Yom:"Niger-Congo","Zan Gula":"Niger-Congo",Zande:"Niger-Congo",Zigula:"Niger-Congo",Mamaindê:"Nambikwara",Aghul:"Nakh-Daghestanian",Akhvakh:"Nakh-Daghestanian",Andi:"Nakh-Daghestanian",Avar:"Nakh-Daghestanian",Bagvalal:"Nakh-Daghestanian",Botlikh:"Nakh-Daghestanian",Chamalal:"Nakh-Daghestanian",Chechen:"Nakh-Daghestanian",Dargwa:"Nakh-Daghestanian",Kaitag:"Nakh-Daghestanian",Karata:"Nakh-Daghestanian",Khvarshi:"Nakh-Daghestanian",Tindi:"Nakh-Daghestanian",Pirahã:"Muran",Bonan:"Mongolic","China Buriat":"Mongolic",Daur:"Mongolic","Kalmyk-Oirat":"Mongolic","Peripheral Mongolian":"Mongolic","Russia Buriat":"Mongolic",Tagdal:"Mixed language",Wutunhua:"Mixed language","Ashéninka Perené":"Maipurean",Baniwa:"Maipurean",Cabiyarí:"Maipurean",Curripaco:"Maipurean","Enawené-Nawé":"Maipurean",Kanamarí:"Maipurean",Mehináku:"Maipurean","Pichis Ashéninka":"Maipurean",Waurá:"Maipurean",Bangime:"Language isolate",Burushaski:"Language isolate","Ai-Cham":"Kra-Dai",Aiton:"Kra-Dai","Dai Zhuang":"Kra-Dai",Duoluo:"Kra-Dai","Green Gelao":"Kra-Dai","Guibian Zhuang":"Kra-Dai",Hlai:"Kra-Dai",Jiamao:"Kra-Dai",Kang:"Kra-Dai",Khün:"Kra-Dai",Kuan:"Kra-Dai",Lachi:"Kra-Dai","Laha (Viet Nam)":"Kra-Dai",Lao:"Kra-Dai",Lü:"Kra-Dai","Mak (China)":"Kra-Dai","Northeastern Thai":"Kra-Dai","Northern Dong":"Kra-Dai","Northern Thai":"Kra-Dai",Nyaw:"Kra-Dai",Phake:"Kra-Dai","Phu Thai":"Kra-Dai",Qabiao:"Kra-Dai",Qau:"Kra-Dai","Qiubei Zhuang":"Kra-Dai",Shan:"Kra-Dai","Southern Dong":"Kra-Dai",Sui:"Kra-Dai","T’en":"Kra-Dai","Tai Daeng":"Kra-Dai","Tai Dam":"Kra-Dai","Tai Hongjin":"Kra-Dai","Tai Nüa":"Kra-Dai","Tai Ya":"Kra-Dai","Tai Yo":"Kra-Dai","Ts’ün-Lao":"Kra-Dai","Yang Zhuang":"Kra-Dai",Yerong:"Kra-Dai",Yong:"Kra-Dai",Yoy:"Kra-Dai",Khoekhoe:"Khoe-Kwadi",Karajá:"Karajá","Pará Gavião":"Jean",Suyá:"Jean",Japanese:"Japonic","Oki-No-Erabu":"Japonic","Toku-No-Shima":"Japonic",Yaeyama:"Japonic",Yoron:"Japonic","Adivasi Oriya":"Indo-European",Afrikaans:"Indo-European","Alviri-Vidari":"Indo-European",Angika:"Indo-European",Assamese:"Indo-European",Bagheli:"Indo-European",Bagri:"Indo-European",Bashkardi:"Indo-European",Bateri:"Indo-European",Bengali:"Indo-European",Bhojpuri:"Indo-European",Bhunjia:"Indo-European",Bilaspuri:"Indo-European",Bosnian:"Indo-European","Braj Bhasha":"Indo-European",Brokskat:"Indo-European",Bundeli:"Indo-European",Catalan:"Indo-European",Chambeali:"Indo-European",Chhattisgarhi:"Indo-European",Dameli:"Indo-European",Dari:"Indo-European",Dhundari:"Indo-European",Dogri:"Indo-European",Domari:"Indo-European",Dutch:"Indo-European","Eastern Balochi":"Indo-European","Eastern Panjabi":"Indo-European","Eastern Yiddish":"Indo-European",English:"Indo-European",French:"Indo-European",Gaddi:"Indo-European",Garhwali:"Indo-European",Gawri:"Indo-European",Gowro:"Indo-European",Gujarati:"Indo-European",Gujari:"Indo-European",Halbi:"Indo-European",Haroti:"Indo-European",Haryanvi:"Indo-European",Hindi:"Indo-European","Indus Kohistani":"Indo-European","Iranian Persian":"Indo-European",Ishkashimi:"Indo-European",Kacchi:"Indo-European",Kalkoti:"Indo-European",Kanauji:"Indo-European",Kangri:"Indo-European",Karingani:"Indo-European",Kashmiri:"Indo-European",Kati:"Indo-European",Khunsari:"Indo-European",Konkani:"Indo-European",Kudmali:"Indo-European",Kumaoni:"Indo-European",Kumzari:"Indo-European",Laki:"Indo-European",Lasi:"Indo-European",Luwati:"Indo-European",Magahi:"Indo-European",Maithili:"Indo-European",Maldivian:"Indo-European",Malvi:"Indo-European",Marathi:"Indo-European","Marwari (India)":"Indo-European",Mewari:"Indo-European",Munji:"Indo-European",Nepali:"Indo-European",Nimadi:"Indo-European","Northern Kurdish":"Indo-European",Odia:"Indo-European","Pahari-Potwari":"Indo-European",Palula:"Indo-European",Panchpargania:"Indo-European",Pangwali:"Indo-European",Parachi:"Indo-European",Portuguese:"Indo-European",Russian:"Indo-European",Sadri:"Indo-European",Sanglechi:"Indo-European",Sarikoli:"Indo-European",Savi:"Indo-European",Semnani:"Indo-European",Serbian:"Indo-European",Shabak:"Indo-European",Shahrudi:"Indo-European",Shekhawati:"Indo-European",Shina:"Indo-European",Shughni:"Indo-European",Shumashti:"Indo-European",Sindhi:"Indo-European",Sivandi:"Indo-European",Soi:"Indo-European","Southern Balochi":"Indo-European",Spanish:"Indo-European",Tajik:"Indo-European",Talysh:"Indo-European",Urdu:"Indo-European",Ushojo:"Indo-European",Vaghri:"Indo-European","Vlax Romani":"Indo-European",Wagdi:"Indo-European",Wakhi:"Indo-European",Waneci:"Indo-European","Western Balochi":"Indo-European",Yadgha:"Indo-European","Bu-Nao Bunu":"Hmong-Mien","Central Mashan Miao":"Hmong-Mien","Chuanqiandian Cluster Miao":"Hmong-Mien","Eastern Xiangxi Miao":"Hmong-Mien","Iu Mien":"Hmong-Mien","Luopohe Miao":"Hmong-Mien","Northern Qiandong Miao":"Hmong-Mien","Pa-Hng":"Hmong-Mien","Southwestern Huishui Miao":"Hmong-Mien","Wunai Bunu":"Hmong-Mien","Younuo Bunu":"Hmong-Mien",Amarakaeri:"Harákmbut",Huachipaeri:"Harákmbut",Cuiba:"Guajiboan",Macaguán:"Guajiboan",Tlicho:"Eyak-Athabaskan",Kannada:"Dravidian",Kodava:"Dravidian","Korra Koraga":"Dravidian","Kota (India)":"Dravidian",Kudiya:"Dravidian",Malapandaram:"Dravidian",Malayalam:"Dravidian",Muthuvan:"Dravidian",Ravula:"Dravidian",Sholaga:"Dravidian",Tamil:"Dravidian",Telugu:"Dravidian",Tulu:"Dravidian",Vishavan:"Dravidian","Andaman Hindi Creole":"Creole","Cocos Islands Malay":"Creole","Juba Arabic":"Creole",Nubi:"Creole",Barí:"Chibchan","Barro Negro Tunebo":"Chibchan","Western Tunebo":"Chibchan",Ikpeng:"Cariban","Kuikúro-Kalapálo":"Cariban",Pemon:"Cariban",Trió:"Cariban","Waimiri-Atroarí":"Cariban",Waiwai:"Cariban",Yukpa:"Cariban",Borôro:"Bororoan",Chachi:"Barbacoan","Abai Sungai":"Austronesian",Ambelau:"Austronesian",Asilulu:"Austronesian",Balaesang:"Austronesian","Batak Alas-Kluet":"Austronesian","Batak Dairi":"Austronesian","Batak Karo":"Austronesian",Bonerate:"Austronesian",Bonggi:"Austronesian",Brunei:"Austronesian","Brunei Bisaya":"Austronesian",Buol:"Austronesian","Central Malay":"Austronesian","Central Sama":"Austronesian","Coastal Konjo":"Austronesian",Dampelas:"Austronesian","Dibabawon Manobo":"Austronesian",Dondo:"Austronesian",Gane:"Austronesian","Geser-Gorom":"Austronesian","Highland Konjo":"Austronesian",Hitu:"Austronesian",Iban:"Austronesian",Indonesian:"Austronesian","Kadazan Dusun":"Austronesian",Kiput:"Austronesian",Komodo:"Austronesian",Kompane:"Austronesian",Lahanan:"Austronesian","Lampung Nyo":"Austronesian","Larike-Wakasihu":"Austronesian",Lola:"Austronesian",Luhu:"Austronesian",Malay:"Austronesian",Mariri:"Austronesian",Moronene:"Austronesian",Rukai:"Austronesian","Sabah Bisaya":"Austronesian","Seit-Kaitetu":"Austronesian",Sula:"Austronesian","Tae’":"Austronesian",Tajio:"Austronesian",Tomini:"Austronesian",Tutong:"Austronesian",Ujir:"Austronesian",Aheu:"Austro-Asiatic",Alak:"Austro-Asiatic",Bit:"Austro-Asiatic","Bo (Laos)":"Austro-Asiatic",Bondo:"Austro-Asiatic",Brao:"Austro-Asiatic",Bugan:"Austro-Asiatic",Chong:"Austro-Asiatic",Chut:"Austro-Asiatic",Danau:"Austro-Asiatic",Halang:"Austro-Asiatic","Halang Doan":"Austro-Asiatic",Hu:"Austro-Asiatic",Hung:"Austro-Asiatic",Ir:"Austro-Asiatic",Kensiu:"Austro-Asiatic",Khmu:"Austro-Asiatic",Khua:"Austro-Asiatic",Khuen:"Austro-Asiatic",Koda:"Austro-Asiatic","Kon Keu":"Austro-Asiatic",Kriang:"Austro-Asiatic",Kuay:"Austro-Asiatic",Laven:"Austro-Asiatic",Maleng:"Austro-Asiatic","Man Met":"Austro-Asiatic",Mang:"Austro-Asiatic",Minriq:"Austro-Asiatic",Mlabri:"Austro-Asiatic",Nguôn:"Austro-Asiatic",Nyaheun:"Austro-Asiatic",Nyahkur:"Austro-Asiatic","O’du":"Austro-Asiatic",Ong:"Austro-Asiatic",Oy:"Austro-Asiatic",Pacoh:"Austro-Asiatic","Phong-Kniang":"Austro-Asiatic",Prai:"Austro-Asiatic",Puoc:"Austro-Asiatic",Rengao:"Austro-Asiatic",Rmeet:"Austro-Asiatic","Rumai Palaung":"Austro-Asiatic",Sapuan:"Austro-Asiatic","Shwe Palaung":"Austro-Asiatic","So (Laos)":"Austro-Asiatic",Sou:"Austro-Asiatic","Tai Loi":"Austro-Asiatic",Talieng:"Austro-Asiatic",Tareng:"Austro-Asiatic",Tho:"Austro-Asiatic",U:"Austro-Asiatic","Upper Ta’oih":"Austro-Asiatic",Vietnamese:"Austro-Asiatic","Western Katu":"Austro-Asiatic",Suruahá:"Arauan",Jarawa:"Andamanese",Öñge:"Andamanese",Sentinel:"Andamanese",Afar:"Afro-Asiatic","Alaba-K’abeena":"Afro-Asiatic",Alagwa:"Afro-Asiatic",Ale:"Afro-Asiatic","Algerian Arabic":"Afro-Asiatic",Amharic:"Afro-Asiatic",Arbore:"Afro-Asiatic","Baharna Arabic":"Afro-Asiatic",Barein:"Afro-Asiatic",Bedawiyet:"Afro-Asiatic",Bidiyo:"Afro-Asiatic",Bilen:"Afro-Asiatic",Birgit:"Afro-Asiatic",Boor:"Afro-Asiatic","Borana-Arsi-Guji Oromo":"Afro-Asiatic",Buduma:"Afro-Asiatic","Central Atlas Tamazight":"Afro-Asiatic","Chadian Arabic":"Afro-Asiatic",Chenoua:"Afro-Asiatic",Ciwogai:"Afro-Asiatic",Daasanach:"Afro-Asiatic",Dabarre:"Afro-Asiatic",Dangaléat:"Afro-Asiatic",Deno:"Afro-Asiatic",Diri:"Afro-Asiatic",Duwai:"Afro-Asiatic","Eastern Egyptian Bedawi Arabic":"Afro-Asiatic","Eastern Oromo":"Afro-Asiatic",Garre:"Afro-Asiatic",Geruma:"Afro-Asiatic",Giiwo:"Afro-Asiatic",Gorowa:"Afro-Asiatic","Gulf Arabic":"Afro-Asiatic","Hadrami Arabic":"Afro-Asiatic",Harsusi:"Afro-Asiatic",Hassaniyya:"Afro-Asiatic",Hausa:"Afro-Asiatic",Hebrew:"Afro-Asiatic","Hijazi Arabic":"Afro-Asiatic",Hobyót:"Afro-Asiatic",Hozo:"Afro-Asiatic",Jiiddu:"Afro-Asiatic",Jimi:"Afro-Asiatic",Jina:"Afro-Asiatic","Jonkor Bourmataguil":"Afro-Asiatic","Judeo-Iraqi Arabic":"Afro-Asiatic","Judeo-Moroccan Arabic":"Afro-Asiatic",Kabalai:"Afro-Asiatic",Kajakse:"Afro-Asiatic",Kariya:"Afro-Asiatic","Karo (Ethiopia)":"Afro-Asiatic",Kutto:"Afro-Asiatic","Levantine Arabic":"Afro-Asiatic",Libido:"Afro-Asiatic","Libyan Arabic":"Afro-Asiatic","Lishana Deni":"Afro-Asiatic",Maaka:"Afro-Asiatic",Maay:"Afro-Asiatic",Majera:"Afro-Asiatic",Male:"Afro-Asiatic",Maslam:"Afro-Asiatic",Masmaje:"Afro-Asiatic",Mburku:"Afro-Asiatic",Mehri:"Afro-Asiatic","Mesopotamian Arabic":"Afro-Asiatic",Migaama:"Afro-Asiatic",Mogum:"Afro-Asiatic","Moroccan Arabic":"Afro-Asiatic",Mositacha:"Afro-Asiatic",Mubi:"Afro-Asiatic",Mukulu:"Afro-Asiatic",Nafusi:"Afro-Asiatic","Najdi Arabic":"Afro-Asiatic",Nyam:"Afro-Asiatic","Omani Arabic":"Afro-Asiatic","Pa’a":"Afro-Asiatic",Saba:"Afro-Asiatic",Saho:"Afro-Asiatic","Sanaani Arabic":"Afro-Asiatic",Seze:"Afro-Asiatic",Shehri:"Afro-Asiatic",Shekkacho:"Afro-Asiatic",Sheko:"Afro-Asiatic","Shihhi Arabic":"Afro-Asiatic",Siwi:"Afro-Asiatic",Sokoro:"Afro-Asiatic",Soqotri:"Afro-Asiatic","South Levantine Arabic":"Afro-Asiatic","Standard Arabic":"Afro-Asiatic","Sudanese Arabic":"Afro-Asiatic","Ta’izzi-Adeni Arabic":"Afro-Asiatic","Tahaggart Tamahaq":"Afro-Asiatic",Taznatit:"Afro-Asiatic",Tigré:"Afro-Asiatic",Toram:"Afro-Asiatic",Tsamai:"Afro-Asiatic",Tumzabt:"Afro-Asiatic",Tunni:"Afro-Asiatic",Waata:"Afro-Asiatic","West Central Oromo":"Afro-Asiatic",Xamtanga:"Afro-Asiatic",Zumbun:"Afro-Asiatic"};function P1(e){return(e==null?void 0:e.properties)||e||{}}function dr(e){return e&&typeof e=="object"&&"label"in e?e.label||e.value||"":e}function L1(e){if(!e||typeof e!="string")return null;const t=e.split(",").map(n=>n.trim()).filter(Boolean);if(t.length>=2){const n=[...t].reverse().join(" ");if(Io[n])return Io[n];if(t.length>=3){const a=[t[1],t[0]].join(" ");if(Io[a])return Io[a]}}if(Io[e])return Io[e];const o=e.replace(/\s*\(.*?\)\s*$/,"").trim();return o!==e&&Io[o]?Io[o]:null}const N1=[[/ sign language$/i,"Sign Language"]];function T1(e){if(!e||typeof e!="string")return"— Unknown —";const t=e.indexOf(",");return t>0?e.slice(0,t).trim():e.trim()}function O1(e){if(!e||typeof e!="string")return null;const t=e.indexOf(",");return t>=0&&e.slice(t+1).trim()||null}function E1(e){var i,s,l,u;const t=e.language_family??e.languageFamily??((i=e._raw)==null?void 0:i.LanguageFamily)??((s=e._raw)==null?void 0:s.language_family),o=dr(t),n=Xi(o);if(n&&n!=="Unknown")return n;const a=e.primary_language??e.primaryLanguage??((l=e._raw)==null?void 0:l.PrimaryLanguage)??((u=e._raw)==null?void 0:u.primary_language),r=dr(a);if(r){const c=String(r).trim(),g=L1(c);if(g)return g;for(const[h,y]of N1)if(h.test(c))return y}return"Unknown"}function I1(e){var a,r;const t=((a=e._raw)==null?void 0:a.primary_language)??((r=e._raw)==null?void 0:r.PrimaryLanguage);if(t){const i=dr(t);if(i)return String(i).trim()}const o=e.primary_language??e.primaryLanguage??e.language,n=dr(o);return n&&String(n).trim()||"— Unknown —"}function F1(e){var o,n;const t=e.population??((o=e._raw)==null?void 0:o.Population)??((n=e._raw)==null?void 0:n.population)??0;return parseInt(t,10)||0}function M1(e,t){var o,n;return(e==null?void 0:e.id)??t.id??t.peopleGroupId??((o=t._raw)==null?void 0:o.PeopleID3)??((n=t._raw)==null?void 0:n.PeopleID)??null}function R1(e,t){return(e==null?void 0:e.clusterId)??t.clusterId??null}function D1(e,t){var a,r,i,s;const o=parseFloat(t.latitude??t.lat??((a=t._raw)==null?void 0:a.Latitude)??((r=t._raw)==null?void 0:r.latitude)),n=parseFloat(t.longitude??t.lng??((i=t._raw)==null?void 0:i.Longitude)??((s=t._raw)==null?void 0:s.longitude));return!isFinite(o)||!isFinite(n)?null:[n,o]}function B1(e,t){return t.peopleGroupCount!==e.peopleGroupCount?t.peopleGroupCount-e.peopleGroupCount:(t.population||0)-(e.population||0)}function gr(e,t){return(t.population||0)!==(e.population||0)?(t.population||0)-(e.population||0):t.peopleGroupCount-e.peopleGroupCount}function zp(e,t={}){t.aggregator;const o=Zo({});function n(p){return!!o[p]}function a(p){o[p]=!o[p]}function r(p){const d=new Set(Array.isArray(p)?p:[]);for(const v of Object.keys(o))d.has(v)||(o[v]=!1);for(const v of d)o[v]=!0}const i=W(()=>{const p=(e==null?void 0:e.value)||[],d=new Map;for(const v of p){const C=P1(v),x=E1(C),k=I1(C),S=T1(k),L=O1(k),w=F1(C),T=M1(v,C),O=R1(v,C),N=D1(v,C);let H=d.get(x);H||(H={key:x,peopleGroupCount:0,population:0,pinIds:[],coords:[],clusterId:null,languages:new Map},d.set(x,H)),H.peopleGroupCount+=1,H.population+=w,T!=null&&H.pinIds.push(T),N&&H.coords.push(N),O&&!H.clusterId&&(H.clusterId=O);let U=H.languages.get(S);if(U||(U={key:S,peopleGroupCount:0,population:0,pinIds:[],coords:[],clusterId:null,dialects:new Map},H.languages.set(S,U)),U.peopleGroupCount+=1,U.population+=w,T!=null&&U.pinIds.push(T),N&&U.coords.push(N),O&&!U.clusterId&&(U.clusterId=O),L){let pe=U.dialects.get(L);pe?pe.originalLabels.add(k):(pe={key:L,peopleGroupCount:0,population:0,pinIds:[],coords:[],originalLabels:new Set([k])},U.dialects.set(L,pe)),pe.peopleGroupCount+=1,pe.population+=w,T!=null&&pe.pinIds.push(T),N&&pe.coords.push(N)}}return d}),s=t.showAllKnown!==!1,l=W(()=>{const p=i.value,d=[],v=new Set;for(const C of p.values())d.push({key:C.key,label:C.key,color:Rt(C.key),peopleGroupCount:C.peopleGroupCount,population:C.population,kind:C.clusterId?"cluster":"family",clusterId:C.clusterId||void 0,pinIds:C.pinIds,coords:C.coords}),v.add(C.key);if(s)for(const C of Object.keys(gp))v.has(C)||d.push({key:C,label:C,color:Rt(C),peopleGroupCount:0,population:0,kind:"family",pinIds:[],coords:[]});return d.sort(B1),d});function u(p){const d=i.value.get(p);if(!d)return[];const v=[];for(const C of d.languages.values())v.push({key:`${p}__${C.key}`,label:C.key,color:Rt(p),peopleGroupCount:C.peopleGroupCount,population:C.population,kind:C.clusterId?"cluster":"language",familyKey:p,clusterId:C.clusterId||void 0,pinIds:C.pinIds,coords:C.coords,hasDialects:C.dialects.size>0});return v.sort(gr),v}function c(p){const d=p.indexOf("__");if(d<0)return[];const v=p.slice(0,d),C=p.slice(d+2),x=i.value.get(v);if(!x)return[];const k=x.languages.get(C);if(!k)return[];const S=[];for(const L of k.dialects.values())S.push({key:`${p}__${L.key}`,label:`${C}, ${L.key}`,dialectOnly:L.key,color:Rt(v),peopleGroupCount:L.peopleGroupCount,population:L.population,kind:"dialect",languageKey:C,familyKey:v,pinIds:L.pinIds,coords:L.coords,originalLabels:Array.from(L.originalLabels)});return S.sort(gr),S}const g=W(()=>{const p=i.value,d=[];for(const v of p.values())for(const C of v.languages.values())d.push({key:`${v.key}__${C.key}`,label:C.key,color:Rt(v.key),peopleGroupCount:C.peopleGroupCount,population:C.population,kind:C.clusterId?"cluster":"language",familyKey:v.key,clusterId:C.clusterId||void 0,pinIds:C.pinIds,coords:C.coords,hasDialects:C.dialects.size>0});return d.sort(gr),d}),h=W(()=>{const p=i.value,d=[];for(const v of p.values())for(const C of v.languages.values())for(const x of C.dialects.values())d.push({key:`${v.key}__${C.key}__${x.key}`,label:`${C.key}, ${x.key}`,dialectOnly:x.key,color:Rt(v.key),peopleGroupCount:x.peopleGroupCount,population:x.population,kind:"dialect",languageKey:C.key,familyKey:v.key,pinIds:x.pinIds,coords:x.coords,originalLabels:Array.from(x.originalLabels)});return d.sort(gr),d});function y(p){let d=p;if(typeof p=="string"&&(d=l.value.find(C=>C.key===p),!d))return;const v={kind:d.kind,pinIds:d.pinIds||[],coords:d.coords||[]};d.kind==="family"&&(v.familyKey=d.label),d.kind==="language"&&(v.languageKey=d.label,v.familyKey=d.familyKey),d.kind==="dialect"&&(v.dialectKey=d.key,v.dialectOnly=d.dialectOnly||d.label,v.languageKey=d.languageKey,v.familyKey=d.familyKey,v.originalLabels=d.originalLabels||[]),d.kind==="cluster"&&(v.clusterId=d.clusterId),typeof window<"u"&&typeof window.CustomEvent=="function"&&window.dispatchEvent(new CustomEvent("legend:highlight",{detail:v}))}const _=W(()=>{const p=i.value,d=[];for(const v of p.values()){const C=Rt(v.key),x=[];for(const S of v.languages.values()){S.key&&S.key!=="— Unknown —"&&x.push(S.key);for(const L of S.dialects.values())for(const w of L.originalLabels)w&&x.push(w)}const k={id:`fam:${v.key}`,label:v.key,color:C,count:v.peopleGroupCount,pop:v.population,filter:x.length>0?["any",["==",["get","languageFamily"],v.key],["in",["get","language"],["literal",Array.from(new Set(x))]]]:["==",["get","languageFamily"],v.key],children:[]};for(const S of v.languages.values()){const L=S.key,w={id:`lang:${v.key}__${L}`,label:L,color:C,count:S.peopleGroupCount,pop:S.population,filter:["any",["==",["get","language"],L],["==",["slice",["get","language"],0,L.length+1],L+","],["in"," "+L,["get","language"]]],children:[]};for(const T of S.dialects.values()){const O=Array.from(T.originalLabels);w.children.push({id:`dial:${v.key}__${L}__${T.key}`,label:`${L}, ${T.key}`,color:C,count:T.peopleGroupCount,pop:T.population,filter:O.length===1?["==",["get","language"],O[0]]:["in",["get","language"],["literal",O]],originalLabels:O,children:[]})}k.children.push(w)}d.push(k)}return d.sort((v,C)=>C.pop-v.pop||C.count-v.count),d});return{rows:l,languageRows:g,dialectRows:h,langTree:_,toggle:a,isExpanded:n,setExpansionToOnly:r,childRowsFor:u,dialectRowsFor:c,highlight:y}}const G1=[];function z1(){const e=pr(fn.RESOURCE);return e&&e.propertyKey==="resourceType"?e:null}function H1(){const e=pr(fn.DOXA_REGION);return e&&e.propertyKey==="doxaRegion"?e.palette||{}:{}}function U1(){const e=pr(fn.AFFINITY_BLOCK);if(!e||e.propertyKey!=="affinityBlock")return{};const t=e.names||{},o=e.palette||{},n={};for(const a of Object.keys(t))n[t[a]]=o[a]||"#999";return n}function Vo(e,t){const a=Object.values(e.sources||{}).flatMap(r=>(r==null?void 0:r.features)||[]).filter(r=>t(r.properties||r));return{count:a.length,population:a.reduce((r,i)=>{var l;const s=i.properties||i;return r+(parseInt(s.population||((l=s._raw)==null?void 0:l.Population)||0)||0)},0)}}const Hp=100;function j1(e){var o;let t=(e==null?void 0:e.peopleCommitted)??((o=e==null?void 0:e._raw)==null?void 0:o.people_committed)??(e==null?void 0:e.people_committed)??null;return t&&typeof t=="object"&&(t=t.value??t.label??0),Number(t)||0}function ys(e){return t=>{const o=j1(t);return e==="fullPrayer"?o>=Hp:e==="hasPrayer"?o>0&&o0||typeof t=="string"&&parseInt(t,10)>0}function jp(e){var o;const t=e.adoptionStatus??((o=e._raw)==null?void 0:o.adopted_by_churches);return t===!0||t===1||t==="1"||t==="true"||typeof t=="number"&&t>0||typeof t=="string"&&parseInt(t,10)>0}const W1={prayer:"legend.title.prayer",engagement:"legend.title.engagement",adoption:"legend.title.adoption","language-families":"legend.title.languageFamilies","language-family":"legend.title.languageFamilies","doxa-regions":"legend.title.doxaRegions","doxa-region":"legend.title.doxaRegions","affinity-blocs":"legend.title.affinityBlocs",countries:"legend.title.countries",country:"legend.title.countries","gospel-resources":"legend.title.gospelResources",religion:"legend.title.religion"};function bn(e){return e==="doxa-region"?"doxa-regions":e==="country"?"countries":e}function K1(e){return e?e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"K":e.toLocaleString():"0"}function V1(e){return(Number(e)||0).toLocaleString()}function Wp(e,t={}){const o=Ce("dataStore"),n=Ce("uiStore"),a=Ce("mapStore"),{t:r}=gt(),i=i1();i.load();const s=W(()=>{if(bn(e.value)==="religion")return"Religion";const v=W1[e.value];return v?r(v):""}),l=W(()=>bn(e.value)==="affinity-blocs"?G1:[{key:"count",label:r("legend.columns.upgs"),width:48},{key:"population",label:r("legend.columns.population"),width:48}]),u=W(()=>{const d=bn(e.value);return d==="prayer"?n.prayerFilter:d==="engagement"?n.engagementFilter:d==="adoption"?n.adoptionFilter:d==="language-families"?a.selectedFamily:d==="doxa-regions"?a.selectedRegion:d==="gospel-resources"?a.selectedResource:d==="religion"?n.religionFilter:null});function c(d){const v=bn(e.value);v==="prayer"?n.setPrayerFilter(n.prayerFilter===d?null:d):v==="engagement"?n.setEngagementFilter(n.engagementFilter===d?null:d):v==="adoption"?n.setAdoptionFilter(n.adoptionFilter===d?null:d):v==="language-families"?a.selectFamily(a.selectedFamily===d?null:d):v==="doxa-regions"?a.selectRegion(a.selectedRegion===d?null:d):v==="gospel-resources"?a.selectResource(a.selectedResource===d?null:d):v==="religion"&&n.setReligionFilter(n.religionFilter===d?null:d)}const g=W(()=>{var v,C,x,k;const d=bn(e.value);if(d==="prayer"){const S=Vo(o,ys("noPrayer")),L=Vo(o,ys("hasPrayer")),w=Vo(o,ys("fullPrayer")),T=i.loaded.value,O=T?i.noPrayerCount():S.count,N=T?i.hasPrayerCount():L.count,H=T?i.fullPrayerCount():w.count;return[{key:"noPrayer",label:r("legend.prayer.needsPrayer"),color:Ko.noPrayer,count:O,population:S.population,filterKey:"noPrayer",children:[{key:"hasPrayer",label:r("legend.prayer.hasPrayer"),color:Ko.hasPrayer,count:N,population:L.population,filterKey:"hasPrayer"},{key:"fullPrayer",label:r("legend.prayer.fullPrayerCoverage"),color:Ko.fullPrayer,count:H,population:w.population,filterKey:"fullPrayer"}]}]}if(d==="engagement"){const S=Vo(o,w=>!Up(w)),L=Vo(o,w=>Up(w));return[{key:"notEngaged",label:r("legend.engagement.needsEngagement"),color:Wi.notEngaged,count:S.count,population:S.population,filterKey:"notEngaged",children:[{key:"hasEngagement",label:r("legend.engagement.hasEngagement"),color:Wi.hasEngagement,count:L.count,population:L.population,filterKey:"hasEngagement"}]}]}if(d==="adoption"){const S=Vo(o,w=>!jp(w)),L=Vo(o,w=>jp(w));return[{key:"notAdopted",label:r("legend.adoption.needsAdoption"),color:qi.notAdopted,count:S.count,population:S.population,filterKey:"notAdopted",children:[{key:"hasAdoption",label:r("legend.adoption.hasAdoption"),color:qi.hasAdoption,count:L.count,population:L.population,filterKey:"hasAdoption"}]}]}if(d==="religion"){const S={},w=Object.values(o.sources||{}).flatMap(N=>(N==null?void 0:N.features)||[]);for(const N of w){const H=N.properties||N,U=H.religion||((C=(v=H._raw)==null?void 0:v.religion)==null?void 0:C.value)||((x=H._raw)==null?void 0:x.religion)||"",pe=ps(U);S[pe]||(S[pe]={count:0,population:0}),S[pe].count+=1,S[pe].population+=parseInt(H.population||((k=H._raw)==null?void 0:k.Population)||0)||0}const T=new Set((t==null?void 0:t.excludeReligionFamilies)||[]);for(const N of T)delete S[N];return Object.keys(S).sort((N,H)=>N==="C"?-1:H==="C"?1:S[H].population-S[N].population).map(N=>({key:N,label:Tp(N)||"Unknown",color:St[N]||"#94a3b8",count:S[N].count,population:S[N].population,filterKey:N,children:[]}))}if(d==="language-families")return(o.languageFamiliesData||[]).map(S=>({key:S.name,label:S.name,color:Rt(S.name),count:S.count,population:S.population,filterKey:S.name,children:[]}));if(d==="language-family"){const L={value:Object.values(o.sources||{}).flatMap(T=>(T==null?void 0:T.features)||[])},{rows:w}=zp(L);return w.value.map(T=>({key:T.key,label:T.label,color:T.color,count:T.peopleGroupCount,population:T.population,filterKey:T.key,children:[]}))}if(d==="doxa-regions"){const S=H1();return(o.doxaRegionsData||[]).map(L=>({key:L.name,label:L.name,color:S[L.name]||"#999",count:L.count,population:L.population,filterKey:L.name,children:[]}))}if(d==="gospel-resources"){const S=z1(),L=(S==null?void 0:S.palette)??{},w=(S==null?void 0:S.names)??{},T=o.resourcesData||[];return Object.keys(L).map(O=>{const N=T.find(H=>H.key===O);return{key:O,label:w[O]||O,color:L[O],count:(N==null?void 0:N.count)||0,population:(N==null?void 0:N.population)||0,filterKey:O,children:((N==null?void 0:N.children)||[]).map(H=>({key:H.key,label:H.name||H.key,color:H.color||L[O],count:H.count||0,population:H.population||0,filterKey:H.key}))}})}if(d==="affinity-blocs"){const S=U1();return Object.keys(S).sort().map(L=>({key:L,label:L,color:S[L],filterKey:L,children:[]}))}return[]});function h(d,v){return d.reduce((C,x)=>{var S;let k=x[v]||0;return(S=x.children)!=null&&S.length&&(k+=h(x.children,v)),C+k},0)}const y=W(()=>h(g.value,"count")),_=W(()=>h(g.value,"population")),p=W(()=>{if(bn(e.value)!=="prayer")return null;const d=g.value[0];if(!d)return null;const v=(d.children||[]).find(w=>w.key==="hasPrayer"),C=(d.children||[]).find(w=>w.key==="fullPrayer"),x=Number(d.count)||0,k=Number(v==null?void 0:v.count)||0,S=Number(C==null?void 0:C.count)||0,L=(Number(v==null?void 0:v.population)||0)+(Number(C==null?void 0:C.population)||0);return{covered:k+S,total:y.value,coveredPopulation:L,totalPopulation:_.value,segments:[{key:"fullPrayer",label:(C==null?void 0:C.label)??r("legend.prayer.fullPrayerCoverage"),count:S,color:Ko.fullPrayer},{key:"hasPrayer",label:(v==null?void 0:v.label)??r("legend.prayer.hasPrayer"),count:k,color:Ko.hasPrayer},{key:"noPrayer",label:d.label,count:x,color:Ko.noPrayer}]}});return{items:g,columns:l,title:s,activeFilter:u,setFilter:c,totalCount:y,totalPopulation:_,progress:p,fmtPop:K1,fmtCount:V1}}const q1="",Y1=["data-covered","data-total","data-pct"],J1={class:"lpb-track"},Z1=["data-span-pct"],X1=["data-seg","data-count","aria-label","onMouseenter","onFocus","onClick"],Q1=["data-seg","data-count","aria-label"],ev=["aria-valuenow","aria-valuemax","aria-valuetext","aria-label","title"],Kp=Wo({__name:"LegendProgressBar",props:{covered:{type:Number,default:0},total:{type:Number,default:0},segments:{type:Array,default:()=>[]},isDark:{type:Boolean,default:!1},label:{type:String,default:""},fmtCount:{type:Function,default:e=>(Number(e)||0).toLocaleString()},compact:{type:Boolean,default:!1},stacked:{type:Boolean,default:!1},showText:{type:Boolean,default:!0}},emits:["select-segment"],setup(e,{emit:t}){const{t:o}=gt();_t(`
.lpb{position:relative;display:flex;align-items:center;gap:8px;min-width:0;width:100%;box-sizing:border-box;}
-.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:10px;border-radius:5px;overflow:hidden;background:rgba(110,118,129,0.25);}
+.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:24px;margin:-7px 0;overflow:hidden;}
+.lpb-track::before{content:'';position:absolute;left:0;right:0;top:7px;height:10px;border-radius:5px;background:rgba(110,118,129,0.25);}
+.lpb-seg{position:relative;}
+.lpb-span:first-child .lpb-seg:first-child{border-top-left-radius:5px 12px;border-bottom-left-radius:5px 12px;}
+.lpb-seg-rest,.lpb-span:last-child .lpb-seg:last-child{border-top-right-radius:5px 12px;border-bottom-right-radius:5px 12px;}
.lpb-span{display:flex;height:100%;min-width:0;flex:0 0 auto;}
-.lpb-seg{appearance:none;-webkit-appearance:none;border:none;margin:0;padding:0;height:100%;display:block;flex:0 0 auto;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;transition:filter 0.12s,box-shadow 0.12s;}
+.lpb-seg{appearance:none;-webkit-appearance:none;border:none;margin:0;padding:7px 0;box-sizing:border-box;background-clip:content-box !important;height:100%;display:block;flex:0 0 auto;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;transition:filter 0.12s,box-shadow 0.12s;}
.lpb-seg-rest{flex:1 1 auto;}
.lpb-seg:focus{outline:none;}
.lpb-seg:hover,.lpb-seg:focus-visible,.lpb-seg.active{filter:brightness(1.18);box-shadow:inset 0 0 0 1px rgba(255,255,255,0.65);}
@@ -480,7 +484,7 @@
.lpb-light .lpb-pop{background:#f6f8fa;border-color:#d0d7de;color:#1f2328;box-shadow:0 8px 24px rgba(31,35,40,0.18);}
.lpb-pop-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-inline-end:6px;vertical-align:-1px;box-shadow:0 0 0 1px rgba(0,0,0,0.4);}
.legend-mobile-sheet.collapsed .lpb{display:none!important;}
-`,"legend-progress-bar");const n=e,a=t,r=W(()=>n.total>0?Math.round(n.covered/n.total*100):0),i=W(()=>n.total>0?Math.min(100,Math.max(0,n.covered/n.total*100)):0),s=W(()=>n.segments.slice(0,-1)),l=W(()=>n.segments.length?n.segments[n.segments.length-1]:null);function u(w){return n.covered>0?Math.min(100,Math.max(0,(w.count||0)/n.covered*100)):0}function c(w){return n.total>0?Math.round((w.count||0)/n.total*100):0}const g=W(()=>n.label||o("legend.progress.covered",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:r.value})),h=W(()=>o("legend.progress.short",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:r.value}));function y(w){return o("legend.progress.segment",{label:w.label,count:n.fmtCount(w.count||0),pct:c(w)})}const _=ue(null),p=ue(null),d={},v=ue(null),C=ue(0),x=W(()=>n.segments.find(w=>w.key===v.value)||null);function k(w,T){T?d[w]=T:delete d[w]}async function S(w){v.value=w,await jt();const T=_.value,O=d[w],N=p.value;if(!T||!O||!N)return;const H=T.getBoundingClientRect(),U=O.getBoundingClientRect(),pe=Math.max(0,H.width-N.offsetWidth);C.value=Math.min(Math.max(0,U.left-H.left),pe)}function L(){v.value=null}return(w,T)=>(I(),D("div",{ref_key:"rootEl",ref:_,class:Se(["lpb",[e.isDark?"lpb-dark":"lpb-light",{"lpb-stacked":e.stacked}]]),"data-covered":e.covered,"data-total":e.total,"data-pct":r.value,onMouseleave:L},[b("div",J1,[b("span",{class:"lpb-span",style:Ue({width:i.value+"%"}),"data-span-pct":i.value.toFixed(2)},[(I(!0),D(be,null,Ze(s.value,O=>(I(),D("button",{key:O.key,type:"button",class:Se(["lpb-seg",{active:v.value===O.key}]),style:Ue({width:u(O)+"%",background:O.color}),"data-seg":O.key,"data-count":O.count||0,"aria-label":y(O),ref_for:!0,ref:N=>k(O.key,N),onMouseenter:N=>S(O.key),onFocus:N=>S(O.key),onBlur:L,onClick:it(N=>a("select-segment",O.key),["stop"])},null,46,X1))),128))],12,Z1),l.value?(I(),D("button",{key:0,type:"button",class:Se(["lpb-seg lpb-seg-rest",{active:v.value===l.value.key}]),style:Ue({background:l.value.color}),"data-seg":l.value.key,"data-count":l.value.count||0,"aria-label":y(l.value),ref:O=>k(l.value.key,O),onMouseenter:T[0]||(T[0]=O=>S(l.value.key)),onFocus:T[1]||(T[1]=O=>S(l.value.key)),onBlur:L,onClick:T[2]||(T[2]=it(O=>a("select-segment",l.value.key),["stop"]))},null,46,Q1)):ge("",!0)]),e.showText?(I(),D("span",{key:0,class:"lpb-text",role:"progressbar","aria-valuenow":e.covered,"aria-valuemin":"0","aria-valuemax":e.total,"aria-valuetext":h.value,"aria-label":g.value,title:g.value},B(e.compact?r.value+"%":h.value),9,ev)):ge("",!0),x.value?(I(),D("div",{key:1,ref_key:"popEl",ref:p,class:"lpb-pop",role:"tooltip",style:Ue({left:C.value+"px"})},[b("span",{class:"lpb-pop-dot",style:Ue({background:x.value.color})},null,4),Ie(B(y(x.value)),1)],4)):ge("",!0)],42,Y1))}},[["styles",[q1]],["__scopeId","data-v-ea8195d3"]]),tv="",ov={key:0,class:"lrg-title-row"},nv={class:"lrg-title-caret-slot"},av={class:"lrg-title"},rv=["data-lrg-filter-key"],iv=["onClick"],sv={key:1,class:"lrg-caret-placeholder","aria-hidden":"true"},lv=["onClick"],cv={class:"lrg-name"},uv={class:"lrg-badge-inner"},pv=["data-lrg-filter-key"],dv=["onClick"],gv={class:"lrg-name"},hv={class:"lrg-badge-inner"},mv={key:1,class:"lrg-footer"},fv={class:"lrg-footer-label"},Vp=Wo({__name:"LegendRows",props:{title:{type:String,default:""},items:{type:Array,required:!0},columns:{type:Array,default:()=>[]},activeFilter:{type:String,default:null},isDark:{type:Boolean,default:!1},hideColumnHeader:{type:Boolean,default:!1},showFooter:{type:Boolean,default:!1},totalCount:{type:Number,default:0},totalPopulation:{type:Number,default:0},fmtPop:{type:Function,default:e=>String(e||0)},fmtCount:{type:Function,default:e=>String(e||0)},disableCollapse:{type:Boolean,default:!1},progress:{type:Object,default:null}},emits:["filter-click"],setup(e){const{t}=gt();_t(`
+`,"legend-progress-bar");const n=e,a=t,r=W(()=>n.total>0?Math.round(n.covered/n.total*100):0),i=W(()=>n.total>0?Math.min(100,Math.max(0,n.covered/n.total*100)):0),s=W(()=>n.segments.slice(0,-1)),l=W(()=>n.segments.length?n.segments[n.segments.length-1]:null);function u(w){return n.covered>0?Math.min(100,Math.max(0,(w.count||0)/n.covered*100)):0}function c(w){return n.total>0?Math.round((w.count||0)/n.total*100):0}const g=W(()=>n.label||o("legend.progress.covered",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:r.value})),h=W(()=>o("legend.progress.short",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:r.value}));function y(w){return o("legend.progress.segment",{label:w.label,count:n.fmtCount(w.count||0),pct:c(w)})}const _=ue(null),p=ue(null),d={},v=ue(null),C=ue(0),x=W(()=>n.segments.find(w=>w.key===v.value)||null);function k(w,T){T?d[w]=T:delete d[w]}async function S(w){v.value=w,await jt();const T=_.value,O=d[w],N=p.value;if(!T||!O||!N)return;const H=T.getBoundingClientRect(),U=O.getBoundingClientRect(),pe=Math.max(0,H.width-N.offsetWidth);C.value=Math.min(Math.max(0,U.left-H.left),pe)}function L(){v.value=null}return(w,T)=>(I(),D("div",{ref_key:"rootEl",ref:_,class:Se(["lpb",[e.isDark?"lpb-dark":"lpb-light",{"lpb-stacked":e.stacked}]]),"data-covered":e.covered,"data-total":e.total,"data-pct":r.value,onMouseleave:L},[b("div",J1,[b("span",{class:"lpb-span",style:Ue({width:i.value+"%"}),"data-span-pct":i.value.toFixed(2)},[(I(!0),D(be,null,Ze(s.value.filter(O=>u(O)>0),O=>(I(),D("button",{key:O.key,type:"button",class:Se(["lpb-seg",{active:v.value===O.key}]),style:Ue({width:u(O)+"%",background:O.color}),"data-seg":O.key,"data-count":O.count||0,"aria-label":y(O),ref_for:!0,ref:N=>k(O.key,N),onMouseenter:N=>S(O.key),onFocus:N=>S(O.key),onBlur:L,onClick:it(N=>a("select-segment",O.key),["stop"])},null,46,X1))),128))],12,Z1),l.value?(I(),D("button",{key:0,type:"button",class:Se(["lpb-seg lpb-seg-rest",{active:v.value===l.value.key}]),style:Ue({background:l.value.color}),"data-seg":l.value.key,"data-count":l.value.count||0,"aria-label":y(l.value),ref:O=>k(l.value.key,O),onMouseenter:T[0]||(T[0]=O=>S(l.value.key)),onFocus:T[1]||(T[1]=O=>S(l.value.key)),onBlur:L,onClick:T[2]||(T[2]=it(O=>a("select-segment",l.value.key),["stop"]))},null,46,Q1)):ge("",!0)]),e.showText?(I(),D("span",{key:0,class:"lpb-text",role:"progressbar","aria-valuenow":e.covered,"aria-valuemin":"0","aria-valuemax":e.total,"aria-valuetext":h.value,"aria-label":g.value,title:g.value},B(e.compact?r.value+"%":h.value),9,ev)):ge("",!0),x.value?(I(),D("div",{key:1,ref_key:"popEl",ref:p,class:"lpb-pop",role:"tooltip",style:Ue({left:C.value+"px"})},[b("span",{class:"lpb-pop-dot",style:Ue({background:x.value.color})},null,4),Ie(B(y(x.value)),1)],4)):ge("",!0)],42,Y1))}},[["styles",[q1]],["__scopeId","data-v-d8b0a5a2"]]),tv="",ov={key:0,class:"lrg-title-row"},nv={class:"lrg-title-caret-slot"},av={class:"lrg-title"},rv=["data-lrg-filter-key"],iv=["onClick"],sv={key:1,class:"lrg-caret-placeholder","aria-hidden":"true"},lv=["onClick"],cv={class:"lrg-name"},uv={class:"lrg-badge-inner"},pv=["data-lrg-filter-key"],dv=["onClick"],gv={class:"lrg-name"},hv={class:"lrg-badge-inner"},mv={key:1,class:"lrg-footer"},fv={class:"lrg-footer-label"},Vp=Wo({__name:"LegendRows",props:{title:{type:String,default:""},items:{type:Array,required:!0},columns:{type:Array,default:()=>[]},activeFilter:{type:String,default:null},isDark:{type:Boolean,default:!1},hideColumnHeader:{type:Boolean,default:!1},showFooter:{type:Boolean,default:!1},totalCount:{type:Number,default:0},totalPopulation:{type:Number,default:0},fmtPop:{type:Function,default:e=>String(e||0)},fmtCount:{type:Function,default:e=>String(e||0)},disableCollapse:{type:Boolean,default:!1},progress:{type:Object,default:null}},emits:["filter-click"],setup(e){const{t}=gt();_t(`
.legend-row-group{--lrg-caret-col:30px;display:flex;flex-direction:column;height:100%;min-height:0;}
.lrg-items{display:grid;grid-template-columns:var(--lrg-grid-cols, var(--lrg-caret-col) minmax(80px,1fr) auto auto var(--lrg-caret-col));grid-auto-rows:auto;align-content:start;column-gap:6px;row-gap:6px;padding:0 0 12px;flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-width:none;-ms-overflow-style:none;}
.lrg-items::-webkit-scrollbar{display:none;width:0;height:0;}
diff --git a/public/dx-maps/cities-internet/connectivity-list/index.html b/public/dx-maps/cities-internet/connectivity-list/index.html
index bc094bf9..c41feb3b 100644
--- a/public/dx-maps/cities-internet/connectivity-list/index.html
+++ b/public/dx-maps/cities-internet/connectivity-list/index.html
@@ -32,7 +32,7 @@
-
+
-
+
-
+
-
+
-
+
-
+
\n\n "
+ "script": "\n\n "
}
},
{
@@ -141,7 +141,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "",
- "script": "\n \n\n\n "
+ "script": "\n \n\n\n "
}
},
{
@@ -156,7 +156,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "",
- "script": "\n\n "
+ "script": "\n\n "
}
}
]
@@ -165,7 +165,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
- "integrity": "sha384-T1wwcgCElOb+VJ68+UTQUklpmdfIgYRoiMfoDCmuRQl5Ug0TMT6TwnxqPZc+Pxr2",
+ "integrity": "sha384-eMaOd1XuV/UbsbgG9CCwqhI+NKX65HXfTbRCvkZ9dzVe4I/iqAQbfENwp9yXloUu",
"elements": [
"country-posters-map"
],
@@ -187,7 +187,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n \n\n\n "
+ "script": "\n \n\n\n "
}
},
{
@@ -202,7 +202,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n\n "
+ "script": "\n\n "
}
},
{
@@ -217,7 +217,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n \n\n\n "
+ "script": "\n \n\n\n "
}
}
]
@@ -371,7 +371,7 @@
"bundle": "doxa-research-map",
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"output": "dx-maps/doxa-research-map/doxa-research-map.js",
- "integrity": "sha384-LkyYrI5KlVPYxWwyRmCmF/HkoJszQcy/QgdEzbCu44V6DBHcHbbhbDzmb5wVWPsg",
+ "integrity": "sha384-iyR3E4UVc4NW5AILqPft8V2l7PsmolOrFZOXNUixoyAOPeWYem65hjuC3C9eKTOJ",
"elements": [
"doxa-research-map"
],
@@ -389,7 +389,7 @@
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"embed": {
"iframe": "",
- "script": "\n \n\n\n "
+ "script": "\n \n\n\n "
},
"instances": [
{
@@ -415,7 +415,7 @@
"bundle": "doxa-simple-map",
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"output": "dx-maps/doxa-simple-map/doxa-simple-map.js",
- "integrity": "sha384-/nvLYlRrOnpEB55b6xO9ifIbOQjA69syL6wIMdPunDeG6BPhs8Uy5hrsQG4LQM20",
+ "integrity": "sha384-WevsqyX+dr9J8OUhxsc64vqurWuUVol1wIa60VXWGmTGVlSGBnfYy4LhCJwpLTpa",
"elements": [
"doxa-map",
"doxa-simple-map"
@@ -434,7 +434,7 @@
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"embed": {
"iframe": "",
- "script": "\n \n\n\n "
+ "script": "\n \n\n\n "
},
"instances": [
{
@@ -814,7 +814,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
- "integrity": "sha384-CrYaPKw7LxAbhbuEL3Z5shABVNO5tu7GvG5qYt9Tbs8uuOYjthuVL5rTnJW2MN+W",
+ "integrity": "sha384-q/V31VqaU1CPKZl+CcPdeyWZRI6D6OPLMvH9N+zO8vfmxI8oncjiRhTo3slfMdYr",
"elements": [
"my-upg-100-list-map"
],
@@ -836,7 +836,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "",
- "script": "\n\n "
+ "script": "\n\n "
}
},
{
@@ -851,7 +851,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "",
- "script": "\n \n\n\n "
+ "script": "\n \n\n\n "
}
},
{
@@ -866,7 +866,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "",
- "script": "\n \n\n\n "
+ "script": "\n \n\n\n "
}
}
]
@@ -938,7 +938,7 @@
"bundle": "plugin-studio",
"scriptUrl": "./plugin-studio/plugin-studio.js",
"output": "dx-maps/plugin-studio/plugin-studio.js",
- "integrity": "sha384-98vItFqz6kqZ9bJt75ba+2IvFEWFdfmHByTPofsvnv4axWI4dyB54pMqu36nXkyC",
+ "integrity": "sha384-8qq9TrPab9dfj4aHBbSMKj2dwCzyuH2gA2xGq4YhK4GNEy38/uVVmeEhJIXiEr1e",
"elements": [
"plugin-studio"
],
@@ -968,7 +968,7 @@
"scriptUrl": "./plugin-studio/plugin-studio.js",
"embed": {
"iframe": "",
- "script": "\n\n "
+ "script": "\n\n "
},
"instances": [
{
@@ -1185,7 +1185,7 @@
"bundle": "rudder-voice",
"scriptUrl": "./rudder-voice/rudder-voice.js",
"output": "dx-maps/rudder-voice/rudder-voice.js",
- "integrity": "sha384-tYeLt2ZP+g7vrgH/9jBug0E95VwqCx0ux3Eb7f9chNmgU8+BLFIBlSoo93aNsNd9",
+ "integrity": "sha384-lHpK3+oBzByGH1hRsz64N7FbKCLl5q6HsVdpSZuv4MHC3PYjYbw0CxF12gnycL/5",
"elements": [
"rudder-voice"
],
@@ -1206,7 +1206,7 @@
"scriptUrl": "./rudder-voice/rudder-voice.js",
"embed": {
"iframe": "",
- "script": "\n\n "
+ "script": "\n\n "
}
}
]
diff --git a/public/dx-maps/doxa-research-map/doxa-research-map.js b/public/dx-maps/doxa-research-map/doxa-research-map.js
index 6c04f2ea..7242b875 100644
--- a/public/dx-maps/doxa-research-map/doxa-research-map.js
+++ b/public/dx-maps/doxa-research-map/doxa-research-map.js
@@ -229,9 +229,13 @@
`||c==="\r")&&!s){if(c==="\r"&&u===`
`&&l++,a||r.length>0){if(r.push(a.trim()),a="",i)n.push(...r),i=!1;else if(r.length>1){const h={};n.forEach((g,L)=>{h[g]=r[L]||""}),o.push(h)}r=[]}}else a+=c}if((a||r.length>0)&&(r.push(a.trim()),!i&&r.length>1)){const l={};n.forEach((c,u)=>{l[c]=r[u]||""}),o.push(l)}return{headers:n,data:o}}async loadCSVData(t){try{const n=await(await fetch(this.baseUrl+t.path)).text(),{headers:a,data:s}=this.parseCSV(n);return this.rawData=s,this.validateFieldMappings(t.fieldMappings,a,t.name),this.normalizeData(t),this.normalizedData}catch(o){throw console.error("Failed to load CSV data:",o),o}}validateFieldMappings(t,o,n){const a=new Set(o.map(r=>r.trim())),s=[];for(const[r,i]of Object.entries(t||{}))if(Array.isArray(i))for(const l of i)a.has(l)||s.push({systemField:r,sourceField:l,isComposite:!0});else i&&!a.has(i)&&s.push({systemField:r,sourceField:i,isComposite:!1});s.length>0&&console.warn(`[DataSourceManager] ${n}: missing CSV columns:`,s)}async loadAPIData(t){try{const n=await(await fetch(t.url||t.endpoint)).json();return this.rawData=await Kg(Array.isArray(n)?n:n.posts||n.data||n.peopleGroups||n.results||[],this.profileConfig),this.normalizeData(t),this.normalizedData}catch(o){throw console.error("Failed to load API data:",o),o}}async loadRestApiData(t){var l,c;const o=Il(),n=((l=t.endpoints)==null?void 0:l.bulk)||"/api/people-groups/list",a=((c=t.defaultFields)==null?void 0:c.join(","))||"",s=[];a&&s.push(`fields=${a}`);const r={...t.queryParams||{}};this.locale&&(r.lang=this.locale);for(const[u,h]of Object.entries(r))s.push(`${u}=${encodeURIComponent(h)}`);const i=`${o}${n}?${s.join("&")}`;try{const u=await fetch(i);if(!u.ok)throw new Error(`HTTP ${u.status}: ${u.statusText}`);const h=u.headers.get("content-type")||"";if(!h.includes("application/json"))throw new Error(`API returned "${h}" instead of JSON. Server at ${o} may be down or misconfigured.`);const g=await u.json();return this.rawData=await Kg(Array.isArray(g)?g:g.posts||g.data||g.results||[],this.profileConfig),this.normalizeData(t),this.normalizedData}catch(u){throw console.error(`[rest-api] Failed to load from ${t.id}:`,u),u}}normalizeData(t){const o=t.fieldMappings||{};this.normalizedData=[],this.dataByUniqueId=new Map;for(const n of this.rawData){const a={};for(const[l,c]of Object.entries(o))if(Array.isArray(c)){const u=c.map(h=>this.getFieldValue(n,h));a[l]=u.join("_")}else a[l]=this.getFieldValue(n,c);if(a.latitude=parseFloat(a.latitude)||0,a.longitude=parseFloat(a.longitude)||0,a.latitude===0&&a.longitude===0)continue;a.populationNum=this.parsePopulation(a.population),a.imageUrl=this.getImageUrl(a,t.imageConfig);const s=a.engagementStatus;s!=null&&(typeof s=="object"&&"value"in s?(a.engagementStatusLabel=s.label||s.value,a.engagementStatus=s.value==="engaged"):a.engagementStatus=s===!0||s===1||s==="1"||s==="true"||typeof s=="number"&&s>0||typeof s=="string"&&parseInt(s,10)>0);const r=a.adoptionStatus;r!=null&&(a.adoptionStatus=r===!0||r===1||r==="1"||r==="true"||typeof r=="object"&&!!(r!=null&&r.value)||typeof r=="number"&&r>0||typeof r=="string"&&parseInt(r,10)>0);const i=["doxaRegion","wagfRegion","wagfBlock","wagfMember","affinityBlock","cluster","peopleGroup","countryIso","religion","religionCode"];for(const l of i){const c=a[l];c!=null&&typeof c=="object"&&"value"in c?(a[l+"Label"]=c.label||c.value,a[l]=c.value):c!=null&&typeof c=="string"&&c!==""&&(a[l+"Label"]||(a[l+"Label"]=c))}a.countryName=a.countryIsoLabel||a.country||a.countryIso||"",a.religionName=a.religionLabel||a.religion||"",a._raw=n,this.normalizedData.push(a),this.dataByUniqueId.set(a.uniqueId,a)}this.rawData=null}getFieldValue(t,o){if(!o)return"";if(t[o]!==void 0)return t[o];const n=o.trim();if(t[n]!==void 0)return t[n];for(const a of Object.keys(t))if(a.trim()===n||a.includes(n)||n.includes(a.trim()))return t[a];return""}parsePopulation(t){if(!t)return 0;const o=String(t).replace(/,/g,"").trim(),n=parseInt(o,10);return isNaN(n)?0:n}getImageUrl(t,o){if(!o)return"";const n=o.placeholderUrl||"",a=t[o.useField];return a&&String(a).trim()&&!String(a).includes("NoImageAvailable")?a:n}getData(){return this.normalizedData}getByUniqueId(t){return this.dataByUniqueId.get(t)}getByCoordinates(t,o,n=1e-4){return this.normalizedData.find(a=>Math.abs(a.latitude-t)o.peopleGroupId===t)}getLanguageFamilies(){const t=new Set;return this.normalizedData.forEach(o=>{o.languageFamily&&t.add(o.languageFamily)}),Array.from(t).sort()}getDataByLanguageFamily(){const t={};return this.normalizedData.forEach(o=>{const n=o.languageFamily||"Unknown";t[n]||(t[n]=[]),t[n].push(o)}),t}getDataByRegion(){const t={};return this.normalizedData.forEach(o=>{const n=o.doxaRegion||"Unknown";t[n]||(t[n]=[]),t[n].push(o)}),t}getStats(){return{totalRecords:this.normalizedData.length,withPeopleId3:this.normalizedData.filter(t=>t.peopleId3).length,withPhotos:this.normalizedData.filter(t=>t.imageUrl&&!String(t.imageUrl).includes("NoImageAvailable")).length,languageFamilies:this.getLanguageFamilies().length,countries:new Set(this.normalizedData.map(t=>t.country)).size,regions:Object.keys(this.getDataByRegion()).length}}}new qg;const Lx="",Cx=["data-covered","data-total","data-pct"],Tx={class:"lpb-track"},Ex=["data-span-pct"],Rx=["data-seg","data-count","aria-label","onMouseenter","onFocus","onClick"],Fx=["data-seg","data-count","aria-label"],Nx=["aria-valuenow","aria-valuemax","aria-valuetext","aria-label","title"],Vg=gn({__name:"LegendProgressBar",props:{covered:{type:Number,default:0},total:{type:Number,default:0},segments:{type:Array,default:()=>[]},isDark:{type:Boolean,default:!1},label:{type:String,default:""},fmtCount:{type:Function,default:e=>(Number(e)||0).toLocaleString()},compact:{type:Boolean,default:!1},stacked:{type:Boolean,default:!1},showText:{type:Boolean,default:!0}},emits:["select-segment"],setup(e,{emit:t}){const{t:o}=_t();Ct(`
.lpb{position:relative;display:flex;align-items:center;gap:8px;min-width:0;width:100%;box-sizing:border-box;}
-.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:10px;border-radius:5px;overflow:hidden;background:rgba(110,118,129,0.25);}
+.lpb-track{position:relative;flex:1 1 auto;min-width:60px;display:flex;height:24px;margin:-7px 0;overflow:hidden;}
+.lpb-track::before{content:'';position:absolute;left:0;right:0;top:7px;height:10px;border-radius:5px;background:rgba(110,118,129,0.25);}
+.lpb-seg{position:relative;}
+.lpb-span:first-child .lpb-seg:first-child{border-top-left-radius:5px 12px;border-bottom-left-radius:5px 12px;}
+.lpb-seg-rest,.lpb-span:last-child .lpb-seg:last-child{border-top-right-radius:5px 12px;border-bottom-right-radius:5px 12px;}
.lpb-span{display:flex;height:100%;min-width:0;flex:0 0 auto;}
-.lpb-seg{appearance:none;-webkit-appearance:none;border:none;margin:0;padding:0;height:100%;display:block;flex:0 0 auto;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;transition:filter 0.12s,box-shadow 0.12s;}
+.lpb-seg{appearance:none;-webkit-appearance:none;border:none;margin:0;padding:7px 0;box-sizing:border-box;background-clip:content-box !important;height:100%;display:block;flex:0 0 auto;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;transition:filter 0.12s,box-shadow 0.12s;}
.lpb-seg-rest{flex:1 1 auto;}
.lpb-seg:focus{outline:none;}
.lpb-seg:hover,.lpb-seg:focus-visible,.lpb-seg.active{filter:brightness(1.18);box-shadow:inset 0 0 0 1px rgba(255,255,255,0.65);}
@@ -245,7 +249,7 @@
.lpb-light .lpb-pop{background:#f6f8fa;border-color:#d0d7de;color:#1f2328;box-shadow:0 8px 24px rgba(31,35,40,0.18);}
.lpb-pop-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-inline-end:6px;vertical-align:-1px;box-shadow:0 0 0 1px rgba(0,0,0,0.4);}
.legend-mobile-sheet.collapsed .lpb{display:none!important;}
-`,"legend-progress-bar");const n=e,a=t,s=ee(()=>n.total>0?Math.round(n.covered/n.total*100):0),r=ee(()=>n.total>0?Math.min(100,Math.max(0,n.covered/n.total*100)):0),i=ee(()=>n.segments.slice(0,-1)),l=ee(()=>n.segments.length?n.segments[n.segments.length-1]:null);function c(_){return n.covered>0?Math.min(100,Math.max(0,(_.count||0)/n.covered*100)):0}function u(_){return n.total>0?Math.round((_.count||0)/n.total*100):0}const h=ee(()=>n.label||o("legend.progress.covered",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:s.value})),g=ee(()=>o("legend.progress.short",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:s.value}));function L(_){return o("legend.progress.segment",{label:_.label,count:n.fmtCount(_.count||0),pct:u(_)})}const w=_e(null),m=_e(null),b={},$=_e(null),d=_e(0),f=ee(()=>n.segments.find(_=>_.key===$.value)||null);function k(_,x){x?b[_]=x:delete b[_]}async function A(_){$.value=_,await Ft();const x=w.value,C=b[_],K=m.value;if(!x||!C||!K)return;const te=x.getBoundingClientRect(),Z=C.getBoundingClientRect(),W=Math.max(0,te.width-K.offsetWidth);d.value=Math.min(Math.max(0,Z.left-te.left),W)}function p(){$.value=null}return(_,x)=>(oe(),de("div",{ref_key:"rootEl",ref:w,class:Fe(["lpb",[e.isDark?"lpb-dark":"lpb-light",{"lpb-stacked":e.stacked}]]),"data-covered":e.covered,"data-total":e.total,"data-pct":s.value,onMouseleave:p},[O("div",Tx,[O("span",{class:"lpb-span",style:pt({width:r.value+"%"}),"data-span-pct":r.value.toFixed(2)},[(oe(!0),de(Ue,null,Bt(i.value,C=>(oe(),de("button",{key:C.key,type:"button",class:Fe(["lpb-seg",{active:$.value===C.key}]),style:pt({width:c(C)+"%",background:C.color}),"data-seg":C.key,"data-count":C.count||0,"aria-label":L(C),ref_for:!0,ref:K=>k(C.key,K),onMouseenter:K=>A(C.key),onFocus:K=>A(C.key),onBlur:p,onClick:ht(K=>a("select-segment",C.key),["stop"])},null,46,Rx))),128))],12,Ex),l.value?(oe(),de("button",{key:0,type:"button",class:Fe(["lpb-seg lpb-seg-rest",{active:$.value===l.value.key}]),style:pt({background:l.value.color}),"data-seg":l.value.key,"data-count":l.value.count||0,"aria-label":L(l.value),ref:C=>k(l.value.key,C),onMouseenter:x[0]||(x[0]=C=>A(l.value.key)),onFocus:x[1]||(x[1]=C=>A(l.value.key)),onBlur:p,onClick:x[2]||(x[2]=ht(C=>a("select-segment",l.value.key),["stop"]))},null,46,Fx)):Se("",!0)]),e.showText?(oe(),de("span",{key:0,class:"lpb-text",role:"progressbar","aria-valuenow":e.covered,"aria-valuemin":"0","aria-valuemax":e.total,"aria-valuetext":g.value,"aria-label":h.value,title:h.value},$e(e.compact?s.value+"%":g.value),9,Nx)):Se("",!0),f.value?(oe(),de("div",{key:1,ref_key:"popEl",ref:m,class:"lpb-pop",role:"tooltip",style:pt({left:d.value+"px"})},[O("span",{class:"lpb-pop-dot",style:pt({background:f.value.color})},null,4),At($e(L(f.value)),1)],4)):Se("",!0)],42,Cx))}},[["styles",[Lx]],["__scopeId","data-v-ea8195d3"]]),Mx="",Ix={key:0,class:"lrg-title-row"},Ox={class:"lrg-title-caret-slot"},Dx={class:"lrg-title"},Gx=["data-lrg-filter-key"],zx=["onClick"],Bx={key:1,class:"lrg-caret-placeholder","aria-hidden":"true"},jx=["onClick"],Ux={class:"lrg-name"},Hx={class:"lrg-badge-inner"},Wx=["data-lrg-filter-key"],Kx=["onClick"],qx={class:"lrg-name"},Vx={class:"lrg-badge-inner"},Yx={key:1,class:"lrg-footer"},Zx={class:"lrg-footer-label"},Yg=gn({__name:"LegendRows",props:{title:{type:String,default:""},items:{type:Array,required:!0},columns:{type:Array,default:()=>[]},activeFilter:{type:String,default:null},isDark:{type:Boolean,default:!1},hideColumnHeader:{type:Boolean,default:!1},showFooter:{type:Boolean,default:!1},totalCount:{type:Number,default:0},totalPopulation:{type:Number,default:0},fmtPop:{type:Function,default:e=>String(e||0)},fmtCount:{type:Function,default:e=>String(e||0)},disableCollapse:{type:Boolean,default:!1},progress:{type:Object,default:null}},emits:["filter-click"],setup(e){const{t}=_t();Ct(`
+`,"legend-progress-bar");const n=e,a=t,s=ee(()=>n.total>0?Math.round(n.covered/n.total*100):0),r=ee(()=>n.total>0?Math.min(100,Math.max(0,n.covered/n.total*100)):0),i=ee(()=>n.segments.slice(0,-1)),l=ee(()=>n.segments.length?n.segments[n.segments.length-1]:null);function c(_){return n.covered>0?Math.min(100,Math.max(0,(_.count||0)/n.covered*100)):0}function u(_){return n.total>0?Math.round((_.count||0)/n.total*100):0}const h=ee(()=>n.label||o("legend.progress.covered",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:s.value})),g=ee(()=>o("legend.progress.short",{covered:n.fmtCount(n.covered),total:n.fmtCount(n.total),pct:s.value}));function L(_){return o("legend.progress.segment",{label:_.label,count:n.fmtCount(_.count||0),pct:u(_)})}const w=_e(null),m=_e(null),b={},$=_e(null),d=_e(0),f=ee(()=>n.segments.find(_=>_.key===$.value)||null);function k(_,x){x?b[_]=x:delete b[_]}async function A(_){$.value=_,await Ft();const x=w.value,C=b[_],K=m.value;if(!x||!C||!K)return;const te=x.getBoundingClientRect(),Z=C.getBoundingClientRect(),W=Math.max(0,te.width-K.offsetWidth);d.value=Math.min(Math.max(0,Z.left-te.left),W)}function p(){$.value=null}return(_,x)=>(oe(),de("div",{ref_key:"rootEl",ref:w,class:Fe(["lpb",[e.isDark?"lpb-dark":"lpb-light",{"lpb-stacked":e.stacked}]]),"data-covered":e.covered,"data-total":e.total,"data-pct":s.value,onMouseleave:p},[O("div",Tx,[O("span",{class:"lpb-span",style:pt({width:r.value+"%"}),"data-span-pct":r.value.toFixed(2)},[(oe(!0),de(Ue,null,Bt(i.value.filter(C=>c(C)>0),C=>(oe(),de("button",{key:C.key,type:"button",class:Fe(["lpb-seg",{active:$.value===C.key}]),style:pt({width:c(C)+"%",background:C.color}),"data-seg":C.key,"data-count":C.count||0,"aria-label":L(C),ref_for:!0,ref:K=>k(C.key,K),onMouseenter:K=>A(C.key),onFocus:K=>A(C.key),onBlur:p,onClick:ht(K=>a("select-segment",C.key),["stop"])},null,46,Rx))),128))],12,Ex),l.value?(oe(),de("button",{key:0,type:"button",class:Fe(["lpb-seg lpb-seg-rest",{active:$.value===l.value.key}]),style:pt({background:l.value.color}),"data-seg":l.value.key,"data-count":l.value.count||0,"aria-label":L(l.value),ref:C=>k(l.value.key,C),onMouseenter:x[0]||(x[0]=C=>A(l.value.key)),onFocus:x[1]||(x[1]=C=>A(l.value.key)),onBlur:p,onClick:x[2]||(x[2]=ht(C=>a("select-segment",l.value.key),["stop"]))},null,46,Fx)):Se("",!0)]),e.showText?(oe(),de("span",{key:0,class:"lpb-text",role:"progressbar","aria-valuenow":e.covered,"aria-valuemin":"0","aria-valuemax":e.total,"aria-valuetext":g.value,"aria-label":h.value,title:h.value},$e(e.compact?s.value+"%":g.value),9,Nx)):Se("",!0),f.value?(oe(),de("div",{key:1,ref_key:"popEl",ref:m,class:"lpb-pop",role:"tooltip",style:pt({left:d.value+"px"})},[O("span",{class:"lpb-pop-dot",style:pt({background:f.value.color})},null,4),At($e(L(f.value)),1)],4)):Se("",!0)],42,Cx))}},[["styles",[Lx]],["__scopeId","data-v-d8b0a5a2"]]),Mx="",Ix={key:0,class:"lrg-title-row"},Ox={class:"lrg-title-caret-slot"},Dx={class:"lrg-title"},Gx=["data-lrg-filter-key"],zx=["onClick"],Bx={key:1,class:"lrg-caret-placeholder","aria-hidden":"true"},jx=["onClick"],Ux={class:"lrg-name"},Hx={class:"lrg-badge-inner"},Wx=["data-lrg-filter-key"],Kx=["onClick"],qx={class:"lrg-name"},Vx={class:"lrg-badge-inner"},Yx={key:1,class:"lrg-footer"},Zx={class:"lrg-footer-label"},Yg=gn({__name:"LegendRows",props:{title:{type:String,default:""},items:{type:Array,required:!0},columns:{type:Array,default:()=>[]},activeFilter:{type:String,default:null},isDark:{type:Boolean,default:!1},hideColumnHeader:{type:Boolean,default:!1},showFooter:{type:Boolean,default:!1},totalCount:{type:Number,default:0},totalPopulation:{type:Number,default:0},fmtPop:{type:Function,default:e=>String(e||0)},fmtCount:{type:Function,default:e=>String(e||0)},disableCollapse:{type:Boolean,default:!1},progress:{type:Object,default:null}},emits:["filter-click"],setup(e){const{t}=_t();Ct(`
.legend-row-group{--lrg-caret-col:30px;display:flex;flex-direction:column;height:100%;min-height:0;}
.lrg-items{display:grid;grid-template-columns:var(--lrg-grid-cols, var(--lrg-caret-col) minmax(80px,1fr) auto auto var(--lrg-caret-col));grid-auto-rows:auto;align-content:start;column-gap:6px;row-gap:6px;padding:0 0 12px;flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-width:none;-ms-overflow-style:none;}
.lrg-items::-webkit-scrollbar{display:none;width:0;height:0;}
diff --git a/public/dx-maps/doxa-research-map/research-map/index.html b/public/dx-maps/doxa-research-map/research-map/index.html
index 67edd5dd..3acfb2d3 100644
--- a/public/dx-maps/doxa-research-map/research-map/index.html
+++ b/public/dx-maps/doxa-research-map/research-map/index.html
@@ -36,7 +36,7 @@
-
+
-
+
-
+
-
+
-
+
-
+
+