diff --git a/internal/generate-tree.mjs b/internal/generate-tree.mjs index c178a03e1..60529df7f 100644 --- a/internal/generate-tree.mjs +++ b/internal/generate-tree.mjs @@ -798,7 +798,12 @@ function studioRootHtml(bakedToken, integrity = '') { ${bakedToken ? ` \n` : ''}${DEST_CONFIG.apiBaseUrl ? ` \n` : ''} @@ -1027,9 +1032,15 @@ ${hintBlock} ${peerBlock} font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; } #status.err { color: #f87171; }` : `/* BARE full-page map — no chrome. The custom element IS the page, so the - browser URL shares the map itself. */ + browser URL shares the map itself. + HEIGHT IN svh, NOT inset:0. A fixed box with inset:0 follows the phone's DYNAMIC viewport, so the map + (and a legend sheet sized from it) changes height every time the address bar hides or shows. 100svh is + the viewport WITH the bar shown and never changes; when the bar hides, the page background shows below + instead of the map resizing. On a desktop svh = the viewport, so nothing moves there. The 100% before it + is the fallback for a browser without svh, which would otherwise size the box to its content. + --profile-min-h:0 opts out of ProfileLoader's 100dvh floor, which would grow the element again. */ html, body { height: 100%; margin: 0; background: #0a0e15; } - #slot, ${esc(tag)} { position: fixed; inset: 0; width: 100%; height: 100%; display: block; } + #slot, ${esc(tag)} { position: fixed; inset: 0 auto auto 0; width: 100%; height: 100%; height: 100svh; --profile-min-h: 0; display: block; } #status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center; padding: 24px; text-align: center; color: #9aa7b8; font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; } diff --git a/internal/plugin-gallery/dev-plugin.mjs b/internal/plugin-gallery/dev-plugin.mjs index 26eca3a9c..5b6ff2b62 100644 --- a/internal/plugin-gallery/dev-plugin.mjs +++ b/internal/plugin-gallery/dev-plugin.mjs @@ -417,7 +417,10 @@ export function autoSpaDevPlugin(token = '') { // depends on no ancestor and stays a floor, so the page can still grow past it. `${tag}{display:block;width:100%;min-height:100dvh;}` : '`, + // 100svh (100% as the fallback), not 100vh: a phone's 100vh is taller than the view while the address + // bar shows, so the page scrolls by the bar and the app seems to resize; --profile-min-h:0 opts out of + // ProfileLoader's 100dvh floor. The same rule as generate-tree's fixed-viewport leaf. + `${tag}{display:block;width:100%;height:100%;height:100svh;--profile-min-h:0;}`, '', // tree-base ON THE LEAF TOO — cap-5 shells nested here fetch a manifest of their own. // doxa-maps-spa (like plugin-studio) discovers its tabs from the tree's manifest.json and @@ -483,7 +486,7 @@ export function autoSpaDevPlugin(token = '') { // that takes up so much space" is a layout requirement, so the host page contributes // ZERO text — every pixel belongs to the studio element. '', + 'plugin-studio{display:block;width:100%;height:100%;height:100svh;--profile-min-h:0;}', '', // tree-base IS REQUIRED IN DEV — without it the studio mounts EMPTY. // The studio builds its tab list at runtime by fetching `manifest.json`, and with diff --git a/library/components/info/MapInfo.vue b/library/components/info/MapInfo.vue index f58736732..fb54bd61f 100644 --- a/library/components/info/MapInfo.vue +++ b/library/components/info/MapInfo.vue @@ -68,9 +68,9 @@ const props = defineProps({ /** * The map's STRATEGY, as a catalogue key PREFIX (e.g. 'mapInfo.tab.upg-100.strategy'): .bottom is * the bottom line, .p1, .p2 … the points, read in order until the first one that is missing. So - * the number of points is the catalogue's business, never a count kept here. With a strategy the popup - * shows the intro (the lead), then the strategy; a map that also has examples gets two tabs, EXAMPLES - * first. The takeaway is not shown beside a strategy, which supersedes it. + * the number of points is the catalogue's business, never a count kept here. The popup shows the short + * description (the lead) first, then the strategy as a collapsed disclosure, then the examples. The + * takeaway is not shown beside a strategy, which supersedes it. */ strategyKey: { type: String, default: '' }, note: { type: String, default: '' }, @@ -130,19 +130,7 @@ function strategyPoints() { return out } const hasStrategy = () => !!strategyBottom() -// Two tabs only where there is something on both sides: examples AND a strategy. -const hasTabs = () => hasStrategy() && (props.examples || []).length > 0 -const view = ref('examples') -// The ARIA tabs pattern: Left/Right move between the tabs and focus follows. With two tabs either key simply -// switches, so a right-to-left reader needs no mirrored mapping. -const tabIds = () => ({ ex: 'mi-tab-ex-' + props.storageKey, st: 'mi-tab-st-' + props.storageKey, panel: 'mi-panel-' + props.storageKey }) -function onTabKey(e) { - if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return - e.preventDefault() - const list = e.currentTarget // null once dispatch ends, so take it now, not inside nextTick - view.value = view.value === 'examples' ? 'strategy' : 'examples' - nextTick(() => { const btn = list && list.querySelector('[aria-selected="true"]'); if (btn) btn.focus() }) -} + const num = (n) => { try { return new Intl.NumberFormat(lang()).format(n) } catch { return String(n) } } const isRtl = () => lang() === 'ar' @@ -199,7 +187,6 @@ const failed = reactive({}) async function show() { opens.value++ open.value = true - view.value = 'examples' // A page-load pointer is done for this load once the popup has been opened (in memory only). if (pageLoad()) pointerDoneThisLoad.add(props.storageKey) await nextTick() @@ -449,17 +436,24 @@ useShadowStyles(` .mi-close:focus-visible, .mi-toggle:focus-visible, .mi-hit:focus-visible, .mi-full:focus-visible{ outline:3px solid #2563eb; outline-offset:2px; } .mi-body{ overflow-y:auto; padding:16px 20px 20px; -webkit-overflow-scrolling:touch; } - .mi-lead{ margin:0 0 6px; font-size:16px; } + /* The short description is the first thing the eye should find: the Rudder blue (#38bdf8) as the ACCENT + (a left bar on a pale band of the same hue), and the text in a darker shade of that hue (#0369a1), since + the accent itself is only ~2.1:1 on white and would be hard to read as text. */ + .mi-bluf{ margin:0 0 12px; padding:10px 12px; background:#f0f9ff; border-inline-start:4px solid #38bdf8; border-radius:6px; } + .mi-bluf-h{ margin:0 0 4px; font-size:13px; font-weight:700; color:#0c4a6e; } + .mi-lead{ margin:0; font-size:16px; font-weight:500; color:#0369a1; } .mi-takeaway{ margin:0 0 14px; color:#3b4256; } .mi-takeaway b{ color:#1b1f2a; } .mi-ex{ margin:18px 0 0; } - .mi-tabs{ display:flex; gap:4px; margin:14px 0 0; border-bottom:1px solid #e6e8ee; } - .mi-tab{ min-height:44px; padding:0 16px; border:none; background:none; color:#3b4256; font:inherit; font-weight:600; - cursor:pointer; border-bottom:3px solid transparent; margin-bottom:-1px; } - .mi-tab[aria-selected="true"]{ color:#1b1f2a; border-bottom-color:#3b463d; } - .mi-tab:focus-visible{ outline:3px solid #2563eb; outline-offset:-3px; } - .mi-strategy{ margin:16px 0 0; } - .mi-strategy h3{ margin:0 0 6px; } + .mi-strategy{ margin:14px 0 0; border:1px solid #e6e8ee; border-radius:10px; } + .mi-strategy-sum{ display:flex; align-items:center; justify-content:space-between; gap:8px; min-height:44px; + padding:0 14px; cursor:pointer; font-weight:700; color:#1b1f2a; list-style:none; } + .mi-strategy-sum::-webkit-details-marker{ display:none; } + .mi-strategy-sum:focus-visible{ outline:3px solid #2563eb; outline-offset:2px; border-radius:10px; } + .mi-chev{ transition:transform .15s; } + .mi-strategy[open] .mi-chev{ transform:rotate(180deg); } + .mi-strategy > .mi-bottom, .mi-strategy > .mi-points{ margin-inline:14px; } + .mi-strategy > :last-child{ margin-bottom:14px; } .mi-bottom{ margin:0 0 8px; font-weight:600; color:#1b1f2a; } .mi-points{ margin:0; padding-inline-start:20px; } .mi-points li{ margin:0 0 6px; } @@ -554,6 +548,7 @@ useShadowStyles(` styles are injected: a page-level rule cannot reach into a shadow root. */ @media (prefers-reduced-motion: reduce){ .mi-pointer, .mi .mi-pointer.mi-ctl{ animation:none; } + .mi-chev{ transition:none; } } `, 'map-info') @@ -594,27 +589,22 @@ useShadowStyles(`
-

