diff --git a/library/packet/circularLayout.js b/library/packet/circularLayout.js new file mode 100644 index 000000000..6740824eb --- /dev/null +++ b/library/packet/circularLayout.js @@ -0,0 +1,487 @@ +/** + * circularLayout.js — the poster "circular logic" (ring order + leader routing + + * numbered four-sides + paired-row forks) as ONE pure, Worker-safe module. + * + * Numbers in, geometry out. The module returns POSITIONS, POLYLINES and STATS — + * never SVG/HTML strings (those stay in poster-render.js). It is pure: no DOM, no + * Vue, no fetch, no canvas, no module-level mutable format state, no Math.random, + * and no reliance on Set/Map iteration order (every tie is broken by id). The same + * input yields byte-identical output, which the byte-proof in step 3 depends on. + * + * It is a PORT of the proven geometry in library/packet/poster-render.js and of + * the classic4 four-sides reference — the same math, lifted so a cheap phone (or a + * Worker with no canvas) can lay a big poster out. The text measurer is PASSED IN + * (`opts.measure`) because a Worker has no canvas; the default is the renderer's + * own CHADV × WFUDGE advance table, lifted verbatim, so the numbers match. + * + * layoutCircular({ pins, states, box, mode, opts }) -> { numbers, pins, rows, leaders, badges, stats } + * + * MODES + * 'sideLanes' — today's large-sheet L/R layout: octilinear leaders in margin + * lanes, photo slots. Pins are NOT moved (byte identity in step 1). + * 'fourSides' — design (1): NUMBERED, NO leaders. Numbers in the pin and before + * the name; names on all four sides; 0 crossings by construction. + * Pins de-overlapped in-state via placePinsInState. + * 'pairedRows' — design (2): two groups per row, TWO leaders per row (octilinear + * forks), in ring order, 0 crossings; pins spread in-state. + * + * `box` carries the per-poster geometry the renderer already computes (all px): + * { PW, PH, MAP_X, MAP_W, MAP_Y, MAP_H, HEADER, FOOTER, SHEET_EDGE, GUTTER, + * C4_BAND, PXI, FRAME_X, FRAME_Y, FRAME_W, FRAME_H, LABEL_TOP, LABEL_BOT, + * proj(lng,lat)->[x,y] } + * `opts`: + * { measure(str,pt)->px, slotOf(pin,pt)->px, photoOf(pin)->url|null, + * fmt:{ nameMaxPt, photos, rangeBadge, classic4, fork, sideLanes, pageWidthIn, + * w, h }, seamYs:[y...], placePinsInState, forkRoute, feats:[...] } + * + * placePinsInState and forkRoute come from their sibling library modules (static + * imports below); a caller may still override either via opts for a Worker that + * injects its own. Both are pure ESM, so the static import stays Worker-portable. + */ +import { placePinsInState as libPlacePinsInState } from './pins-in-state.js'; +import { forkRoute as libForkRoute } from './fork-leaders.js'; + +// ── text advance (lifted verbatim from poster-render.js: CHADV × WFUDGE) ─────── +// Worker-safe: a static char-advance table, no canvas. Callers may override via +// opts.measure, but the default MUST equal the renderer's or the bytes move. +const CHADV = (ch) => { + if ('iIjl|.,:;\'! '.includes(ch)) return 0.28; + if ('ftr()[]-'.includes(ch)) return 0.34; + if ('mwMW'.includes(ch)) return 0.86; + if ('ABCDEFGHKLNOPQRSTUVXYZ0123456789'.includes(ch)) return 0.63; + return 0.53; +}; +const WFUDGE = 1.11; +export const textW = (str, fs) => [...String(str)].reduce((a, ch) => a + CHADV(ch), 0) * fs * WFUDGE; + +// ── octilinear leader primitives (lifted verbatim from poster-render.js) ─────── +export function octiRoute(it, laneX, dirOut, dfrac) { + const STUB = 7; + const P = [it.px, it.py]; + const xStub = it.px + dirOut * STUB; + const dy = it.labelY - it.py; + const vdir = dy === 0 ? 0 : Math.sign(dy); + const gap = Math.abs(xStub - laneX); + const diag = Math.max(0, Math.min(dfrac * Math.abs(dy), gap)); + const feeler = gap - diag; + const pts = [P, [xStub, it.py]]; + pts.push([xStub + dirOut * feeler, it.py]); + pts.push([laneX, it.py + vdir * diag]); + pts.push([laneX, it.labelY]); + return pts; +} +export function isOctilinear(pts, eps = 0.6) { + for (let i = 0; i < pts.length - 1; i++) { + const dx = Math.abs(pts[i + 1][0] - pts[i][0]), dy = Math.abs(pts[i + 1][1] - pts[i][1]); + if (dx < eps || dy < eps) continue; + if (Math.abs(dx - dy) < eps) continue; + return false; + } + return true; +} +// Deterministic id tie-break by CODE UNIT (locale-independent, unlike localeCompare), +// so the layout is byte-identical on any runtime/locale (Node, a phone in ar or zh). +function cmpId(a, b) { const x = String(a), y = String(b); return x < y ? -1 : x > y ? 1 : 0; } + +function segCrossXY(p, q) { + const o = (ax, ay, bx, by, cx, cy) => Math.sign((bx - ax) * (cy - ay) - (by - ay) * (cx - ax)); + const [a, b] = p, [c, d] = q; + const same = (u, v) => Math.abs(u[0] - v[0]) < 0.5 && Math.abs(u[1] - v[1]) < 0.5; + if (same(a, c) || same(a, d) || same(b, c) || same(b, d)) return false; + const o1 = o(...a, ...b, ...c), o2 = o(...a, ...b, ...d), o3 = o(...c, ...d, ...a), o4 = o(...c, ...d, ...b); + return o1 !== o2 && o3 !== o4 && o1 !== 0 && o2 !== 0 && o3 !== 0 && o4 !== 0; +} +// Count crossings between polylines, skipping same-polyline pairs (same li). A +// FORK must be passed as ONE polyline id (its unique segs), never its two polys. +export function countPolyCrossings(polys) { + const segs = []; + polys.forEach((pts, li) => { for (let i = 0; i < pts.length - 1; i++) segs.push({ s: [pts[i], pts[i + 1]], li }); }); + let n = 0; + for (let i = 0; i < segs.length; i++) for (let j = i + 1; j < segs.length; j++) { + if (segs[i].li === segs[j].li) continue; + if (segCrossXY(segs[i].s, segs[j].s)) n++; + } + return n; +} +// A leader that passes within r of ANOTHER group's dot ("touch"). segs: array of +// { li, pts }, dots: [{ id, x, y }]. Skips the leader's own endpoint dot by id. +function countTouches(leaderPolys, dots, r) { + let touches = 0; + for (const lp of leaderPolys) { + for (const dot of dots) { + if (dot.id === lp.id) continue; + if (polyNearPoint(lp.pts, dot.x, dot.y, r)) touches++; + } + } + return touches; +} +function polyNearPoint(pts, px, py, r) { + for (let i = 0; i < pts.length - 1; i++) { + if (distPointToSeg(px, py, pts[i][0], pts[i][1], pts[i + 1][0], pts[i + 1][1]) <= r) return true; + } + return false; +} +function distPointToSeg(px, py, ax, ay, bx, by) { + const abx = bx - ax, aby = by - ay; + const len2 = abx * abx + aby * aby; + let t = len2 ? ((px - ax) * abx + (py - ay) * aby) / len2 : 0; + t = t < 0 ? 0 : t > 1 ? 1 : t; + const x = ax + t * abx, y = ay + t * aby; + return Math.hypot(px - x, py - y); +} + +// ── point-in-ring / state polygon helpers (for in-state de-overlap inputs) ───── +function inRing(lng, lat, r) { + let o = false; + for (let i = 0, j = r.length - 1; i < r.length; j = i++) { + const xi = r[i][0], yi = r[i][1], xj = r[j][0], yj = r[j][1]; + if (((yi > lat) !== (yj > lat)) && (lng < (xj - xi) * (lat - yi) / (yj - yi) + xi)) o = !o; + } + return o; +} +function inPoly(lng, lat, p) { + if (!inRing(lng, lat, p[0])) return false; + for (let k = 1; k < p.length; k++) if (inRing(lng, lat, p[k])) return false; + return true; +} +function inFeat(lng, lat, g) { + return g.type === 'Polygon' ? inPoly(lng, lat, g.coordinates) + : g.type === 'MultiPolygon' ? g.coordinates.some((p) => inPoly(lng, lat, p)) : false; +} +// Nearest ADM1 shapeName for an off-polygon point (island fallback), lifted from +// classic4Layout.nearest. Deterministic: first feature wins a tie by scan order. +function nearestState(lng, lat, feats) { + let best = null, bd = Infinity; + const cl = Math.cos(lat * Math.PI / 180); + for (const f of feats) { + const ps = f.geometry.type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates; + for (const po of ps) for (const r of po) for (let i = 0, j = r.length - 1; i < r.length; j = i++) { + const ax = r[j][0], ay = r[j][1], bx = r[i][0], by = r[i][1]; + const dx = (bx - ax) * cl, dy = by - ay, pax = (lng - ax) * cl, pay = lat - ay, l2 = dx * dx + dy * dy; + let t = l2 ? (pax * dx + pay * dy) / l2 : 0; t = Math.max(0, Math.min(1, t)); + const qx = pax - t * dx, qy = pay - t * dy, d = qx * qx + qy * qy; + if (d < bd) { bd = d; best = f.properties.shapeName; } + } + } + return best; +} +// Build { shapeName: polygonsPx } for placePinsInState, projecting every ring. +function statesToPx(feats, proj) { + const statesPx = {}; + for (const f of feats) { + const sn = f.properties.shapeName; + const ps = f.geometry.type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates; + const parts = ps.map((poly) => poly.map((ring) => ring.map(([lng, lat]) => proj(lng, lat)))); + (statesPx[sn] = statesPx[sn] || []).push(...parts); + } + return statesPx; +} + +// ── dependency resolution (injected; lazy Node fallback) ─────────────────────── +function resolveDeps(opts) { + // The library implementations by default; a Worker may inject its own via opts. + return { + placePinsInState: (opts && opts.placePinsInState) || libPlacePinsInState, + forkRoute: (opts && opts.forkRoute) || libForkRoute, + }; +} + +// ── clustering (id-stable; CL_R on the given coordinate pair) ────────────────── +// Scans pins in input order; nearest existing cluster within CL_R wins, ties by +// cluster creation order (first found). Deterministic — no Map/Set ordering. +function clusterBy(pins, CL_R, getX, getY) { + const clusters = []; + for (const p of pins) { + let best = null, bd = CL_R; + for (const c of clusters) { + const d = Math.hypot(getX(c) - getX(p), getY(c) - getY(p)); + if (d <= bd) { bd = d; best = c; } + } + if (best) { + best.items.push(p); + best._sx += (getX(p) - best._sx) / best.items.length; + best._sy += (getY(p) - best._sy) / best.items.length; + } else { + clusters.push({ items: [p], _sx: getX(p), _sy: getY(p) }); + } + } + return clusters; +} + +// ───────────────────────────────── MAIN ─────────────────────────────────────── +export async function layoutCircular({ pins, states, box, mode, opts = {} }) { + if (!Array.isArray(pins)) throw new TypeError('pins must be an array'); + if (!box || typeof box !== 'object') throw new TypeError('box (geometry) is required'); + if (!['sideLanes', 'fourSides', 'pairedRows'].includes(mode)) throw new TypeError(`unknown mode: ${mode}`); + const measure = opts.measure || textW; + const fmt = opts.fmt || {}; + const { placePinsInState, forkRoute } = resolveDeps(opts); + if (mode === 'fourSides') return fourSides({ pins, box, opts, fmt, measure, placePinsInState }); + if (mode === 'pairedRows') return pairedRows({ pins, box, opts, fmt, measure, placePinsInState, forkRoute }); + return sideLanes({ pins, box, opts, fmt, measure }); +} + +// ── FOUR-SIDES (design 1): numbered, no leaders. Ported from classic4Layout. ─── +function fourSides({ pins0 = null, pins, box, opts, fmt, measure, placePinsInState }) { + const { PW, PH, MAP_X, MAP_W, MAP_Y, MAP_H, HEADER, FOOTER, SHEET_EDGE, C4_BAND, PXI, FRAME_X, FRAME_Y, FRAME_W, FRAME_H, proj } = box; + const feats = opts.feats || []; + const RANGE = !!fmt.rangeBadge; + // project pins; px/py are the engine-settled coords passed in on each pin as _px/_py. + const P = pins.map((p) => { + const [rx, ry] = proj ? proj(+p.lng, +p.lat) : [p._px, p._py]; + return { ...p, px: p._px, py: p._py, rx, ry }; + }); + // state assignment + island fallback (deterministic scan order) + let islandFallback = 0; + for (const p of P) { + let st = null; + for (const f of feats) if (inFeat(+p.lng, +p.lat, f.geometry)) { st = f.properties.shapeName; break; } + if (!st) { st = nearestState(+p.lng, +p.lat, feats); if (st) islandFallback++; } + p._state = st; + } + // cluster co-located pins on RAW projected coords (CL_R=12), consecutive numbers + const CL_R = 12; + const clusters = []; + for (const p of P) { + let c = null, bd = CL_R; + for (const q of clusters) { const d = Math.hypot(q.rx - p.rx, q.ry - p.ry); if (d <= bd) { bd = d; c = q; } } + if (c) { + c.items.push(p); + c.rx += (p.rx - c.rx) / c.items.length; c.ry += (p.ry - c.ry) / c.items.length; + c.cx += (p.px - c.cx) / c.items.length; c.cy += (p.py - c.cy) / c.items.length; + } else clusters.push({ items: [p], rx: p.rx, ry: p.ry, cx: p.px, cy: p.py, state: p._state }); + } + // geometry: L/R columns at the sheet edges; T/B rows on the reserved bands + const colTop = HEADER, colBot = PH - FOOTER, colH = colBot - colTop; + const lcolX = SHEET_EDGE + 6, rcolX = PW - SHEET_EDGE - 6; + const rowY_T = HEADER + C4_BAND - 8, rowX0 = MAP_X + 8, rowX1 = MAP_X + MAP_W - 8, rowW = rowX1 - rowX0; + const pitchCol = (f) => f * 1.14, pitchRow = (f) => f * 1.5; + const capCol = (f) => Math.floor(colH / pitchCol(f)), capRow = (f) => Math.floor(rowW / pitchRow(f)); + const fCap = (fmt.nameMaxPt || 11) / 72 * PXI; + let font = 5; + for (let f = fCap; f >= 5; f -= 0.1) { if (2 * capRow(f) + 2 * capCol(f) >= P.length) { font = f; break; } } + // assign whole clusters to sides (balanced by member count) + const fRow = Math.min(capRow(font), Math.max(1, Math.floor(P.length * 0.22))); + const byCy = [...clusters].sort((a, b) => a.cy - b.cy || a.cx - b.cx); + const used = new Set(); + const T = [], B = []; let cT = 0, cB = 0; + for (const c of byCy) { if (cT + c.items.length <= fRow) { T.push(c); cT += c.items.length; used.add(c); } else break; } + for (let i = byCy.length - 1; i >= 0; i--) { const c = byCy[i]; if (used.has(c)) break; if (cB + c.items.length <= fRow) { B.push(c); cB += c.items.length; used.add(c); } else break; } + const midc = clusters.filter((c) => !used.has(c)).sort((a, b) => a.cx - b.cx || a.cy - b.cy); + const halfN = Math.ceil(midc.reduce((a, c) => a + c.items.length, 0) / 2); + const L = [], R = []; let cL = 0; + for (const c of midc) { if (cL < halfN) { L.push(c); cL += c.items.length; } else R.push(c); } + L.sort((a, b) => b.cy - a.cy || a.cx - b.cx); R.sort((a, b) => a.cy - b.cy || a.cx - b.cx); + T.sort((a, b) => a.cx - b.cx || a.cy - b.cy); B.sort((a, b) => b.cx - a.cx || a.cy - b.cy); + // number 1..N in ring order [L, T, R, B] + const ringC = [...L, ...T, ...R, ...B]; let num = 0; + const sideOf = new Map(); L.forEach((c) => sideOf.set(c, 'L')); T.forEach((c) => sideOf.set(c, 'T')); R.forEach((c) => sideOf.set(c, 'R')); B.forEach((c) => sideOf.set(c, 'B')); + for (const c of ringC) { c.first = num + 1; const sd = sideOf.get(c); for (const p of c.items) { p.num = ++num; p.side = sd; } c.last = num; } + const Lp = L.flatMap((c) => c.items), Rp = R.flatMap((c) => c.items), Tp = T.flatMap((c) => c.items), Bp = B.flatMap((c) => c.items); + // bottom row hug: slant -45, anchor one longest-name diagonal below the map + const nmLen = (p) => `${p.num}. ${String(p.name)}`.length; + const maxBExt = Bp.length ? Math.max(...Bp.map((p) => nmLen(p) * font * 0.55 * Math.SQRT1_2)) : 0; + const rowY_B = Math.min(PH - FOOTER - 8, MAP_Y + MAP_H + maxBExt + 10); + // in-state de-overlap (clusters in range mode, else individual pins) + const rBadge = Math.max(5, font * 0.62); + const statesPx = statesToPx(feats, proj); + let dres; + if (RANGE) { + dres = placePinsInState({ pins: clusters.map((c, i) => ({ id: i, x: c.cx, y: c.cy, stateId: c.state })), states: statesPx, r: rBadge, gap: 1, iters: 400, maxDrift: 80 }); + const byId = new Map(dres.pins.map((q) => [q.id, q])); + clusters.forEach((c, i) => { const q = byId.get(i); c.bx = q ? q.x : c.cx; c.by = q ? q.y : c.cy; }); + } else { + dres = placePinsInState({ pins: P.map((p, i) => ({ id: i, x: p.px, y: p.py, stateId: p._state })), states: statesPx, r: rBadge, gap: 1, iters: 400, maxDrift: 80 }); + const byId = new Map(dres.pins.map((q) => [q.id, q])); + P.forEach((p, i) => { const q = byId.get(i); if (q) { p.px = q.x; p.py = q.y; } }); + } + // emit geometry (NOT svg): label positions + badge/dot positions + const labels = []; + const placeCol = (arr, x, anchor, side) => { const n = arr.length || 1, pitch = colH / n; arr.forEach((p, i) => labels.push({ x, y: colTop + pitch * (i + 0.5), pt: font, anchor, rotate: 0, num: p.num, name: String(p.name), side })); }; + const placeRow = (arr, y, side) => { const n = arr.length || 1, pitch = rowW / n; arr.forEach((p, i) => { const x = rowX0 + pitch * (i + 0.5); labels.push({ x, y, pt: font, anchor: 'start', rotate: -45, num: p.num, name: String(p.name), side }); }); }; + placeCol(Lp, lcolX, 'start', 'L'); placeCol(Rp, rcolX, 'end', 'R'); placeRow(Tp, rowY_T, 'T'); placeRow(Bp, rowY_B, 'B'); + const badges = []; + if (RANGE) for (const c of clusters) badges.push({ x: c.bx, y: c.by, first: c.first, last: c.last, k: c.items.length, pt: font }); + const dots = RANGE ? clusters.map((c) => ({ x: c.bx, y: c.by })) : P.map((p) => ({ x: p.px, y: p.py, num: p.num })); + // stats + const marks = RANGE ? clusters.map((c) => ({ px: c.bx, py: c.by })) : P; + const offFrame = marks.filter((q) => q.px < FRAME_X || q.px > FRAME_X + FRAME_W || q.py < FRAME_Y || q.py > FRAME_Y + FRAME_H).length; + const allNums = P.map((p) => p.num).sort((a, b) => a - b); + const numberDupes = allNums.filter((n, i) => i && n === allNums[i - 1]).length; + const numbersRingOrdered = allNums.every((n, i) => n === i + 1); + const stats = { + placed: P.length, total: pins.length, namePt: +(font / PXI * 72).toFixed(1), + crossings: 0, touches: 0, overflow: offFrame, + numberDupes, numbersRingOrdered, + pinsOutsideState: dres.outsideState, pinOverlaps: dres.overlapsRemaining, + pinsMoved: dres.moved, maxDisp: +dres.maxDisp.toFixed(1), islandFallback, infeasible: dres.infeasible.length, + sides: { L: Lp.length, R: Rp.length, T: Tp.length, B: Bp.length }, + rangeBadges: RANGE ? clusters.filter((c) => c.items.length > 1).length : 0, + }; + return { numbers: P.map((p) => ({ id: p.id, num: p.num })), pins: P.map((p) => ({ id: p.id, x: p.px, y: p.py, num: p.num, side: p.side, state: p._state })), rows: { L: Lp.length, R: Rp.length, T: Tp.length, B: Bp.length }, leaders: [], badges, labels, dots, stats }; +} + +// ── PAIRED-ROWS (design 2): two groups per row, two octilinear leaders. ──────── +function pairedRows({ pins, box, opts, fmt, measure, placePinsInState, forkRoute }) { + const { MAP_X, MAP_W, MAP_Y, MAP_H, PXI, FRAME_X, FRAME_Y, FRAME_W, FRAME_H, proj } = box; + const feats = opts.feats || []; + const P = pins.map((p) => ({ ...p, px: p._px, py: p._py })); + // state assignment for spreading + for (const p of P) { + let st = null; + for (const f of feats) if (inFeat(+p.lng, +p.lat, f.geometry)) { st = f.properties.shapeName; break; } + if (!st) st = nearestState(+p.lng, +p.lat, feats); + p._state = st; + } + // spread pins in-state (design 2 moves pins) + const font = Math.max(5, (fmt.nameMaxPt || 11) / 72 * PXI); + const rPin = Math.max(5, font * 0.62); + const statesPx = statesToPx(feats, proj); + if (feats.length) { + const dres = placePinsInState({ pins: P.map((p, i) => ({ id: i, x: p.px, y: p.py, stateId: p._state })), states: statesPx, r: rPin, gap: 1, iters: 400, maxDrift: 80 }); + const byId = new Map(dres.pins.map((q) => [q.id, q])); + P.forEach((p, i) => { const q = byId.get(i); if (q) { p.px = q.x; p.py = q.y; } }); + var spread = { outsideState: dres.outsideState, overlaps: dres.overlapsRemaining, moved: dres.moved, maxDisp: dres.maxDisp, infeasible: dres.infeasible.length }; + } else { + var spread = { outsideState: 0, overlaps: 0, moved: 0, maxDisp: 0, infeasible: 0 }; + } + // ring order: median x-split into L/R, then y within a side (monotone, non-crossing) + const byX = [...P].sort((a, b) => a.px - b.px || cmpId(a.id, b.id)); + const half = Math.ceil(byX.length / 2); + byX.forEach((p, i) => { p.side = i < half ? 'L' : 'R'; }); + const rowsOut = []; const leaderPolys = []; const allDots = P.map((p) => ({ id: p.id, x: p.px, y: p.py })); + const bandTop = box.LABEL_TOP ?? MAP_Y, bandBot = box.LABEL_BOT ?? (MAP_Y + MAP_H); + for (const side of ['L', 'R']) { + const dirOut = side === 'L' ? -1 : 1; + const laneX = side === 'L' ? MAP_X - 6 : MAP_X + MAP_W + 6; + const col = P.filter((p) => p.side === side).sort((a, b) => a.py - b.py || cmpId(a.id, b.id)); + // two groups per row, in ring (y) order. A row's PORT sits at the mean y of its + // own two pins (not an evenly-pitched slot): because the pairs are consecutive + // in y, these port ys are themselves monotone, so each fork stays local to its + // pins and neighbouring forks cannot sweep across one another. (An evenly-pitched + // port put a row's port far from its pins and produced same-side crossings.) + for (let i = 0; i < col.length; i += 2) { + const a = col[i], b = col[i + 1] || col[i]; + const rowY = (a.py + (b.py)) / 2; + const fork = forkRoute({ + lane: { x: laneX, y: rowY }, + a: { id: a.id, x: a.px, y: a.py }, b: { id: b.id, x: b.px, y: b.py }, + dirOut, mode: 'row', + obstacles: allDots.filter((d) => d.id !== a.id && d.id !== b.id), r: rPin, + }); + rowsOut.push({ side, rowY, aId: a.id, bId: b.id, octilinear: fork.octilinear, throughPins: fork.throughPins }); + leaderPolys.push({ id: `${side}${i}`, polys: fork.polys, segs: fork.segs }); + } + } + // crossings: one polyline id per fork (its deduped segs), skip same-id + // build as [ [segpts...] per fork ] so countPolyCrossings skips intra-fork + const perFork = leaderPolys.map((lp) => lp.segs.map((s) => [s[0], s[1]])); + let crossings = 0; + { + const segs = []; + perFork.forEach((forkSegs, li) => { for (const s of forkSegs) segs.push({ s, li }); }); + for (let i = 0; i < segs.length; i++) for (let j = i + 1; j < segs.length; j++) { + if (segs[i].li === segs[j].li) continue; + if (segCrossXY(segs[i].s, segs[j].s)) crossings++; + } + } + const touches = rowsOut.reduce((a, r) => a + (r.throughPins ? r.throughPins.length : 0), 0); + const octilinear = rowsOut.every((r) => r.octilinear); + const offFrame = P.filter((q) => q.px < FRAME_X || q.px > FRAME_X + FRAME_W || q.py < FRAME_Y || q.py > FRAME_Y + FRAME_H).length; + const stats = { + placed: P.length, total: pins.length, rows: rowsOut.length, namePt: +(font / PXI * 72).toFixed(1), + crossings, touches, overflow: offFrame, octilinear, + pinsOutsideState: spread.outsideState, pinOverlaps: spread.overlaps, + pinsMoved: spread.moved, maxDisp: +(+spread.maxDisp).toFixed(1), infeasible: spread.infeasible, + }; + return { numbers: P.map((p, i) => ({ id: p.id, num: i + 1 })), pins: P.map((p) => ({ id: p.id, x: p.px, y: p.py, state: p._state })), rows: rowsOut, leaders: leaderPolys.map((lp) => ({ id: lp.id, polys: lp.polys || null, segs: lp.segs })), badges: [], labels: [], dots: allDots, stats }; +} + +// ── SIDE-LANES (today's L/R large sheet). Ported from layoutLabels + lanes. ───── +// Pins are NOT moved. Returns leader polylines (octilinear) + label ports + stats. +function sideLanes({ pins, box, opts, fmt, measure }) { + const { MAP_X, MAP_W, MAP_Y, MAP_H, SHEET_EDGE, GUTTER, PXI, FRAME_X, FRAME_Y, FRAME_W, FRAME_H, proj } = box; + const photoOf = opts.photoOf || (() => null); + // project + clamp into the map rect (uses engine-settled _px/_py when present) + const items = pins.map((p) => { + const [lx, ly] = (p._fixed || p._px != null) ? [p._px, p._py] : proj(+p.lng, +p.lat); + const x = Math.max(MAP_X + 2, Math.min(MAP_X + MAP_W - 2, lx)); + const y = Math.max(MAP_Y + 2, Math.min(MAP_Y + MAP_H - 2, ly)); + return { ...p, px: x, py: y }; + }); + // median x-split → L/R (disjoint bands, non-crossing by construction) + const byX = [...items].sort((a, b) => a.px - b.px || cmpId(a.id, b.id)); + const halfB = Math.ceil(byX.length / 2); + byX.forEach((it, i) => { it.side = i < halfB ? 'L' : 'R'; }); + const LABEL_TOP = box.LABEL_TOP ?? MAP_Y, LABEL_BOT = box.LABEL_BOT ?? (MAP_Y + MAP_H); + const segCross = (a, b) => { + const d = (p1, p2, p3) => (p3.y - p1.y) * (p2.x - p1.x) - (p2.y - p1.y) * (p3.x - p1.x); + const A1 = { x: a.sx, y: a.sy }, A2 = { x: a.px, y: a.py }; + const B1 = { x: b.sx, y: b.sy }, B2 = { x: b.px, y: b.py }; + return (d(A1, A2, B1) > 0) !== (d(A1, A2, B2) > 0) && (d(B1, B2, A1) > 0) !== (d(B1, B2, A2) > 0); + }; + // intra-column ring order by greedy crossing repair (bubble on adjacent pairs) + for (const sd of ['L', 'R']) { + const col = items.filter((i) => i.side === sd).sort((a, b) => a.py - b.py || cmpId(a.id, b.id)); + const n2 = col.length || 1; + const rowH0 = (LABEL_BOT - LABEL_TOP) / n2; + const sxCol = sd === 'L' ? MAP_X - 6 : MAP_X + MAP_W + 6; + col.forEach((it, i) => { it.sx = sxCol; it.sy = LABEL_TOP + rowH0 * (i + 0.5); }); + for (let pass = 0; pass < 60; pass++) { + let improved = false; + for (let i = 0; i < col.length - 1; i++) { + const a = col[i], b = col[i + 1]; + const before = segCross(a, b) ? 1 : 0; + const ay = a.sy, by = b.sy; a.sy = by; b.sy = ay; + const after = segCross(a, b) ? 1 : 0; + if (after < before) { col[i] = b; col[i + 1] = a; improved = true; } else { a.sy = ay; b.sy = by; } + } + if (!improved) break; + } + col.forEach((it, i) => { it.ringIdx = i; }); + } + // place rows in ring order, evenly pitched over the band; assign lanes + const SHEET_MARGIN = SHEET_EDGE + 8; + const LANE_EDGE_GAP = 6, LABEL_GAP = 6, LANE_PITCH_MAX = 3; + const LABEL_RES = GUTTER - SHEET_MARGIN - LANE_EDGE_GAP - 24; + const laid = []; + for (const side of ['L', 'R']) { + const col = items.filter((i) => i.side === side).sort((a, b) => a.ringIdx - b.ringIdx); + const n = col.length || 1; + const bandH = LABEL_BOT - LABEL_TOP, rowH = bandH / n; + const dirOut = side === 'L' ? -1 : 1; + const edge = side === 'L' ? MAP_X : MAP_X + MAP_W; + const band = MAP_X - LABEL_RES - LANE_EDGE_GAP - LABEL_GAP; + const pitch = Math.min(LANE_PITCH_MAX, band / Math.max(1, n - 1)); + col.forEach((it, k) => { + it.labelY = LABEL_TOP + rowH * (k + 0.5); + it.laneX = edge + dirOut * (LANE_EDGE_GAP + k * pitch); + it.dirOut = dirOut; + it.tanchor = side === 'L' ? 'end' : 'start'; + laid.push(it); + }); + } + // route leaders at dfrac 0 (current dev sweep is [0]); octilinear by construction + const routesFor = (d) => laid.map((it) => octiRoute(it, it.laneX, it.dirOut, d)); + let bestD = 0, bestX = Infinity; + for (const d of [0]) { const x = countPolyCrossings(routesFor(d)); if (x < bestX) { bestX = x; bestD = d; } } + const routes = routesFor(bestD); + const octiOK = routes.every((r) => isOctilinear(r)); + const dots = laid.map((it) => ({ id: it.id, x: it.px, y: it.py })); + const touches = countTouches(routes.map((pts, i) => ({ id: laid[i].id, pts })), dots, 2); + const stats = { + placed: laid.length, total: pins.length, crossings: bestX, touches, overflow: 0, + octilinear: octiOK, dfrac: bestD, pinsOutsideState: 0, + photos: laid.filter((it) => photoOf(it)).length, + sides: { L: items.filter((i) => i.side === 'L').length, R: items.filter((i) => i.side === 'R').length }, + }; + return { + numbers: [], pins: laid.map((it) => ({ id: it.id, x: it.px, y: it.py })), + rows: [], badges: [], + labels: laid.map((it) => ({ x: it.laneX, y: it.labelY, anchor: it.tanchor, name: String(it.name) })), + leaders: routes.map((pts, i) => ({ id: laid[i].id, pts })), + dots, stats, + }; +} + +export default layoutCircular; diff --git a/library/packet/scripts/check-circular-layout.mjs b/library/packet/scripts/check-circular-layout.mjs new file mode 100644 index 000000000..f7f88f63f --- /dev/null +++ b/library/packet/scripts/check-circular-layout.mjs @@ -0,0 +1,238 @@ +#!/usr/bin/env node +/** + * check-circular-layout.mjs — gate for library/packet/circularLayout.js. + * + * Builds a FROZEN synthetic 855-pin India-scale fixture (sheet 2500x4000, the + * classic4 60x96 constants) with a grid of fake ADM1 "states", then asserts: + * + * fourSides — numbersRingOrdered true, numberDupes 0, pinsOutsideState 0, + * >= 800 pins placed (no vacuous pass). + * pairedRows — 0 crossings on the clean set, pinsOutsideState 0, octilinear true. + * sideLanes — runs, returns stats with a pin-count floor and octilinear leaders. + * determinism — two fourSides runs byte-identical. + * poison — a state far too small for its pins is NAMED in infeasible + * (never silently stacked), surfaced via stats.infeasible > 0. + * budget — fourSides on 855 pins runs under BUDGET_MS (1.5x a measured + * baseline) and under a heap ceiling. Median of 3, so it is not flaky. + * + * Node only, headless, deterministic. Exit 0 PASS / 1 FAIL / 75 import failure. + */ +let layoutCircular; +try { ({ layoutCircular } = await import('../circularLayout.js')); } +catch (e) { console.error(`NOT MEASURED: cannot import circularLayout.js — ${e.message}`); process.exit(75); } + +const fails = [], note = []; + +// Independent recounts — defined HERE, not imported from the module, so a broken +// counter in circularLayout.js cannot make its own stats agree with itself. +function gateInRing(px, py, ring) { + let hit = false; + for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { + const [xi, yi] = ring[i], [xj, yj] = ring[j]; + if ((yi > py) !== (yj > py) && px < ((xj - xi) * (py - yi)) / ((yj - yi) || 1e-12) + xi) hit = !hit; + } + return hit; +} +function gateInState(px, py, parts) { + for (const part of parts) { + const [outer, ...holes] = part; + if (!outer || outer.length < 3 || !gateInRing(px, py, outer)) continue; + if (!holes.some((h) => h && h.length >= 3 && gateInRing(px, py, h))) return true; + } + return false; +} +// Recount, from the OUTPUT pins, how many sit outside their own state. Projects the +// fixture feats to px the same way the module does (box.proj), keyed by shapeName. +function recountOutside(outPins, feats) { + const statesPx = {}; + for (const f of feats) { + const sn = f.properties.shapeName; + const ps = f.geometry.type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates; + const parts = ps.map((poly) => poly.map((ring) => ring.map(([lng, lat]) => box.proj(lng, lat)))); + (statesPx[sn] = statesPx[sn] || []).push(...parts); + } + let out = 0; + for (const p of outPins) { + const parts = statesPx[p.state]; + if (parts && !gateInState(p.x, p.y, parts)) out++; + } + return out; +} +function segCrossGate(p, q) { + const o = (ax, ay, bx, by, cx, cy) => Math.sign((bx - ax) * (cy - ay) - (by - ay) * (cx - ax)); + const [a, b] = p, [c, d] = q; + return o(a[0], a[1], b[0], b[1], c[0], c[1]) !== o(a[0], a[1], b[0], b[1], d[0], d[1]) && + o(c[0], c[1], d[0], d[1], a[0], a[1]) !== o(c[0], c[1], d[0], d[1], b[0], b[1]); +} + +// ── 60x96 classic4 geometry (the 60x96c4b FORMATS entry: w2500 h4000 gutter165) ── +const PW = 2500, PH = 4000, PXI = PW / 60; // pageWidthIn 60 +const SHEET_EDGE = 26, GUTTER = 165; +const HEADER = 355, FOOTER = 316; +const C4_BAND = Math.round(PH * 0.096); +const MAP_X = GUTTER, MAP_W = PW - 2 * GUTTER; +const MAP_Y = HEADER + C4_BAND, MAP_H = PH - HEADER - FOOTER - 2 * C4_BAND; +const FRAME_X = MAP_X, FRAME_Y = MAP_Y, FRAME_W = MAP_W, FRAME_H = MAP_H; + +// A linear "projection" over an India-ish bbox (68..98 lng, 6..37 lat) into the map rect. +const LNG0 = 68, LNG1 = 98, LAT0 = 6, LAT1 = 37; +const proj = (lng, lat) => [ + MAP_X + ((lng - LNG0) / (LNG1 - LNG0)) * MAP_W, + MAP_Y + ((LAT1 - lat) / (LAT1 - LAT0)) * MAP_H, +]; +const box = { PW, PH, MAP_X, MAP_W, MAP_Y, MAP_H, HEADER, FOOTER, SHEET_EDGE, GUTTER, C4_BAND, PXI, FRAME_X, FRAME_Y, FRAME_W, FRAME_H, proj }; + +// ── fixture: 855 pins across a 6x5 grid of rectangular "states" (lng/lat space) ─ +// Deterministic PRNG (mulberry32) so pins are varied but fixed run-to-run. +function rng(seed) { return () => { seed |= 0; seed = (seed + 0x6D2B79F5) | 0; let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } +function makeFixture(N) { + const cols = 6, rows = 5, feats = []; + const dLng = (LNG1 - LNG0) / cols, dLat = (LAT1 - LAT0) / rows; + for (let c = 0; c < cols; c++) for (let r = 0; r < rows; r++) { + const x0 = LNG0 + c * dLng, x1 = x0 + dLng, y0 = LAT0 + r * dLat, y1 = y0 + dLat; + feats.push({ properties: { shapeName: `S${c}_${r}` }, geometry: { type: 'Polygon', coordinates: [[[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]]] } }); + } + const rand = rng(12345); + const pins = []; + for (let i = 0; i < N; i++) { + // keep a margin inside a state so the point-in-poly is unambiguous + const lng = LNG0 + 0.5 + rand() * (LNG1 - LNG0 - 1); + const lat = LAT0 + 0.5 + rand() * (LAT1 - LAT0 - 1); + const [px, py] = proj(lng, lat); + pins.push({ id: `p${i}`, name: `Group ${i} ${'abcdefgh'[i % 8]}`, lng, lat, _px: px, _py: py, image_url: null }); + } + return { pins, feats }; +} + +const { pins, feats } = makeFixture(855); +const fmt4 = { nameMaxPt: 11, classic4: true, rangeBadge: true, photos: false, pageWidthIn: 60, w: PW, h: PH }; +const optsBase = { fmt: fmt4, feats }; + +// ── 1. fourSides ────────────────────────────────────────────────────────────── +const four = await layoutCircular({ pins, states: null, box, mode: 'fourSides', opts: optsBase }); +note.push(`fourSides: placed ${four.stats.placed}, ringOrdered ${four.stats.numbersRingOrdered}, dupes ${four.stats.numberDupes}, outsideState ${four.stats.pinsOutsideState}, islandFallback ${four.stats.islandFallback}, namePt ${four.stats.namePt}, sides ${JSON.stringify(four.stats.sides)}`); +if (!four.stats.numbersRingOrdered) fails.push('fourSides: numbers not ring-ordered 1..N'); +if (four.stats.numberDupes !== 0) fails.push(`fourSides: ${four.stats.numberDupes} duplicate numbers`); +if (four.stats.pinsOutsideState !== 0) fails.push(`fourSides: ${four.stats.pinsOutsideState} pins outside their state`); +if (four.stats.placed < 800) fails.push(`fourSides: only ${four.stats.placed} pins placed (floor 800) — vacuous`); +// (3) SIDE SEQUENCE: numbering must run L < T < R < B, and be monotone within each +// side — numbersRingOrdered alone (1..N, no gap) passes a scrambled side order too. +{ + const SEQ = ['L', 'T', 'R', 'B']; + const bySide = { L: [], T: [], R: [], B: [] }; + for (const p of four.pins) (bySide[p.side] || (bySide[p.side] = [])).push(p.num); + // side ranges from the pins (for the L p.side === sd).sort((a, b) => a.num - b.num); + if (ps.length < 3) continue; + const [axis, dir] = AXIS[sd]; + const coords = ps.map((p) => p[axis] * dir); // expected non-decreasing + const span = coords[coords.length - 1] - coords[0]; + const tol = 0.6 * Math.abs(span) / (coords.length - 1); // 60% of mean step + let inversions = 0; + for (let i = 1; i < coords.length; i++) if (coords[i] < coords[i - 1] - tol) inversions++; + // a correct side has only small local drift inversions; a reversed sort inverts ~everything + if (inversions > coords.length * 0.2) { monoOk = false; monoWhy = `${sd}:${inversions}/${coords.length}`; } + } + if (!seqOk) fails.push('fourSides: numbers not in side sequence L { for (const s of (lf.segs || [])) segs.push({ s, li }); }); + let xr = 0; + for (let i = 0; i < segs.length; i++) for (let j = i + 1; j < segs.length; j++) { + if (segs[i].li === segs[j].li) continue; + if (segCrossGate(segs[i].s, segs[j].s)) xr++; + } + if (xr !== 0) fails.push(`pairedRows: recount found ${xr} crossings (stats said ${paired.stats.crossings})`); + const outPr = recountOutside(paired.pins, feats); + if (outPr !== 0) fails.push(`pairedRows: recount found ${outPr} pins outside their state`); + // (P3) every leader must actually carry its two polylines — a dropped polys passed before. + const badPolys = paired.leaders.filter((l) => !Array.isArray(l.polys) || l.polys.length !== 2).length; + if (badPolys !== 0) fails.push(`pairedRows: ${badPolys} leaders missing polys.length===2`); + note.push(`pairedRows check: crossings(recount) ${xr}, outside(recount) ${outPr}, leaders-with-2-polys ${paired.leaders.length - badPolys}/${paired.leaders.length}`); +} + +// ── 3. sideLanes ────────────────────────────────────────────────────────────── +const sideFmt = { nameMaxPt: 14, sideLanes: true, photos: false, pageWidthIn: 60, w: PW, h: PH }; +const side = await layoutCircular({ pins, states: null, box: { ...box, LABEL_TOP: HEADER - 40, LABEL_BOT: PH - FOOTER + 40 }, mode: 'sideLanes', opts: { fmt: sideFmt, feats } }); +note.push(`sideLanes: placed ${side.stats.placed}, crossings ${side.stats.crossings}, touches ${side.stats.touches}, octilinear ${side.stats.octilinear}, sides ${JSON.stringify(side.stats.sides)}`); +if (side.stats.placed < 800) fails.push(`sideLanes: only ${side.stats.placed} placed (floor 800)`); +if (!side.stats.octilinear) fails.push('sideLanes: leaders not all octilinear'); + +// ── 4. determinism ──────────────────────────────────────────────────────────── +const four2 = await layoutCircular({ pins, states: null, box, mode: 'fourSides', opts: optsBase }); +if (JSON.stringify(four.labels) !== JSON.stringify(four2.labels) || JSON.stringify(four.dots) !== JSON.stringify(four2.dots)) fails.push('NON-DETERMINISTIC: two fourSides runs differ'); + +// ── 5. poison — a tiny state cannot hold its pins → infeasible NAMED ─────────── +{ + // one tiny state in lng/lat; pack many pins into it + const tinyFeats = [{ properties: { shapeName: 'TINY' }, geometry: { type: 'Polygon', coordinates: [[[80, 20], [80.02, 20], [80.02, 20.02], [80, 20.02], [80, 20]]] } }]; + const tinyPins = []; + for (let i = 0; i < 40; i++) { const [px, py] = proj(80.01, 20.01); tinyPins.push({ id: `t${i}`, name: `T${i}`, lng: 80.01, lat: 20.01, _px: px, _py: py }); } + const pf = { nameMaxPt: 11, classic4: true, rangeBadge: false, photos: false, pageWidthIn: 60, w: PW, h: PH }; + const pres = await layoutCircular({ pins: tinyPins, states: null, box, mode: 'fourSides', opts: { fmt: pf, feats: tinyFeats } }); + note.push(`poison: infeasible ${pres.stats.infeasible}, outsideState ${pres.stats.pinsOutsideState}`); + if (pres.stats.infeasible < 1) fails.push('POISON: a too-small state was NOT named in infeasible'); + if (pres.stats.pinsOutsideState !== 0) fails.push(`POISON: ${pres.stats.pinsOutsideState} pins escaped the tiny state`); +} + +// ── 6. budget (step 2 node half): median of 3, heap delta ─────────────────────── +// BASELINE: measured on this box at ~1.0-1.4s for 855 pins (fourSides, placePinsInState +// 400 iters). Budget fails at 1.5x of 3500ms = 5250ms to leave headroom on a slow CI +// box; the browser half (390px, 6x CPU throttle) is measured separately on the shared browser. +const BUDGET_MS = 5250; +const HEAP_CEIL_MB = 300; +const times = []; +let heapDelta = 0; +for (let k = 0; k < 3; k++) { + if (global.gc) global.gc(); + const h0 = process.memoryUsage().heapUsed; + const t0 = process.hrtime.bigint(); + await layoutCircular({ pins, states: null, box, mode: 'fourSides', opts: optsBase }); + const t1 = process.hrtime.bigint(); + times.push(Number(t1 - t0) / 1e6); + heapDelta = Math.max(heapDelta, (process.memoryUsage().heapUsed - h0) / 1048576); +} +times.sort((a, b) => a - b); +const medMs = times[1]; +note.push(`budget: fourSides median ${medMs.toFixed(0)}ms (runs ${times.map((t) => t.toFixed(0)).join('/')}), heapΔ ~${heapDelta.toFixed(0)}MB; budget ${BUDGET_MS}ms / ${HEAP_CEIL_MB}MB`); +if (medMs > BUDGET_MS) fails.push(`budget: ${medMs.toFixed(0)}ms over ${BUDGET_MS}ms`); +if (heapDelta > HEAP_CEIL_MB) fails.push(`budget: heapΔ ${heapDelta.toFixed(0)}MB over ${HEAP_CEIL_MB}MB`); + +for (const n of note) console.log(` · ${n}`); +if (fails.length) { + console.error('FAIL check:circular-layout'); + for (const f of fails) console.error(` ✗ ${f}`); + process.exit(1); +} else { + console.log(`PASS check:circular-layout — fourSides ring-ordered 0-dupes 0-outside, pairedRows 0-crossings, sideLanes octilinear, deterministic, poison named, ${medMs.toFixed(0)}ms < ${BUDGET_MS}ms budget`); +} diff --git a/package.json b/package.json index 7a3474453..a9750bab0 100644 --- a/package.json +++ b/package.json @@ -225,6 +225,7 @@ "check:selector-fields:self-test": "node internal/check-selector-fields.mjs --self-test", "check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs", "check:packet-variants": "node internal/check-packet-variants.mjs", + "check:circular-layout": "node library/packet/scripts/check-circular-layout.mjs", "check:fork-leaders": "node library/packet/scripts/check-fork-leaders.mjs", "check:pins-in-state": "node library/packet/scripts/check-pins-in-state.mjs", "check:label-key-coverage": "node internal/check-label-key-coverage.mjs", @@ -356,7 +357,7 @@ "fix:sri": "node internal/fix-sri.mjs", "gallery": "node internal/generate-gallery.mjs", "gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test check:host-floor:self-test check:phone-print-keep:self-test", - "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep", + "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:circular-layout check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep", "gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs", "internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs", "library-index": "node internal/generate-library-index.mjs", @@ -589,6 +590,7 @@ "check:packet-variants": "the poster wall's Prayer List prints the PACKET (not the columnar list), and every variant it offers exists in packet-render's VARIANTS. A variant may drop fixed pages but never people: lightweight keeps every group. By the UPG-100 owner; mutation-tested. Headless; in gates:pre.", "check:pins-in-state": "library/packet/pins-in-state.js places whole-India poster pins so no two overlap AND every pin stays inside its own ADM1 state polygon, at the 60x96in sheet scale (2500x4000px, PIN_R 8). A deterministic repulsion relaxation (imitating poster-render.js, read not called) with the map-rect clamp replaced by a per-state multipolygon clamp: a pin pushed off its land is pulled to the nearest point just inside, holes and multi-part states (islands/coasts) honoured. Runs on the real India 855-pin data when present, else a synthetic 60x96 fixture (floor 200 pins, no vacuous pass); asserts 0 pins outside their state and 0 overlaps that are not NAMED in `infeasible` (a too-small state is reported, never silently stacked), plus a hole/island arm and a poison arm. Node only, headless, deterministic.", "check:fork-leaders": "library/packet/fork-leaders.js routes ONE paired-row leader as an octilinear fork: a stem forks at a 45° chamfer into two short H/V legs, one per pin. Modes row and trunk. Returns polylines + a deduped segs list (one polyline id per fork for crossings). Reports octilinear + throughPins. Pure, px-only, deterministic. The gate asserts both modes octilinear, legs land on pins, 0 crossings on a 220-row set (floor 200), through-pin hit+miss, determinism, and a poison. Node only, headless.", + "check:circular-layout": "library/packet/circularLayout.js is the poster circular-logic (ring order + leader routing + numbered four-sides + paired-row forks) as one pure Worker-safe module: numbers in, geometry out, no SVG. Modes sideLanes, fourSides (numbered, no leaders), pairedRows (two leaders/row). forkRoute + placePinsInState are static imports (opts may override). Gate asserts on a frozen 855-pin 60x96 fixture: fourSides ring-ordered + 0 dupes + 0 pins-outside-state + side sequence L[]},groups:{type:Array,default:()=>[]},navLabel:{type:String,default:"Sections"},labels:{type:Object,default:()=>({})},initialNavOpen:{type:Boolean,default:!0},header:{type:Boolean,default:!0},initialRailCollapsed:{type:Boolean,default:!1},homeHref:{type:String,default:""},homeLabel:{type:String,default:""},shellMode:{type:String,default:"auto"}},emits:["update:active"],setup(){return hl(Km,"vertical-tabs-layout"),{T:Bm}},data(){const e=(()=>{try{return typeof window<"u"&&!!window.matchMedia&&window.matchMedia("(max-width: 760px)").matches}catch{return!1}})();return{navOpen:this.initialNavOpen&&!e,railCollapsed:e?!1:this.initialRailCollapsed,_tabEls:{},folderOverride:{},narrow:e,_mql:null,_onMedia:null,activatedKeys:{},hostState:{},hostError:{},_hostSlots:{},_hostMounted:{},hostEls:{},mode:"app"}},computed:{phoneBarShown(){return this.narrow&&!this.header},railEndShown(){return!this.narrow&&!this.header},activeHostEl(){return this.hostEls[this.active]||null},railShown(){return this.narrow?this.navOpen:!0},homeLink(){const e=String(this.homeHref||"").trim();return!e||/^(javascript|data|vbscript):/i.test(e.replace(/[\u0000- ]/g,""))?"":e},homeText(){return this.homeLabel||this.label("home")},allRows(){if(this.groups&&this.groups.length){const e=[];for(const t of this.groups)for(const a of t.rows||[])e.push(a);return e}return this.items},orderedKeys(){return this.allRows.map(e=>e.key)},elementRows(){return this.allRows.filter(e=>e&&e.element)},mountedElementRows(){return this.elementRows.filter(e=>this.activatedKeys[e.key])}},watch:{shellMode(){this.resolveShellMode()},active:{immediate:!0,handler(e){this.activateElement(e)}}},mounted(){this.resolveShellMode();try{window.matchMedia&&(this._mql=window.matchMedia("(max-width: 760px)"),this.narrow=this._mql.matches,this.reconcileForWidth(),this._onMedia=e=>{this.narrow=e.matches,this.reconcileForWidth()},this._mql.addEventListener?this._mql.addEventListener("change",this._onMedia):this._mql.addListener&&this._mql.addListener(this._onMedia))}catch{}this.$nextTick(()=>this.activateElement(this.active))},beforeUnmount(){try{this._mql&&this._onMedia&&(this._mql.removeEventListener?this._mql.removeEventListener("change",this._onMedia):this._mql.removeListener&&this._mql.removeListener(this._onMedia))}catch{}this._mql=null,this._onMedia=null},methods:{resolveShellMode(){const e=a=>a==="web"||a==="app"?a:null;let t=e(this.shellMode);if(!t)try{const a=this.$el&&this.$el.getRootNode?this.$el.getRootNode():null,o=a&&a.host;t=e(o&&o.getAttribute?o.getAttribute("shell-mode"):null)}catch{}if(!t)try{const a=window.matchMedia&&window.matchMedia("(display-mode: standalone)").matches||navigator.standalone===!0;t=window.Capacitor||a||window.MAP_OWNS_PAGE===!0?"app":"web"}catch{t="app"}this.mode=t},registerTab(e,t){t&&(this._tabEls[e]=t)},isNarrow(){return this.narrow},reconcileForWidth(){this.narrow?(this.navOpen=!1,this.railCollapsed=!1):this.navOpen=!0},label(e){const t={toggleList:"Toggle list",openViews:"Open the list of views",expandList:"Expand list",collapseList:"Collapse list",collapse:"Collapse",home:"Back to homepage"};return this.labels&&this.labels[e]||t[e]},expandRail(){!this.narrow&&this.railCollapsed&&(this.railCollapsed=!1)},toggleRail(){this.narrow?this.navOpen=!this.navOpen:this.railCollapsed=!this.railCollapsed},setHostSlot(e,t){t&&(this._hostSlots[e]=t,this.$nextTick(()=>this.mountHostElement(e)))},activateElement(e){!e||!this.elementRows.find(a=>a.key===e)||(this.activatedKeys[e]||(this.activatedKeys={...this.activatedKeys,[e]:!0},this.$nextTick(()=>this.watchContextLoss(e)),this.hostState={...this.hostState,[e]:"loading"}),this.$nextTick(()=>this.mountHostElement(e)))},deepCanvases(e,t=[]){if(!e)return t;for(const a of e.querySelectorAll("*"))a.tagName==="CANVAS"&&t.push(a),a.shadowRoot&&this.deepCanvases(a.shadowRoot,t);return t},watchContextLoss(e){const t=this._hostSlots[e];if(t)for(const a of this.deepCanvases(t))a.__vtlLossBound||(a.__vtlLossBound=!0,a.addEventListener("webglcontextlost",()=>{this.hostState={...this.hostState,[e]:"lost"}},{once:!0}))},reloadHostElement(e){const t=this._hostSlots[e];t&&(t.innerHTML=""),this._hostMounted[e]=!1,this.hostState={...this.hostState,[e]:"loading"},this.$nextTick(()=>this.mountHostElement(e))},mountHostElement(e){if(this._hostMounted[e])return;const t=this.elementRows.find(o=>o.key===e),a=this._hostSlots[e];!t||!a||(this._hostMounted[e]=!0,this.hostState={...this.hostState,[e]:"loading"},Vm(t.src).then(()=>Gm(t.element)).then(()=>{Wm(t.element,t.src),a.innerHTML="";const o=document.createElement(t.element),n=t.attrs||{};for(const s in n){const r=n[s],i=r!=null&&typeof r=="object"?JSON.stringify(r):String(r);try{o[s]=r}catch{}try{o.setAttribute(s,i)}catch{}}o.style.cssText="display:block;position:absolute;inset:0;width:100%;height:100%;",a.appendChild(o);for(const s of[0,250,1500])setTimeout(()=>this.watchContextLoss(e),s);this.hostState={...this.hostState,[e]:"ready"}}).catch(o=>{this._hostMounted[e]=!1,this.hostState={...this.hostState,[e]:"error"},this.hostError={...this.hostError,[e]:o&&o.message||String(o)}}))},iconSvg(e){return e?$l(e):""},initial(e){const t=(e||"").trim();return t?t.charAt(0).toUpperCase():"•"},isFolderClosed(e){const t=this.folderOverride[e.key];return t!=null?!t:e.open!==!0},toggleFolder(e){const t=this.folderOverride[e],a=(this.groups||[]).find(n=>n.key===e),o=t??(a&&a.open===!0);this.folderOverride={...this.folderOverride,[e]:!o}},pick(e){this.$emit("update:active",e),this.narrow&&(this.navOpen=!1)},onKey(e){const t=this.orderedKeys;if(!t.length)return;const a=t.indexOf(this.active);let o=-1;if(e.key==="ArrowDown"||e.key==="ArrowRight")o=(a+1)%t.length;else if(e.key==="ArrowUp"||e.key==="ArrowLeft")o=(a-1+t.length)%t.length;else if(e.key==="Home")o=0;else if(e.key==="End")o=t.length-1;else return;e.preventDefault();const n=t[o];this.pick(n),this.$nextTick(()=>{const s=this._tabEls[n];s&&s.focus&&s.focus()})}}}),Ym={key:0,class:"vtl-top"},Jm=["aria-expanded","aria-label"],Zm={class:"vtl-brand"},Qm={class:"vtl-title"},ef={class:"vtl-meta"},tf=["aria-expanded","aria-label"],af={class:"vtl-pbar-end"},of={class:"vtl-body"},nf=["aria-label","aria-hidden","role","aria-orientation"],sf=["href","aria-label","title"],rf=["innerHTML"],lf={class:"vtl-home-tx"},cf=["aria-expanded","aria-label"],pf=["innerHTML"],uf={class:"vtl-collapse-tx"},df=["aria-expanded","onClick"],gf={key:0,class:"vtl-cap"},mf={class:"vtl-tl"},ff={key:1,class:"vtl-td"},hf=["innerHTML"],bf={key:1,class:"vtl-tier"},$f={key:0,class:"vtl-cap"},_f={class:"vtl-tl"},vf={key:1,class:"vtl-td"},yf={class:"vtl-rows"},wf=["aria-selected","aria-disabled","tabindex","onClick"],kf=["innerHTML"],xf={key:1,class:"vtl-tab-emoji","aria-hidden":"true"},Sf={key:2,class:"vtl-tab-fallback","aria-hidden":"true"},Lf={class:"vtl-tab-text"},Tf={class:"vtl-tab-label"},Pf={key:0,class:"vtl-tab-badge"},Ef={key:0,class:"vtl-tab-sub"},Of=["innerHTML"],Mf=["aria-selected","aria-disabled","tabindex","onClick"],Af=["innerHTML"],Cf={key:1,class:"vtl-tab-emoji","aria-hidden":"true"},If={key:2,class:"vtl-tab-fallback","aria-hidden":"true"},Nf={class:"vtl-tab-text"},Ff={class:"vtl-tab-label"},Df={key:0,class:"vtl-tab-sub"},Rf={key:4,class:"vtl-rail-end"},zf={class:"vtl-main"},qf={class:"vtl-content"},Wf=["hidden"],jf={key:0,class:"vtl-host-hint"},Uf={key:1,class:"vtl-host-hint"},Hf=["onClick"],Bf={key:2,class:"vtl-host-hint err"};function Vf(e,t,a,o,n,s){const r=Wa("BackToTopButton");return C(),N("div",{class:Ge(["vtl",{"nav-open":e.navOpen,"rail-collapsed":e.railCollapsed,"no-header":!e.header,"has-phone-bar":e.phoneBarShown}])},[e.header?(C(),N("header",Ym,[O("button",{class:"vtl-menu",type:"button","aria-expanded":String(e.navOpen),onClick:t[0]||(t[0]=i=>e.navOpen=!e.navOpen),"aria-label":e.label("toggleList")},"☰",8,Jm),O("span",Zm,[ra(e.$slots,"brand")]),O("b",Qm,V(e.title),1),O("span",ef,[ra(e.$slots,"meta")])])):se("",!0),e.phoneBarShown?(C(),N("div",{key:1,class:Ge(["vtl-pbar",{"is-web":e.mode==="web"}])},[O("button",{class:"vtl-phone-menu",type:"button","aria-expanded":String(e.navOpen),onClick:t[1]||(t[1]=i=>e.navOpen=!e.navOpen),"aria-label":e.label("toggleList")},"☰",8,tf),O("span",af,[ra(e.$slots,"phone-end")])],2)):se("",!0),e.narrow?(C(),ya(r,{key:2,target:e.activeHostEl,labels:{backToTop:e.T("actions.backToTop","Back to top")}},null,8,["target","labels"])):se("",!0),O("div",of,[O("nav",{class:Ge(["vtl-nav",{"has-rail-end":e.railEndShown&&!!e.$slots["rail-end"]}]),"aria-label":e.navLabel,"aria-hidden":String(!e.railShown),role:e.$slots.rail?null:"tablist","aria-orientation":e.$slots.rail?null:"vertical"},[e.homeLink?(C(),N("a",{key:0,class:"vtl-home",href:e.homeLink,target:"_top","aria-label":e.homeText,title:e.railCollapsed&&!e.narrow?e.homeText:null},[O("span",{class:"vtl-home-ic","aria-hidden":"true",innerHTML:e.iconSvg("home")},null,8,rf),O("span",lf,V(e.homeText),1)],8,sf)):se("",!0),O("button",{type:"button",class:"vtl-collapse","aria-expanded":String(!e.railCollapsed),"aria-label":e.railCollapsed?e.label("expandList"):e.label("collapseList"),onClick:t[2]||(t[2]=(...i)=>e.toggleRail&&e.toggleRail(...i))},[O("span",{class:"vtl-collapse-ic","aria-hidden":"true",innerHTML:e.iconSvg("chevron")},null,8,pf),O("span",uf,V(e.label("collapse")),1)],8,cf),e.$slots.rail?ra(e.$slots,"rail",{collapsed:e.railCollapsed&&!e.narrow,expand:e.expandRail,pick:e.pick},void 0,void 0,1):e.groups&&e.groups.length?(C(!0),N(ne,{key:2},Xe(e.groups,i=>(C(),N("div",{class:"vtl-grp",key:i.key},[i.folder?(C(),N("button",{key:0,type:"button",class:"vtl-tier vtl-tier-btn","aria-expanded":String(!e.isFolderClosed(i)),onClick:l=>e.toggleFolder(i.key)},[i.badge?(C(),N("span",gf,V(i.badge),1)):se("",!0),O("span",mf,V(i.label),1),i.count!=null?(C(),N("span",ff,V(i.count),1)):se("",!0),O("span",{class:Ge(["vtl-chev",{open:!e.isFolderClosed(i)}]),"aria-hidden":"true",innerHTML:e.iconSvg("chevron")},null,10,hf)],8,df)):(C(),N("div",bf,[i.badge?(C(),N("span",$f,V(i.badge),1)):se("",!0),O("span",_f,V(i.label),1),i.count!=null?(C(),N("span",vf,V(i.count),1)):se("",!0)])),Ra(O("div",yf,[(C(!0),N(ne,null,Xe(i.rows,l=>(C(),N("button",{key:l.key,type:"button",role:"tab",class:Ge(["vtl-tab",{active:l.key===e.active,disabled:l.disabled}]),"aria-selected":String(l.key===e.active),"aria-disabled":l.disabled?"true":null,tabindex:l.key===e.active?0:-1,ref_for:!0,ref:p=>e.registerTab(l.key,p),onClick:p=>e.pick(l.key),onKeydown:t[3]||(t[3]=p=>e.onKey(p))},[l.icon?(C(),N("span",{key:0,class:"vtl-tab-ic","aria-hidden":"true",innerHTML:e.iconSvg(l.icon)},null,8,kf)):l.emoji?(C(),N("span",xf,V(l.emoji),1)):(C(),N("span",Sf,V(e.initial(l.label)),1)),O("span",Lf,[O("span",Tf,[Kt(V(l.label),1),l.badge?(C(),N("span",Pf,V(l.badge),1)):se("",!0)]),l.sub?(C(),N("span",Ef,[O("span",{innerHTML:l.sub},null,8,Of)])):se("",!0)])],42,wf))),128))],512),[[Za,!i.folder||!e.isFolderClosed(i)]])]))),128)):(C(!0),N(ne,{key:3},Xe(e.items,i=>(C(),N("button",{key:i.key,type:"button",role:"tab",class:Ge(["vtl-tab",{active:i.key===e.active,disabled:i.disabled}]),"aria-selected":String(i.key===e.active),"aria-disabled":i.disabled?"true":null,tabindex:i.key===e.active?0:-1,ref_for:!0,ref:l=>e.registerTab(i.key,l),onClick:l=>e.pick(i.key),onKeydown:t[4]||(t[4]=l=>e.onKey(l))},[i.icon?(C(),N("span",{key:0,class:"vtl-tab-ic","aria-hidden":"true",innerHTML:e.iconSvg(i.icon)},null,8,Af)):i.emoji?(C(),N("span",Cf,V(i.emoji),1)):(C(),N("span",If,V(e.initial(i.label)),1)),O("span",Nf,[O("span",Ff,V(i.label),1),i.sub?(C(),N("span",Df,V(i.sub),1)):se("",!0)])],42,Mf))),128)),e.railEndShown&&e.$slots["rail-end"]?(C(),N("div",Rf,[ra(e.$slots,"rail-end",{collapsed:e.railCollapsed})])):se("",!0)],10,nf),O("main",zf,[e.header?ra(e.$slots,"toolbar",{},void 0,void 0,0):se("",!0),O("div",qf,[(C(!0),N(ne,null,Xe(e.mountedElementRows,i=>Ra((C(),N("section",{key:i.key,class:"vtl-host",hidden:i.key!==e.active,ref_for:!0,ref:l=>{l?e.hostEls[i.key]=l:delete e.hostEls[i.key]}},[O("div",{class:"vtl-host-slot",ref_for:!0,ref:l=>e.setHostSlot(i.key,l)},null,512),e.hostState[i.key]==="loading"?(C(),N("div",jf,V(e.T("messages.loadingNamed","Loading {name}…").replace("{name}",i.label)),1)):e.hostState[i.key]==="lost"?(C(),N("div",Uf,[Kt(V(e.T("messages.mapContextLost","This map was unloaded to free graphics memory."))+" ",1),O("button",{type:"button",class:"vtl-host-reload",onClick:l=>e.reloadHostElement(i.key)},V(e.T("buttons.reloadMap","Reload Map")),9,Hf)])):e.hostState[i.key]==="error"?(C(),N("div",Bf,[Kt(V(e.T("messages.couldNotLoadNamed","Could not load {name}.").replace("{name}",i.label)),1),t[5]||(t[5]=O("br",null,null,-1)),O("small",null,V(e.hostError[i.key]),1)])):se("",!0)],8,Wf)),[[Za,i.key===e.active]])),128)),ra(e.$slots,"default")])])])],2)}const Gf=Uo(Xm,[["render",Vf],["styles",[Hm]]]),cs=[{code:"en",name:"English",nativeName:"English",flag:"🇺🇸"},{code:"es",name:"Spanish",nativeName:"Español",flag:"🇪🇸"},{code:"fr",name:"French",nativeName:"Français",flag:"🇫🇷"},{code:"pt",name:"Portuguese",nativeName:"Português",flag:"🇵🇹"},{code:"ar",name:"Arabic",nativeName:"العربية",flag:"🇸🇦",dir:"rtl"},{code:"ru",name:"Russian",nativeName:"Русский",flag:"🇷🇺"},{code:"de",name:"German",nativeName:"Deutsch",flag:"🇩🇪",hideFromSwitcher:!0},{code:"hi",name:"Hindi",nativeName:"हिन्दी",flag:"🇮🇳",hideFromSwitcher:!0},{code:"it",name:"Italian",nativeName:"Italiano",flag:"🇮🇹",hideFromSwitcher:!0},{code:"ro",name:"Romanian",nativeName:"Română",flag:"🇷🇴",hideFromSwitcher:!0},{code:"zh",name:"Chinese",nativeName:"中文",flag:"🇨🇳",hideFromSwitcher:!0}],kl=cs.filter(e=>!e.hideFromSwitcher);function ps(e){return cs.find(t=>t.code===e)}function xl(e){const t=String(e||"").split("-")[0].toLowerCase();return cs.some(a=>a.code===t)?t:"en"}const Sl="doxa-maps:locale";function Kf(){try{const e=localStorage.getItem(Sl);if(e)return xl(e)}catch{}return xl(navigator==null?void 0:navigator.language)}function Xf(e){try{localStorage.setItem(Sl,e)}catch{}}const Yf="doxa-language-request";function Jf(e){try{const t=e&&e.getRootNode&&e.getRootNode();if(t&&t.host)return t.host}catch{}return e||null}function Zf(e,t){const a=Jf(e);return!a||typeof a.dispatchEvent!="function"?!0:a.dispatchEvent(new CustomEvent(Yf,{detail:{language:t},bubbles:!0,composed:!0,cancelable:!0}))}function Qf(e,t=typeof window<"u"?window:null){try{if(!t||t.MAP_OWNS_PAGE!==!0||!t.history||!t.location)return!1;const a=new URL(t.location.href);return a.searchParams.set("lang",e),t.history.replaceState(t.history.state,"",a.toString()),!0}catch{return!1}}const eh=".slm[data-v-12760774]{position:relative;display:inline-flex;align-items:center;min-height:44px;max-width:100%;box-sizing:border-box;border-radius:8px;color:#3b463d}.slm-face[data-v-12760774]{display:inline-flex;align-items:center;gap:6px;padding-inline:10px;min-width:0;font-size:13px;font-weight:500;line-height:1.2}.slm-ic[data-v-12760774]{flex:0 0 auto;display:inline-flex;color:var(--vtl-muted, #6d766e)}.slm-ic[data-v-12760774] svg{width:18px;height:18px}.slm-name[data-v-12760774]{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.slm-code[data-v-12760774]{font-weight:600;font-size:12px;letter-spacing:.03em}.slm-caret[data-v-12760774]{flex:0 0 auto;color:var(--vtl-muted, #6d766e);font-size:11px}.slm.collapsed .slm-face[data-v-12760774]{flex-direction:column;gap:2px;padding-inline:4px}.slm-select[data-v-12760774]{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;opacity:0;margin:0;border:0;cursor:pointer;font-size:16px}.slm[data-v-12760774]:hover{background:var(--vtl-accented, #d1d4cd)}.slm[data-v-12760774]:focus-within{outline:2px solid var(--vtl-accent, #92b195);outline-offset:-2px}@media (prefers-reduced-motion: reduce){.slm[data-v-12760774]{transition:none}}",th={class:"slm-face","aria-hidden":"true"},ah=["innerHTML"],oh={key:0,class:"slm-code"},nh=["lang","dir"],sh={key:2,class:"slm-caret"},rh=["value","aria-label","title"],ih=["value","lang","dir"],lh=Uo({__name:"ShellLanguageMenu",props:{current:{type:String,default:"en"},collapsed:{type:Boolean,default:!1}},emits:["switch"],setup(e,{emit:t}){const a=e,o=t,n=Je(()=>{const p=kl.slice();if(a.current&&!p.some(c=>c.code===a.current)){const c=ps(a.current);c&&p.push(c)}return p}),s=Je(()=>ps(a.current)||{code:a.current,nativeName:a.current}),r=Je(()=>Wt("controls.language",a.current)||"Language"),i=$l("globe");function l(p){const c=p.target,m=c.value;!m||m===a.current||(Zf(c,m)?(o("switch",m),Qf(m)):c.value=a.current)}return(p,c)=>(C(),N("span",{class:Ge(["slm",{collapsed:e.collapsed}])},[O("span",th,[O("span",{class:"slm-ic",innerHTML:ye(i)},null,8,ah),e.collapsed?(C(),N("span",oh,V(String(e.current).toUpperCase()),1)):(C(),N("span",{key:1,class:"slm-name",lang:s.value.code,dir:s.value.dir||null},V(s.value.nativeName),9,nh)),e.collapsed?se("",!0):(C(),N("span",sh,"▾"))]),O("select",{class:"slm-select",value:e.current,"aria-label":r.value,title:r.value,onChange:l},[(C(!0),N(ne,null,Xe(n.value,m=>(C(),N("option",{key:m.code,value:m.code,lang:m.code,dir:m.dir||null},V(m.nativeName),9,ih))),128))],40,rh)],2))}},[["styles",[eh]],["__scopeId","data-v-12760774"]]),io={en:{backToTop:"Back to top","sec.dashboards":"Dashboards","sec.prayer":"Prayer Mobilization","sec.gospel":"Gospel Research","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Big Picture","sub.dashboard-plus-grid":"dashboard + people-groups grid, stacked","tab.research-map":"Research Map","sub.research-map":"prayer · engagement · regions","tab.research-map-plus-grid":"Simple Research","sub.research-map-plus-grid":"research map + people-groups grid","tab.prayer-map":"Prayer Progress","sub.prayer-map":"simple world prayer map","tab.cities-internet":"Cities with Internet","sub.cities-internet":"connectivity of key cities","tab.gospel-resources":"Gospel Resources","sub.gospel-resources":"resources by place","tab.regions-dashboard":"Regions","sub.regions-dashboard":"regions and their countries","tab.upg-100":"UPG 100 List Posters","sub.upg-100":"my unreached list + posters",navLabel:"Research maps",meta:"{n} maps",loading:"Loading {label}…",loadFailed:"Could not load {label}.",manifestHint:"This tab is composed from manifest.json — build the tree, or serve the built tree, to load it.",close:"Close",help:"About this map",desktopOnly:"desktop only",lab:"lab",experimentalSub:"experimental bundle",mobileNotBuilt:"not built for mobile",dismiss:"Dismiss",desktopOnly:"desktop only",lab:"lab",experimentalSub:"experimental bundle",toggleList:"Toggle list",openViews:"Open the list of views",expandList:"Expand list",collapseList:"Collapse list",collapse:"Collapse",home:"Back to homepage","tab.prayer-walk-planner":"Prayer Walk Planner","tab.upg-neighbor":"UPG Neighbor","tab.key-cities":"Key Cities","tab.all-people-groups":"All People Groups","tab.pplr-data-maps":"PPLR Data Maps","tab.wall-of-people-groups":"Wall of People Groups","tab.bless-india-posters":"Bless India Posters","tab.country-posters":"Country Posters","sub.country-posters":"a poster for every country","tab.poster-design":"Poster Design","sub.poster-design":"every poster style and size","tab.doxa-vision-card":"Doxa Vision Card"},es:{backToTop:"Volver arriba","sec.dashboards":"Paneles","sec.prayer":"Movilización de oración","sec.gospel":"Investigación del Evangelio","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Panorama general","sub.dashboard-plus-grid":"panel + cuadrícula de pueblos","tab.research-map":"Mapa de investigación","sub.research-map":"oración · compromiso · regiones","tab.research-map-plus-grid":"Investigación simple","sub.research-map-plus-grid":"mapa de investigación + cuadrícula de pueblos","tab.prayer-map":"Progreso de oración","sub.prayer-map":"mapa mundial de oración","tab.cities-internet":"Ciudades con Internet","sub.cities-internet":"conectividad de ciudades clave","tab.gospel-resources":"Recursos del Evangelio","sub.gospel-resources":"recursos por lugar","tab.regions-dashboard":"Regiones","sub.regions-dashboard":"regiones y sus países","tab.upg-100":"Carteles de la lista UPG 100","sub.upg-100":"mi lista de no alcanzados + carteles",navLabel:"Mapas de investigación",meta:"{n} mapas",loading:"Cargando {label}…",loadFailed:"No se pudo cargar {label}.",manifestHint:"Esta pestaña se compone a partir de manifest.json: compila el árbol o sirve el árbol compilado para cargarla.",close:"Cerrar",help:"Acerca de este mapa",desktopOnly:"solo escritorio",lab:"beta",experimentalSub:"mapa experimental",mobileNotBuilt:"no diseñado para móvil",dismiss:"Cerrar",desktopOnly:"solo escritorio",lab:"beta",experimentalSub:"mapa experimental",toggleList:"Mostrar u ocultar la lista",openViews:"Abrir la lista de vistas",expandList:"Expandir la lista",collapseList:"Contraer la lista",collapse:"Contraer",home:"Volver a la página de inicio","tab.prayer-walk-planner":"Caminatas de oración","tab.upg-neighbor":"Vecino UPG","tab.key-cities":"Ciudades clave","tab.all-people-groups":"Todos los pueblos","tab.pplr-data-maps":"Mapas de datos PPLR","tab.wall-of-people-groups":"Muro de los pueblos","tab.bless-india-posters":"Pósteres «Bendice a la India»","tab.country-posters":"Carteles por país","sub.country-posters":"un cartel para cada país","tab.poster-design":"Diseño de carteles","sub.poster-design":"todos los estilos y tamaños de cartel","tab.doxa-vision-card":"Tarjeta de visión Doxa"},fr:{backToTop:"Retour en haut","sec.dashboards":"Tableaux de bord","sec.prayer":"Mobilisation à la prière","sec.gospel":"Recherche sur l'Évangile","sec.experimental":"Expérimental","tab.dashboard-plus-grid":"Vue d'ensemble","sub.dashboard-plus-grid":"tableau de bord + grille des peuples","tab.research-map":"Carte de recherche","sub.research-map":"prière · engagement · régions","tab.research-map-plus-grid":"Recherche simple","sub.research-map-plus-grid":"carte de recherche + grille des peuples","tab.prayer-map":"Progression de la prière","sub.prayer-map":"carte mondiale de la prière","tab.cities-internet":"Villes avec Internet","sub.cities-internet":"connectivité des villes clés","tab.gospel-resources":"Ressources de l'Évangile","sub.gospel-resources":"ressources par lieu","tab.regions-dashboard":"Régions","sub.regions-dashboard":"régions et leurs pays","tab.upg-100":"Affiches de la liste UPG 100","sub.upg-100":"ma liste des non atteints + affiches",navLabel:"Cartes de recherche",meta:"{n} cartes",loading:"Chargement de {label}…",loadFailed:"Impossible de charger {label}.",manifestHint:"Cet onglet est composé à partir de manifest.json : construisez l'arborescence ou servez l'arborescence construite pour le charger.",close:"Fermer",help:"À propos de cette carte",desktopOnly:"ordinateur uniquement",lab:"bêta",experimentalSub:"carte expérimentale",mobileNotBuilt:"pas conçu pour mobile",dismiss:"Fermer",desktopOnly:"ordinateur uniquement",lab:"bêta",experimentalSub:"carte expérimentale",toggleList:"Afficher ou masquer la liste",openViews:"Ouvrir la liste des vues",expandList:"Déplier la liste",collapseList:"Replier la liste",collapse:"Replier",home:"Retour à l’accueil","tab.prayer-walk-planner":"Marches de prière","tab.upg-neighbor":"Voisin UPG","tab.key-cities":"Villes clés","tab.all-people-groups":"Tous les peuples","tab.pplr-data-maps":"Cartes de données PPLR","tab.wall-of-people-groups":"Mur des peuples","tab.bless-india-posters":"Affiches « Bénis l’Inde »","tab.country-posters":"Affiches par pays","sub.country-posters":"une affiche pour chaque pays","tab.poster-design":"Conception d’affiches","sub.poster-design":"tous les styles et formats d’affiche","tab.doxa-vision-card":"Carte de vision Doxa"},de:{backToTop:"Nach oben","sec.dashboards":"Dashboards","sec.prayer":"Gebetsmobilisierung","sec.gospel":"Evangeliumsforschung","sec.experimental":"Experimentell","tab.dashboard-plus-grid":"Gesamtbild","sub.dashboard-plus-grid":"Dashboard + Raster der Volksgruppen","tab.research-map":"Forschungskarte","sub.research-map":"Gebet · Engagement · Regionen","tab.research-map-plus-grid":"Einfache Recherche","sub.research-map-plus-grid":"Forschungskarte + Raster der Volksgruppen","tab.prayer-map":"Gebetsfortschritt","sub.prayer-map":"einfache Weltkarte des Gebets","tab.cities-internet":"Städte mit Internet","sub.cities-internet":"Konnektivität wichtiger Städte","tab.gospel-resources":"Evangeliumsressourcen","sub.gospel-resources":"Ressourcen nach Ort","tab.regions-dashboard":"Regionen","sub.regions-dashboard":"Regionen und ihre Länder","tab.upg-100":"Poster der UPG 100 Liste","sub.upg-100":"meine Liste der Unerreichten + Poster",navLabel:"Forschungskarten",meta:"{n} Karten",loading:"{label} wird geladen…",loadFailed:"{label} konnte nicht geladen werden.",manifestHint:"Dieser Tab wird aus manifest.json zusammengesetzt – bauen Sie den Baum oder stellen Sie den gebauten Baum bereit, um ihn zu laden.",close:"Schließen",help:"Über diese Karte",desktopOnly:"nur Desktop",lab:"Beta",experimentalSub:"experimentelle Karte",mobileNotBuilt:"nicht für Mobilgeräte",dismiss:"Schließen",desktopOnly:"nur Desktop",lab:"Beta",experimentalSub:"experimentelle Karte",toggleList:"Liste ein-/ausblenden",openViews:"Ansichtsliste öffnen",expandList:"Liste ausklappen",collapseList:"Liste einklappen",collapse:"Einklappen",home:"Zurück zur Startseite","tab.prayer-walk-planner":"Gebetsspaziergänge","tab.upg-neighbor":"UPG-Nachbar","tab.key-cities":"Schlüsselstädte","tab.all-people-groups":"Alle Volksgruppen","tab.pplr-data-maps":"PPLR-Datenkarten","tab.wall-of-people-groups":"Wand der Volksgruppen","tab.bless-india-posters":"„Segne Indien“-Poster","tab.country-posters":"Länderposter","sub.country-posters":"ein Poster für jedes Land","tab.poster-design":"Postergestaltung","sub.poster-design":"alle Posterstile und -größen","tab.doxa-vision-card":"Doxa-Visionskarte"},pt:{backToTop:"Voltar ao topo","sec.dashboards":"Painéis","sec.prayer":"Mobilização de oração","sec.gospel":"Pesquisa do Evangelho","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Visão geral","sub.dashboard-plus-grid":"painel + grade de povos","tab.research-map":"Mapa de pesquisa","sub.research-map":"oração · engajamento · regiões","tab.research-map-plus-grid":"Pesquisa simples","sub.research-map-plus-grid":"mapa de pesquisa + grade de povos","tab.prayer-map":"Progresso da oração","sub.prayer-map":"mapa mundial de oração","tab.cities-internet":"Cidades com Internet","sub.cities-internet":"conectividade das principais cidades","tab.gospel-resources":"Recursos do Evangelho","sub.gospel-resources":"recursos por lugar","tab.regions-dashboard":"Regiões","sub.regions-dashboard":"regiões e seus países","tab.upg-100":"Cartazes da lista UPG 100","sub.upg-100":"minha lista de não alcançados + cartazes",navLabel:"Mapas de pesquisa",meta:"{n} mapas",loading:"Carregando {label}…",loadFailed:"Não foi possível carregar {label}.",manifestHint:"Esta aba é composta a partir do manifest.json — compile a árvore ou sirva a árvore compilada para carregá-la.",close:"Fechar",help:"Sobre este mapa",desktopOnly:"só no computador",lab:"beta",experimentalSub:"mapa experimental",mobileNotBuilt:"não feito para celular",dismiss:"Fechar",desktopOnly:"só no computador",lab:"beta",experimentalSub:"mapa experimental",toggleList:"Mostrar ou ocultar a lista",openViews:"Abrir a lista de visualizações",expandList:"Expandir a lista",collapseList:"Recolher a lista",collapse:"Recolher",home:"Voltar à página inicial","tab.prayer-walk-planner":"Caminhadas de oração","tab.upg-neighbor":"Vizinho UPG","tab.key-cities":"Cidades-chave","tab.all-people-groups":"Todos os povos","tab.pplr-data-maps":"Mapas de dados PPLR","tab.wall-of-people-groups":"Mural dos povos","tab.bless-india-posters":"Pôsteres «Abençoe a Índia»","tab.country-posters":"Cartazes por país","sub.country-posters":"um cartaz para cada país","tab.poster-design":"Design de cartazes","sub.poster-design":"todos os estilos e tamanhos de cartaz","tab.doxa-vision-card":"Cartão de visão Doxa"},it:{backToTop:"Torna su","sec.dashboards":"Dashboard","sec.prayer":"Mobilitazione alla preghiera","sec.gospel":"Ricerca sul Vangelo","sec.experimental":"Sperimentale","tab.dashboard-plus-grid":"Quadro d'insieme","sub.dashboard-plus-grid":"dashboard + griglia dei popoli","tab.research-map":"Mappa di ricerca","sub.research-map":"preghiera · impegno · regioni","tab.research-map-plus-grid":"Ricerca semplice","sub.research-map-plus-grid":"mappa di ricerca + griglia dei popoli","tab.prayer-map":"Avanzamento preghiera","sub.prayer-map":"mappa mondiale della preghiera","tab.cities-internet":"Città con Internet","sub.cities-internet":"connettività delle città chiave","tab.gospel-resources":"Risorse del Vangelo","sub.gospel-resources":"risorse per luogo","tab.regions-dashboard":"Regioni","sub.regions-dashboard":"regioni e i loro paesi","tab.upg-100":"Poster della lista UPG 100","sub.upg-100":"la mia lista dei non raggiunti + poster",navLabel:"Mappe di ricerca",meta:"{n} mappe",loading:"Caricamento di {label}…",loadFailed:"Impossibile caricare {label}.",manifestHint:"Questa scheda è composta da manifest.json: compila l'albero o pubblica l'albero compilato per caricarla.",close:"Chiudi",help:"Informazioni su questa mappa",desktopOnly:"solo desktop",lab:"beta",experimentalSub:"mappa sperimentale",mobileNotBuilt:"non pensato per mobile",dismiss:"Chiudi",desktopOnly:"solo desktop",lab:"beta",experimentalSub:"mappa sperimentale",toggleList:"Mostra o nascondi l’elenco",openViews:"Apri l’elenco delle viste",expandList:"Espandi l’elenco",collapseList:"Comprimi l’elenco",collapse:"Comprimi",home:"Torna alla home page","tab.prayer-walk-planner":"Passeggiate di preghiera","tab.upg-neighbor":"Vicino UPG","tab.key-cities":"Città chiave","tab.all-people-groups":"Tutti i popoli","tab.pplr-data-maps":"Mappe dati PPLR","tab.wall-of-people-groups":"Muro dei popoli","tab.bless-india-posters":"Poster «Benedici l’India»","tab.country-posters":"Poster per paese","sub.country-posters":"un poster per ogni paese","tab.poster-design":"Design dei poster","sub.poster-design":"tutti gli stili e i formati dei poster","tab.doxa-vision-card":"Scheda visione Doxa"},ro:{backToTop:"Sus","sec.dashboards":"Panouri","sec.prayer":"Mobilizare la rugăciune","sec.gospel":"Cercetarea Evangheliei","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Imaginea de ansamblu","sub.dashboard-plus-grid":"panou + grila popoarelor","tab.research-map":"Hartă de cercetare","sub.research-map":"rugăciune · implicare · regiuni","tab.research-map-plus-grid":"Cercetare simplă","sub.research-map-plus-grid":"hartă de cercetare + grila popoarelor","tab.prayer-map":"Progresul rugăciunii","sub.prayer-map":"hartă mondială a rugăciunii","tab.cities-internet":"Orașe cu internet","sub.cities-internet":"conectivitatea orașelor-cheie","tab.gospel-resources":"Resurse ale Evangheliei","sub.gospel-resources":"resurse după loc","tab.regions-dashboard":"Regiuni","sub.regions-dashboard":"regiunile și țările lor","tab.upg-100":"Afișe din lista UPG 100","sub.upg-100":"lista mea de neatinși + afișe",navLabel:"Hărți de cercetare",meta:"{n} hărți",loading:"Se încarcă {label}…",loadFailed:"{label} nu a putut fi încărcat.",manifestHint:"Această filă este compusă din manifest.json — construiți arborele sau serviți arborele construit pentru a o încărca.",close:"Închide",help:"Despre această hartă",desktopOnly:"doar pe desktop",lab:"beta",experimentalSub:"hartă experimentală",mobileNotBuilt:"nu e făcut pentru mobil",dismiss:"Închide",desktopOnly:"doar pe desktop",lab:"beta",experimentalSub:"hartă experimentală",toggleList:"Afișează sau ascunde lista",openViews:"Deschide lista de vizualizări",expandList:"Extinde lista",collapseList:"Restrânge lista",collapse:"Restrânge",home:"Înapoi la pagina principală","tab.prayer-walk-planner":"Plimbări de rugăciune","tab.upg-neighbor":"Vecin UPG","tab.key-cities":"Orașe-cheie","tab.all-people-groups":"Toate popoarele","tab.pplr-data-maps":"Hărți de date PPLR","tab.wall-of-people-groups":"Zidul popoarelor","tab.bless-india-posters":"Postere „Binecuvântează India”","tab.country-posters":"Afișe pe țări","sub.country-posters":"un afiș pentru fiecare țară","tab.poster-design":"Design de afișe","sub.poster-design":"toate stilurile și dimensiunile de afiș","tab.doxa-vision-card":"Card de viziune Doxa"},ru:{backToTop:"Наверх","sec.dashboards":"Панели","sec.prayer":"Молитвенная мобилизация","sec.gospel":"Исследование Евангелия","sec.experimental":"Экспериментальное","tab.dashboard-plus-grid":"Общая картина","sub.dashboard-plus-grid":"панель + таблица народов","tab.research-map":"Исследовательская карта","sub.research-map":"молитва · вовлечённость · регионы","tab.research-map-plus-grid":"Простое исследование","sub.research-map-plus-grid":"исследовательская карта + таблица народов","tab.prayer-map":"Прогресс молитвы","sub.prayer-map":"простая карта молитвы мира","tab.cities-internet":"Города с интернетом","sub.cities-internet":"подключённость ключевых городов","tab.gospel-resources":"Евангельские ресурсы","sub.gospel-resources":"ресурсы по местам","tab.regions-dashboard":"Регионы","sub.regions-dashboard":"регионы и их страны","tab.upg-100":"Плакаты списка UPG 100","sub.upg-100":"мой список недостигнутых + плакаты",navLabel:"Исследовательские карты",meta:"Карт: {n}",loading:"Загрузка: {label}…",loadFailed:"Не удалось загрузить: {label}.",manifestHint:"Эта вкладка собирается из manifest.json — соберите дерево или разместите собранное дерево, чтобы загрузить её.",close:"Закрыть",help:"Об этой карте",desktopOnly:"только на компьютере",lab:"бета",experimentalSub:"экспериментальная карта",mobileNotBuilt:"не для телефона",dismiss:"Закрыть",desktopOnly:"только на компьютере",lab:"бета",experimentalSub:"экспериментальная карта",toggleList:"Показать или скрыть список",openViews:"Открыть список представлений",expandList:"Развернуть список",collapseList:"Свернуть список",collapse:"Свернуть",home:"На главную страницу","tab.prayer-walk-planner":"Молитвенные прогулки","tab.upg-neighbor":"Сосед UPG","tab.key-cities":"Ключевые города","tab.all-people-groups":"Все народы","tab.pplr-data-maps":"Карты данных PPLR","tab.wall-of-people-groups":"Стена народов","tab.bless-india-posters":"Плакаты «Благослови Индию»","tab.country-posters":"Плакаты по странам","sub.country-posters":"плакат для каждой страны","tab.poster-design":"Дизайн плакатов","sub.poster-design":"все стили и размеры плакатов","tab.doxa-vision-card":"Карточка видения Doxa"},ar:{backToTop:"إلى الأعلى","sec.dashboards":"لوحات المعلومات","sec.prayer":"التعبئة للصلاة","sec.gospel":"أبحاث الإنجيل","sec.experimental":"تجريبي","tab.dashboard-plus-grid":"الصورة الكاملة","sub.dashboard-plus-grid":"لوحة معلومات + شبكة الشعوب","tab.research-map":"خريطة البحث","sub.research-map":"الصلاة · الانخراط · المناطق","tab.research-map-plus-grid":"بحث مبسّط","sub.research-map-plus-grid":"خريطة البحث + شبكة الشعوب","tab.prayer-map":"تقدم الصلاة","sub.prayer-map":"خريطة عالمية مبسّطة للصلاة","tab.cities-internet":"مدن متصلة بالإنترنت","sub.cities-internet":"اتصال المدن الرئيسية","tab.gospel-resources":"موارد الإنجيل","sub.gospel-resources":"الموارد حسب المكان","tab.regions-dashboard":"المناطق","sub.regions-dashboard":"المناطق ودولها","tab.upg-100":"ملصقات قائمة UPG 100","sub.upg-100":"قائمتي لغير المُبشَّرين + ملصقات",navLabel:"خرائط البحث",meta:"عدد الخرائط: {n}",loading:"جارٍ تحميل {label}…",loadFailed:"تعذّر تحميل {label}.",manifestHint:"تُبنى علامة التبويب هذه من manifest.json — ابنِ الشجرة أو قدّم الشجرة المبنية لتحميلها.",close:"إغلاق",help:"عن هذه الخريطة",desktopOnly:"على الحاسوب فقط",lab:"تجريبي",experimentalSub:"خريطة تجريبية",mobileNotBuilt:"غير مُعدّ للهاتف",dismiss:"إغلاق",desktopOnly:"على الحاسوب فقط",lab:"تجريبي",experimentalSub:"خريطة تجريبية",toggleList:"إظهار القائمة أو إخفاؤها",openViews:"فتح قائمة العروض",expandList:"توسيع القائمة",collapseList:"طي القائمة",collapse:"طي",home:"العودة إلى الصفحة الرئيسية","tab.prayer-walk-planner":"جولات الصلاة","tab.upg-neighbor":"جار UPG","tab.key-cities":"المدن الرئيسية","tab.all-people-groups":"جميع الشعوب","tab.pplr-data-maps":"خرائط بيانات PPLR","tab.wall-of-people-groups":"جدار الشعوب","tab.bless-india-posters":"ملصقات باركوا الهند","tab.country-posters":"ملصقات الدول","sub.country-posters":"ملصق لكل دولة","tab.poster-design":"تصميم الملصقات","sub.poster-design":"كل أنماط الملصقات وأحجامها","tab.doxa-vision-card":"بطاقة رؤية Doxa"},hi:{backToTop:"ऊपर जाएँ","sec.dashboards":"डैशबोर्ड","sec.prayer":"प्रार्थना संचालन","sec.gospel":"सुसमाचार शोध","sec.experimental":"प्रायोगिक","tab.dashboard-plus-grid":"संपूर्ण चित्र","sub.dashboard-plus-grid":"डैशबोर्ड + जनसमूहों की ग्रिड","tab.research-map":"शोध मानचित्र","sub.research-map":"प्रार्थना · सहभागिता · क्षेत्र","tab.research-map-plus-grid":"सरल शोध","sub.research-map-plus-grid":"शोध मानचित्र + जनसमूहों की ग्रिड","tab.prayer-map":"प्रार्थना प्रगति","sub.prayer-map":"सरल विश्व प्रार्थना मानचित्र","tab.cities-internet":"इंटरनेट वाले शहर","sub.cities-internet":"प्रमुख शहरों की कनेक्टिविटी","tab.gospel-resources":"सुसमाचार संसाधन","sub.gospel-resources":"स्थान के अनुसार संसाधन","tab.regions-dashboard":"क्षेत्र","sub.regions-dashboard":"क्षेत्र और उनके देश","tab.upg-100":"UPG 100 सूची पोस्टर","sub.upg-100":"मेरी अप्राप्त सूची + पोस्टर",navLabel:"शोध मानचित्र",meta:"{n} मानचित्र",loading:"{label} लोड हो रहा है…",loadFailed:"{label} लोड नहीं हो सका।",manifestHint:"यह टैब manifest.json से बनता है — इसे लोड करने के लिए ट्री बनाएँ या बना हुआ ट्री सर्व करें।",close:"बंद करें",help:"इस नक्शे के बारे में",desktopOnly:"केवल डेस्कटॉप",lab:"बीटा",experimentalSub:"प्रायोगिक मानचित्र",mobileNotBuilt:"मोबाइल के लिए नहीं बना",dismiss:"बंद करें",desktopOnly:"केवल डेस्कटॉप",lab:"बीटा",experimentalSub:"प्रायोगिक मानचित्र",toggleList:"सूची दिखाएँ या छिपाएँ",openViews:"दृश्यों की सूची खोलें",expandList:"सूची फैलाएँ",collapseList:"सूची संक्षिप्त करें",collapse:"संक्षिप्त करें",home:"होमपेज पर वापस जाएँ","tab.prayer-walk-planner":"प्रार्थना पदयात्रा","tab.upg-neighbor":"UPG पड़ोसी","tab.key-cities":"प्रमुख शहर","tab.all-people-groups":"सभी जनसमूह","tab.pplr-data-maps":"PPLR डेटा मानचित्र","tab.wall-of-people-groups":"जनसमूहों की दीवार","tab.bless-india-posters":"भारत को आशीष दें — पोस्टर","tab.country-posters":"देश पोस्टर","sub.country-posters":"हर देश के लिए एक पोस्टर","tab.poster-design":"पोस्टर डिज़ाइन","sub.poster-design":"हर पोस्टर शैली और आकार","tab.doxa-vision-card":"Doxa दर्शन कार्ड"},zh:{backToTop:"回到顶部","sec.dashboards":"仪表板","sec.prayer":"祷告动员","sec.gospel":"福音研究","sec.experimental":"实验性","tab.dashboard-plus-grid":"全景","sub.dashboard-plus-grid":"仪表板 + 民族网格","tab.research-map":"研究地图","sub.research-map":"祷告 · 参与 · 区域","tab.research-map-plus-grid":"简易研究","sub.research-map-plus-grid":"研究地图 + 民族网格","tab.prayer-map":"祷告进度","sub.prayer-map":"简易世界祷告地图","tab.cities-internet":"有互联网的城市","sub.cities-internet":"主要城市的网络连接","tab.gospel-resources":"福音资源","sub.gospel-resources":"按地点划分的资源","tab.regions-dashboard":"区域","sub.regions-dashboard":"区域及其国家","tab.upg-100":"UPG 100 名单海报","sub.upg-100":"我的未得之民名单 + 海报",navLabel:"研究地图",meta:"{n} 张地图",loading:"正在加载{label}…",loadFailed:"无法加载{label}。",manifestHint:"此标签页由 manifest.json 组成——请构建目录树或提供已构建的目录树以加载它。",close:"关闭",help:"关于此地图",desktopOnly:"仅限桌面",lab:"测试版",experimentalSub:"实验性地图",mobileNotBuilt:"未针对手机设计",dismiss:"关闭",desktopOnly:"仅限桌面",lab:"测试版",experimentalSub:"实验性地图",toggleList:"显示或隐藏列表",openViews:"打开视图列表",expandList:"展开列表",collapseList:"折叠列表",collapse:"折叠",home:"返回首页","tab.prayer-walk-planner":"祷告行走","tab.upg-neighbor":"UPG 邻居","tab.key-cities":"重点城市","tab.all-people-groups":"所有民族","tab.pplr-data-maps":"PPLR 数据地图","tab.wall-of-people-groups":"民族之墙","tab.bless-india-posters":"祝福印度海报","tab.country-posters":"国家海报","sub.country-posters":"每个国家一张海报","tab.poster-design":"海报设计","sub.poster-design":"所有海报样式与尺寸","tab.doxa-vision-card":"Doxa 异象卡"}},us=Object.keys(io);function ch(e){return Object.prototype.hasOwnProperty.call(io.en,e)}function Qo(e,t,a){let n=(io[e]||io.en)[t]??io.en[t]??t;if(a)for(const s in a)n=n.split("{"+s+"}").join(String(a[s]));return n}const Ll={en:{"prayer-walk-planner":{what:"Plan a prayer walk: draw the areas your teams will cover and print each one as a one-page map.",how:"Draw a zone, name it, select the zones you need and press Print. Each zone prints on its own page with coordinates, one map per team."},"gospel-internet":{what:"See which of the ~2,000 people groups already have gospel resources in their language, and where the internet can carry those resources to them.",how:"Explore the language tree, the bounded and centered sets and the map. Pick a list on the side to narrow every view."},"country-posters":{what:"Printable prayer posters, one per country, showing its people groups.",how:"Pick a country from the poster list or on the map, then print it straight from your browser."},"all-people-groups":{what:"Every people group on earth on one map, colored by how reached it is, with the DOXA prayer set highlighted.",how:"Click a category in the legend to show only those groups. Click a dot for its details and prayer link."},"doxa-vision-card":{what:"The remaining task in one card: all people groups, then the unreached, then the frontier, then the DOXA prayer set.",how:"Read it from top to bottom. Every number is computed from the people-group data, not typed in."},"pplr-data-maps":{what:"People-group research data on maps: language families, people clusters, religions and data-source coverage.",how:"Choose a view from the tabs, then use the legend to highlight a family, cluster or religion on the map."},"wall-of-people-groups":{what:"Every DOXA people group on one wall, to bless them. In Bless mode the wall is the printable poster (5 × 8 ft by default), each group with a blessing verse; in Social mode it is a shared prayer wall.",how:"Bless: look around the poster exactly as it will print, pick a size and press Print HD poster. Social: pray for a group, read or leave comments, and share its prayer page."}},es:{"prayer-walk-planner":{what:"Planifique una caminata de oración: dibuje las zonas que recorrerán sus equipos e imprima cada una como un mapa de una página.",how:"Dibuje una zona, póngale nombre, seleccione las zonas que necesita y pulse Imprimir. Cada zona se imprime en su propia página con coordenadas: un mapa por equipo."},"gospel-internet":{what:"Vea cuáles de los ~2000 grupos de pueblos ya tienen recursos del evangelio en su idioma, y dónde internet puede llevárselos.",how:"Explore el árbol de idiomas, los conjuntos delimitado y centrado y el mapa. Elija una lista al lado para acotar cada vista."},"country-posters":{what:"Carteles de oración para imprimir, uno por país, con sus grupos de pueblos.",how:"Elija un país en la lista de carteles o en el mapa, e imprímalo directamente desde el navegador."},"all-people-groups":{what:"Todos los grupos de pueblos del mundo en un mapa, coloreados según lo alcanzados que están, con el conjunto de oración de DOXA resaltado.",how:"Haga clic en una categoría de la leyenda para ver solo esos grupos. Haga clic en un punto para ver sus datos y el enlace de oración."},"doxa-vision-card":{what:"La tarea pendiente en una tarjeta: todos los grupos de pueblos, luego los no alcanzados, luego la frontera y luego el conjunto de oración de DOXA.",how:"Léala de arriba abajo. Cada cifra se calcula a partir de los datos, no se escribe a mano."},"pplr-data-maps":{what:"Datos de investigación sobre grupos de pueblos en mapas: familias lingüísticas, agrupaciones de pueblos, religiones y cobertura de fuentes.",how:"Elija una vista en las pestañas y use la leyenda para resaltar una familia, agrupación o religión en el mapa."},"wall-of-people-groups":{what:"Todos los grupos de pueblos de DOXA en un muro, para bendecirlos. En el modo Bendecir el muro es el póster para imprimir (5 × 8 pies por defecto), cada grupo con un versículo de bendición; en el modo Social es un muro de oración compartido.",how:"Bendecir: recorra el póster tal como se imprimirá, elija un tamaño y pulse Imprimir póster HD. Social: ore por un grupo, lea o deje comentarios y comparta su página de oración."}},fr:{"prayer-walk-planner":{what:"Préparez une marche de prière : dessinez les zones que vos équipes parcourront et imprimez chacune sur une carte d’une page.",how:"Dessinez une zone, nommez-la, sélectionnez les zones voulues et cliquez sur Imprimer. Chaque zone s’imprime sur sa propre page avec ses coordonnées : une carte par équipe."},"gospel-internet":{what:"Voyez lesquels des ~2 000 groupes de peuples ont déjà des ressources de l’Évangile dans leur langue, et où internet peut les leur apporter.",how:"Explorez l’arbre des langues, les ensembles délimité et centré et la carte. Choisissez une liste sur le côté pour affiner chaque vue."},"country-posters":{what:"Des affiches de prière à imprimer, une par pays, avec ses groupes de peuples.",how:"Choisissez un pays dans la liste des affiches ou sur la carte, puis imprimez-le directement depuis le navigateur."},"all-people-groups":{what:"Tous les groupes de peuples du monde sur une carte, colorés selon leur degré d’atteinte, avec l’ensemble de prière DOXA mis en valeur.",how:"Cliquez sur une catégorie de la légende pour n’afficher que ces groupes. Cliquez sur un point pour ses détails et son lien de prière."},"doxa-vision-card":{what:"La tâche restante en une carte : tous les groupes de peuples, puis les non atteints, puis la frontière, puis l’ensemble de prière DOXA.",how:"Lisez-la de haut en bas. Chaque chiffre est calculé à partir des données, pas saisi à la main."},"pplr-data-maps":{what:"Des données de recherche sur les groupes de peuples, en cartes : familles de langues, grappes de peuples, religions et couverture des sources.",how:"Choisissez une vue dans les onglets, puis utilisez la légende pour mettre en valeur une famille, une grappe ou une religion."},"wall-of-people-groups":{what:"Tous les groupes de peuples DOXA sur un seul mur, pour les bénir. En mode Bénir, le mur est l’affiche à imprimer (5 × 8 pieds par défaut), chaque groupe avec un verset de bénédiction ; en mode Social, c’est un mur de prière partagé.",how:"Bénir : parcourez l’affiche telle qu’elle s’imprimera, choisissez une taille et appuyez sur Imprimer l’affiche HD. Social : priez pour un groupe, lisez ou laissez des commentaires et partagez sa page de prière."}},de:{"prayer-walk-planner":{what:"Einen Gebetsspaziergang planen: die Gebiete Ihrer Teams zeichnen und jedes als einseitige Karte drucken.",how:"Zeichnen Sie ein Gebiet, benennen Sie es, wählen Sie die nötigen Gebiete und drücken Sie auf Drucken. Jedes Gebiet wird mit Koordinaten auf einer eigenen Seite gedruckt: eine Karte pro Team."},"gospel-internet":{what:"Sehen, welche der ~2.000 Volksgruppen schon Evangeliumsressourcen in ihrer Sprache haben und wo das Internet sie zu ihnen bringen kann.",how:"Erkunden Sie den Sprachbaum, die begrenzte und die zentrierte Menge und die Karte. Wählen Sie seitlich eine Liste, um jede Ansicht einzugrenzen."},"country-posters":{what:"Druckbare Gebetsplakate, eines pro Land, mit seinen Volksgruppen.",how:"Wählen Sie ein Land in der Plakatliste oder auf der Karte und drucken Sie es direkt aus dem Browser."},"all-people-groups":{what:"Alle Volksgruppen der Welt auf einer Karte, nach Erreichtheit eingefärbt, mit dem DOXA-Gebetsset hervorgehoben.",how:"Klicken Sie auf eine Kategorie der Legende, um nur diese Gruppen zu zeigen. Klicken Sie auf einen Punkt für Details und den Gebetslink."},"doxa-vision-card":{what:"Die verbleibende Aufgabe auf einer Karte: alle Volksgruppen, dann die unerreichten, dann die Frontier-Gruppen, dann das DOXA-Gebetsset.",how:"Lesen Sie sie von oben nach unten. Jede Zahl wird aus den Daten berechnet, nicht eingetippt."},"pplr-data-maps":{what:"Forschungsdaten zu Volksgruppen auf Karten: Sprachfamilien, Volkscluster, Religionen und Quellenabdeckung.",how:"Wählen Sie oben eine Ansicht und heben Sie mit der Legende eine Familie, einen Cluster oder eine Religion hervor."},"wall-of-people-groups":{what:"Jede DOXA-Volksgruppe auf einer Wand, um sie zu segnen. Im Modus Segnen ist die Wand das druckbare Poster (standardmäßig 5 × 8 Fuß), jede Gruppe mit einem Segensvers; im Modus Sozial ist sie eine gemeinsame Gebetswand.",how:"Segnen: Sehen Sie sich das Poster genau so an, wie es gedruckt wird, wählen Sie eine Größe und drücken Sie HD-Poster drucken. Sozial: Beten Sie für eine Gruppe, lesen oder schreiben Sie Kommentare und teilen Sie ihre Gebetsseite."}},pt:{"prayer-walk-planner":{what:"Planeje uma caminhada de oração: desenhe as áreas que suas equipes vão cobrir e imprima cada uma como um mapa de uma página.",how:"Desenhe uma zona, dê um nome, selecione as zonas necessárias e clique em Imprimir. Cada zona sai em sua própria página com coordenadas: um mapa por equipe."},"gospel-internet":{what:"Veja quais dos ~2.000 grupos de povos já têm recursos do evangelho no seu idioma, e onde a internet pode levá-los até eles.",how:"Explore a árvore de idiomas, os conjuntos delimitado e centrado e o mapa. Escolha uma lista ao lado para filtrar cada vista."},"country-posters":{what:"Cartazes de oração para imprimir, um por país, com seus grupos de povos.",how:"Escolha um país na lista de cartazes ou no mapa e imprima direto do navegador."},"all-people-groups":{what:"Todos os grupos de povos do mundo num mapa, coloridos pelo grau de alcance, com o conjunto de oração da DOXA em destaque.",how:"Clique numa categoria da legenda para ver só esses grupos. Clique num ponto para ver detalhes e o link de oração."},"doxa-vision-card":{what:"A tarefa restante num cartão: todos os grupos de povos, depois os não alcançados, depois a fronteira, depois o conjunto de oração da DOXA.",how:"Leia de cima para baixo. Cada número é calculado a partir dos dados, não digitado."},"pplr-data-maps":{what:"Dados de pesquisa sobre grupos de povos em mapas: famílias linguísticas, agrupamentos de povos, religiões e cobertura das fontes.",how:"Escolha uma vista nas abas e use a legenda para destacar uma família, agrupamento ou religião no mapa."},"wall-of-people-groups":{what:"Todos os grupos de povos da DOXA num só mural, para abençoá-los. No modo Abençoar, o mural é o cartaz para imprimir (5 × 8 pés por padrão), cada grupo com um versículo de bênção; no modo Social, é um mural de oração partilhado.",how:"Abençoar: percorra o cartaz tal como será impresso, escolha um tamanho e prima Imprimir cartaz HD. Social: ore por um grupo, leia ou deixe comentários e partilhe a sua página de oração."}},it:{"prayer-walk-planner":{what:"Pianifica una camminata di preghiera: disegna le zone che le tue squadre percorreranno e stampa ognuna come mappa di una pagina.",how:"Disegna una zona, dalle un nome, seleziona le zone che servono e premi Stampa. Ogni zona si stampa su una pagina con le coordinate: una mappa per squadra."},"gospel-internet":{what:"Scopri quali dei ~2.000 gruppi etnici hanno già risorse del vangelo nella loro lingua, e dove internet può portargliele.",how:"Esplora l’albero delle lingue, gli insiemi delimitato e centrato e la mappa. Scegli un elenco a lato per restringere ogni vista."},"country-posters":{what:"Poster di preghiera da stampare, uno per paese, con i suoi gruppi etnici.",how:"Scegli un paese dall’elenco dei poster o sulla mappa e stampalo direttamente dal browser."},"all-people-groups":{what:"Tutti i gruppi etnici del mondo su una mappa, colorati secondo quanto sono raggiunti, con l’insieme di preghiera DOXA evidenziato.",how:"Clicca una categoria della legenda per mostrare solo quei gruppi. Clicca un punto per dettagli e link di preghiera."},"doxa-vision-card":{what:"Il compito rimanente in una scheda: tutti i gruppi etnici, poi i non raggiunti, poi la frontiera, poi l’insieme di preghiera DOXA.",how:"Leggila dall’alto in basso. Ogni numero è calcolato dai dati, non digitato."},"pplr-data-maps":{what:"Dati di ricerca sui gruppi etnici su mappa: famiglie linguistiche, cluster di popoli, religioni e copertura delle fonti.",how:"Scegli una vista dalle schede, poi usa la legenda per evidenziare una famiglia, un cluster o una religione."},"wall-of-people-groups":{what:"Tutti i gruppi etnici DOXA su un solo muro, per benedirli. In modalità Benedire il muro è il poster da stampare (5 × 8 piedi di default), ogni gruppo con un versetto di benedizione; in modalità Social è un muro di preghiera condiviso.",how:"Benedire: esplora il poster esattamente come verrà stampato, scegli una dimensione e premi Stampa poster HD. Social: prega per un gruppo, leggi o lascia commenti e condividi la sua pagina di preghiera."}},ro:{"prayer-walk-planner":{what:"Planificați o plimbare de rugăciune: desenați zonele pe care le vor parcurge echipele și tipăriți fiecare ca hartă de o pagină.",how:"Desenați o zonă, dați-i un nume, selectați zonele necesare și apăsați Tipărire. Fiecare zonă se tipărește pe o pagină, cu coordonate: câte o hartă pentru fiecare echipă."},"gospel-internet":{what:"Vedeți care dintre cele ~2.000 de grupuri de popoare au deja resurse ale Evangheliei în limba lor și unde le poate duce internetul.",how:"Explorați arborele limbilor, mulțimile delimitată și centrată și harta. Alegeți o listă din lateral pentru a restrânge fiecare vedere."},"country-posters":{what:"Afișe de rugăciune de tipărit, câte unul pentru fiecare țară, cu grupurile ei de popoare.",how:"Alegeți o țară din lista de afișe sau de pe hartă și tipăriți-o direct din browser."},"all-people-groups":{what:"Toate grupurile de popoare ale lumii pe o hartă, colorate după cât sunt de atinse, cu setul de rugăciune DOXA evidențiat.",how:"Faceți clic pe o categorie din legendă pentru a afișa doar acele grupuri. Faceți clic pe un punct pentru detalii și linkul de rugăciune."},"doxa-vision-card":{what:"Sarcina rămasă într-un card: toate grupurile de popoare, apoi cele neatinse, apoi cele de frontieră, apoi setul de rugăciune DOXA.",how:"Citiți-l de sus în jos. Fiecare număr este calculat din date, nu tastat."},"pplr-data-maps":{what:"Date de cercetare despre grupurile de popoare pe hărți: familii de limbi, grupuri de popoare, religii și acoperirea surselor.",how:"Alegeți o vedere din file, apoi folosiți legenda pentru a evidenția o familie, un grup sau o religie."},"wall-of-people-groups":{what:"Toate grupurile de popoare DOXA pe un singur perete, ca să le binecuvântăm. În modul Binecuvântare, peretele este afișul de tipărit (5 × 8 picioare implicit), fiecare grup cu un verset de binecuvântare; în modul Social este un perete de rugăciune comun.",how:"Binecuvântare: explorați afișul exact cum se va tipări, alegeți o dimensiune și apăsați Tipărește afișul HD. Social: rugați-vă pentru un grup, citiți sau lăsați comentarii și distribuiți pagina lui de rugăciune."}},ru:{"prayer-walk-planner":{what:"Спланируйте молитвенную прогулку: нарисуйте районы для ваших команд и распечатайте каждый как карту на одну страницу.",how:"Нарисуйте зону, назовите её, выберите нужные зоны и нажмите «Печать». Каждая зона печатается на отдельной странице с координатами — по карте на команду."},"gospel-internet":{what:"Узнайте, у каких из ~2000 народов уже есть евангельские материалы на их языке и где интернет может донести их до них.",how:"Исследуйте языковое дерево, ограниченное и центрированное множества и карту. Выберите список сбоку, чтобы сузить любой вид."},"country-posters":{what:"Молитвенные плакаты для печати, по одному на страну, с её народами.",how:"Выберите страну в списке плакатов или на карте и распечатайте прямо из браузера."},"all-people-groups":{what:"Все народы мира на одной карте, окрашенные по степени достигнутости, с выделенным молитвенным набором DOXA.",how:"Нажмите категорию в легенде, чтобы показать только эти группы. Нажмите точку, чтобы увидеть сведения и ссылку для молитвы."},"doxa-vision-card":{what:"Оставшаяся задача на одной карточке: все народы, затем недостигнутые, затем пограничные, затем молитвенный набор DOXA.",how:"Читайте сверху вниз. Каждое число вычислено из данных, а не введено вручную."},"pplr-data-maps":{what:"Исследовательские данные о народах на картах: языковые семьи, кластеры народов, религии и охват источников.",how:"Выберите вид во вкладках, затем выделите семью, кластер или религию с помощью легенды."},"wall-of-people-groups":{what:"Все народы DOXA на одной стене, чтобы благословлять их. В режиме «Благословение» стена — это плакат для печати (по умолчанию 5 × 8 футов), у каждого народа — стих благословения; в режиме «Соцсеть» это общая молитвенная стена.",how:"«Благословение»: просмотрите плакат точно таким, каким он будет напечатан, выберите размер и нажмите «Печать плаката HD». «Соцсеть»: молитесь о народе, читайте и оставляйте комментарии, делитесь его молитвенной страницей."}},ar:{"prayer-walk-planner":{what:"خطِّط لجولة صلاة: ارسم المناطق التي ستغطيها فرقك واطبع كل منطقة خريطةً من صفحة واحدة.",how:"ارسم منطقة وسمِّها، واختر المناطق التي تحتاجها ثم اضغط «طباعة». تُطبع كل منطقة في صفحة مستقلة مع إحداثياتها: خريطة لكل فريق."},"gospel-internet":{what:"اعرف أيَّ الشعوب الألفين تقريبًا لديها بالفعل موارد للإنجيل بلغتها، وأين يمكن للإنترنت أن يوصلها إليها.",how:"استكشف شجرة اللغات والمجموعتين المحدودة والمتمركزة والخريطة. اختر قائمة من الجانب لتضييق كل عرض."},"country-posters":{what:"ملصقات صلاة للطباعة، ملصق لكل بلد، تُظهر شعوبه.",how:"اختر بلدًا من قائمة الملصقات أو على الخريطة، ثم اطبعه مباشرة من المتصفح."},"all-people-groups":{what:"كل شعوب العالم على خريطة واحدة، ملوَّنة بحسب درجة وصول الإنجيل إليها، مع إبراز مجموعة صلاة DOXA.",how:"انقر فئة في مفتاح الخريطة لعرض تلك الشعوب فقط. انقر نقطة لترى تفاصيلها ورابط الصلاة."},"doxa-vision-card":{what:"المهمة المتبقية في بطاقة واحدة: كل الشعوب، ثم غير المُبشَّرة، ثم الحدودية، ثم مجموعة صلاة DOXA.",how:"اقرأها من الأعلى إلى الأسفل. كل رقم محسوب من البيانات، وليس مكتوبًا يدويًا."},"pplr-data-maps":{what:"بيانات بحثية عن الشعوب على خرائط: العائلات اللغوية والعناقيد الشعبية والأديان وتغطية المصادر.",how:"اختر عرضًا من التبويبات، ثم استخدم مفتاح الخريطة لإبراز عائلة أو عنقود أو دين."},"wall-of-people-groups":{what:"كل شعوب DOXA على جدار واحد لنباركها. في وضع «بركة» يكون الجدار هو الملصق الجاهز للطباعة (5 × 8 أقدام افتراضيًا)، ولكل شعب آية بركة؛ وفي وضع «تفاعلي» يكون جدار صلاة مشتركًا.",how:"«بركة»: تجوّل في الملصق كما سيُطبع تمامًا، واختر المقاس ثم اضغط «طباعة ملصق عالي الدقة». «تفاعلي»: صلِّ لأجل شعب، واقرأ التعليقات أو اكتب تعليقًا، وشارك صفحة الصلاة الخاصة به."}},hi:{"prayer-walk-planner":{what:"प्रार्थना-यात्रा की योजना बनाएँ: अपनी टीमों के क्षेत्र बनाएँ और हर क्षेत्र को एक पन्ने के नक्शे के रूप में छापें।",how:"एक क्षेत्र बनाएँ, उसे नाम दें, ज़रूरी क्षेत्र चुनें और प्रिंट दबाएँ। हर क्षेत्र निर्देशांकों के साथ अपने पन्ने पर छपता है: हर टीम के लिए एक नक्शा।"},"gospel-internet":{what:"देखें कि लगभग 2,000 जन-समूहों में से किनके पास उनकी भाषा में सुसमाचार के संसाधन पहले से हैं, और इंटरनेट उन्हें कहाँ तक पहुँचा सकता है।",how:"भाषा-वृक्ष, सीमित और केंद्रित समुच्चय और नक्शा देखें। हर दृश्य को सीमित करने के लिए बगल में एक सूची चुनें।"},"country-posters":{what:"छापने योग्य प्रार्थना-पोस्टर, हर देश के लिए एक, उसके जन-समूहों के साथ।",how:"पोस्टर सूची में या नक्शे पर कोई देश चुनें, फिर सीधे ब्राउज़र से छापें।"},"all-people-groups":{what:"दुनिया के सभी जन-समूह एक नक्शे पर, इस आधार पर रंगे हुए कि वे कितने पहुँचे हुए हैं, DOXA प्रार्थना-समूह उभारा हुआ।",how:"केवल वे समूह देखने के लिए लीजेंड में एक श्रेणी पर क्लिक करें। विवरण और प्रार्थना-लिंक के लिए किसी बिंदु पर क्लिक करें।"},"doxa-vision-card":{what:"बचा हुआ काम एक कार्ड में: सभी जन-समूह, फिर अपहुँचे, फिर सीमांत, फिर DOXA प्रार्थना-समूह।",how:"इसे ऊपर से नीचे पढ़ें। हर संख्या आँकड़ों से गणना की गई है, हाथ से नहीं लिखी गई।"},"pplr-data-maps":{what:"जन-समूहों पर शोध-आँकड़े नक्शों पर: भाषा-परिवार, जन-क्लस्टर, धर्म और स्रोतों की पहुँच।",how:"टैब से कोई दृश्य चुनें, फिर लीजेंड से किसी परिवार, क्लस्टर या धर्म को नक्शे पर उभारें।"},"wall-of-people-groups":{what:"हर DOXA जन-समूह एक ही दीवार पर, उन्हें आशीष देने के लिए। आशीष मोड में दीवार छापने योग्य पोस्टर है (डिफ़ॉल्ट 5 × 8 फ़ुट), हर समूह के साथ आशीष का एक पद; सामाजिक मोड में यह एक साझा प्रार्थना दीवार है।",how:"आशीष: पोस्टर को ठीक वैसा ही देखें जैसा वह छपेगा, आकार चुनें और HD पोस्टर छापें दबाएँ। सामाजिक: किसी समूह के लिए प्रार्थना करें, टिप्पणियाँ पढ़ें या लिखें, और उसका प्रार्थना पेज साझा करें।"}},zh:{"prayer-walk-planner":{what:"规划祷告行走:画出各小组要走的区域,把每个区域打印成一页地图。",how:"画一个区域并命名,选择需要的区域后点“打印”。每个区域连同坐标单独打印一页:每个小组一张地图。"},"gospel-internet":{what:"看看约 2,000 个族群中,哪些已经有母语的福音资源,以及互联网能把这些资源带到哪里。",how:"浏览语言树、有界集与中心集以及地图。在侧边选择一个名单,可缩小每个视图的范围。"},"country-posters":{what:"可打印的祷告海报,每个国家一张,列出其中的族群。",how:"在海报列表或地图上选择一个国家,然后直接从浏览器打印。"},"all-people-groups":{what:"世界上所有族群都在一张地图上,按福音进展着色,并突出显示 DOXA 祷告名单。",how:"点击图例中的类别,只显示这些族群。点击一个点,查看详情和祷告链接。"},"doxa-vision-card":{what:"一张卡片看清剩下的使命:所有族群,然后是未得之民,再是前沿族群,最后是 DOXA 祷告名单。",how:"从上往下读。每个数字都由数据计算得出,而不是手工填写。"},"pplr-data-maps":{what:"族群研究数据地图:语系、民族集群、宗教和数据来源覆盖情况。",how:"在标签页中选择一个视图,然后用图例在地图上突出显示某个语系、集群或宗教。"},"wall-of-people-groups":{what:"所有 DOXA 族群都在一面墙上,为要祝福他们。在“祝福”模式下,这面墙就是可打印的海报(默认 5 × 8 英尺),每个族群配一节祝福经文;在“社交”模式下,它是一面共同的祷告墙。",how:"祝福:按打印时的样子浏览海报,选择尺寸,然后点击“打印高清海报”。社交:为某个族群祷告,阅读或留下评论,并分享它的祷告页面。"}}};function Tl(e,t){var o;const a=String(t||"en").toLowerCase().split(/[-_]/)[0];return((o=Ll[a])==null?void 0:o[e])||Ll.en[e]||null}const Pl="previews/examples/middle-east-core",ph=Array.from({length:12},(e,t)=>`${Pl}/packet-p${String(t+1).padStart(2,"0")}.webp`),uh=Array.from({length:70},(e,t)=>`${Pl}/card-${String(t+1).padStart(2,"0")}.webp`),dh={"upg-100":{storageKey:"upg-100",trigger:"control",pos:{side:"56px",narrow:{placement:"top-left",side:"12px"}},leadKey:"mapInfo.tab.upg-100.lead",strategyKey:"mapStrategy.tab.upg-100",takeawayKey:"mapInfo.tab.upg-100.takeaway",noteKey:"pins.approximate",examples:[{kind:"image",src:"previews/middle-east-core.webp",full:"posters/middle-east-core.html"},{kind:"pages",pages:ph},{kind:"deck",cards:uh}]},"regions-dashboard":{storageKey:"regions-dashboard",pos:{side:"56px",narrow:{placement:"top-left",side:"8px"}},leadKey:"mapInfo.tab.regions-dashboard.lead",strategyKey:"mapStrategy.tab.regions-dashboard",trigger:"control",takeawayKey:"mapInfo.tab.regions-dashboard.takeaway",noteKey:"mapInfo.tab.regions-dashboard.note",examples:[]},"dashboard-plus-grid":{storageKey:"dashboard-plus-grid",trigger:"control",pos:{top:"46px",raise:!0},leadKey:"mapInfo.tab.dashboard-plus-grid.lead",strategyKey:"mapStrategy.tab.dashboard-plus-grid",takeawayKey:"mapInfo.tab.dashboard-plus-grid.takeaway",examples:[{kind:"table",columns:[{key:"mapInfo.col.peopleGroup"},{key:"mapInfo.col.country"},{key:"mapInfo.col.region"},{key:"mapInfo.col.population"}],rows:[["Hararghe Oromo","Ethiopia","Africa",6355e3],["Gaaliin","Sudan","Middle East",452e4],["White Moor","Morocco","Middle East",312e4],["Baloch, Western","Iran","Middle East",3035e3],["Baloch, Eastern","Pakistan","Asia",298e4],["Bhuiyar","India","Asia",2925e3]],total:2107}]},"research-map-plus-grid":{storageKey:"research-map-plus-grid",trigger:"control",pos:{top:"56px",side:"56px"},leadKey:"mapInfo.tab.research-map-plus-grid.lead",strategyKey:"mapStrategy.tab.research-map-plus-grid",noteKey:"pins.approximate",examples:[]},"prayer-map":{storageKey:"prayer-map",trigger:"control",pos:{side:"56px"},leadKey:"mapInfo.tab.prayer-map.lead",strategyKey:"mapStrategy.tab.prayer-map",takeawayKey:"mapInfo.tab.prayer-map.takeaway",noteKey:"pins.approximate",examples:[]},"cities-internet":{storageKey:"cities-internet",trigger:"control",pos:{side:"56px",narrow:{side:"4px"}},leadKey:"mapInfo.tab.cities-internet.lead",strategyKey:"mapStrategy.tab.cities-internet",takeawayKey:"mapInfo.tab.cities-internet.takeaway",noteKey:"pins.approximate",examples:[]},"gospel-resources":{storageKey:"gospel-resources",trigger:"control",pos:{side:"56px",narrow:{side:"4px"}},leadKey:"mapInfo.tab.gospel-resources.lead",strategyKey:"mapStrategy.tab.gospel-resources",takeawayKey:"mapInfo.tab.gospel-resources.takeaway",noteKey:"pins.approximate",examples:[]}};function El(e,t){return t?String(e).replace(/\{(\w+)\}/g,(a,o)=>t[o]!=null?String(t[o]):a):String(e)}function Ol(e,t,a=ls()){const o=String(e).split(/\s*\|\s*/);if(o.length<2||typeof t!="number")return String(e);const n=String(a||"en").split("-")[0].toLowerCase();if(n!=="en")return o[fl(t,o.length,n)];const s=Math.abs(t),r=o.length===2?s?s>1?1:0:1:s?Math.min(s,2):0;return o[Math.min(r,o.length-1)]}function ds(e,t){const a=String(t);return e&&!a.startsWith(`${e}.`)?`${e}.${a}`:a}function gh(e,t={}){const a=typeof t.fallback=="function"?(o,n,s)=>El(Ol(t.fallback(ds(e,o),n)??o,s),n):(o,n,s)=>El(Ol(Wt(ds(e,o))??o,s),n);try{const o=Zo();return(n,s,r)=>{const i=ds(e,n),l=typeof r=="number"?o.t(i,s||{},r):o.t(i,s||{});return l===i?a(n,s,r):l}}catch{return a}}const mh=["dir","lang"],fh=["aria-label","title","aria-expanded"],hh={viewBox:"0 0 16 16",focusable:"false"},bh={key:0,d:"M14 8H3M7 3.5 2.5 8 7 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},$h={key:1,d:"M2 8h11M9 3.5 13.5 8 9 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},_h=["dir"],vh={viewBox:"0 0 16 16",focusable:"false"},yh={key:0,d:"M14 8H3M7 3.5 2.5 8 7 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},wh={key:1,d:"M2 8h11M9 3.5 13.5 8 9 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},kh=["aria-labelledby"],xh={class:"mi-head"},Sh=["id"],Lh=["aria-label"],Th={class:"mi-body"},Ph=["aria-labelledby"],Eh=["id"],Oh={class:"mi-lead"},Mh={key:1,class:"mi-takeaway"},Ah={class:"mi-strategy-sum"},Ch={class:"mi-bottom"},Ih={key:0,class:"mi-points"},Nh=["src","alt","onError"],Fh={key:0,class:"mi-center"},Dh=["href"],Rh=["aria-label","onClick"],zh=["src","alt","onError"],qh={key:0,class:"mi-center"},Wh=["onClick"],jh={class:"mi-center"},Uh=["onClick"],Hh={class:"mi-pages"},Bh=["aria-label","onClick"],Vh=["src","alt"],Gh={class:"mi-center"},Kh=["onClick"],Xh=["aria-label","onClick"],Yh=["src","onError"],Jh={class:"mi-center"},Zh=["onClick"],Qh={class:"mi-center"},eb=["onClick"],tb={class:"mi-grid"},ab=["src","alt"],ob={class:"mi-center"},nb=["onClick"],sb={class:"mi-table-wrap"},rb={class:"mi-table"},ib={key:0,class:"mi-more"},lb={key:3,class:"mi-note"},cb={key:0,class:"mi-foot"},pb=["checked"],lo=new Set,ub={__name:"MapInfo",props:{storageKey:{type:String,required:!0},leadKey:{type:String,default:""},lead:{type:String,default:""},takeawayKey:{type:String,default:""},takeaway:{type:String,default:""},noteKey:{type:String,default:""},strategyKey:{type:String,default:""},note:{type:String,default:""},examples:{type:Array,default:()=>[]},assetsBase:{type:String,default:""},placement:{type:String,default:"top-right",validator:e=>["top-right","top-left"].includes(e)},locale:{type:String,default:""},trigger:{type:String,default:"floating",validator:e=>["floating","control"].includes(e)},popupOnly:{type:Boolean,default:!1}},setup(e){const t=e,a=()=>t.trigger==="control",o=Me(0);function n(){return o.value,String(t.locale||ls()||"en").split("-")[0].toLowerCase()}const s=gh("",{fallback:k=>Wt(k,n())}),r=(k,K)=>k?s(k):K,i=8,l=()=>t.strategyKey&&Wt(t.strategyKey+".bottom",n())||"";function p(){const k=[];if(!t.strategyKey)return k;for(let K=1;K<=i;K++){const Y=Wt(t.strategyKey+".p"+K,n());if(!Y)break;k.push(Y)}return k}const c=()=>!!l(),m=k=>{try{return new Intl.NumberFormat(n()).format(k)}catch{return String(k)}},v=()=>n()==="ar",w="dxmaps.mapInfo.seen.",q="dxmaps.mapInfo.pointer",M="dxmaps:mapinfo-pointer-off";function U(k){try{return window.localStorage.getItem(k)}catch{return null}}function j(k,K){try{window.localStorage.setItem(k,K)}catch{}}const W=Me(!1),y=Me(!1);function b(){if(y.value=U(q)==="off",a()){W.value=!lo.has(t.storageKey);return}W.value=!y.value&&U(w+t.storageKey)!=="1"}function A(){a()||b()}function P(k){a()||(!k||k.key===q||k.key===w+t.storageKey)&&b()}sa(()=>{b(),window.addEventListener(M,A),window.addEventListener("storage",P)}),qa(()=>{window.removeEventListener(M,A),window.removeEventListener("storage",P)});function F(k){y.value=k,j(q,k?"off":"on"),W.value=!k&&U(w+t.storageKey)!=="1";try{window.dispatchEvent(new CustomEvent(M))}catch{}}const H=Me(!1),B=Me(null),fe=Me(null),ke=Na({}),Q=Na({});async function xe(){var k;o.value++,H.value=!0,a()&&lo.add(t.storageKey),await Po(),(k=fe.value)==null||k.focus()}async function nt(){H.value=!1,a()||j(w+t.storageKey,"1"),W.value=!1,await Po();const k=t.trigger==="control"&&le.value&&ie.value?ie.value:B.value;k==null||k.focus()}const Ne=Me(null),ie=Me(null),le=Me(!1);let ae=null,Se=null,Ce=null,Le=null;function Ee(k){for(let K=k;K;K=K.parentNode||K.host)if(K.nodeType===1&&(K.hasAttribute("inert")||K.getAttribute("aria-hidden")==="true"))return!0;return!1}const Lt=3e4,Tt=9e4;let mt=!1;const je=Me(t.trigger==="control");let tt=0;function it(){const k=Ne.value&&Ne.value.parentElement,K=k&&k.getBoundingClientRect();return!!k&&!Ee(k)&&K.width>0&&K.height>0}function Pt(){if(je.value){if(!it()){tt=0;return}tt?Date.now()-tt>=(mt?Tt:Lt)&&(je.value=!1):tt=Date.now()}}function Fe(){Pt(),a()&&W.value&&lo.has(t.storageKey)&&!it()&&(W.value=!1);const k=ie.value;let K=!1;if(k&&k.isConnected&&!Ee(k)){const Y=k.getBoundingClientRect();if(K=Y.width>0&&Y.height>0&&Y.bottom>0&&Y.right>0&&Y.topk.classList.contains("mcb--rounded-square");function h(k){const K=Ne.value&&Ne.value.offsetParent;if(!K)return null;const Y=K.getBoundingClientRect(),oe=Y.left+K.clientLeft,ge=Y.top+K.clientTop,Oe=Math.round(k.top-ge+K.scrollTop+k.height/2);return k.left+k.width/2>oe+K.clientWidth/2?{side:"right",top:Oe,right:Math.round(oe+K.clientWidth-k.left+jt)}:{side:"left",top:Oe,left:Math.round(k.right-oe+jt)}}const f=Me(null),x=()=>{const k=f.value;return k.side==="right"?{top:k.top+"px",right:k.right+"px"}:{top:k.top+"px",left:k.left+"px"}},E=()=>Fe();function T(k){if(!k||k===ie.value){Fe();return}try{Se==null||Se.disconnect(),Ce==null||Ce.disconnect()}catch{}ie.value=k,je.value=!1;try{Se=new IntersectionObserver(Fe),Se.observe(k)}catch{Se=null}try{Ce=new ResizeObserver(Fe),Ce.observe(k)}catch{Ce=null}k.setAttribute("aria-expanded",String(H.value)),Fe()}const R=k=>!!ae&&typeof k.composedPath=="function"&&k.composedPath().includes(ae);function D(k){R(k)&&(k.stopPropagation(),k.detail&&(k.detail.ack=!0),T(k.detail&&k.detail.el))}function u(k){R(k)&&(k.stopPropagation(),mt=!!(k.detail&&k.detail.pending),!mt&&!ie.value&&(je.value=!1))}function d(k){R(k)&&(k.stopPropagation(),T(k.detail&&k.detail.el),H.value||xe())}sa(()=>{if(t.trigger==="control"&&(ae=Ne.value&&Ne.value.parentElement,!!ae)){ae.addEventListener("dxmaps:mapinfo-control",D),ae.addEventListener("dxmaps:mapinfo-open",d),ae.addEventListener("dxmaps:mapinfo-pending",u),Le=window.setInterval(Fe,1500),window.addEventListener("resize",E);try{window.dispatchEvent(new CustomEvent("dxmaps:mapinfo-host",{detail:{local:t.popupOnly}}))}catch{}}}),qa(()=>{ae&&(ae.removeEventListener("dxmaps:mapinfo-control",D),ae.removeEventListener("dxmaps:mapinfo-open",d),ae.removeEventListener("dxmaps:mapinfo-pending",u));try{Se==null||Se.disconnect(),Ce==null||Ce.disconnect()}catch{}Le&&window.clearInterval(Le),window.removeEventListener("resize",E)}),Gt(H,k=>{var K;(K=ie.value)==null||K.setAttribute("aria-expanded",String(k))});let $=null;function S(){W.value&&(W.value=!1,a()&&lo.add(t.storageKey))}sa(()=>{$=Ne.value&&(Ne.value.parentElement||Ne.value.getRootNode()),$&&$.addEventListener("dxmaps:panel-open",S)}),qa(()=>{$&&$.removeEventListener("dxmaps:panel-open",S),$=null});const z=()=>!t.popupOnly&&(t.trigger!=="control"||!je.value&&!le.value);Gt(()=>a()&&W.value&&!H.value&&!z()&&!!f.value,k=>{k&&lo.add(t.storageKey)},{immediate:!0});const I=Me(null);function _(k){const K=I.value;if(!K)return;const Y=[...K.querySelectorAll('button, a[href], input, [tabindex]:not([tabindex="-1"])')].filter(Ht=>!Ht.disabled&&Ht.offsetParent!==null);if(!Y.length)return;const oe=K.getRootNode().activeElement,ge=Y[0],Oe=Y[Y.length-1];k.shiftKey&&(oe===ge||!K.contains(oe))?(k.preventDefault(),Oe.focus()):!k.shiftKey&&(oe===Oe||!K.contains(oe))&&(k.preventDefault(),ge.focus())}function L(k){k.key==="Escape"?(k.stopPropagation(),nt()):k.key==="Tab"&&_(k)}function G(k){ke[k]=!ke[k]}function ee(k){const K=k&&k.target;K&&(K.style.display="none")}function ce(k){const K=String(k||"");return!K||/^(?:[a-z]+:|\/)/i.test(K)||!t.assetsBase?K:t.assetsBase.replace(/\/+$/,"")+"/"+K.replace(/^\.?\//,"")}const Ue={image:"mapInfo.poster",pages:"mapInfo.packet",deck:"mapInfo.cards",table:"mapInfo.spreadsheet"};function Te(k){return s(k.titleKey||Ue[k.kind]||"mapInfo.example")}const Ut=k=>(k.cards||[]).slice(0,5),co=k=>k&&typeof k=="object"?k.key?s(k.key):String(k.label||""):String(k??"");return hl(` +`,Xm=$a({name:"VerticalTabsLayout",components:{BackToTopButton:ri},props:{active:{type:String,default:""},title:{type:String,default:""},items:{type:Array,default:()=>[]},groups:{type:Array,default:()=>[]},navLabel:{type:String,default:"Sections"},labels:{type:Object,default:()=>({})},initialNavOpen:{type:Boolean,default:!0},header:{type:Boolean,default:!0},initialRailCollapsed:{type:Boolean,default:!1},homeHref:{type:String,default:""},homeLabel:{type:String,default:""},shellMode:{type:String,default:"auto"}},emits:["update:active"],setup(){return hl(Km,"vertical-tabs-layout"),{T:Bm}},data(){const e=(()=>{try{return typeof window<"u"&&!!window.matchMedia&&window.matchMedia("(max-width: 760px)").matches}catch{return!1}})();return{navOpen:this.initialNavOpen&&!e,railCollapsed:e?!1:this.initialRailCollapsed,_tabEls:{},folderOverride:{},narrow:e,_mql:null,_onMedia:null,activatedKeys:{},hostState:{},hostError:{},_hostSlots:{},_hostMounted:{},hostEls:{},mode:"app"}},computed:{phoneBarShown(){return this.narrow&&!this.header},railEndShown(){return!this.narrow&&!this.header},activeHostEl(){return this.hostEls[this.active]||null},railShown(){return this.narrow?this.navOpen:!0},homeLink(){const e=String(this.homeHref||"").trim();return!e||/^(javascript|data|vbscript):/i.test(e.replace(/[\u0000- ]/g,""))?"":e},homeText(){return this.homeLabel||this.label("home")},allRows(){if(this.groups&&this.groups.length){const e=[];for(const t of this.groups)for(const a of t.rows||[])e.push(a);return e}return this.items},orderedKeys(){return this.allRows.map(e=>e.key)},elementRows(){return this.allRows.filter(e=>e&&e.element)},mountedElementRows(){return this.elementRows.filter(e=>this.activatedKeys[e.key])}},watch:{shellMode(){this.resolveShellMode()},active:{immediate:!0,handler(e){this.activateElement(e)}}},mounted(){this.resolveShellMode();try{window.matchMedia&&(this._mql=window.matchMedia("(max-width: 760px)"),this.narrow=this._mql.matches,this.reconcileForWidth(),this._onMedia=e=>{this.narrow=e.matches,this.reconcileForWidth()},this._mql.addEventListener?this._mql.addEventListener("change",this._onMedia):this._mql.addListener&&this._mql.addListener(this._onMedia))}catch{}this.$nextTick(()=>this.activateElement(this.active))},beforeUnmount(){try{this._mql&&this._onMedia&&(this._mql.removeEventListener?this._mql.removeEventListener("change",this._onMedia):this._mql.removeListener&&this._mql.removeListener(this._onMedia))}catch{}this._mql=null,this._onMedia=null},methods:{resolveShellMode(){const e=a=>a==="web"||a==="app"?a:null;let t=e(this.shellMode);if(!t)try{const a=this.$el&&this.$el.getRootNode?this.$el.getRootNode():null,o=a&&a.host;t=e(o&&o.getAttribute?o.getAttribute("shell-mode"):null)}catch{}if(!t)try{const a=window.matchMedia&&window.matchMedia("(display-mode: standalone)").matches||navigator.standalone===!0;t=window.Capacitor||a||window.MAP_OWNS_PAGE===!0?"app":"web"}catch{t="app"}this.mode=t},registerTab(e,t){t&&(this._tabEls[e]=t)},isNarrow(){return this.narrow},reconcileForWidth(){this.narrow?(this.navOpen=!1,this.railCollapsed=!1):this.navOpen=!0},label(e){const t={toggleList:"Toggle list",openViews:"Open the list of views",expandList:"Expand list",collapseList:"Collapse list",collapse:"Collapse",home:"Back to homepage"};return this.labels&&this.labels[e]||t[e]},expandRail(){!this.narrow&&this.railCollapsed&&(this.railCollapsed=!1)},toggleRail(){this.narrow?this.navOpen=!this.navOpen:this.railCollapsed=!this.railCollapsed},setHostSlot(e,t){t&&(this._hostSlots[e]=t,this.$nextTick(()=>this.mountHostElement(e)))},activateElement(e){!e||!this.elementRows.find(a=>a.key===e)||(this.activatedKeys[e]||(this.activatedKeys={...this.activatedKeys,[e]:!0},this.$nextTick(()=>this.watchContextLoss(e)),this.hostState={...this.hostState,[e]:"loading"}),this.$nextTick(()=>this.mountHostElement(e)))},deepCanvases(e,t=[]){if(!e)return t;for(const a of e.querySelectorAll("*"))a.tagName==="CANVAS"&&t.push(a),a.shadowRoot&&this.deepCanvases(a.shadowRoot,t);return t},watchContextLoss(e){const t=this._hostSlots[e];if(t)for(const a of this.deepCanvases(t))a.__vtlLossBound||(a.__vtlLossBound=!0,a.addEventListener("webglcontextlost",()=>{this.hostState={...this.hostState,[e]:"lost"}},{once:!0}))},reloadHostElement(e){const t=this._hostSlots[e];t&&(t.innerHTML=""),this._hostMounted[e]=!1,this.hostState={...this.hostState,[e]:"loading"},this.$nextTick(()=>this.mountHostElement(e))},mountHostElement(e){if(this._hostMounted[e])return;const t=this.elementRows.find(o=>o.key===e),a=this._hostSlots[e];!t||!a||(this._hostMounted[e]=!0,this.hostState={...this.hostState,[e]:"loading"},Vm(t.src).then(()=>Gm(t.element)).then(()=>{Wm(t.element,t.src),a.innerHTML="";const o=document.createElement(t.element),n=t.attrs||{};for(const s in n){const r=n[s],i=r!=null&&typeof r=="object"?JSON.stringify(r):String(r);try{o[s]=r}catch{}try{o.setAttribute(s,i)}catch{}}o.style.cssText="display:block;position:absolute;inset:0;width:100%;height:100%;",a.appendChild(o);for(const s of[0,250,1500])setTimeout(()=>this.watchContextLoss(e),s);this.hostState={...this.hostState,[e]:"ready"}}).catch(o=>{this._hostMounted[e]=!1,this.hostState={...this.hostState,[e]:"error"},this.hostError={...this.hostError,[e]:o&&o.message||String(o)}}))},iconSvg(e){return e?$l(e):""},initial(e){const t=(e||"").trim();return t?t.charAt(0).toUpperCase():"•"},isFolderClosed(e){const t=this.folderOverride[e.key];return t!=null?!t:e.open!==!0},toggleFolder(e){const t=this.folderOverride[e],a=(this.groups||[]).find(n=>n.key===e),o=t??(a&&a.open===!0);this.folderOverride={...this.folderOverride,[e]:!o}},pick(e){this.$emit("update:active",e),this.narrow&&(this.navOpen=!1)},onKey(e){const t=this.orderedKeys;if(!t.length)return;const a=t.indexOf(this.active);let o=-1;if(e.key==="ArrowDown"||e.key==="ArrowRight")o=(a+1)%t.length;else if(e.key==="ArrowUp"||e.key==="ArrowLeft")o=(a-1+t.length)%t.length;else if(e.key==="Home")o=0;else if(e.key==="End")o=t.length-1;else return;e.preventDefault();const n=t[o];this.pick(n),this.$nextTick(()=>{const s=this._tabEls[n];s&&s.focus&&s.focus()})}}}),Ym={key:0,class:"vtl-top"},Jm=["aria-expanded","aria-label"],Zm={class:"vtl-brand"},Qm={class:"vtl-title"},ef={class:"vtl-meta"},tf=["aria-expanded","aria-label"],af={class:"vtl-pbar-end"},of={class:"vtl-body"},nf=["aria-label","aria-hidden","role","aria-orientation"],sf=["href","aria-label","title"],rf=["innerHTML"],lf={class:"vtl-home-tx"},cf=["aria-expanded","aria-label"],pf=["innerHTML"],uf={class:"vtl-collapse-tx"},df=["aria-expanded","onClick"],gf={key:0,class:"vtl-cap"},mf={class:"vtl-tl"},ff={key:1,class:"vtl-td"},hf=["innerHTML"],bf={key:1,class:"vtl-tier"},$f={key:0,class:"vtl-cap"},_f={class:"vtl-tl"},vf={key:1,class:"vtl-td"},yf={class:"vtl-rows"},wf=["aria-selected","aria-disabled","tabindex","onClick"],kf=["innerHTML"],xf={key:1,class:"vtl-tab-emoji","aria-hidden":"true"},Sf={key:2,class:"vtl-tab-fallback","aria-hidden":"true"},Lf={class:"vtl-tab-text"},Tf={class:"vtl-tab-label"},Pf={key:0,class:"vtl-tab-badge"},Ef={key:0,class:"vtl-tab-sub"},Of=["innerHTML"],Mf=["aria-selected","aria-disabled","tabindex","onClick"],Af=["innerHTML"],Cf={key:1,class:"vtl-tab-emoji","aria-hidden":"true"},If={key:2,class:"vtl-tab-fallback","aria-hidden":"true"},Nf={class:"vtl-tab-text"},Ff={class:"vtl-tab-label"},Df={key:0,class:"vtl-tab-sub"},Rf={key:4,class:"vtl-rail-end"},zf={class:"vtl-main"},qf={class:"vtl-content"},Wf=["hidden"],jf={key:0,class:"vtl-host-hint"},Uf={key:1,class:"vtl-host-hint"},Hf=["onClick"],Bf={key:2,class:"vtl-host-hint err"};function Vf(e,t,a,o,n,s){const r=Wa("BackToTopButton");return C(),N("div",{class:Ge(["vtl",{"nav-open":e.navOpen,"rail-collapsed":e.railCollapsed,"no-header":!e.header,"has-phone-bar":e.phoneBarShown}])},[e.header?(C(),N("header",Ym,[O("button",{class:"vtl-menu",type:"button","aria-expanded":String(e.navOpen),onClick:t[0]||(t[0]=i=>e.navOpen=!e.navOpen),"aria-label":e.label("toggleList")},"☰",8,Jm),O("span",Zm,[ra(e.$slots,"brand")]),O("b",Qm,V(e.title),1),O("span",ef,[ra(e.$slots,"meta")])])):se("",!0),e.phoneBarShown?(C(),N("div",{key:1,class:Ge(["vtl-pbar",{"is-web":e.mode==="web"}])},[O("button",{class:"vtl-phone-menu",type:"button","aria-expanded":String(e.navOpen),onClick:t[1]||(t[1]=i=>e.navOpen=!e.navOpen),"aria-label":e.label("toggleList")},"☰",8,tf),O("span",af,[ra(e.$slots,"phone-end")])],2)):se("",!0),e.narrow?(C(),ya(r,{key:2,target:e.activeHostEl,labels:{backToTop:e.T("actions.backToTop","Back to top")}},null,8,["target","labels"])):se("",!0),O("div",of,[O("nav",{class:Ge(["vtl-nav",{"has-rail-end":e.railEndShown&&!!e.$slots["rail-end"]}]),"aria-label":e.navLabel,"aria-hidden":String(!e.railShown),role:e.$slots.rail?null:"tablist","aria-orientation":e.$slots.rail?null:"vertical"},[e.homeLink?(C(),N("a",{key:0,class:"vtl-home",href:e.homeLink,target:"_top","aria-label":e.homeText,title:e.railCollapsed&&!e.narrow?e.homeText:null},[O("span",{class:"vtl-home-ic","aria-hidden":"true",innerHTML:e.iconSvg("home")},null,8,rf),O("span",lf,V(e.homeText),1)],8,sf)):se("",!0),O("button",{type:"button",class:"vtl-collapse","aria-expanded":String(!e.railCollapsed),"aria-label":e.railCollapsed?e.label("expandList"):e.label("collapseList"),onClick:t[2]||(t[2]=(...i)=>e.toggleRail&&e.toggleRail(...i))},[O("span",{class:"vtl-collapse-ic","aria-hidden":"true",innerHTML:e.iconSvg("chevron")},null,8,pf),O("span",uf,V(e.label("collapse")),1)],8,cf),e.$slots.rail?ra(e.$slots,"rail",{collapsed:e.railCollapsed&&!e.narrow,expand:e.expandRail,pick:e.pick},void 0,void 0,1):e.groups&&e.groups.length?(C(!0),N(ne,{key:2},Xe(e.groups,i=>(C(),N("div",{class:"vtl-grp",key:i.key},[i.folder?(C(),N("button",{key:0,type:"button",class:"vtl-tier vtl-tier-btn","aria-expanded":String(!e.isFolderClosed(i)),onClick:l=>e.toggleFolder(i.key)},[i.badge?(C(),N("span",gf,V(i.badge),1)):se("",!0),O("span",mf,V(i.label),1),i.count!=null?(C(),N("span",ff,V(i.count),1)):se("",!0),O("span",{class:Ge(["vtl-chev",{open:!e.isFolderClosed(i)}]),"aria-hidden":"true",innerHTML:e.iconSvg("chevron")},null,10,hf)],8,df)):(C(),N("div",bf,[i.badge?(C(),N("span",$f,V(i.badge),1)):se("",!0),O("span",_f,V(i.label),1),i.count!=null?(C(),N("span",vf,V(i.count),1)):se("",!0)])),Ra(O("div",yf,[(C(!0),N(ne,null,Xe(i.rows,l=>(C(),N("button",{key:l.key,type:"button",role:"tab",class:Ge(["vtl-tab",{active:l.key===e.active,disabled:l.disabled}]),"aria-selected":String(l.key===e.active),"aria-disabled":l.disabled?"true":null,tabindex:l.key===e.active?0:-1,ref_for:!0,ref:p=>e.registerTab(l.key,p),onClick:p=>e.pick(l.key),onKeydown:t[3]||(t[3]=p=>e.onKey(p))},[l.icon?(C(),N("span",{key:0,class:"vtl-tab-ic","aria-hidden":"true",innerHTML:e.iconSvg(l.icon)},null,8,kf)):l.emoji?(C(),N("span",xf,V(l.emoji),1)):(C(),N("span",Sf,V(e.initial(l.label)),1)),O("span",Lf,[O("span",Tf,[Kt(V(l.label),1),l.badge?(C(),N("span",Pf,V(l.badge),1)):se("",!0)]),l.sub?(C(),N("span",Ef,[O("span",{innerHTML:l.sub},null,8,Of)])):se("",!0)])],42,wf))),128))],512),[[Za,!i.folder||!e.isFolderClosed(i)]])]))),128)):(C(!0),N(ne,{key:3},Xe(e.items,i=>(C(),N("button",{key:i.key,type:"button",role:"tab",class:Ge(["vtl-tab",{active:i.key===e.active,disabled:i.disabled}]),"aria-selected":String(i.key===e.active),"aria-disabled":i.disabled?"true":null,tabindex:i.key===e.active?0:-1,ref_for:!0,ref:l=>e.registerTab(i.key,l),onClick:l=>e.pick(i.key),onKeydown:t[4]||(t[4]=l=>e.onKey(l))},[i.icon?(C(),N("span",{key:0,class:"vtl-tab-ic","aria-hidden":"true",innerHTML:e.iconSvg(i.icon)},null,8,Af)):i.emoji?(C(),N("span",Cf,V(i.emoji),1)):(C(),N("span",If,V(e.initial(i.label)),1)),O("span",Nf,[O("span",Ff,V(i.label),1),i.sub?(C(),N("span",Df,V(i.sub),1)):se("",!0)])],42,Mf))),128)),e.railEndShown&&e.$slots["rail-end"]?(C(),N("div",Rf,[ra(e.$slots,"rail-end",{collapsed:e.railCollapsed})])):se("",!0)],10,nf),O("main",zf,[e.header?ra(e.$slots,"toolbar",{},void 0,void 0,0):se("",!0),O("div",qf,[(C(!0),N(ne,null,Xe(e.mountedElementRows,i=>Ra((C(),N("section",{key:i.key,class:"vtl-host",hidden:i.key!==e.active,ref_for:!0,ref:l=>{l?e.hostEls[i.key]=l:delete e.hostEls[i.key]}},[O("div",{class:"vtl-host-slot",ref_for:!0,ref:l=>e.setHostSlot(i.key,l)},null,512),e.hostState[i.key]==="loading"?(C(),N("div",jf,V(e.T("messages.loadingNamed","Loading {name}…").replace("{name}",i.label)),1)):e.hostState[i.key]==="lost"?(C(),N("div",Uf,[Kt(V(e.T("messages.mapContextLost","This map was unloaded to free graphics memory."))+" ",1),O("button",{type:"button",class:"vtl-host-reload",onClick:l=>e.reloadHostElement(i.key)},V(e.T("buttons.reloadMap","Reload Map")),9,Hf)])):e.hostState[i.key]==="error"?(C(),N("div",Bf,[Kt(V(e.T("messages.couldNotLoadNamed","Could not load {name}.").replace("{name}",i.label)),1),t[5]||(t[5]=O("br",null,null,-1)),O("small",null,V(e.hostError[i.key]),1)])):se("",!0)],8,Wf)),[[Za,i.key===e.active]])),128)),ra(e.$slots,"default")])])])],2)}const Gf=Uo(Xm,[["render",Vf],["styles",[Hm]]]),cs=[{code:"en",name:"English",nativeName:"English",flag:"🇺🇸"},{code:"es",name:"Spanish",nativeName:"Español",flag:"🇪🇸"},{code:"fr",name:"French",nativeName:"Français",flag:"🇫🇷"},{code:"pt",name:"Portuguese",nativeName:"Português",flag:"🇵🇹"},{code:"ar",name:"Arabic",nativeName:"العربية",flag:"🇸🇦",dir:"rtl"},{code:"ru",name:"Russian",nativeName:"Русский",flag:"🇷🇺"},{code:"de",name:"German",nativeName:"Deutsch",flag:"🇩🇪",hideFromSwitcher:!0},{code:"hi",name:"Hindi",nativeName:"हिन्दी",flag:"🇮🇳",hideFromSwitcher:!0},{code:"it",name:"Italian",nativeName:"Italiano",flag:"🇮🇹",hideFromSwitcher:!0},{code:"ro",name:"Romanian",nativeName:"Română",flag:"🇷🇴",hideFromSwitcher:!0},{code:"zh",name:"Chinese",nativeName:"中文",flag:"🇨🇳",hideFromSwitcher:!0}],kl=cs.filter(e=>!e.hideFromSwitcher);function ps(e){return cs.find(t=>t.code===e)}function xl(e){const t=String(e||"").split("-")[0].toLowerCase();return cs.some(a=>a.code===t)?t:"en"}const Sl="doxa-maps:locale";function Kf(){try{const e=localStorage.getItem(Sl);if(e)return xl(e)}catch{}return xl(navigator==null?void 0:navigator.language)}function Xf(e){try{localStorage.setItem(Sl,e)}catch{}}const Yf="doxa-language-request";function Jf(e){try{const t=e&&e.getRootNode&&e.getRootNode();if(t&&t.host)return t.host}catch{}return e||null}function Zf(e,t){const a=Jf(e);return!a||typeof a.dispatchEvent!="function"?!0:a.dispatchEvent(new CustomEvent(Yf,{detail:{language:t},bubbles:!0,composed:!0,cancelable:!0}))}function Qf(e,t=typeof window<"u"?window:null){try{if(!t||t.MAP_OWNS_PAGE!==!0||!t.history||!t.location)return!1;const a=new URL(t.location.href);return a.searchParams.set("lang",e),t.history.replaceState(t.history.state,"",a.toString()),!0}catch{return!1}}const eh=".slm[data-v-12760774]{position:relative;display:inline-flex;align-items:center;min-height:44px;max-width:100%;box-sizing:border-box;border-radius:8px;color:#3b463d}.slm-face[data-v-12760774]{display:inline-flex;align-items:center;gap:6px;padding-inline:10px;min-width:0;font-size:13px;font-weight:500;line-height:1.2}.slm-ic[data-v-12760774]{flex:0 0 auto;display:inline-flex;color:var(--vtl-muted, #6d766e)}.slm-ic[data-v-12760774] svg{width:18px;height:18px}.slm-name[data-v-12760774]{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.slm-code[data-v-12760774]{font-weight:600;font-size:12px;letter-spacing:.03em}.slm-caret[data-v-12760774]{flex:0 0 auto;color:var(--vtl-muted, #6d766e);font-size:11px}.slm.collapsed .slm-face[data-v-12760774]{flex-direction:column;gap:2px;padding-inline:4px}.slm-select[data-v-12760774]{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;opacity:0;margin:0;border:0;cursor:pointer;font-size:16px}.slm[data-v-12760774]:hover{background:var(--vtl-accented, #d1d4cd)}.slm[data-v-12760774]:focus-within{outline:2px solid var(--vtl-accent, #92b195);outline-offset:-2px}@media (prefers-reduced-motion: reduce){.slm[data-v-12760774]{transition:none}}",th={class:"slm-face","aria-hidden":"true"},ah=["innerHTML"],oh={key:0,class:"slm-code"},nh=["lang","dir"],sh={key:2,class:"slm-caret"},rh=["value","aria-label","title"],ih=["value","lang","dir"],lh=Uo({__name:"ShellLanguageMenu",props:{current:{type:String,default:"en"},collapsed:{type:Boolean,default:!1}},emits:["switch"],setup(e,{emit:t}){const a=e,o=t,n=Je(()=>{const p=kl.slice();if(a.current&&!p.some(c=>c.code===a.current)){const c=ps(a.current);c&&p.push(c)}return p}),s=Je(()=>ps(a.current)||{code:a.current,nativeName:a.current}),r=Je(()=>Wt("controls.language",a.current)||"Language"),i=$l("globe");function l(p){const c=p.target,m=c.value;!m||m===a.current||(Zf(c,m)?(o("switch",m),Qf(m)):c.value=a.current)}return(p,c)=>(C(),N("span",{class:Ge(["slm",{collapsed:e.collapsed}])},[O("span",th,[O("span",{class:"slm-ic",innerHTML:ye(i)},null,8,ah),e.collapsed?(C(),N("span",oh,V(String(e.current).toUpperCase()),1)):(C(),N("span",{key:1,class:"slm-name",lang:s.value.code,dir:s.value.dir||null},V(s.value.nativeName),9,nh)),e.collapsed?se("",!0):(C(),N("span",sh,"▾"))]),O("select",{class:"slm-select",value:e.current,"aria-label":r.value,title:r.value,onChange:l},[(C(!0),N(ne,null,Xe(n.value,m=>(C(),N("option",{key:m.code,value:m.code,lang:m.code,dir:m.dir||null},V(m.nativeName),9,ih))),128))],40,rh)],2))}},[["styles",[eh]],["__scopeId","data-v-12760774"]]),io={en:{backToTop:"Back to top","sec.dashboards":"Dashboards","sec.prayer":"Prayer Mobilization","sec.gospel":"Gospel Research","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Big Picture","sub.dashboard-plus-grid":"dashboard + people-groups grid, stacked","tab.research-map":"Research Map","sub.research-map":"prayer · engagement · regions","tab.research-map-plus-grid":"Simple Research","sub.research-map-plus-grid":"research map + people-groups grid","tab.prayer-map":"Prayer Progress","sub.prayer-map":"simple world prayer map","tab.cities-internet":"Cities with Internet","sub.cities-internet":"connectivity of key cities","tab.gospel-resources":"Gospel Resources","sub.gospel-resources":"resources by place","tab.regions-dashboard":"Regions","sub.regions-dashboard":"regions and their countries","tab.upg-100":"UPG 100 List Posters","sub.upg-100":"my unreached list + posters",navLabel:"Research maps",meta:"{n} maps",loading:"Loading {label}…",loadFailed:"Could not load {label}.",manifestHint:"This tab is composed from manifest.json — build the tree, or serve the built tree, to load it.",close:"Close",help:"About this map",desktopOnly:"desktop only",lab:"lab",experimentalSub:"experimental bundle",mobileNotBuilt:"not built for mobile",dismiss:"Dismiss",desktopOnly:"desktop only",lab:"lab",experimentalSub:"experimental bundle",toggleList:"Toggle list",openViews:"Open the list of views",expandList:"Expand list",collapseList:"Collapse list",collapse:"Collapse",home:"Back to homepage","tab.prayer-walk-planner":"Prayer Walk Planner","tab.upg-neighbor":"UPG Neighbor","tab.key-cities":"Key Cities","tab.all-people-groups":"All People Groups","tab.pplr-data-maps":"PPLR Data Maps","tab.wall-of-people-groups":"Wall of People Groups","tab.bless-india-posters":"Bless India Posters","tab.gospel-internet":"Gospel × Internet","tab.country-posters":"Country Posters","sub.country-posters":"a poster for every country","tab.poster-design":"Poster Design","sub.poster-design":"every poster style and size","tab.doxa-vision-card":"Doxa Vision Card"},es:{backToTop:"Volver arriba","sec.dashboards":"Paneles","sec.prayer":"Movilización de oración","sec.gospel":"Investigación del Evangelio","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Panorama general","sub.dashboard-plus-grid":"panel + cuadrícula de pueblos","tab.research-map":"Mapa de investigación","sub.research-map":"oración · compromiso · regiones","tab.research-map-plus-grid":"Investigación simple","sub.research-map-plus-grid":"mapa de investigación + cuadrícula de pueblos","tab.prayer-map":"Progreso de oración","sub.prayer-map":"mapa mundial de oración","tab.cities-internet":"Ciudades con Internet","sub.cities-internet":"conectividad de ciudades clave","tab.gospel-resources":"Recursos del Evangelio","sub.gospel-resources":"recursos por lugar","tab.regions-dashboard":"Regiones","sub.regions-dashboard":"regiones y sus países","tab.upg-100":"Carteles de la lista UPG 100","sub.upg-100":"mi lista de no alcanzados + carteles",navLabel:"Mapas de investigación",meta:"{n} mapas",loading:"Cargando {label}…",loadFailed:"No se pudo cargar {label}.",manifestHint:"Esta pestaña se compone a partir de manifest.json: compila el árbol o sirve el árbol compilado para cargarla.",close:"Cerrar",help:"Acerca de este mapa",desktopOnly:"solo escritorio",lab:"beta",experimentalSub:"mapa experimental",mobileNotBuilt:"no diseñado para móvil",dismiss:"Cerrar",desktopOnly:"solo escritorio",lab:"beta",experimentalSub:"mapa experimental",toggleList:"Mostrar u ocultar la lista",openViews:"Abrir la lista de vistas",expandList:"Expandir la lista",collapseList:"Contraer la lista",collapse:"Contraer",home:"Volver a la página de inicio","tab.prayer-walk-planner":"Caminatas de oración","tab.upg-neighbor":"Vecino UPG","tab.key-cities":"Ciudades clave","tab.all-people-groups":"Todos los pueblos","tab.pplr-data-maps":"Mapas de datos PPLR","tab.wall-of-people-groups":"Muro de los pueblos","tab.bless-india-posters":"Pósteres «Bendice a la India»","tab.gospel-internet":"Evangelio × Internet","tab.country-posters":"Carteles por país","sub.country-posters":"un cartel para cada país","tab.poster-design":"Diseño de carteles","sub.poster-design":"todos los estilos y tamaños de cartel","tab.doxa-vision-card":"Tarjeta de visión Doxa"},fr:{backToTop:"Retour en haut","sec.dashboards":"Tableaux de bord","sec.prayer":"Mobilisation à la prière","sec.gospel":"Recherche sur l'Évangile","sec.experimental":"Expérimental","tab.dashboard-plus-grid":"Vue d'ensemble","sub.dashboard-plus-grid":"tableau de bord + grille des peuples","tab.research-map":"Carte de recherche","sub.research-map":"prière · engagement · régions","tab.research-map-plus-grid":"Recherche simple","sub.research-map-plus-grid":"carte de recherche + grille des peuples","tab.prayer-map":"Progression de la prière","sub.prayer-map":"carte mondiale de la prière","tab.cities-internet":"Villes avec Internet","sub.cities-internet":"connectivité des villes clés","tab.gospel-resources":"Ressources de l'Évangile","sub.gospel-resources":"ressources par lieu","tab.regions-dashboard":"Régions","sub.regions-dashboard":"régions et leurs pays","tab.upg-100":"Affiches de la liste UPG 100","sub.upg-100":"ma liste des non atteints + affiches",navLabel:"Cartes de recherche",meta:"{n} cartes",loading:"Chargement de {label}…",loadFailed:"Impossible de charger {label}.",manifestHint:"Cet onglet est composé à partir de manifest.json : construisez l'arborescence ou servez l'arborescence construite pour le charger.",close:"Fermer",help:"À propos de cette carte",desktopOnly:"ordinateur uniquement",lab:"bêta",experimentalSub:"carte expérimentale",mobileNotBuilt:"pas conçu pour mobile",dismiss:"Fermer",desktopOnly:"ordinateur uniquement",lab:"bêta",experimentalSub:"carte expérimentale",toggleList:"Afficher ou masquer la liste",openViews:"Ouvrir la liste des vues",expandList:"Déplier la liste",collapseList:"Replier la liste",collapse:"Replier",home:"Retour à l’accueil","tab.prayer-walk-planner":"Marches de prière","tab.upg-neighbor":"Voisin UPG","tab.key-cities":"Villes clés","tab.all-people-groups":"Tous les peuples","tab.pplr-data-maps":"Cartes de données PPLR","tab.wall-of-people-groups":"Mur des peuples","tab.bless-india-posters":"Affiches « Bénis l’Inde »","tab.gospel-internet":"Évangile × Internet","tab.country-posters":"Affiches par pays","sub.country-posters":"une affiche pour chaque pays","tab.poster-design":"Conception d’affiches","sub.poster-design":"tous les styles et formats d’affiche","tab.doxa-vision-card":"Carte de vision Doxa"},de:{backToTop:"Nach oben","sec.dashboards":"Dashboards","sec.prayer":"Gebetsmobilisierung","sec.gospel":"Evangeliumsforschung","sec.experimental":"Experimentell","tab.dashboard-plus-grid":"Gesamtbild","sub.dashboard-plus-grid":"Dashboard + Raster der Volksgruppen","tab.research-map":"Forschungskarte","sub.research-map":"Gebet · Engagement · Regionen","tab.research-map-plus-grid":"Einfache Recherche","sub.research-map-plus-grid":"Forschungskarte + Raster der Volksgruppen","tab.prayer-map":"Gebetsfortschritt","sub.prayer-map":"einfache Weltkarte des Gebets","tab.cities-internet":"Städte mit Internet","sub.cities-internet":"Konnektivität wichtiger Städte","tab.gospel-resources":"Evangeliumsressourcen","sub.gospel-resources":"Ressourcen nach Ort","tab.regions-dashboard":"Regionen","sub.regions-dashboard":"Regionen und ihre Länder","tab.upg-100":"Poster der UPG 100 Liste","sub.upg-100":"meine Liste der Unerreichten + Poster",navLabel:"Forschungskarten",meta:"{n} Karten",loading:"{label} wird geladen…",loadFailed:"{label} konnte nicht geladen werden.",manifestHint:"Dieser Tab wird aus manifest.json zusammengesetzt – bauen Sie den Baum oder stellen Sie den gebauten Baum bereit, um ihn zu laden.",close:"Schließen",help:"Über diese Karte",desktopOnly:"nur Desktop",lab:"Beta",experimentalSub:"experimentelle Karte",mobileNotBuilt:"nicht für Mobilgeräte",dismiss:"Schließen",desktopOnly:"nur Desktop",lab:"Beta",experimentalSub:"experimentelle Karte",toggleList:"Liste ein-/ausblenden",openViews:"Ansichtsliste öffnen",expandList:"Liste ausklappen",collapseList:"Liste einklappen",collapse:"Einklappen",home:"Zurück zur Startseite","tab.prayer-walk-planner":"Gebetsspaziergänge","tab.upg-neighbor":"UPG-Nachbar","tab.key-cities":"Schlüsselstädte","tab.all-people-groups":"Alle Volksgruppen","tab.pplr-data-maps":"PPLR-Datenkarten","tab.wall-of-people-groups":"Wand der Volksgruppen","tab.bless-india-posters":"„Segne Indien“-Poster","tab.gospel-internet":"Evangelium × Internet","tab.country-posters":"Länderposter","sub.country-posters":"ein Poster für jedes Land","tab.poster-design":"Postergestaltung","sub.poster-design":"alle Posterstile und -größen","tab.doxa-vision-card":"Doxa-Visionskarte"},pt:{backToTop:"Voltar ao topo","sec.dashboards":"Painéis","sec.prayer":"Mobilização de oração","sec.gospel":"Pesquisa do Evangelho","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Visão geral","sub.dashboard-plus-grid":"painel + grade de povos","tab.research-map":"Mapa de pesquisa","sub.research-map":"oração · engajamento · regiões","tab.research-map-plus-grid":"Pesquisa simples","sub.research-map-plus-grid":"mapa de pesquisa + grade de povos","tab.prayer-map":"Progresso da oração","sub.prayer-map":"mapa mundial de oração","tab.cities-internet":"Cidades com Internet","sub.cities-internet":"conectividade das principais cidades","tab.gospel-resources":"Recursos do Evangelho","sub.gospel-resources":"recursos por lugar","tab.regions-dashboard":"Regiões","sub.regions-dashboard":"regiões e seus países","tab.upg-100":"Cartazes da lista UPG 100","sub.upg-100":"minha lista de não alcançados + cartazes",navLabel:"Mapas de pesquisa",meta:"{n} mapas",loading:"Carregando {label}…",loadFailed:"Não foi possível carregar {label}.",manifestHint:"Esta aba é composta a partir do manifest.json — compile a árvore ou sirva a árvore compilada para carregá-la.",close:"Fechar",help:"Sobre este mapa",desktopOnly:"só no computador",lab:"beta",experimentalSub:"mapa experimental",mobileNotBuilt:"não feito para celular",dismiss:"Fechar",desktopOnly:"só no computador",lab:"beta",experimentalSub:"mapa experimental",toggleList:"Mostrar ou ocultar a lista",openViews:"Abrir a lista de visualizações",expandList:"Expandir a lista",collapseList:"Recolher a lista",collapse:"Recolher",home:"Voltar à página inicial","tab.prayer-walk-planner":"Caminhadas de oração","tab.upg-neighbor":"Vizinho UPG","tab.key-cities":"Cidades-chave","tab.all-people-groups":"Todos os povos","tab.pplr-data-maps":"Mapas de dados PPLR","tab.wall-of-people-groups":"Mural dos povos","tab.bless-india-posters":"Pôsteres «Abençoe a Índia»","tab.gospel-internet":"Evangelho × Internet","tab.country-posters":"Cartazes por país","sub.country-posters":"um cartaz para cada país","tab.poster-design":"Design de cartazes","sub.poster-design":"todos os estilos e tamanhos de cartaz","tab.doxa-vision-card":"Cartão de visão Doxa"},it:{backToTop:"Torna su","sec.dashboards":"Dashboard","sec.prayer":"Mobilitazione alla preghiera","sec.gospel":"Ricerca sul Vangelo","sec.experimental":"Sperimentale","tab.dashboard-plus-grid":"Quadro d'insieme","sub.dashboard-plus-grid":"dashboard + griglia dei popoli","tab.research-map":"Mappa di ricerca","sub.research-map":"preghiera · impegno · regioni","tab.research-map-plus-grid":"Ricerca semplice","sub.research-map-plus-grid":"mappa di ricerca + griglia dei popoli","tab.prayer-map":"Avanzamento preghiera","sub.prayer-map":"mappa mondiale della preghiera","tab.cities-internet":"Città con Internet","sub.cities-internet":"connettività delle città chiave","tab.gospel-resources":"Risorse del Vangelo","sub.gospel-resources":"risorse per luogo","tab.regions-dashboard":"Regioni","sub.regions-dashboard":"regioni e i loro paesi","tab.upg-100":"Poster della lista UPG 100","sub.upg-100":"la mia lista dei non raggiunti + poster",navLabel:"Mappe di ricerca",meta:"{n} mappe",loading:"Caricamento di {label}…",loadFailed:"Impossibile caricare {label}.",manifestHint:"Questa scheda è composta da manifest.json: compila l'albero o pubblica l'albero compilato per caricarla.",close:"Chiudi",help:"Informazioni su questa mappa",desktopOnly:"solo desktop",lab:"beta",experimentalSub:"mappa sperimentale",mobileNotBuilt:"non pensato per mobile",dismiss:"Chiudi",desktopOnly:"solo desktop",lab:"beta",experimentalSub:"mappa sperimentale",toggleList:"Mostra o nascondi l’elenco",openViews:"Apri l’elenco delle viste",expandList:"Espandi l’elenco",collapseList:"Comprimi l’elenco",collapse:"Comprimi",home:"Torna alla home page","tab.prayer-walk-planner":"Passeggiate di preghiera","tab.upg-neighbor":"Vicino UPG","tab.key-cities":"Città chiave","tab.all-people-groups":"Tutti i popoli","tab.pplr-data-maps":"Mappe dati PPLR","tab.wall-of-people-groups":"Muro dei popoli","tab.bless-india-posters":"Poster «Benedici l’India»","tab.gospel-internet":"Vangelo × Internet","tab.country-posters":"Poster per paese","sub.country-posters":"un poster per ogni paese","tab.poster-design":"Design dei poster","sub.poster-design":"tutti gli stili e i formati dei poster","tab.doxa-vision-card":"Scheda visione Doxa"},ro:{backToTop:"Sus","sec.dashboards":"Panouri","sec.prayer":"Mobilizare la rugăciune","sec.gospel":"Cercetarea Evangheliei","sec.experimental":"Experimental","tab.dashboard-plus-grid":"Imaginea de ansamblu","sub.dashboard-plus-grid":"panou + grila popoarelor","tab.research-map":"Hartă de cercetare","sub.research-map":"rugăciune · implicare · regiuni","tab.research-map-plus-grid":"Cercetare simplă","sub.research-map-plus-grid":"hartă de cercetare + grila popoarelor","tab.prayer-map":"Progresul rugăciunii","sub.prayer-map":"hartă mondială a rugăciunii","tab.cities-internet":"Orașe cu internet","sub.cities-internet":"conectivitatea orașelor-cheie","tab.gospel-resources":"Resurse ale Evangheliei","sub.gospel-resources":"resurse după loc","tab.regions-dashboard":"Regiuni","sub.regions-dashboard":"regiunile și țările lor","tab.upg-100":"Afișe din lista UPG 100","sub.upg-100":"lista mea de neatinși + afișe",navLabel:"Hărți de cercetare",meta:"{n} hărți",loading:"Se încarcă {label}…",loadFailed:"{label} nu a putut fi încărcat.",manifestHint:"Această filă este compusă din manifest.json — construiți arborele sau serviți arborele construit pentru a o încărca.",close:"Închide",help:"Despre această hartă",desktopOnly:"doar pe desktop",lab:"beta",experimentalSub:"hartă experimentală",mobileNotBuilt:"nu e făcut pentru mobil",dismiss:"Închide",desktopOnly:"doar pe desktop",lab:"beta",experimentalSub:"hartă experimentală",toggleList:"Afișează sau ascunde lista",openViews:"Deschide lista de vizualizări",expandList:"Extinde lista",collapseList:"Restrânge lista",collapse:"Restrânge",home:"Înapoi la pagina principală","tab.prayer-walk-planner":"Plimbări de rugăciune","tab.upg-neighbor":"Vecin UPG","tab.key-cities":"Orașe-cheie","tab.all-people-groups":"Toate popoarele","tab.pplr-data-maps":"Hărți de date PPLR","tab.wall-of-people-groups":"Zidul popoarelor","tab.bless-india-posters":"Postere „Binecuvântează India”","tab.gospel-internet":"Evanghelie × internet","tab.country-posters":"Afișe pe țări","sub.country-posters":"un afiș pentru fiecare țară","tab.poster-design":"Design de afișe","sub.poster-design":"toate stilurile și dimensiunile de afiș","tab.doxa-vision-card":"Card de viziune Doxa"},ru:{backToTop:"Наверх","sec.dashboards":"Панели","sec.prayer":"Молитвенная мобилизация","sec.gospel":"Исследование Евангелия","sec.experimental":"Экспериментальное","tab.dashboard-plus-grid":"Общая картина","sub.dashboard-plus-grid":"панель + таблица народов","tab.research-map":"Исследовательская карта","sub.research-map":"молитва · вовлечённость · регионы","tab.research-map-plus-grid":"Простое исследование","sub.research-map-plus-grid":"исследовательская карта + таблица народов","tab.prayer-map":"Прогресс молитвы","sub.prayer-map":"простая карта молитвы мира","tab.cities-internet":"Города с интернетом","sub.cities-internet":"подключённость ключевых городов","tab.gospel-resources":"Евангельские ресурсы","sub.gospel-resources":"ресурсы по местам","tab.regions-dashboard":"Регионы","sub.regions-dashboard":"регионы и их страны","tab.upg-100":"Плакаты списка UPG 100","sub.upg-100":"мой список недостигнутых + плакаты",navLabel:"Исследовательские карты",meta:"Карт: {n}",loading:"Загрузка: {label}…",loadFailed:"Не удалось загрузить: {label}.",manifestHint:"Эта вкладка собирается из manifest.json — соберите дерево или разместите собранное дерево, чтобы загрузить её.",close:"Закрыть",help:"Об этой карте",desktopOnly:"только на компьютере",lab:"бета",experimentalSub:"экспериментальная карта",mobileNotBuilt:"не для телефона",dismiss:"Закрыть",desktopOnly:"только на компьютере",lab:"бета",experimentalSub:"экспериментальная карта",toggleList:"Показать или скрыть список",openViews:"Открыть список представлений",expandList:"Развернуть список",collapseList:"Свернуть список",collapse:"Свернуть",home:"На главную страницу","tab.prayer-walk-planner":"Молитвенные прогулки","tab.upg-neighbor":"Сосед UPG","tab.key-cities":"Ключевые города","tab.all-people-groups":"Все народы","tab.pplr-data-maps":"Карты данных PPLR","tab.wall-of-people-groups":"Стена народов","tab.bless-india-posters":"Плакаты «Благослови Индию»","tab.gospel-internet":"Евангелие × интернет","tab.country-posters":"Плакаты по странам","sub.country-posters":"плакат для каждой страны","tab.poster-design":"Дизайн плакатов","sub.poster-design":"все стили и размеры плакатов","tab.doxa-vision-card":"Карточка видения Doxa"},ar:{backToTop:"إلى الأعلى","sec.dashboards":"لوحات المعلومات","sec.prayer":"التعبئة للصلاة","sec.gospel":"أبحاث الإنجيل","sec.experimental":"تجريبي","tab.dashboard-plus-grid":"الصورة الكاملة","sub.dashboard-plus-grid":"لوحة معلومات + شبكة الشعوب","tab.research-map":"خريطة البحث","sub.research-map":"الصلاة · الانخراط · المناطق","tab.research-map-plus-grid":"بحث مبسّط","sub.research-map-plus-grid":"خريطة البحث + شبكة الشعوب","tab.prayer-map":"تقدم الصلاة","sub.prayer-map":"خريطة عالمية مبسّطة للصلاة","tab.cities-internet":"مدن متصلة بالإنترنت","sub.cities-internet":"اتصال المدن الرئيسية","tab.gospel-resources":"موارد الإنجيل","sub.gospel-resources":"الموارد حسب المكان","tab.regions-dashboard":"المناطق","sub.regions-dashboard":"المناطق ودولها","tab.upg-100":"ملصقات قائمة UPG 100","sub.upg-100":"قائمتي لغير المُبشَّرين + ملصقات",navLabel:"خرائط البحث",meta:"عدد الخرائط: {n}",loading:"جارٍ تحميل {label}…",loadFailed:"تعذّر تحميل {label}.",manifestHint:"تُبنى علامة التبويب هذه من manifest.json — ابنِ الشجرة أو قدّم الشجرة المبنية لتحميلها.",close:"إغلاق",help:"عن هذه الخريطة",desktopOnly:"على الحاسوب فقط",lab:"تجريبي",experimentalSub:"خريطة تجريبية",mobileNotBuilt:"غير مُعدّ للهاتف",dismiss:"إغلاق",desktopOnly:"على الحاسوب فقط",lab:"تجريبي",experimentalSub:"خريطة تجريبية",toggleList:"إظهار القائمة أو إخفاؤها",openViews:"فتح قائمة العروض",expandList:"توسيع القائمة",collapseList:"طي القائمة",collapse:"طي",home:"العودة إلى الصفحة الرئيسية","tab.prayer-walk-planner":"جولات الصلاة","tab.upg-neighbor":"جار UPG","tab.key-cities":"المدن الرئيسية","tab.all-people-groups":"جميع الشعوب","tab.pplr-data-maps":"خرائط بيانات PPLR","tab.wall-of-people-groups":"جدار الشعوب","tab.bless-india-posters":"ملصقات باركوا الهند","tab.gospel-internet":"الإنجيل × الإنترنت","tab.country-posters":"ملصقات الدول","sub.country-posters":"ملصق لكل دولة","tab.poster-design":"تصميم الملصقات","sub.poster-design":"كل أنماط الملصقات وأحجامها","tab.doxa-vision-card":"بطاقة رؤية Doxa"},hi:{backToTop:"ऊपर जाएँ","sec.dashboards":"डैशबोर्ड","sec.prayer":"प्रार्थना संचालन","sec.gospel":"सुसमाचार शोध","sec.experimental":"प्रायोगिक","tab.dashboard-plus-grid":"संपूर्ण चित्र","sub.dashboard-plus-grid":"डैशबोर्ड + जनसमूहों की ग्रिड","tab.research-map":"शोध मानचित्र","sub.research-map":"प्रार्थना · सहभागिता · क्षेत्र","tab.research-map-plus-grid":"सरल शोध","sub.research-map-plus-grid":"शोध मानचित्र + जनसमूहों की ग्रिड","tab.prayer-map":"प्रार्थना प्रगति","sub.prayer-map":"सरल विश्व प्रार्थना मानचित्र","tab.cities-internet":"इंटरनेट वाले शहर","sub.cities-internet":"प्रमुख शहरों की कनेक्टिविटी","tab.gospel-resources":"सुसमाचार संसाधन","sub.gospel-resources":"स्थान के अनुसार संसाधन","tab.regions-dashboard":"क्षेत्र","sub.regions-dashboard":"क्षेत्र और उनके देश","tab.upg-100":"UPG 100 सूची पोस्टर","sub.upg-100":"मेरी अप्राप्त सूची + पोस्टर",navLabel:"शोध मानचित्र",meta:"{n} मानचित्र",loading:"{label} लोड हो रहा है…",loadFailed:"{label} लोड नहीं हो सका।",manifestHint:"यह टैब manifest.json से बनता है — इसे लोड करने के लिए ट्री बनाएँ या बना हुआ ट्री सर्व करें।",close:"बंद करें",help:"इस नक्शे के बारे में",desktopOnly:"केवल डेस्कटॉप",lab:"बीटा",experimentalSub:"प्रायोगिक मानचित्र",mobileNotBuilt:"मोबाइल के लिए नहीं बना",dismiss:"बंद करें",desktopOnly:"केवल डेस्कटॉप",lab:"बीटा",experimentalSub:"प्रायोगिक मानचित्र",toggleList:"सूची दिखाएँ या छिपाएँ",openViews:"दृश्यों की सूची खोलें",expandList:"सूची फैलाएँ",collapseList:"सूची संक्षिप्त करें",collapse:"संक्षिप्त करें",home:"होमपेज पर वापस जाएँ","tab.prayer-walk-planner":"प्रार्थना पदयात्रा","tab.upg-neighbor":"UPG पड़ोसी","tab.key-cities":"प्रमुख शहर","tab.all-people-groups":"सभी जनसमूह","tab.pplr-data-maps":"PPLR डेटा मानचित्र","tab.wall-of-people-groups":"जनसमूहों की दीवार","tab.bless-india-posters":"भारत को आशीष दें — पोस्टर","tab.gospel-internet":"सुसमाचार × इंटरनेट","tab.country-posters":"देश पोस्टर","sub.country-posters":"हर देश के लिए एक पोस्टर","tab.poster-design":"पोस्टर डिज़ाइन","sub.poster-design":"हर पोस्टर शैली और आकार","tab.doxa-vision-card":"Doxa दर्शन कार्ड"},zh:{backToTop:"回到顶部","sec.dashboards":"仪表板","sec.prayer":"祷告动员","sec.gospel":"福音研究","sec.experimental":"实验性","tab.dashboard-plus-grid":"全景","sub.dashboard-plus-grid":"仪表板 + 民族网格","tab.research-map":"研究地图","sub.research-map":"祷告 · 参与 · 区域","tab.research-map-plus-grid":"简易研究","sub.research-map-plus-grid":"研究地图 + 民族网格","tab.prayer-map":"祷告进度","sub.prayer-map":"简易世界祷告地图","tab.cities-internet":"有互联网的城市","sub.cities-internet":"主要城市的网络连接","tab.gospel-resources":"福音资源","sub.gospel-resources":"按地点划分的资源","tab.regions-dashboard":"区域","sub.regions-dashboard":"区域及其国家","tab.upg-100":"UPG 100 名单海报","sub.upg-100":"我的未得之民名单 + 海报",navLabel:"研究地图",meta:"{n} 张地图",loading:"正在加载{label}…",loadFailed:"无法加载{label}。",manifestHint:"此标签页由 manifest.json 组成——请构建目录树或提供已构建的目录树以加载它。",close:"关闭",help:"关于此地图",desktopOnly:"仅限桌面",lab:"测试版",experimentalSub:"实验性地图",mobileNotBuilt:"未针对手机设计",dismiss:"关闭",desktopOnly:"仅限桌面",lab:"测试版",experimentalSub:"实验性地图",toggleList:"显示或隐藏列表",openViews:"打开视图列表",expandList:"展开列表",collapseList:"折叠列表",collapse:"折叠",home:"返回首页","tab.prayer-walk-planner":"祷告行走","tab.upg-neighbor":"UPG 邻居","tab.key-cities":"重点城市","tab.all-people-groups":"所有民族","tab.pplr-data-maps":"PPLR 数据地图","tab.wall-of-people-groups":"民族之墙","tab.bless-india-posters":"祝福印度海报","tab.gospel-internet":"福音 × 互联网","tab.country-posters":"国家海报","sub.country-posters":"每个国家一张海报","tab.poster-design":"海报设计","sub.poster-design":"所有海报样式与尺寸","tab.doxa-vision-card":"Doxa 异象卡"}},us=Object.keys(io);function ch(e){return Object.prototype.hasOwnProperty.call(io.en,e)}function Qo(e,t,a){let n=(io[e]||io.en)[t]??io.en[t]??t;if(a)for(const s in a)n=n.split("{"+s+"}").join(String(a[s]));return n}const Ll={en:{"prayer-walk-planner":{what:"Plan a prayer walk: draw the areas your teams will cover and print each one as a one-page map.",how:"Draw a zone, name it, select the zones you need and press Print. Each zone prints on its own page with coordinates, one map per team."},"gospel-internet":{what:"See which of the ~2,000 people groups already have gospel resources in their language, and where the internet can carry those resources to them.",how:"Explore the language tree, the bounded and centered sets and the map. Pick a list on the side to narrow every view."},"country-posters":{what:"Printable prayer posters, one per country, showing its people groups.",how:"Pick a country from the poster list or on the map, then print it straight from your browser."},"all-people-groups":{what:"Every people group on earth on one map, colored by how reached it is, with the DOXA prayer set highlighted.",how:"Click a category in the legend to show only those groups. Click a dot for its details and prayer link."},"doxa-vision-card":{what:"The remaining task in one card: all people groups, then the unreached, then the frontier, then the DOXA prayer set.",how:"Read it from top to bottom. Every number is computed from the people-group data, not typed in."},"pplr-data-maps":{what:"People-group research data on maps: language families, people clusters, religions and data-source coverage.",how:"Choose a view from the tabs, then use the legend to highlight a family, cluster or religion on the map."},"wall-of-people-groups":{what:"Every DOXA people group on one wall, to bless them. In Bless mode the wall is the printable poster (5 × 8 ft by default), each group with a blessing verse; in Social mode it is a shared prayer wall.",how:"Bless: look around the poster exactly as it will print, pick a size and press Print HD poster. Social: pray for a group, read or leave comments, and share its prayer page."}},es:{"prayer-walk-planner":{what:"Planifique una caminata de oración: dibuje las zonas que recorrerán sus equipos e imprima cada una como un mapa de una página.",how:"Dibuje una zona, póngale nombre, seleccione las zonas que necesita y pulse Imprimir. Cada zona se imprime en su propia página con coordenadas: un mapa por equipo."},"gospel-internet":{what:"Vea cuáles de los ~2000 grupos de pueblos ya tienen recursos del evangelio en su idioma, y dónde internet puede llevárselos.",how:"Explore el árbol de idiomas, los conjuntos delimitado y centrado y el mapa. Elija una lista al lado para acotar cada vista."},"country-posters":{what:"Carteles de oración para imprimir, uno por país, con sus grupos de pueblos.",how:"Elija un país en la lista de carteles o en el mapa, e imprímalo directamente desde el navegador."},"all-people-groups":{what:"Todos los grupos de pueblos del mundo en un mapa, coloreados según lo alcanzados que están, con el conjunto de oración de DOXA resaltado.",how:"Haga clic en una categoría de la leyenda para ver solo esos grupos. Haga clic en un punto para ver sus datos y el enlace de oración."},"doxa-vision-card":{what:"La tarea pendiente en una tarjeta: todos los grupos de pueblos, luego los no alcanzados, luego la frontera y luego el conjunto de oración de DOXA.",how:"Léala de arriba abajo. Cada cifra se calcula a partir de los datos, no se escribe a mano."},"pplr-data-maps":{what:"Datos de investigación sobre grupos de pueblos en mapas: familias lingüísticas, agrupaciones de pueblos, religiones y cobertura de fuentes.",how:"Elija una vista en las pestañas y use la leyenda para resaltar una familia, agrupación o religión en el mapa."},"wall-of-people-groups":{what:"Todos los grupos de pueblos de DOXA en un muro, para bendecirlos. En el modo Bendecir el muro es el póster para imprimir (5 × 8 pies por defecto), cada grupo con un versículo de bendición; en el modo Social es un muro de oración compartido.",how:"Bendecir: recorra el póster tal como se imprimirá, elija un tamaño y pulse Imprimir póster HD. Social: ore por un grupo, lea o deje comentarios y comparta su página de oración."}},fr:{"prayer-walk-planner":{what:"Préparez une marche de prière : dessinez les zones que vos équipes parcourront et imprimez chacune sur une carte d’une page.",how:"Dessinez une zone, nommez-la, sélectionnez les zones voulues et cliquez sur Imprimer. Chaque zone s’imprime sur sa propre page avec ses coordonnées : une carte par équipe."},"gospel-internet":{what:"Voyez lesquels des ~2 000 groupes de peuples ont déjà des ressources de l’Évangile dans leur langue, et où internet peut les leur apporter.",how:"Explorez l’arbre des langues, les ensembles délimité et centré et la carte. Choisissez une liste sur le côté pour affiner chaque vue."},"country-posters":{what:"Des affiches de prière à imprimer, une par pays, avec ses groupes de peuples.",how:"Choisissez un pays dans la liste des affiches ou sur la carte, puis imprimez-le directement depuis le navigateur."},"all-people-groups":{what:"Tous les groupes de peuples du monde sur une carte, colorés selon leur degré d’atteinte, avec l’ensemble de prière DOXA mis en valeur.",how:"Cliquez sur une catégorie de la légende pour n’afficher que ces groupes. Cliquez sur un point pour ses détails et son lien de prière."},"doxa-vision-card":{what:"La tâche restante en une carte : tous les groupes de peuples, puis les non atteints, puis la frontière, puis l’ensemble de prière DOXA.",how:"Lisez-la de haut en bas. Chaque chiffre est calculé à partir des données, pas saisi à la main."},"pplr-data-maps":{what:"Des données de recherche sur les groupes de peuples, en cartes : familles de langues, grappes de peuples, religions et couverture des sources.",how:"Choisissez une vue dans les onglets, puis utilisez la légende pour mettre en valeur une famille, une grappe ou une religion."},"wall-of-people-groups":{what:"Tous les groupes de peuples DOXA sur un seul mur, pour les bénir. En mode Bénir, le mur est l’affiche à imprimer (5 × 8 pieds par défaut), chaque groupe avec un verset de bénédiction ; en mode Social, c’est un mur de prière partagé.",how:"Bénir : parcourez l’affiche telle qu’elle s’imprimera, choisissez une taille et appuyez sur Imprimer l’affiche HD. Social : priez pour un groupe, lisez ou laissez des commentaires et partagez sa page de prière."}},de:{"prayer-walk-planner":{what:"Einen Gebetsspaziergang planen: die Gebiete Ihrer Teams zeichnen und jedes als einseitige Karte drucken.",how:"Zeichnen Sie ein Gebiet, benennen Sie es, wählen Sie die nötigen Gebiete und drücken Sie auf Drucken. Jedes Gebiet wird mit Koordinaten auf einer eigenen Seite gedruckt: eine Karte pro Team."},"gospel-internet":{what:"Sehen, welche der ~2.000 Volksgruppen schon Evangeliumsressourcen in ihrer Sprache haben und wo das Internet sie zu ihnen bringen kann.",how:"Erkunden Sie den Sprachbaum, die begrenzte und die zentrierte Menge und die Karte. Wählen Sie seitlich eine Liste, um jede Ansicht einzugrenzen."},"country-posters":{what:"Druckbare Gebetsplakate, eines pro Land, mit seinen Volksgruppen.",how:"Wählen Sie ein Land in der Plakatliste oder auf der Karte und drucken Sie es direkt aus dem Browser."},"all-people-groups":{what:"Alle Volksgruppen der Welt auf einer Karte, nach Erreichtheit eingefärbt, mit dem DOXA-Gebetsset hervorgehoben.",how:"Klicken Sie auf eine Kategorie der Legende, um nur diese Gruppen zu zeigen. Klicken Sie auf einen Punkt für Details und den Gebetslink."},"doxa-vision-card":{what:"Die verbleibende Aufgabe auf einer Karte: alle Volksgruppen, dann die unerreichten, dann die Frontier-Gruppen, dann das DOXA-Gebetsset.",how:"Lesen Sie sie von oben nach unten. Jede Zahl wird aus den Daten berechnet, nicht eingetippt."},"pplr-data-maps":{what:"Forschungsdaten zu Volksgruppen auf Karten: Sprachfamilien, Volkscluster, Religionen und Quellenabdeckung.",how:"Wählen Sie oben eine Ansicht und heben Sie mit der Legende eine Familie, einen Cluster oder eine Religion hervor."},"wall-of-people-groups":{what:"Jede DOXA-Volksgruppe auf einer Wand, um sie zu segnen. Im Modus Segnen ist die Wand das druckbare Poster (standardmäßig 5 × 8 Fuß), jede Gruppe mit einem Segensvers; im Modus Sozial ist sie eine gemeinsame Gebetswand.",how:"Segnen: Sehen Sie sich das Poster genau so an, wie es gedruckt wird, wählen Sie eine Größe und drücken Sie HD-Poster drucken. Sozial: Beten Sie für eine Gruppe, lesen oder schreiben Sie Kommentare und teilen Sie ihre Gebetsseite."}},pt:{"prayer-walk-planner":{what:"Planeje uma caminhada de oração: desenhe as áreas que suas equipes vão cobrir e imprima cada uma como um mapa de uma página.",how:"Desenhe uma zona, dê um nome, selecione as zonas necessárias e clique em Imprimir. Cada zona sai em sua própria página com coordenadas: um mapa por equipe."},"gospel-internet":{what:"Veja quais dos ~2.000 grupos de povos já têm recursos do evangelho no seu idioma, e onde a internet pode levá-los até eles.",how:"Explore a árvore de idiomas, os conjuntos delimitado e centrado e o mapa. Escolha uma lista ao lado para filtrar cada vista."},"country-posters":{what:"Cartazes de oração para imprimir, um por país, com seus grupos de povos.",how:"Escolha um país na lista de cartazes ou no mapa e imprima direto do navegador."},"all-people-groups":{what:"Todos os grupos de povos do mundo num mapa, coloridos pelo grau de alcance, com o conjunto de oração da DOXA em destaque.",how:"Clique numa categoria da legenda para ver só esses grupos. Clique num ponto para ver detalhes e o link de oração."},"doxa-vision-card":{what:"A tarefa restante num cartão: todos os grupos de povos, depois os não alcançados, depois a fronteira, depois o conjunto de oração da DOXA.",how:"Leia de cima para baixo. Cada número é calculado a partir dos dados, não digitado."},"pplr-data-maps":{what:"Dados de pesquisa sobre grupos de povos em mapas: famílias linguísticas, agrupamentos de povos, religiões e cobertura das fontes.",how:"Escolha uma vista nas abas e use a legenda para destacar uma família, agrupamento ou religião no mapa."},"wall-of-people-groups":{what:"Todos os grupos de povos da DOXA num só mural, para abençoá-los. No modo Abençoar, o mural é o cartaz para imprimir (5 × 8 pés por padrão), cada grupo com um versículo de bênção; no modo Social, é um mural de oração partilhado.",how:"Abençoar: percorra o cartaz tal como será impresso, escolha um tamanho e prima Imprimir cartaz HD. Social: ore por um grupo, leia ou deixe comentários e partilhe a sua página de oração."}},it:{"prayer-walk-planner":{what:"Pianifica una camminata di preghiera: disegna le zone che le tue squadre percorreranno e stampa ognuna come mappa di una pagina.",how:"Disegna una zona, dalle un nome, seleziona le zone che servono e premi Stampa. Ogni zona si stampa su una pagina con le coordinate: una mappa per squadra."},"gospel-internet":{what:"Scopri quali dei ~2.000 gruppi etnici hanno già risorse del vangelo nella loro lingua, e dove internet può portargliele.",how:"Esplora l’albero delle lingue, gli insiemi delimitato e centrato e la mappa. Scegli un elenco a lato per restringere ogni vista."},"country-posters":{what:"Poster di preghiera da stampare, uno per paese, con i suoi gruppi etnici.",how:"Scegli un paese dall’elenco dei poster o sulla mappa e stampalo direttamente dal browser."},"all-people-groups":{what:"Tutti i gruppi etnici del mondo su una mappa, colorati secondo quanto sono raggiunti, con l’insieme di preghiera DOXA evidenziato.",how:"Clicca una categoria della legenda per mostrare solo quei gruppi. Clicca un punto per dettagli e link di preghiera."},"doxa-vision-card":{what:"Il compito rimanente in una scheda: tutti i gruppi etnici, poi i non raggiunti, poi la frontiera, poi l’insieme di preghiera DOXA.",how:"Leggila dall’alto in basso. Ogni numero è calcolato dai dati, non digitato."},"pplr-data-maps":{what:"Dati di ricerca sui gruppi etnici su mappa: famiglie linguistiche, cluster di popoli, religioni e copertura delle fonti.",how:"Scegli una vista dalle schede, poi usa la legenda per evidenziare una famiglia, un cluster o una religione."},"wall-of-people-groups":{what:"Tutti i gruppi etnici DOXA su un solo muro, per benedirli. In modalità Benedire il muro è il poster da stampare (5 × 8 piedi di default), ogni gruppo con un versetto di benedizione; in modalità Social è un muro di preghiera condiviso.",how:"Benedire: esplora il poster esattamente come verrà stampato, scegli una dimensione e premi Stampa poster HD. Social: prega per un gruppo, leggi o lascia commenti e condividi la sua pagina di preghiera."}},ro:{"prayer-walk-planner":{what:"Planificați o plimbare de rugăciune: desenați zonele pe care le vor parcurge echipele și tipăriți fiecare ca hartă de o pagină.",how:"Desenați o zonă, dați-i un nume, selectați zonele necesare și apăsați Tipărire. Fiecare zonă se tipărește pe o pagină, cu coordonate: câte o hartă pentru fiecare echipă."},"gospel-internet":{what:"Vedeți care dintre cele ~2.000 de grupuri de popoare au deja resurse ale Evangheliei în limba lor și unde le poate duce internetul.",how:"Explorați arborele limbilor, mulțimile delimitată și centrată și harta. Alegeți o listă din lateral pentru a restrânge fiecare vedere."},"country-posters":{what:"Afișe de rugăciune de tipărit, câte unul pentru fiecare țară, cu grupurile ei de popoare.",how:"Alegeți o țară din lista de afișe sau de pe hartă și tipăriți-o direct din browser."},"all-people-groups":{what:"Toate grupurile de popoare ale lumii pe o hartă, colorate după cât sunt de atinse, cu setul de rugăciune DOXA evidențiat.",how:"Faceți clic pe o categorie din legendă pentru a afișa doar acele grupuri. Faceți clic pe un punct pentru detalii și linkul de rugăciune."},"doxa-vision-card":{what:"Sarcina rămasă într-un card: toate grupurile de popoare, apoi cele neatinse, apoi cele de frontieră, apoi setul de rugăciune DOXA.",how:"Citiți-l de sus în jos. Fiecare număr este calculat din date, nu tastat."},"pplr-data-maps":{what:"Date de cercetare despre grupurile de popoare pe hărți: familii de limbi, grupuri de popoare, religii și acoperirea surselor.",how:"Alegeți o vedere din file, apoi folosiți legenda pentru a evidenția o familie, un grup sau o religie."},"wall-of-people-groups":{what:"Toate grupurile de popoare DOXA pe un singur perete, ca să le binecuvântăm. În modul Binecuvântare, peretele este afișul de tipărit (5 × 8 picioare implicit), fiecare grup cu un verset de binecuvântare; în modul Social este un perete de rugăciune comun.",how:"Binecuvântare: explorați afișul exact cum se va tipări, alegeți o dimensiune și apăsați Tipărește afișul HD. Social: rugați-vă pentru un grup, citiți sau lăsați comentarii și distribuiți pagina lui de rugăciune."}},ru:{"prayer-walk-planner":{what:"Спланируйте молитвенную прогулку: нарисуйте районы для ваших команд и распечатайте каждый как карту на одну страницу.",how:"Нарисуйте зону, назовите её, выберите нужные зоны и нажмите «Печать». Каждая зона печатается на отдельной странице с координатами — по карте на команду."},"gospel-internet":{what:"Узнайте, у каких из ~2000 народов уже есть евангельские материалы на их языке и где интернет может донести их до них.",how:"Исследуйте языковое дерево, ограниченное и центрированное множества и карту. Выберите список сбоку, чтобы сузить любой вид."},"country-posters":{what:"Молитвенные плакаты для печати, по одному на страну, с её народами.",how:"Выберите страну в списке плакатов или на карте и распечатайте прямо из браузера."},"all-people-groups":{what:"Все народы мира на одной карте, окрашенные по степени достигнутости, с выделенным молитвенным набором DOXA.",how:"Нажмите категорию в легенде, чтобы показать только эти группы. Нажмите точку, чтобы увидеть сведения и ссылку для молитвы."},"doxa-vision-card":{what:"Оставшаяся задача на одной карточке: все народы, затем недостигнутые, затем пограничные, затем молитвенный набор DOXA.",how:"Читайте сверху вниз. Каждое число вычислено из данных, а не введено вручную."},"pplr-data-maps":{what:"Исследовательские данные о народах на картах: языковые семьи, кластеры народов, религии и охват источников.",how:"Выберите вид во вкладках, затем выделите семью, кластер или религию с помощью легенды."},"wall-of-people-groups":{what:"Все народы DOXA на одной стене, чтобы благословлять их. В режиме «Благословение» стена — это плакат для печати (по умолчанию 5 × 8 футов), у каждого народа — стих благословения; в режиме «Соцсеть» это общая молитвенная стена.",how:"«Благословение»: просмотрите плакат точно таким, каким он будет напечатан, выберите размер и нажмите «Печать плаката HD». «Соцсеть»: молитесь о народе, читайте и оставляйте комментарии, делитесь его молитвенной страницей."}},ar:{"prayer-walk-planner":{what:"خطِّط لجولة صلاة: ارسم المناطق التي ستغطيها فرقك واطبع كل منطقة خريطةً من صفحة واحدة.",how:"ارسم منطقة وسمِّها، واختر المناطق التي تحتاجها ثم اضغط «طباعة». تُطبع كل منطقة في صفحة مستقلة مع إحداثياتها: خريطة لكل فريق."},"gospel-internet":{what:"اعرف أيَّ الشعوب الألفين تقريبًا لديها بالفعل موارد للإنجيل بلغتها، وأين يمكن للإنترنت أن يوصلها إليها.",how:"استكشف شجرة اللغات والمجموعتين المحدودة والمتمركزة والخريطة. اختر قائمة من الجانب لتضييق كل عرض."},"country-posters":{what:"ملصقات صلاة للطباعة، ملصق لكل بلد، تُظهر شعوبه.",how:"اختر بلدًا من قائمة الملصقات أو على الخريطة، ثم اطبعه مباشرة من المتصفح."},"all-people-groups":{what:"كل شعوب العالم على خريطة واحدة، ملوَّنة بحسب درجة وصول الإنجيل إليها، مع إبراز مجموعة صلاة DOXA.",how:"انقر فئة في مفتاح الخريطة لعرض تلك الشعوب فقط. انقر نقطة لترى تفاصيلها ورابط الصلاة."},"doxa-vision-card":{what:"المهمة المتبقية في بطاقة واحدة: كل الشعوب، ثم غير المُبشَّرة، ثم الحدودية، ثم مجموعة صلاة DOXA.",how:"اقرأها من الأعلى إلى الأسفل. كل رقم محسوب من البيانات، وليس مكتوبًا يدويًا."},"pplr-data-maps":{what:"بيانات بحثية عن الشعوب على خرائط: العائلات اللغوية والعناقيد الشعبية والأديان وتغطية المصادر.",how:"اختر عرضًا من التبويبات، ثم استخدم مفتاح الخريطة لإبراز عائلة أو عنقود أو دين."},"wall-of-people-groups":{what:"كل شعوب DOXA على جدار واحد لنباركها. في وضع «بركة» يكون الجدار هو الملصق الجاهز للطباعة (5 × 8 أقدام افتراضيًا)، ولكل شعب آية بركة؛ وفي وضع «تفاعلي» يكون جدار صلاة مشتركًا.",how:"«بركة»: تجوّل في الملصق كما سيُطبع تمامًا، واختر المقاس ثم اضغط «طباعة ملصق عالي الدقة». «تفاعلي»: صلِّ لأجل شعب، واقرأ التعليقات أو اكتب تعليقًا، وشارك صفحة الصلاة الخاصة به."}},hi:{"prayer-walk-planner":{what:"प्रार्थना-यात्रा की योजना बनाएँ: अपनी टीमों के क्षेत्र बनाएँ और हर क्षेत्र को एक पन्ने के नक्शे के रूप में छापें।",how:"एक क्षेत्र बनाएँ, उसे नाम दें, ज़रूरी क्षेत्र चुनें और प्रिंट दबाएँ। हर क्षेत्र निर्देशांकों के साथ अपने पन्ने पर छपता है: हर टीम के लिए एक नक्शा।"},"gospel-internet":{what:"देखें कि लगभग 2,000 जन-समूहों में से किनके पास उनकी भाषा में सुसमाचार के संसाधन पहले से हैं, और इंटरनेट उन्हें कहाँ तक पहुँचा सकता है।",how:"भाषा-वृक्ष, सीमित और केंद्रित समुच्चय और नक्शा देखें। हर दृश्य को सीमित करने के लिए बगल में एक सूची चुनें।"},"country-posters":{what:"छापने योग्य प्रार्थना-पोस्टर, हर देश के लिए एक, उसके जन-समूहों के साथ।",how:"पोस्टर सूची में या नक्शे पर कोई देश चुनें, फिर सीधे ब्राउज़र से छापें।"},"all-people-groups":{what:"दुनिया के सभी जन-समूह एक नक्शे पर, इस आधार पर रंगे हुए कि वे कितने पहुँचे हुए हैं, DOXA प्रार्थना-समूह उभारा हुआ।",how:"केवल वे समूह देखने के लिए लीजेंड में एक श्रेणी पर क्लिक करें। विवरण और प्रार्थना-लिंक के लिए किसी बिंदु पर क्लिक करें।"},"doxa-vision-card":{what:"बचा हुआ काम एक कार्ड में: सभी जन-समूह, फिर अपहुँचे, फिर सीमांत, फिर DOXA प्रार्थना-समूह।",how:"इसे ऊपर से नीचे पढ़ें। हर संख्या आँकड़ों से गणना की गई है, हाथ से नहीं लिखी गई।"},"pplr-data-maps":{what:"जन-समूहों पर शोध-आँकड़े नक्शों पर: भाषा-परिवार, जन-क्लस्टर, धर्म और स्रोतों की पहुँच।",how:"टैब से कोई दृश्य चुनें, फिर लीजेंड से किसी परिवार, क्लस्टर या धर्म को नक्शे पर उभारें।"},"wall-of-people-groups":{what:"हर DOXA जन-समूह एक ही दीवार पर, उन्हें आशीष देने के लिए। आशीष मोड में दीवार छापने योग्य पोस्टर है (डिफ़ॉल्ट 5 × 8 फ़ुट), हर समूह के साथ आशीष का एक पद; सामाजिक मोड में यह एक साझा प्रार्थना दीवार है।",how:"आशीष: पोस्टर को ठीक वैसा ही देखें जैसा वह छपेगा, आकार चुनें और HD पोस्टर छापें दबाएँ। सामाजिक: किसी समूह के लिए प्रार्थना करें, टिप्पणियाँ पढ़ें या लिखें, और उसका प्रार्थना पेज साझा करें।"}},zh:{"prayer-walk-planner":{what:"规划祷告行走:画出各小组要走的区域,把每个区域打印成一页地图。",how:"画一个区域并命名,选择需要的区域后点“打印”。每个区域连同坐标单独打印一页:每个小组一张地图。"},"gospel-internet":{what:"看看约 2,000 个族群中,哪些已经有母语的福音资源,以及互联网能把这些资源带到哪里。",how:"浏览语言树、有界集与中心集以及地图。在侧边选择一个名单,可缩小每个视图的范围。"},"country-posters":{what:"可打印的祷告海报,每个国家一张,列出其中的族群。",how:"在海报列表或地图上选择一个国家,然后直接从浏览器打印。"},"all-people-groups":{what:"世界上所有族群都在一张地图上,按福音进展着色,并突出显示 DOXA 祷告名单。",how:"点击图例中的类别,只显示这些族群。点击一个点,查看详情和祷告链接。"},"doxa-vision-card":{what:"一张卡片看清剩下的使命:所有族群,然后是未得之民,再是前沿族群,最后是 DOXA 祷告名单。",how:"从上往下读。每个数字都由数据计算得出,而不是手工填写。"},"pplr-data-maps":{what:"族群研究数据地图:语系、民族集群、宗教和数据来源覆盖情况。",how:"在标签页中选择一个视图,然后用图例在地图上突出显示某个语系、集群或宗教。"},"wall-of-people-groups":{what:"所有 DOXA 族群都在一面墙上,为要祝福他们。在“祝福”模式下,这面墙就是可打印的海报(默认 5 × 8 英尺),每个族群配一节祝福经文;在“社交”模式下,它是一面共同的祷告墙。",how:"祝福:按打印时的样子浏览海报,选择尺寸,然后点击“打印高清海报”。社交:为某个族群祷告,阅读或留下评论,并分享它的祷告页面。"}}};function Tl(e,t){var o;const a=String(t||"en").toLowerCase().split(/[-_]/)[0];return((o=Ll[a])==null?void 0:o[e])||Ll.en[e]||null}const Pl="previews/examples/middle-east-core",ph=Array.from({length:12},(e,t)=>`${Pl}/packet-p${String(t+1).padStart(2,"0")}.webp`),uh=Array.from({length:70},(e,t)=>`${Pl}/card-${String(t+1).padStart(2,"0")}.webp`),dh={"upg-100":{storageKey:"upg-100",trigger:"control",pos:{side:"56px",narrow:{placement:"top-left",side:"12px"}},leadKey:"mapInfo.tab.upg-100.lead",strategyKey:"mapStrategy.tab.upg-100",takeawayKey:"mapInfo.tab.upg-100.takeaway",noteKey:"pins.approximate",examples:[{kind:"image",src:"previews/middle-east-core.webp",full:"posters/middle-east-core.html"},{kind:"pages",pages:ph},{kind:"deck",cards:uh}]},"regions-dashboard":{storageKey:"regions-dashboard",pos:{side:"56px",narrow:{placement:"top-left",side:"8px"}},leadKey:"mapInfo.tab.regions-dashboard.lead",strategyKey:"mapStrategy.tab.regions-dashboard",trigger:"control",takeawayKey:"mapInfo.tab.regions-dashboard.takeaway",noteKey:"mapInfo.tab.regions-dashboard.note",examples:[]},"dashboard-plus-grid":{storageKey:"dashboard-plus-grid",trigger:"control",pos:{top:"46px",raise:!0},leadKey:"mapInfo.tab.dashboard-plus-grid.lead",strategyKey:"mapStrategy.tab.dashboard-plus-grid",takeawayKey:"mapInfo.tab.dashboard-plus-grid.takeaway",examples:[{kind:"table",columns:[{key:"mapInfo.col.peopleGroup"},{key:"mapInfo.col.country"},{key:"mapInfo.col.region"},{key:"mapInfo.col.population"}],rows:[["Hararghe Oromo","Ethiopia","Africa",6355e3],["Gaaliin","Sudan","Middle East",452e4],["White Moor","Morocco","Middle East",312e4],["Baloch, Western","Iran","Middle East",3035e3],["Baloch, Eastern","Pakistan","Asia",298e4],["Bhuiyar","India","Asia",2925e3]],total:2107}]},"research-map-plus-grid":{storageKey:"research-map-plus-grid",trigger:"control",pos:{top:"56px",side:"56px"},leadKey:"mapInfo.tab.research-map-plus-grid.lead",strategyKey:"mapStrategy.tab.research-map-plus-grid",noteKey:"pins.approximate",examples:[]},"prayer-map":{storageKey:"prayer-map",trigger:"control",pos:{side:"56px"},leadKey:"mapInfo.tab.prayer-map.lead",strategyKey:"mapStrategy.tab.prayer-map",takeawayKey:"mapInfo.tab.prayer-map.takeaway",noteKey:"pins.approximate",examples:[]},"cities-internet":{storageKey:"cities-internet",trigger:"control",pos:{side:"56px",narrow:{side:"4px"}},leadKey:"mapInfo.tab.cities-internet.lead",strategyKey:"mapStrategy.tab.cities-internet",takeawayKey:"mapInfo.tab.cities-internet.takeaway",noteKey:"pins.approximate",examples:[]},"gospel-resources":{storageKey:"gospel-resources",trigger:"control",pos:{side:"56px",narrow:{side:"4px"}},leadKey:"mapInfo.tab.gospel-resources.lead",strategyKey:"mapStrategy.tab.gospel-resources",takeawayKey:"mapInfo.tab.gospel-resources.takeaway",noteKey:"pins.approximate",examples:[]}};function El(e,t){return t?String(e).replace(/\{(\w+)\}/g,(a,o)=>t[o]!=null?String(t[o]):a):String(e)}function Ol(e,t,a=ls()){const o=String(e).split(/\s*\|\s*/);if(o.length<2||typeof t!="number")return String(e);const n=String(a||"en").split("-")[0].toLowerCase();if(n!=="en")return o[fl(t,o.length,n)];const s=Math.abs(t),r=o.length===2?s?s>1?1:0:1:s?Math.min(s,2):0;return o[Math.min(r,o.length-1)]}function ds(e,t){const a=String(t);return e&&!a.startsWith(`${e}.`)?`${e}.${a}`:a}function gh(e,t={}){const a=typeof t.fallback=="function"?(o,n,s)=>El(Ol(t.fallback(ds(e,o),n)??o,s),n):(o,n,s)=>El(Ol(Wt(ds(e,o))??o,s),n);try{const o=Zo();return(n,s,r)=>{const i=ds(e,n),l=typeof r=="number"?o.t(i,s||{},r):o.t(i,s||{});return l===i?a(n,s,r):l}}catch{return a}}const mh=["dir","lang"],fh=["aria-label","title","aria-expanded"],hh={viewBox:"0 0 16 16",focusable:"false"},bh={key:0,d:"M14 8H3M7 3.5 2.5 8 7 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},$h={key:1,d:"M2 8h11M9 3.5 13.5 8 9 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},_h=["dir"],vh={viewBox:"0 0 16 16",focusable:"false"},yh={key:0,d:"M14 8H3M7 3.5 2.5 8 7 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},wh={key:1,d:"M2 8h11M9 3.5 13.5 8 9 12.5",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},kh=["aria-labelledby"],xh={class:"mi-head"},Sh=["id"],Lh=["aria-label"],Th={class:"mi-body"},Ph=["aria-labelledby"],Eh=["id"],Oh={class:"mi-lead"},Mh={key:1,class:"mi-takeaway"},Ah={class:"mi-strategy-sum"},Ch={class:"mi-bottom"},Ih={key:0,class:"mi-points"},Nh=["src","alt","onError"],Fh={key:0,class:"mi-center"},Dh=["href"],Rh=["aria-label","onClick"],zh=["src","alt","onError"],qh={key:0,class:"mi-center"},Wh=["onClick"],jh={class:"mi-center"},Uh=["onClick"],Hh={class:"mi-pages"},Bh=["aria-label","onClick"],Vh=["src","alt"],Gh={class:"mi-center"},Kh=["onClick"],Xh=["aria-label","onClick"],Yh=["src","onError"],Jh={class:"mi-center"},Zh=["onClick"],Qh={class:"mi-center"},eb=["onClick"],tb={class:"mi-grid"},ab=["src","alt"],ob={class:"mi-center"},nb=["onClick"],sb={class:"mi-table-wrap"},rb={class:"mi-table"},ib={key:0,class:"mi-more"},lb={key:3,class:"mi-note"},cb={key:0,class:"mi-foot"},pb=["checked"],lo=new Set,ub={__name:"MapInfo",props:{storageKey:{type:String,required:!0},leadKey:{type:String,default:""},lead:{type:String,default:""},takeawayKey:{type:String,default:""},takeaway:{type:String,default:""},noteKey:{type:String,default:""},strategyKey:{type:String,default:""},note:{type:String,default:""},examples:{type:Array,default:()=>[]},assetsBase:{type:String,default:""},placement:{type:String,default:"top-right",validator:e=>["top-right","top-left"].includes(e)},locale:{type:String,default:""},trigger:{type:String,default:"floating",validator:e=>["floating","control"].includes(e)},popupOnly:{type:Boolean,default:!1}},setup(e){const t=e,a=()=>t.trigger==="control",o=Me(0);function n(){return o.value,String(t.locale||ls()||"en").split("-")[0].toLowerCase()}const s=gh("",{fallback:k=>Wt(k,n())}),r=(k,K)=>k?s(k):K,i=8,l=()=>t.strategyKey&&Wt(t.strategyKey+".bottom",n())||"";function p(){const k=[];if(!t.strategyKey)return k;for(let K=1;K<=i;K++){const Y=Wt(t.strategyKey+".p"+K,n());if(!Y)break;k.push(Y)}return k}const c=()=>!!l(),m=k=>{try{return new Intl.NumberFormat(n()).format(k)}catch{return String(k)}},v=()=>n()==="ar",w="dxmaps.mapInfo.seen.",q="dxmaps.mapInfo.pointer",M="dxmaps:mapinfo-pointer-off";function U(k){try{return window.localStorage.getItem(k)}catch{return null}}function j(k,K){try{window.localStorage.setItem(k,K)}catch{}}const W=Me(!1),y=Me(!1);function b(){if(y.value=U(q)==="off",a()){W.value=!lo.has(t.storageKey);return}W.value=!y.value&&U(w+t.storageKey)!=="1"}function A(){a()||b()}function P(k){a()||(!k||k.key===q||k.key===w+t.storageKey)&&b()}sa(()=>{b(),window.addEventListener(M,A),window.addEventListener("storage",P)}),qa(()=>{window.removeEventListener(M,A),window.removeEventListener("storage",P)});function F(k){y.value=k,j(q,k?"off":"on"),W.value=!k&&U(w+t.storageKey)!=="1";try{window.dispatchEvent(new CustomEvent(M))}catch{}}const H=Me(!1),B=Me(null),fe=Me(null),ke=Na({}),Q=Na({});async function xe(){var k;o.value++,H.value=!0,a()&&lo.add(t.storageKey),await Po(),(k=fe.value)==null||k.focus()}async function nt(){H.value=!1,a()||j(w+t.storageKey,"1"),W.value=!1,await Po();const k=t.trigger==="control"&&le.value&&ie.value?ie.value:B.value;k==null||k.focus()}const Ne=Me(null),ie=Me(null),le=Me(!1);let ae=null,Se=null,Ce=null,Le=null;function Ee(k){for(let K=k;K;K=K.parentNode||K.host)if(K.nodeType===1&&(K.hasAttribute("inert")||K.getAttribute("aria-hidden")==="true"))return!0;return!1}const Lt=3e4,Tt=9e4;let mt=!1;const je=Me(t.trigger==="control");let tt=0;function it(){const k=Ne.value&&Ne.value.parentElement,K=k&&k.getBoundingClientRect();return!!k&&!Ee(k)&&K.width>0&&K.height>0}function Pt(){if(je.value){if(!it()){tt=0;return}tt?Date.now()-tt>=(mt?Tt:Lt)&&(je.value=!1):tt=Date.now()}}function Fe(){Pt(),a()&&W.value&&lo.has(t.storageKey)&&!it()&&(W.value=!1);const k=ie.value;let K=!1;if(k&&k.isConnected&&!Ee(k)){const Y=k.getBoundingClientRect();if(K=Y.width>0&&Y.height>0&&Y.bottom>0&&Y.right>0&&Y.topk.classList.contains("mcb--rounded-square");function h(k){const K=Ne.value&&Ne.value.offsetParent;if(!K)return null;const Y=K.getBoundingClientRect(),oe=Y.left+K.clientLeft,ge=Y.top+K.clientTop,Oe=Math.round(k.top-ge+K.scrollTop+k.height/2);return k.left+k.width/2>oe+K.clientWidth/2?{side:"right",top:Oe,right:Math.round(oe+K.clientWidth-k.left+jt)}:{side:"left",top:Oe,left:Math.round(k.right-oe+jt)}}const f=Me(null),x=()=>{const k=f.value;return k.side==="right"?{top:k.top+"px",right:k.right+"px"}:{top:k.top+"px",left:k.left+"px"}},E=()=>Fe();function T(k){if(!k||k===ie.value){Fe();return}try{Se==null||Se.disconnect(),Ce==null||Ce.disconnect()}catch{}ie.value=k,je.value=!1;try{Se=new IntersectionObserver(Fe),Se.observe(k)}catch{Se=null}try{Ce=new ResizeObserver(Fe),Ce.observe(k)}catch{Ce=null}k.setAttribute("aria-expanded",String(H.value)),Fe()}const R=k=>!!ae&&typeof k.composedPath=="function"&&k.composedPath().includes(ae);function D(k){R(k)&&(k.stopPropagation(),k.detail&&(k.detail.ack=!0),T(k.detail&&k.detail.el))}function u(k){R(k)&&(k.stopPropagation(),mt=!!(k.detail&&k.detail.pending),!mt&&!ie.value&&(je.value=!1))}function d(k){R(k)&&(k.stopPropagation(),T(k.detail&&k.detail.el),H.value||xe())}sa(()=>{if(t.trigger==="control"&&(ae=Ne.value&&Ne.value.parentElement,!!ae)){ae.addEventListener("dxmaps:mapinfo-control",D),ae.addEventListener("dxmaps:mapinfo-open",d),ae.addEventListener("dxmaps:mapinfo-pending",u),Le=window.setInterval(Fe,1500),window.addEventListener("resize",E);try{window.dispatchEvent(new CustomEvent("dxmaps:mapinfo-host",{detail:{local:t.popupOnly}}))}catch{}}}),qa(()=>{ae&&(ae.removeEventListener("dxmaps:mapinfo-control",D),ae.removeEventListener("dxmaps:mapinfo-open",d),ae.removeEventListener("dxmaps:mapinfo-pending",u));try{Se==null||Se.disconnect(),Ce==null||Ce.disconnect()}catch{}Le&&window.clearInterval(Le),window.removeEventListener("resize",E)}),Gt(H,k=>{var K;(K=ie.value)==null||K.setAttribute("aria-expanded",String(k))});let $=null;function S(){W.value&&(W.value=!1,a()&&lo.add(t.storageKey))}sa(()=>{$=Ne.value&&(Ne.value.parentElement||Ne.value.getRootNode()),$&&$.addEventListener("dxmaps:panel-open",S)}),qa(()=>{$&&$.removeEventListener("dxmaps:panel-open",S),$=null});const z=()=>!t.popupOnly&&(t.trigger!=="control"||!je.value&&!le.value);Gt(()=>a()&&W.value&&!H.value&&!z()&&!!f.value,k=>{k&&lo.add(t.storageKey)},{immediate:!0});const I=Me(null);function _(k){const K=I.value;if(!K)return;const Y=[...K.querySelectorAll('button, a[href], input, [tabindex]:not([tabindex="-1"])')].filter(Ht=>!Ht.disabled&&Ht.offsetParent!==null);if(!Y.length)return;const oe=K.getRootNode().activeElement,ge=Y[0],Oe=Y[Y.length-1];k.shiftKey&&(oe===ge||!K.contains(oe))?(k.preventDefault(),Oe.focus()):!k.shiftKey&&(oe===Oe||!K.contains(oe))&&(k.preventDefault(),ge.focus())}function L(k){k.key==="Escape"?(k.stopPropagation(),nt()):k.key==="Tab"&&_(k)}function G(k){ke[k]=!ke[k]}function ee(k){const K=k&&k.target;K&&(K.style.display="none")}function ce(k){const K=String(k||"");return!K||/^(?:[a-z]+:|\/)/i.test(K)||!t.assetsBase?K:t.assetsBase.replace(/\/+$/,"")+"/"+K.replace(/^\.?\//,"")}const Ue={image:"mapInfo.poster",pages:"mapInfo.packet",deck:"mapInfo.cards",table:"mapInfo.spreadsheet"};function Te(k){return s(k.titleKey||Ue[k.kind]||"mapInfo.example")}const Ut=k=>(k.cards||[]).slice(0,5),co=k=>k&&typeof k=="object"?k.key?s(k.key):String(k.label||""):String(k??"");return hl(` .mi-btn{ position:absolute; z-index:20; top:var(--map-info-top, 12px); width:40px; height:40px; border-radius:50%; border:1px solid rgba(0,0,0,.18); background:#fff; color:#1f2a44; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; padding:0; diff --git a/public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html b/public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html index fab69ca30..7ae599699 100644 --- a/public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html +++ b/public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html @@ -67,7 +67,7 @@ - + - + \n\n\n" + "script": "\n\n\n" }, "instances": [ { diff --git a/public/dx-maps/manifest.json b/public/dx-maps/manifest.json index d4ac31d26..f60c90164 100644 --- a/public/dx-maps/manifest.json +++ b/public/dx-maps/manifest.json @@ -446,7 +446,7 @@ "bundle": "doxa-maps-spa", "scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js", "output": "dx-maps/doxa-maps-spa/doxa-maps-spa.js", - "integrity": "sha384-npxyK5BtVl4vjxoMg11/0pGcwx2AOeSs26c7nf5GuwCU9tSjEB0WX3vwWT0ieiZj", + "integrity": "sha384-Iupu7V/Y1jCpZPFNt6vYRrUFBmdTydxMoFxaQ0M/w1XHT2DhonFofebe1p/SuYoJ", "elements": [ "doxa-maps-spa" ], @@ -476,7 +476,7 @@ "scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js", "embed": { "iframe": "", - "script": "\n\n\n" + "script": "\n\n\n" }, "instances": [ {