{{ text(leadKey, lead) }}

+ +
+

{{ t('mapInfo.shortDescription') }}

+

{{ text(leadKey, lead) }}

+

{{ t('mapInfo.takeaway') }} {{ text(takeawayKey, takeaway) }}

-
- - -
-
- -
-

{{ t('mapInfo.tabStrategy') }}

+ +
+ {{ t('mapInfo.tabStrategy') }}

{{ strategyBottom() }}

  • {{ p }}
-
- - - -
-

{{ text(noteKey, note) }}

diff --git a/library/i18n/locales/ar/mapInfo.json b/library/i18n/locales/ar/mapInfo.json index 13564a40b..800b9bd23 100644 --- a/library/i18n/locales/ar/mapInfo.json +++ b/library/i18n/locales/ar/mapInfo.json @@ -1,6 +1,7 @@ { "open": "ماذا يمكن أن تقدّم لك هذه الخريطة؟", "title": "ماذا يمكن أن تقدّم لك هذه الخريطة؟", + "shortDescription": "وصف موجز", "pointer": "ماذا تفعل هذه الخريطة؟", "close": "إغلاق", "takeaway": "ما تحصل عليه:", diff --git a/library/i18n/locales/de/mapInfo.json b/library/i18n/locales/de/mapInfo.json index 98061fb2f..b1659a33e 100644 --- a/library/i18n/locales/de/mapInfo.json +++ b/library/i18n/locales/de/mapInfo.json @@ -1,6 +1,7 @@ { "open": "Was kann diese Karte für Sie tun?", "title": "Was kann diese Karte für Sie tun?", + "shortDescription": "Kurzbeschreibung", "pointer": "Was kann diese Karte?", "close": "Schließen", "takeaway": "Was Sie mitnehmen:", diff --git a/library/i18n/locales/en/mapInfo.json b/library/i18n/locales/en/mapInfo.json index c06b3228b..3ae726f40 100644 --- a/library/i18n/locales/en/mapInfo.json +++ b/library/i18n/locales/en/mapInfo.json @@ -1,6 +1,7 @@ { "open": "What can this map do for you?", "title": "What can this map do for you?", + "shortDescription": "Short description", "pointer": "What can this map do?", "close": "Close", "takeaway": "What you take away:", diff --git a/library/i18n/locales/es/mapInfo.json b/library/i18n/locales/es/mapInfo.json index 6e20e9d74..7067ddf62 100644 --- a/library/i18n/locales/es/mapInfo.json +++ b/library/i18n/locales/es/mapInfo.json @@ -1,6 +1,7 @@ { "open": "¿Qué puede hacer este mapa por usted?", "title": "¿Qué puede hacer este mapa por usted?", + "shortDescription": "Descripción breve", "pointer": "¿Qué hace este mapa?", "close": "Cerrar", "takeaway": "Lo que se lleva:", diff --git a/library/i18n/locales/fr/mapInfo.json b/library/i18n/locales/fr/mapInfo.json index a096ee0dc..ae7ed9182 100644 --- a/library/i18n/locales/fr/mapInfo.json +++ b/library/i18n/locales/fr/mapInfo.json @@ -1,6 +1,7 @@ { "open": "Que peut faire cette carte pour vous ?", "title": "Que peut faire cette carte pour vous ?", + "shortDescription": "Description courte", "pointer": "Que fait cette carte ?", "close": "Fermer", "takeaway": "Ce que vous emportez :", diff --git a/library/i18n/locales/hi/mapInfo.json b/library/i18n/locales/hi/mapInfo.json index 38d431c96..0efbab720 100644 --- a/library/i18n/locales/hi/mapInfo.json +++ b/library/i18n/locales/hi/mapInfo.json @@ -1,6 +1,7 @@ { "open": "यह नक्शा आपके लिए क्या कर सकता है?", "title": "यह नक्शा आपके लिए क्या कर सकता है?", + "shortDescription": "संक्षिप्त विवरण", "pointer": "यह नक्शा क्या करता है?", "close": "बंद करें", "takeaway": "आपको क्या मिलता है:", diff --git a/library/i18n/locales/it/mapInfo.json b/library/i18n/locales/it/mapInfo.json index 7e527ad40..24fab46b3 100644 --- a/library/i18n/locales/it/mapInfo.json +++ b/library/i18n/locales/it/mapInfo.json @@ -1,6 +1,7 @@ { "open": "Che cosa può fare questa mappa per te?", "title": "Che cosa può fare questa mappa per te?", + "shortDescription": "Breve descrizione", "pointer": "Che cosa fa questa mappa?", "close": "Chiudi", "takeaway": "Che cosa ottieni:", diff --git a/library/i18n/locales/pt/mapInfo.json b/library/i18n/locales/pt/mapInfo.json index 1c2d1f842..bc9358ca2 100644 --- a/library/i18n/locales/pt/mapInfo.json +++ b/library/i18n/locales/pt/mapInfo.json @@ -1,6 +1,7 @@ { "open": "O que este mapa pode fazer por você?", "title": "O que este mapa pode fazer por você?", + "shortDescription": "Descrição breve", "pointer": "O que este mapa faz?", "close": "Fechar", "takeaway": "O que você leva:", diff --git a/library/i18n/locales/ro/mapInfo.json b/library/i18n/locales/ro/mapInfo.json index 319ddd2aa..d683d6ffc 100644 --- a/library/i18n/locales/ro/mapInfo.json +++ b/library/i18n/locales/ro/mapInfo.json @@ -1,6 +1,7 @@ { "open": "Ce poate face această hartă pentru tine?", "title": "Ce poate face această hartă pentru tine?", + "shortDescription": "Descriere scurtă", "pointer": "Ce face această hartă?", "close": "Închide", "takeaway": "Ce primești:", diff --git a/library/i18n/locales/ru/mapInfo.json b/library/i18n/locales/ru/mapInfo.json index ac6262b19..207b6a8fd 100644 --- a/library/i18n/locales/ru/mapInfo.json +++ b/library/i18n/locales/ru/mapInfo.json @@ -1,6 +1,7 @@ { "open": "Чем эта карта может быть вам полезна?", "title": "Чем эта карта может быть вам полезна?", + "shortDescription": "Краткое описание", "pointer": "Что умеет эта карта?", "close": "Закрыть", "takeaway": "Что вы получаете:", diff --git a/library/i18n/locales/zh/mapInfo.json b/library/i18n/locales/zh/mapInfo.json index efdca9bea..c3ed0425b 100644 --- a/library/i18n/locales/zh/mapInfo.json +++ b/library/i18n/locales/zh/mapInfo.json @@ -1,6 +1,7 @@ { "open": "这张地图能为你做什么?", "title": "这张地图能为你做什么?", + "shortDescription": "简要说明", "pointer": "这张地图能做什么?", "close": "关闭", "takeaway": "你能带走的:", diff --git a/plugin-designs/cities-internet/connectivity-map.vue b/plugin-designs/cities-internet/connectivity-map.vue index 9fed464f1..3d5e0d680 100644 --- a/plugin-designs/cities-internet/connectivity-map.vue +++ b/plugin-designs/cities-internet/connectivity-map.vue @@ -93,6 +93,39 @@ const mapboxToken = computed(() => profileConfig.value?.tk || injectedTk?.value const assetsBase = computed(() => resolveAssetsBase(profileConfig.value)) const mapEl = ref(null) +// ★ THE LICENCE CREDIT, COMPACT (plugin-designer 5.8, the bottom-of-map standard). The engine's own +// AttributionControl, with geoBoundaries as custom attribution (its licence asks for the name and a link back). +// On a narrow map the engine draws it compact, EXPANDED at first; CREDIT_FOLD_MS after it first shows, +// foldCredit folds it to its (i) the way the engine's own drag does (it drops the -compact-show class), and a +// tap on the (i) opens it again. The style may still be loading when the map is built, so it looks every half +// second, for up to 30 s, and stops once the credit is there and folded, or is full width (not compact). +const GB_CREDIT = 'geoBoundaries' +const CREDIT_FOLD_MS = 3000 +let creditTimer = 0 +function foldCredit(triesLeft = 60) { + creditTimer = 0 + const box = mapEl.value + if (!box) return + const shown = box.querySelector('.maplibregl-ctrl-attrib.maplibregl-compact-show, .mapboxgl-ctrl-attrib.mapboxgl-compact-show') + // The clock starts once the STYLE's own credit has joined geoBoundaries (it arrives with the style's + // sources), so the first expanded show names the basemap too; measured, the fold otherwise came while + // only "geoBoundaries" had shown. After 30 s it starts anyway. + const inner = shown && shown.querySelector('.maplibregl-ctrl-attrib-inner, .mapboxgl-ctrl-attrib-inner') + const full = !!(inner && inner.textContent.replace('geoBoundaries', '').replace(/[|\s]/g, '').length) + if (shown && (full || triesLeft <= 0)) { + creditTimer = setTimeout(() => { creditTimer = 0; shown.classList.remove('maplibregl-compact-show', 'mapboxgl-compact-show') }, CREDIT_FOLD_MS) + return + } + const credit = box.querySelector('.maplibregl-ctrl-attrib, .mapboxgl-ctrl-attrib') + if (!shown && credit && !/attrib-empty/.test(credit.className)) return + if (triesLeft > 0) creditTimer = setTimeout(() => foldCredit(triesLeft - 1), 500) +} +function addCompactCredit(map, gl) { + if (typeof gl?.AttributionControl !== 'function') return false + try { map.addControl(new gl.AttributionControl({ customAttribution: GB_CREDIT }), 'bottom-right') } catch (_) { return false } + foldCredit() + return true +} const mapRef = shallowRef(null) const rows = ref([]) const selected = ref(null) @@ -517,7 +550,10 @@ onMounted(async () => { // NO attribution at all. It SHADES geoBoundaries polygons, so the geoBoundaries // credit + link back is required, and gbOpen is mixed-licence with several // countries carrying a real attribution requirement (India CC BY 2.5 IN, etc). - addMapAttribution(map, { boundaries: true }) + // ★ NOW ONE CREDIT, THE ENGINE'S OWN COMPACT CONTROL (plugin-designer 5.8): the basemap credit comes from the + // style's own sources and geoBoundaries rides in it as custom attribution, with its link back, so every term + // above is still met. An engine without the control keeps the shared credit line. + if (!addCompactCredit(map, gl)) addMapAttribution(map, { boundaries: true }) map.once('load', async () => { // The BASEMAP's place names in the reader's language (Mapbox setLanguage / MapLibre name:), // the library's own helper. useMapInstance applies it for every map built through it; this map is @@ -538,6 +574,7 @@ onMounted(async () => { } catch (e) { err.value = String(e.message || e); loading.value = false } }) onBeforeUnmount(() => { + if (creditTimer) { clearTimeout(creditTimer); creditTimer = 0 } if (typeof window !== 'undefined') window.removeEventListener('dxmaps:mapinfo-host', reannounceInfoPending) try { legendRO?.disconnect() } catch (_) {} try { detachEdges?.() } catch (_) {} @@ -583,11 +620,15 @@ useShadowStyles( ':host{display:block;position:relative;width:100%;height:100%;min-height:420px}' + '.gm{position:absolute;inset:0;overflow:hidden}' + '.gm-map{position:absolute;inset:0}' + - // THE GALLERY'S ROUND ☰ sits at a phone's bottom corner (bottom-left; bottom-right in Arabic) and publishes - // --map-fab-clearance while it shows. The engine's bottom corners, where this map's licence credit (and a - // Mapbox wordmark) live, lift by it: the rule every LegendMobile map already has. Unset anywhere else, so - // desktop and a map without the button are unchanged. It covered the credit's first/last ~30-40px before. - '.mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl-bottom-right,.maplibregl-ctrl-bottom-left,.maplibregl-ctrl-bottom-right{transform:translateY(calc(-1 * var(--map-fab-clearance,0px)))}' + + // THE CREDIT CORNER (plugin-designer 5.8; this map has no footer sheet, its legend panel is at the top). The + // engine's compact credit sits at the bottom END corner, opposite the gallery's round ☰ (bottom-right; + // bottom-left in Arabic), and is NOT lifted by --map-fab-clearance: lifted, it floated up the page. Where the + // round button can show, the corner sits above the scroll-edge strips so a tap on its (i) lands, and the + // expanded credit stops 64px short of the far edge, clear of the button. + '@media(max-width:767.98px){' + + '.gm .maplibregl-ctrl-bottom-right,.gm .mapboxgl-ctrl-bottom-right{inset-inline-end:0 !important;inset-inline-start:auto !important;max-width:calc(100% - 64px);z-index:3}' + + '.gm .maplibregl-ctrl-bottom-right .maplibregl-ctrl,.gm .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl{margin:0 0 10px;margin-inline-end:10px}' + + '}' + // LEGEND: a BOUNDED, left-anchored panel — NOT a full-width strip. // It used to be `left:8px;right:8px`, which is a strip spanning the whole map // (MEASURED 1264px of a 1280px map, 98.75%). That collided with the floating diff --git a/plugin-designs/gospel-resources/resources-map.vue b/plugin-designs/gospel-resources/resources-map.vue index 784e88a47..fabe6f5ef 100644 --- a/plugin-designs/gospel-resources/resources-map.vue +++ b/plugin-designs/gospel-resources/resources-map.vue @@ -136,6 +136,39 @@ const grProvider = resolveMapProvider(profileConfig) const assetsBase = computed(() => resolveAssetsBase(profileConfig.value)) const mapEl = ref(null) +// ★ THE LICENCE CREDIT, COMPACT (plugin-designer 5.8, the bottom-of-map standard). The engine's own +// AttributionControl, with geoBoundaries as custom attribution (its licence asks for the name and a link back). +// On a narrow map the engine draws it compact, EXPANDED at first; CREDIT_FOLD_MS after it first shows, +// foldCredit folds it to its (i) the way the engine's own drag does (it drops the -compact-show class), and a +// tap on the (i) opens it again. The style may still be loading when the map is built, so it looks every half +// second, for up to 30 s, and stops once the credit is there and folded, or is full width (not compact). +const GB_CREDIT = 'geoBoundaries' +const CREDIT_FOLD_MS = 3000 +let creditTimer = 0 +function foldCredit(triesLeft = 60) { + creditTimer = 0 + const box = mapEl.value + if (!box) return + const shown = box.querySelector('.maplibregl-ctrl-attrib.maplibregl-compact-show, .mapboxgl-ctrl-attrib.mapboxgl-compact-show') + // The clock starts once the STYLE's own credit has joined geoBoundaries (it arrives with the style's + // sources), so the first expanded show names the basemap too; measured, the fold otherwise came while + // only "geoBoundaries" had shown. After 30 s it starts anyway. + const inner = shown && shown.querySelector('.maplibregl-ctrl-attrib-inner, .mapboxgl-ctrl-attrib-inner') + const full = !!(inner && inner.textContent.replace('geoBoundaries', '').replace(/[|\s]/g, '').length) + if (shown && (full || triesLeft <= 0)) { + creditTimer = setTimeout(() => { creditTimer = 0; shown.classList.remove('maplibregl-compact-show', 'mapboxgl-compact-show') }, CREDIT_FOLD_MS) + return + } + const credit = box.querySelector('.maplibregl-ctrl-attrib, .mapboxgl-ctrl-attrib') + if (!shown && credit && !/attrib-empty/.test(credit.className)) return + if (triesLeft > 0) creditTimer = setTimeout(() => foldCredit(triesLeft - 1), 500) +} +function addCompactCredit(map, gl) { + if (typeof gl?.AttributionControl !== 'function') return false + try { map.addControl(new gl.AttributionControl({ customAttribution: GB_CREDIT }), 'bottom-right') } catch (_) { return false } + foldCredit() + return true +} const mapRef = shallowRef(null) const rows = ref([]) const selected = ref(null) @@ -1828,10 +1861,11 @@ onMounted(async () => { * against https://tiles.openfreemap.org/planet before writing this. */ const isMaplibre = grProvider === 'maplibre' - addMapAttribution(map, { boundaries: true, basemap: !isMaplibre }) - if (isMaplibre && typeof gl.AttributionControl === 'function') { - try { map.addControl(new gl.AttributionControl({ compact: false })) } catch (_) { /* credit is not worth breaking the map for */ } - } + // ★ NOW ONE CREDIT: the engine's own control on BOTH engines, compact on a narrow map (plugin-designer 5.8), + // with geoBoundaries as custom attribution. It still reads the basemap wording from the style, as above; under + // Mapbox the style names Mapbox and OpenStreetMap. An engine without the control keeps the shared line, with + // the basemap credit it can name. + if (!addCompactCredit(map, gl)) addMapAttribution(map, { boundaries: true, basemap: !isMaplibre }) /** * ═══ 'load' MAY HAVE ALREADY FIRED ════════════════════════════════════════════════════ * @@ -1862,6 +1896,7 @@ onMounted(async () => { } catch (e) { err.value = String(e.message || e) } }) onBeforeUnmount(() => { + if (creditTimer) { clearTimeout(creditTimer); creditTimer = 0 } if (typeof window !== 'undefined') window.removeEventListener('dxmaps:mapinfo-host', reannounceInfoPending) try { legendRO?.disconnect() } catch (_) {} try { detachEdges?.() } catch (_) {} @@ -2012,11 +2047,15 @@ useShadowStyles( // 2,107 pins to queryRenderedFeatures, because that API does not care whether anything // is visible. The size is stated outright now so no engine stylesheet can take it. '.gm-map{position:absolute;inset:0;width:100%;height:100%}' + - // THE GALLERY'S ROUND ☰ sits at a phone's bottom corner (bottom-left; bottom-right in Arabic) and publishes - // --map-fab-clearance while it shows. The engine's bottom corners, where this map's licence credit (and a - // Mapbox wordmark) live, lift by it: the rule every LegendMobile map already has. Unset anywhere else, so - // desktop and a map without the button are unchanged. It covered the credit's first/last ~30-40px before. - '.mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl-bottom-right,.maplibregl-ctrl-bottom-left,.maplibregl-ctrl-bottom-right{transform:translateY(calc(-1 * var(--map-fab-clearance,0px)))}' + + // THE CREDIT CORNER (plugin-designer 5.8; this map has no footer sheet, its legend panel is at the top). The + // engine's compact credit sits at the bottom END corner, opposite the gallery's round ☰ (bottom-right; + // bottom-left in Arabic), and is NOT lifted by --map-fab-clearance: lifted, it floated up the page. Where the + // round button can show, the corner sits above the scroll-edge strips so a tap on its (i) lands, and the + // expanded credit stops 64px short of the far edge, clear of the button. + '@media(max-width:767.98px){' + + '.gm .maplibregl-ctrl-bottom-right,.gm .mapboxgl-ctrl-bottom-right{inset-inline-end:0 !important;inset-inline-start:auto !important;max-width:calc(100% - 64px);z-index:3}' + + '.gm .maplibregl-ctrl-bottom-right .maplibregl-ctrl,.gm .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl{margin:0 0 10px;margin-inline-end:10px}' + + '}' + // LEGEND: a BOUNDED, left-anchored panel — NOT a full-width strip. // It used to be `left:8px;right:8px`, which is a strip spanning the whole map // (MEASURED 1264px of a 1280px map, 98.75%). That collided with the floating diff --git a/public/dx-maps/all-people-groups/all-people-groups/index.html b/public/dx-maps/all-people-groups/all-people-groups/index.html index 77b413376..0f34812e8 100644 --- a/public/dx-maps/all-people-groups/all-people-groups/index.html +++ b/public/dx-maps/all-people-groups/all-people-groups/index.html @@ -26,9 +26,15 @@ check:leaf-engine-per-provider (0 Mapbox requests under maplibre; Mapbox unchanged). --> ${l(a||n)}`;if(mu()){let x=null;const k=()=>setTimeout($,0),$=()=>{clearTimeout(x),window.removeEventListener("afterprint",k);try{URL.revokeObjectURL(i)}catch{}};return window.addEventListener("afterprint",k),x=setTimeout($,as),fu(c,{onNeedsTap:r,labels:s}).then(b=>{b==="blocked"&&$()},$),!0}const u=hu(c,{autoprint:!0});if(!u)return URL.revokeObjectURL(i),!1;let g=null;const h=()=>{clearTimeout(g);try{URL.revokeObjectURL(i)}catch{}};g=setTimeout(h,as);try{u.addEventListener("afterprint",()=>setTimeout(h,0)),u.addEventListener("load",()=>setTimeout(h,ns),{once:!0})}catch{setTimeout(h,ns)}return!0}async function dA({waitFor:e=[],onBusy:t=null,timeoutMs:o=8e3}={}){const n=s=>{try{t&&t(s)}catch{}};n(!0);let a="ready",r=null;try{const s=(Array.isArray(e)?e:[e]).filter(Boolean);if(s.length&&o>0){const i=Promise.allSettled(s).then(()=>"ready"),l=new Promise(c=>{r=setTimeout(()=>c("timeout"),o)});a=await Promise.race([i,l])}return typeof window>"u"||typeof window.print!="function"?a="no-print":(window.print(),a)}finally{r&&clearTimeout(r),n(!1)}}const pA=Object.freeze(Object.defineProperty({__proto__:null,FRAME_CEILING_MS:as,FRAME_KEEP_MS:ns,PRINT_ROOT_ID:Di,TAP_ID:bu,isPhonePrint:mu,openPrintFrame:hu,printDocument:fu,printImageBlob:uA,printWhenReady:dA},Symbol.toStringTag,{value:"Module"})),gA='class="stage"';async function hA(e){const t=new Uint8Array(await e.slice(0,12).arrayBuffer()),o=(n,a)=>String.fromCharCode(...t.slice(n,a));return o(0,4)==="RIFF"&&o(8,12)==="WEBP"||t[0]===137&&o(1,4)==="PNG"||t[0]===255&&t[1]===216&&t[2]===255}async function fA(e,t=fetch){let o;try{o=await t(e)}catch{return null}if(!o||!o.ok)return null;const n=(o.headers.get("content-type")||"").toLowerCase();if(n.startsWith("text/"))return null;const a=await o.blob();return n.startsWith("image/")||await hA(a)?a:null}async function mA(e,t=fetch){const o=await t(e);if(!o.ok)return{html:null,status:o.status};const n=await o.text();return n.includes(gA)?{html:n,status:o.status}:{html:null,status:404}}const bA=(e,t)=>String(e).replace(/\{(\w+)\}/g,(o,n)=>t&&t[n]!=null?String(t[n]):o);function zi(e,t,o={}){const n=new Error(bA(t,o));return n.i18nKey=e,n.i18nParams=o,n}function $A(e,t){if(e&&e.i18nKey&&typeof t=="function"){const o=t(e.i18nKey,e.i18nParams||{});if(typeof o=="string"&&o&&o!==e.i18nKey)return o}return String(e&&e.message||e)}const _A={map_page_kicker:"UNENGAGED PEOPLE GROUPS",map_page_sub:"A prayer list of {count} unreached people groups",map_unavailable:"The map for this prayer list is not included in this printing.",map_page_series:"One of {k} UPG 100 prayer lists",zone_affinity_A017:"Deaf people groups",list_header:"{from}–{to} of {count} · pray for them by name",people_suffix:"people",no_photo:"no photo available",deck_title:"Prayer Cards",card_country:"Country",card_population:"People",card_language:"Language",card_religion:"Religion",card_cta:"Pray for them",points_title:"12 Prayer Points",points_intro:"One hour with God, five minutes each. Pray these twelve for the people groups in this packet.",points_attrib:"Prayer rhythms inspired by Metacamp · metacamp.org",wheel_title:"Prayer Wheel",wheel_intro:"Spend an hour with God? It is easier than you think. Break it into 5-minute blocks and focus on one of these during each segment.",wheel_minutes:"minutes",wheel_attrib:"Prayer Wheel · Metacamp Module 1 · metacamp.org",privacy_note:"Marker positions are approximate · people-group locations are generalised at the source for privacy",rhythms:{praise:"PRAISE",wait:"WAIT",confess:"CONFESS",read:"READ THE WORD",ask:"ASK",intercede:"INTERCESSION",pray_word:"PRAY THE WORD",thank:"THANK",sing:"SING",meditate:"MEDITATE",listen:"LISTEN",praise_end:"PRAISE"},points:[{n:1,rhythm:"praise",sub:"Two-Cross Gospel",text:"That they accept Christ as King, forgiven by His cross — and take up their own cross, willing to suffer in a persecution zone. May the Lamb receive the reward of His suffering, starting with me.",refs:["Matthew 27:27","Matthew 16:24–25"]},{n:2,rhythm:"wait",sub:"Holy Spirit & deliverance",text:"That they are filled with the Spirit and made bold witnesses; not isolated; baptized in water and Spirit; every evil grip broken. That believers in neighbouring peoples are mobilised as laborers.",refs:["Acts 1:8","John 14:26"]},{n:3,rhythm:"confess",sub:"",text:"We confess unbelief that this is too big for God. That He revives our love and faith for the global unreached.",refs:["John 3:16","1 John 1:9"]},{n:4,rhythm:"read",sub:"the Father's love",text:"That they would be SAVED and KNOW their heavenly Father loves them — and that through them He would bring salvation to their family, friends, neighbours, coworkers, classmates, everyone they know personally.",refs:["Mark 5:1–20","Acts 16:31"],print_passage:!0},{n:5,rhythm:"ask",sub:"baptisms",text:"New baptisms among the locals, and every baptized one trained to baptize others, multiplying.",refs:["Acts 16","Acts 10"]},{n:6,rhythm:"intercede",sub:"laborers & shepherds",text:"Laborers sent to deeply love a people they have never met; good shepherds raised up from within.",refs:["Luke 10:2","John 10:11–15"]},{n:7,rhythm:"pray_word",sub:"Persons & Houses of Peace",text:"That laborers and local leaders find the receptive person and the house of peace, and disciple them well.",refs:["Luke 10:5–7","Matthew 10:5–14"]},{n:8,rhythm:"thank",sub:"rest",text:"Thank God for the rest He has given; that they enter that rest in Christ, and that their spiritual family is a safe place.",refs:["Mark 4:26–29","Hebrews 4:9–11"]},{n:9,rhythm:"sing",sub:"",text:"That the people group is flooded with worshippers — worship everywhere, in freedom and in chains.",refs:["Acts 16:25–26","Colossians 3:16"]},{n:10,rhythm:"meditate",sub:"communion",text:"That they take communion together and grasp the weight of His death, burial and resurrection.",refs:["Matthew 26:26–30","1 Corinthians 11:23–26"]},{n:11,rhythm:"listen",sub:"accountability",text:"Not only that they would listen, but that they would have people in their life who help them read the Word, pray, commit to community, and respond to suffering well — resisting the adversary who comes after believers across the whole world.",refs:["1 Peter 5:6–11","Hebrews 10:24–25"]},{n:12,rhythm:"praise_end",sub:"great · greater · greatest",text:"Praise in advance — for salvations; GREATER, for spiritual families multiplying; GREATEST, for lives yielded and local church planters raised up who multiply healthy churches across the whole people group, no matter the cost.",refs:["Acts 2:42–47","Matthew 28:19–20"]}],point4_passage:"And they came to the other side of the sea, into the country of the Gadarenes. When he had come out of the boat, immediately a man with an unclean spirit met him out of the tombs. He lived in the tombs. Nobody could bind him any more, not even with chains, because he had been often bound with fetters and chains, and the chains had been torn apart by him, and the fetters broken in pieces. Nobody had the strength to tame him. Always, night and day, in the tombs and in the mountains, he was crying out, and cutting himself with stones. When he saw Jesus from afar, he ran and bowed down to him, and crying out with a loud voice, he said, “What have I to do with you, Jesus, you Son of the Most High God? I adjure you by God, don’t torment me.” For he said to him, “Come out of the man, you unclean spirit!” He asked him, “What is your name?” He said to him, “My name is Legion, for we are many.” He begged him much that he would not send them away out of the country. Now on the mountainside there was a great herd of pigs feeding. All the demons begged him, saying, “Send us into the pigs, that we may enter into them.” At once Jesus gave them permission. The unclean spirits came out and entered into the pigs. The herd of about two thousand rushed down the steep bank into the sea, and they were drowned in the sea. Those who fed them fled, and told it in the city and in the country. The people came to see what it was that had happened. They came to Jesus, and saw him who had been possessed by demons sitting, clothed, and in his right mind, even him who had the legion; and they were afraid. Those who saw it declared to them what happened to him who was possessed by demons, and about the pigs. They began to beg him to depart from their region. As he was entering into the boat, he who had been possessed by demons begged him that he might be with him. He didn’t allow him, but said to him, “Go to your house, to your friends, and tell them what great things the Lord has done for you and how he had mercy on you.” He went his way, and began to proclaim in Decapolis how Jesus had done great things for him, and everyone marveled.",point4_passage_credit:"Mark 5:1–20 · World English Bible (public domain)",point4_heart_title:"THE HEAVENLY FATHER'S HEART",point4_heart_lede:"They need to know who their Father in heaven is: He loves them, He wants to save them, and through them He wants to reach their neighbours who have never heard. Read how He did it for these seven:",point4_heart:[{who:"Noah",ref:"Genesis 6:11–7:1"},{who:"Lot",ref:"Genesis 19:12–23"},{who:"Rahab",ref:"Joshua 2:17–20"},{who:"The Gerasene",ref:"Mark 5:12–20"},{who:"Cornelius",ref:"Acts 10:23–25"},{who:"Lydia",ref:"Acts 16:14–15"},{who:"The jailer",ref:"Acts 16:31"}],funnel_kicker:"UPG 100",funnel_title:"How this list was found",funnel_intro:"Every people group on earth, narrowed to the ones with no church and no worker.",funnel_all:"people groups in the world",funnel_unreached:"are unreached — under 2% evangelical",funnel_frontier:"are frontier — 0.1% or less, and almost no one is going",funnel_doxa:"are on this prayer network’s list",funnel_doxa_note:"{frontier} of them are frontier",funnel_this:"are in THIS packet",funnel_packets:"UPG 100 prayer packets divide the {total} among them",funnel_share:"This packet is 1 of {k} lists · {pct}% of the {total}",funnel_foot:"Counts from {imb_pub}, captured {imb_date}. {unassigned} of the {total} are not yet in any list.",funnel_people:"people",funnel_foot_source:"Counts from {imb_pub}, captured {imb_date}.",funnel_share_pct:"This packet is {pct}% of the {total}",funnel_list_doxa:"Project 2000: this prayer network’s people groups",funnel_list_unreached:"Project 7000: unreached people groups",funnel_list_frontier:"Project 4700: frontier people groups","funnel_list_frontier-500k":"Project 700: frontier people groups of 500,000 or more",funnel_list_unengaged:"Unengaged unreached people groups",zone_join:"{a} + {b}",zone_join_more:"{a} + {n} more",life_kicker:"Perspective",life_title:"A human life, in weeks",life_lede:"One hundred years. Fifty-two weeks each. Five thousand two hundred boxes — and that is the whole of it. Every week you will ever pray is one of these.",life_foot:"100 years x 52 weeks = 5,200 weeks.",life_weeks:"weeks",christian_kicker:"The same 5,200 boxes",christian_title:"Who among them knows Christ",christian_lede:"Each box is an equal share of the people in this list. The shaded boxes are the share who follow Jesus (evangelical); the rest are still waiting.",christian_reached:"Followers of Jesus",christian_lost:"Not yet following Jesus",christian_people:"people",christian_coverage:"computed over {matched} of the {total} groups in this list",christian_foot:'Population-weighted from peoplegroups.org EvangelicalLevel, taking each published band at its midpoint and 0 for "No Known Evangelicals"',pray_blessed:"Blessed them",pray_know:"What to know",pray_do:"What to do",funnel_eq_title:"How each number is reached",funnel_tip:"This packet is 1 of {n} UPG-100 lists",desc_where_both:"The {people} live in {country} and speak {language}.",desc_where_country:"The {people} live in {country}.",desc_where_language:"The {people} speak {language}.",desc_gsec_0:"There are no known evangelical Christians among them, no church, and no gospel resource in their language.",desc_gsec_1:"Fewer than 2% are evangelical, and no one has been planting churches among them in the last two years.",desc_gsec_2:"Fewer than 2% are evangelical, though church planting has begun in some communities.",desc_gsec_3:"Fewer than 2% are evangelical, and scattered church planting has begun among them.",desc_gsec_4:"Between 2% and 5% are evangelical.",desc_gsec_5:"Between 5% and 10% are evangelical.",desc_gsec_6:"More than 10% are evangelical.",sets_kicker:"Who is near, and who is not",sets_title:"Two ways to see the same people",sets_lede:"The circle asks whether a people has been reached. The rings ask how far they sit from someone who could reach them.",sets_bounded_title:"Reached, and not",sets_centered_title:"Distance from a neighbour",sets_bounded_tally:"{inside} inside · {outside} outside · {unknown} unknown",sets_centered_tally:"{placed} placed · {centre} at the centre · {unplaced} unplaced",ring_band_dialect:"Same dialect",ring_band_language:"Same language",ring_band_family:"Same language family",ring_band_geography:"Nearby only",ring_culture_same:"same people",ring_culture_near:"near culture",ring_culture_different:"different culture",ring_join:"{band}, {culture}",life_jesus:"The life of Jesus, about 33 years",life_full:"A full life of 100 years",sets_head_bounded:"Bounded set",sets_head_centered:"Centered set",sets_rings_head:"The rings, from the centre outward",light_intro:"You might take one people group a day and pray for them by name. A tick in a box is a simple way to remember who you have carried to God — there is no pace to keep.",light_col_group:"People group",books:{Matthew:"Matthew",Acts:"Acts",John:"John","1 John":"1 John",Mark:"Mark",Luke:"Luke",Hebrews:"Hebrews",Colossians:"Colossians","1 Corinthians":"1 Corinthians","1 Peter":"1 Peter",Genesis:"Genesis",Joshua:"Joshua"},christian_per_cell:"Each box ≈ {n} people"},yA="sha384-4WSG3wwwJPbiHnP+Jn6hxsv1KpgOp4BL9NWA19W3DcYbb9DBrrhpQZDAoPzVj1wo";async function vA(e){const t=new Uint8Array(await crypto.subtle.digest("SHA-384",new TextEncoder().encode(e)));let o="";for(const n of t)o+=String.fromCharCode(n);return`sha384-${btoa(o)}`}const wA=((Cm=globalThis.document)==null?void 0:Cm.baseURI)||void 0,$u=Object.freeze({en:_A}),ji=new Map;function im(e){if(!e)return Promise.resolve($u);let t;try{t=new URL(`${e}/lib/packet-strings.json`,wA).href}catch{return Promise.resolve($u)}if(!ji.has(t)){const o=(async()=>{const n=await fetch(t,{cache:"force-cache"});if(!n.ok)throw new Error(`not served (${n.status})`);const a=await n.text();if(await vA(a)!==yA)throw new Error("failed its integrity check");return JSON.parse(a)})().catch(n=>(console.warn(`[packetStrings] ${t}: ${(n==null?void 0:n.message)||n} — printing in English`),ji.delete(t),$u));ji.set(t,o)}return ji.get(t)}const hr=e=>String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'");function kA(e={}){const t=e.state||ke(null),o=()=>t.value!==null,n=e.label||(y=>(y==null?void 0:y.label)||(y==null?void 0:y.id)||""),a=e.lang||(()=>"en"),r=e.stock||(()=>"poker"),s=(y,f,w=null,A=void 0)=>{const S=()=>String(f).replace(/\{(\w+)\}/g,(W,K)=>w&&w[K]!=null?String(w[K]):W);if(typeof e.t!="function")return S();const G=e.t(y,w||{},A);return G&&G!==y?G:S()},i=(y,f,w,A,S)=>{t.value={label:n(y),kind:f,phase:w,message:A,pct:S}},l=(y,f)=>{i(y,f,"done",s("wall.hud.printOpened","print dialog opened"),100),setTimeout(()=>{t.value=null},900)},c=(y,f,w)=>{i(y,f,"error",$A(w,(A,S)=>s(A,A,S)).slice(0,140),0)};function u(){const y=t.value;t.value=null;try{y&&typeof y.cancel=="function"&&y.cancel()}catch{}}async function g(y,f,w){await fu(y,{onNeedsTap:e.tapToPrint?(S,G)=>{t.value={label:n(f),kind:w,phase:"ready",message:s("wall.hud.readyToPrint","ready to print"),pct:100,action:()=>{t.value=null,S()},cancel:G}}:null,labels:{print:s("wall.hud.printNow","Print"),close:s("buttons.close","Close")}})!=="needs-tap"&&l(f,w)}async function h(y){if(typeof e.records!="function")throw new Error("this wall supplies no records provider");const f=await e.records(y);if(!f||!f.length)throw zi("wall.hud.noRecords","that list has no people groups to print");return f}async function x(y){if(o())return;const f=s("wall.hud.kindPoster","Poster"),w=e.assetsBase?e.assetsBase():null,A=(y==null?void 0:y.id)??y;i(y,f,"fetch",s("wall.hud.fetchingPoster","fetching the poster"),25);try{if(e.sheet){i(y,f,"render",s("wall.hud.generatingPoster","generating the poster"),35);const T=await Promise.resolve().then(()=>e.sheet(y,"letter")).catch(()=>"");if(T){i(y,f,"save",s("wall.hud.openingPrint","opening the print dialog"),80),await g(T,y,f);return}}const G=(e.posterUrl?e.posterUrl(y):null)||(w?`${w}/posters/${A}.webp`:null),W=G?await fA(G):null;if(W){await k(W,y,f);return}if(!w)throw zi("wall.hud.noPoster","no poster is available for this list");i(y,f,"render",s("wall.hud.generatingPoster","generating the poster"),45);const K=await mA(`${w}/posters/${A}.html`);if(!K.html)throw zi("wall.hud.posterNotFound","poster not found ({status})",{status:K.status});const{rasterPoster:v,looksRendered:I}=await Bt(async()=>{const{rasterPoster:T,looksRendered:U}=await Promise.resolve().then(()=>AA);return{rasterPoster:T,looksRendered:U}},void 0),P=await v(K.html,{scale:2});if(!await I(P))throw zi("wall.hud.posterBlank","the generated poster came out blank");await k(P,y,f)}catch(S){c(y,f,S)}}async function k(y,f,w){i(f,w,"save",s("wall.hud.openingPrint","opening the print dialog"),80);const A=URL.createObjectURL(y),S=`${e.downloadPrefix||"doxa"} — ${n(f)}`;await g(''+hr(S)+`${hr(n(f))}`,f,w)}async function $(y,{columns:f,title:w}={}){if(o())return;const A=s("wall.hud.kindList","List");i(y,A,"fetch",s("wall.hud.fetchingRecords","fetching records"),30);try{const S=await h(y),G=f||[{key:"name",head:"Name",cls:"c-name"},{key:"country_code_label",head:"Country"},{key:"population",head:"People",cls:"c-num",num:!0},{key:"religion_label",head:"Religion"}],W=G.map(I=>`${hr(I.head)}`).join("")+'✓',K=S.map(I=>""+G.map(P=>{const T=I[P.key]??I[`${P.key}_label`]??"—",U=P.num&&Number.isFinite(Number(T))?Number(T).toLocaleString(a()):T;return`${hr(U)}`}).join("")+'□').join("");i(y,A,"render",s("wall.hud.layingOutRows","laying out {n} rows",{n:S.length},S.length),70);const v=w||`${n(y)} — ${S.length}`;await g(`${hr(v)}

${hr(v)}

${S.length} entries · pray for them by name

${W}${K}
`,y,A)}catch(S){c(y,A,S)}}async function b(y){if(o())return;const f=s("wall.hud.kindCardDeck","Card Deck");i(y,f,"fetch",s("wall.hud.fetchingRecords","fetching records"),30);try{const w=await h(y),[{createCardRenderer:A},S]=await Promise.all([Bt(()=>Promise.resolve().then(()=>MA),void 0),e.strings?e.strings():im(e.assetsBase?e.assetsBase():null)]);i(y,f,"render",s("wall.hud.layingOutCards","laying out {n} cards",{n:w.length},w.length),70);const G=A({strings:S,lang:a(),stock:r()});await g(G.buildDeck(y,w),y,f)}catch(w){c(y,f,w)}}function m(y,f,w=null){return f.liftMap(y,{labelFor:w})}async function d(){if(!e.iso3Names)return null;try{const[y,{makeCountryLabelFor:f},w]=await Promise.all([Promise.resolve().then(()=>e.iso3Names()),Bt(()=>Promise.resolve().then(()=>FA),void 0),Bt(()=>Promise.resolve().then(()=>fm),void 0).then(A=>A.countryName||A.default)]);return f({iso3Name:y,countryName:w,lang:a()})}catch{return null}}async function _(y,f,w){for(const A of w.mapSheetFiles(f))try{const S=await fetch(`${y}/posters/${A}`,{cache:"force-cache"});if(!S.ok)continue;const G=await S.text();if(w.liftMap(G))return G}catch{}return""}async function p(y,{mapSvg:f="",funnel:w=null,variant:A="full"}={}){if(o())return;const S=s("wall.hud.kindPacket","Prayer Packet");i(y,S,"fetch",s("wall.hud.fetchingGroups","fetching people groups"),30);try{const G=await h(y),[{createPacketRenderer:W},{buildPacketSets:K},{evangelicalShareFor:v},I,P,T,U]=await Promise.all([Bt(()=>Promise.resolve().then(()=>QA),void 0),Bt(()=>Promise.resolve().then(()=>tL),void 0),Bt(()=>Promise.resolve().then(()=>nL),void 0),e.strings?e.strings():im(e.assetsBase?e.assetsBase():null),e.christian?e.christian():Promise.resolve(null),e.christian?Promise.resolve(null):Bt(()=>Promise.resolve().then(()=>aL),void 0).then(pe=>pe.default||pe).catch(()=>null),e.sets?Promise.resolve().then(()=>e.sets()).catch(()=>null):Promise.resolve(null)]),C=P||v(G,T),O=await Promise.all([Bt(()=>Promise.resolve().then(()=>cL),void 0).then(pe=>pe.describeGroup).catch(()=>null),Bt(()=>Promise.resolve().then(()=>fm),void 0).then(pe=>pe.countryName||pe.default).catch(()=>null)]).then(([pe,ye])=>pe?Se=>pe(Se,a(),{strings:I,countryName:ye||void 0}):null),N=W({strings:I,lang:a(),describe:O}),q=new Map(G.filter(pe=>pe.image_url).map(pe=>[pe.slug,pe.image_url]));i(y,S,"render",s("wall.hud.printingGroups","printing {n} people groups",{n:G.length},G.length),70),w||(w=await Bt(()=>Promise.resolve().then(()=>uL),void 0).then(pe=>{const ye=pe.default||pe;return ye&&Array.isArray(ye.steps)&&ye.steps.length?{steps:ye.steps}:null}).catch(()=>null));const D=y&&"mapId"in y?y.mapId:y==null?void 0:y.id;if(!f&&e.sheet&&D){const pe=D===(y==null?void 0:y.id)?y:{...y,id:D},ye=await Promise.resolve().then(()=>e.sheet(pe,"packet")).catch(()=>"");ye&&(f=N.liftMap(ye,{labelFor:await d()}))}if(!f&&e.mapImage){const pe=await Promise.resolve().then(()=>e.mapImage(y)).catch(()=>null);f=pe?N.mapFromImage(pe):""}if(!f&&e.assetsBase&&D){const[pe,ye]=await Promise.all([_(e.assetsBase(),D,N),d()]);f=m(pe,N,ye)}const ae=U?K(G,U):null;await g(N.buildPacket(y,G,q,f,w,C,A,ae),y,S)}catch(G){c(y,S,G)}}return{exportState:t,dismiss:u,printPoster:x,printList:$,printCardDeck:b,printPacket:p}}const xA=Object.freeze(Object.defineProperty({__proto__:null,usePosterPrints:kA},Symbol.toStringTag,{value:"Module"}));function lm(e){const t=(e.match(//)||[])[0];if(!t)throw new Error("poster HTML contains no ");const n=((e.match(/`}const yu={poker:{w:2.5,h:3.5,label:"Card 2.5 × 3.5 in",noun:"card"},pokerLandscape:{w:3.5,h:2.5,label:"Card 3.5 × 2.5 in (landscape)",noun:"card",side:!0},postcard:{w:4,h:6,label:"Postcard 4 × 6 in",noun:"postcard"},postcardLandscape:{w:6,h:4,label:"Postcard 6 × 4 in (landscape)",noun:"postcard",side:!0},a6:{w:4.13,h:5.83,label:"A6 105 × 148 mm"}},Gi=96;function EA({strings:e,lang:t="en",stock:o="poker",siteBase:n="https://pray.doxa.life"}={}){const a=e.en,r=e[t]||{},s=A=>r[A]!==void 0?r[A]:a[A],i=A=>String(A??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,"""),l=(A,S)=>{const G=A[`${S}_label`];if(G)return String(G);const W=A[S];return W&&typeof W=="object"?String(W.label??W.value??""):W==null?"":String(W)},c=A=>Number.isFinite(Number(A))?Number(A).toLocaleString(t):"—",u=yu[o]||yu.poker,g=Math.round(u.w*Gi),h=Math.round(u.h*Gi),x=u.side===!0,k=180,$=Math.round(200/k*Gi),b=Math.round(250/k*Gi),m=Math.round(g*.42),d=Math.max(Math.round(h*.46),b+24),_=A=>t==="en"?`${n}/${A}`:`${n}/${t}/${A}`;function p(){return` + `}function Pa(e){return e==="<"?"<":e===">"?">":"&"}const sm=Object.freeze(Object.defineProperty({__proto__:null,posterPixels:em,useMapPoster:J2},Symbol.toStringTag,{value:"Module"}));function hu(e,{autoprint:t=!1}={}){const o=document.createElement("iframe");o.setAttribute("aria-hidden","true"),o.style.cssText="position:fixed;right:0;bottom:0;width:1px;height:1px;opacity:0;border:0",document.body.appendChild(o);const n=o.contentDocument;if(!n)return o.remove(),null;n.open(),n.write(e),t&&n.write(' - + diff --git a/public/dx-maps/manifest.json b/public/dx-maps/manifest.json index c89f7c6bb..990bec30d 100644 --- a/public/dx-maps/manifest.json +++ b/public/dx-maps/manifest.json @@ -110,7 +110,7 @@ "bundle": "cities-internet", "scriptUrl": "./cities-internet/cities-internet.js", "output": "dx-maps/cities-internet/cities-internet.js", - "integrity": "sha384-tif0pG2wzTpnl7vOsbbl8NEwHo/dzH5M3Nt1o2Reb7YjKNdVHnp3VaTC6yfXLOZH", + "integrity": "sha384-L8esYBcQEbfGmPZOoGz0i5ZXg78NPjrnhZw5l7QNc0Nxbvk8LAIgVh5aVPFDhi4n", "elements": [ "cities-internet-map" ], @@ -129,7 +129,7 @@ "scriptUrl": "./cities-internet/cities-internet.js", "embed": { "iframe": "", - "script": "\n\n\n" + "script": "\n\n\n" } }, { @@ -145,8 +145,8 @@ "scriptUrl": "./cities-internet/cities-internet.js", "embed": { "iframe": "", - "script": "\n\n\n\n\n\n", - "scriptMapbox": "\n\n\n\n\n\n" + "script": "\n\n\n\n\n\n", + "scriptMapbox": "\n\n\n\n\n\n" } }, { @@ -162,7 +162,7 @@ "scriptUrl": "./cities-internet/cities-internet.js", "embed": { "iframe": "", - "script": "\n\n\n" + "script": "\n\n\n" } } ] @@ -266,7 +266,7 @@ "bundle": "dashboard-plus-grid", "scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js", "output": "dx-maps/dashboard-plus-grid/dashboard-plus-grid.js", - "integrity": "sha384-a+tiDKhGFDFdp7hwxKvDlkJe3P2Q9MdhFdtEbbvpTcQL6BSQsBk1LoGeZJwolbq0", + "integrity": "sha384-d3czDxQB26tYiOeHqFWB2YRJ7vRIeJmBxmHSDuNlCiR6GAIP9yyZXDRh8jaqwR/U", "elements": [ "dashboard-plus-grid" ], @@ -290,8 +290,8 @@ "scriptUrl": "./dashboard-plus-grid/dashboard-plus-grid.js", "embed": { "iframe": "", - "script": "\n\n\n\n\n\n", - "scriptMapbox": "\n\n\n\n\n\n" + "script": "\n\n\n\n\n\n", + "scriptMapbox": "\n\n\n\n\n\n" }, "instances": [ { @@ -361,7 +361,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-EREDaDeIxIktotZCkSABFU3A4tC0BgYv6z3nrb4iBKZjcZU1Y3Zrdzp+vBxziRfj", + "integrity": "sha384-plsKE4b58wCdqTuVCcU9yyy0BOf3Yc83/V3XBQNDOhlrNTAcWbwRyZNUf5C+pMfH", "elements": [ "doxa-maps-spa" ], @@ -389,7 +389,7 @@ "scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js", "embed": { "iframe": "", - "script": "\n\n\n" + "script": "\n\n\n" }, "instances": [ { @@ -416,7 +416,7 @@ "bundle": "doxa-research-map", "scriptUrl": "./doxa-research-map/doxa-research-map.js", "output": "dx-maps/doxa-research-map/doxa-research-map.js", - "integrity": "sha384-djHb+zHwzGct4AEoPQH9qHQ7yBM7QZQ2CRdldYx1v+OinweFEQ8wzXDIZNbPFLme", + "integrity": "sha384-VrZwy4rE4u08ZDOoUR25po3cewKY0OFlcSzlLapbHgymUJozJ0h5OMGUOYgVx4XA", "elements": [ "doxa-research-map" ], @@ -435,8 +435,8 @@ "scriptUrl": "./doxa-research-map/doxa-research-map.js", "embed": { "iframe": "", - "script": "\n\n\n\n\n\n", - "scriptMapbox": "\n\n\n\n\n\n" + "script": "\n\n\n\n\n\n", + "scriptMapbox": "\n\n\n\n\n\n" }, "instances": [ { @@ -462,7 +462,7 @@ "bundle": "doxa-simple-map", "scriptUrl": "./doxa-simple-map/doxa-simple-map.js", "output": "dx-maps/doxa-simple-map/doxa-simple-map.js", - "integrity": "sha384-ds7okf/X8r7u/6dBhE3pWFbEhANjZxTitJe8vpm9ymAS3SGudtOLWuOm4OL5x9DS", + "integrity": "sha384-rWyPXX+JpYEmvOzWVRLPmWk7kru7FuwCYQJapQN9Z+uIcqxozXWLzML7gf1hZrz+", "elements": [ "doxa-map", "doxa-simple-map" @@ -482,8 +482,8 @@ "scriptUrl": "./doxa-simple-map/doxa-simple-map.js", "embed": { "iframe": "", - "script": "\n\n\n\n\n\n", - "scriptMapbox": "\n\n\n\n\n\n" + "script": "\n\n\n\n\n\n", + "scriptMapbox": "\n\n\n\n\n\n" }, "instances": [ { @@ -782,7 +782,7 @@ "bundle": "gospel-resources", "scriptUrl": "./gospel-resources/gospel-resources.js", "output": "dx-maps/gospel-resources/gospel-resources.js", - "integrity": "sha384-mxkrThGB0uYGk9rFlpe/lQXazJAhEejyoDXJ8nu2dxnJX8CTABzet+/78lf1jHwX", + "integrity": "sha384-kQvWf/yZkq107UE4L1Hk82rgKvy/4IfgCUevdgq56fXBxZdE40Zt/2u/ERWYXb4X", "elements": [ "gospel-resources-map" ], @@ -805,7 +805,7 @@ "scriptUrl": "./gospel-resources/gospel-resources.js", "embed": { "iframe": "", - "script": "\n\n\n" + "script": "\n\n\n" } }, { @@ -821,7 +821,7 @@ "scriptUrl": "./gospel-resources/gospel-resources.js", "embed": { "iframe": "", - "script": "\n\n\n" + "script": "\n\n\n" } }, { @@ -837,8 +837,8 @@ "scriptUrl": "./gospel-resources/gospel-resources.js", "embed": { "iframe": "", - "script": "\n\n\n\n\n\n", - "scriptMapbox": "\n\n\n\n\n\n" + "script": "\n\n\n\n\n\n", + "scriptMapbox": "\n\n\n\n\n\n" } }, { @@ -854,7 +854,7 @@ "scriptUrl": "./gospel-resources/gospel-resources.js", "embed": { "iframe": "", - "script": "\n\n\n" + "script": "\n\n\n" } }, { @@ -870,8 +870,8 @@ "scriptUrl": "./gospel-resources/gospel-resources.js", "embed": { "iframe": "", - "script": "\n\n\n\n\n\n", - "scriptMapbox": "\n\n\n\n\n\n" + "script": "\n\n\n\n\n\n", + "scriptMapbox": "\n\n\n\n\n\n" } } ] @@ -1192,7 +1192,7 @@ "bundle": "research-dashboard", "scriptUrl": "./research-dashboard/research-dashboard.js", "output": "dx-maps/research-dashboard/research-dashboard.js", - "integrity": "sha384-iZ5r/lnLROwFUySFeiBBYCBNDGmmDHr04Ry4+I8xP1kvDqkRurrYtYzrnseHLUAa", + "integrity": "sha384-gaOt8WKJbyNMc3K9RwILEb3iM2y6BjQsXfA94Gu8LSARUJKYjV0jXOvp4A1oTU6G", "elements": [ "research-dashboard-map" ], @@ -1211,8 +1211,8 @@ "scriptUrl": "./research-dashboard/research-dashboard.js", "embed": { "iframe": "", - "script": "\n\n\n\n\n\n", - "scriptMapbox": "\n\n\n\n\n\n" + "script": "\n\n\n\n\n\n", + "scriptMapbox": "\n\n\n\n\n\n" }, "bundle": "research-dashboard" } diff --git a/public/dx-maps/my-upg-100-list/poster-wall/index.html b/public/dx-maps/my-upg-100-list/poster-wall/index.html index 8cbc5aee8..d29e3c0f5 100644 --- a/public/dx-maps/my-upg-100-list/poster-wall/index.html +++ b/public/dx-maps/my-upg-100-list/poster-wall/index.html @@ -17,9 +17,15 @@ which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->