One map-print template, piloted on Simple Research: landscape, the map's name, the legend beside it, a prayer line

The map's "Export poster…" now prints through a shared template: always landscape Letter; a header with the
map's name (from the host's profile-config mapTitle), the view on screen as its subtitle; the legend as a
side panel; and a footer line from the prayer packet's final page (prayer point 6 with Luke 10:2 and
John 10:11-15, in the reader's language). While it renders, a loading screen like the poster wall's shows
the renderer's own progress. Without a mapTitle the view on screen stays the title, as before.
This commit is contained in:
Doxa Maps 2026-09-30 09:36:48 +00:00
parent f065ee504e
commit 70c716057a
8 changed files with 235 additions and 133 deletions

View file

@ -0,0 +1,45 @@
<!--
MapPrintProgress.vue — SHARED. The loading screen of a map print (mapPrintTemplate.js).
★ THE SAME HUD THE POSTER WALL SHOWS while it prepares a poster (the same card, bar and phase line), so a
map print looks like the posters' prints rather than a button that goes quiet for a minute. It shows the
renderer's own progress (useMapPoster's `progress`: percent + message), so a long render says how far along it
is. Styles go in through useShadowStyles, because it renders inside the map's shadow root.
-->
<template>
<div v-if="visible" class="mpp-scrim" role="dialog" aria-live="polite" :aria-label="kind">
<div class="mpp-card">
<div class="mpp-kind">{{ kind }}</div>
<div class="mpp-title">{{ title }}</div>
<div class="mpp-bar"><div class="mpp-fill" :style="{ width: pct + '%' }"></div></div>
<div class="mpp-phase">{{ message }}</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useShadowStyles } from '../../composables/useShadowStyles.js'
const props = defineProps({
visible: { type: Boolean, default: false },
kind: { type: String, default: '' },
title: { type: String, default: '' },
/** useMapPoster's progress: { percent, message } */
progress: { type: Object, default: null },
})
// Never an empty bar while the renderer warms up: 3% until it reports.
const pct = computed(() => Math.max(3, Math.min(100, Number(props.progress?.percent) || 0)))
const message = computed(() => String(props.progress?.message || ''))
useShadowStyles(
'.mpp-scrim{position:fixed;inset:0;z-index:1000;background:rgba(8,12,16,.55);display:grid;place-items:center;padding:22px}' +
'.mpp-card{width:min(340px,100%);background:#fff;color:#0f1419;border-radius:14px;padding:20px 22px;' +
'box-shadow:0 10px 40px rgba(0,0,0,.35);text-align:center}' +
'.mpp-kind{font-size:10px;text-transform:uppercase;letter-spacing:.1em;opacity:.55}' +
'.mpp-title{margin:3px 0 14px;font-size:15px;font-weight:700;line-height:1.3}' +
'.mpp-bar{height:5px;border-radius:99px;background:rgba(0,0,0,.10);overflow:hidden}' +
'.mpp-fill{height:100%;background:#10b981;border-radius:99px;transition:width .25s ease}' +
'.mpp-phase{margin-top:10px;font-size:12.5px;font-weight:600;min-height:1.2em}'
)
</script>

View file

@ -0,0 +1,61 @@
/**
* mapPrintTemplate.js — SHARED (@map/composables/poster/mapPrintTemplate.js).
*
* ★ ONE TEMPLATE FOR A MAP'S PRINT (2026-09-30: "the map download/print should look finished"). Every map's
* "Export poster…" builds its sheet here, so they all look the same:
* - LANDSCAPE Letter, always: a map is wider than a page is tall, and a standard sheet prints anywhere;
* - a HEADER with the MAP's own name, the view on screen ("Prayer", "Religion"…) as its subtitle;
* - the LEGEND as a side panel (computeLayout's right-hand column);
* - a FOOTER line from the prayer packet's final page, the 12 prayer points: point 6, the prayer for laborers
* and shepherds with its scripture references, in the reader's language. packet-strings.json already
* carries it, with the book names, in all 11 locales, so no scripture text is newly translated.
* The loading screen that shows the renderer's progress is MapPrintProgress.vue.
* Piloted on Simple Research's map; the other maps adopt it after it has been seen.
*/
import { defaultPosterSpec } from '../../constants/posterDefaults.js'
/** The prayer point (1-12) whose references the footer quotes. */
export const MAP_PRINT_POINT = 6
/**
* The poster spec for a map print. Rendered by useMapPoster, then printed by printImageBlob.
* @param {{ title: string, subtitle?: string, legendRows?: Array, legendTitle?: string, dir?: 'ltr'|'rtl',
* footerLeft?: string, footerRight?: string, attribution?: string }} o
*/
export function mapPrintSpec(o = {}) {
const spec = defaultPosterSpec({ widthIn: 11, heightIn: 8.5, dpi: 200, orientation: 'landscape' })
spec.dir = o.dir === 'rtl' ? 'rtl' : 'ltr'
// The composed sheet: title band, map, legend column, footer (see useMapPoster, layout 'poster').
spec.layout = 'poster'
spec.slots.title = { ...spec.slots.title, text: o.title || '', ...(o.subtitle ? { subtitle: o.subtitle } : {}) }
spec.slots.legend = { ...spec.slots.legend, rows: o.legendRows || [], title: o.legendTitle || '' }
// No scale bar or north arrow: the headless renderer's are placeholders, and a globe has no single scale.
spec.slots.scaleBar = false
spec.slots.north = false
spec.slots.footer = {
...spec.slots.footer,
left: o.footerLeft || '',
right: o.footerRight || '',
...(o.attribution ? { attribution: o.attribution } : {}),
}
return spec
}
/**
* The footer line in `lang`: the prayer point's heading and its references, with the book names from the same
* table ("laborers & shepherds — Luke 10:2 · John 10:11–15"). '' when the table has no such point.
* @param {Object} strings loadPacketStrings()'s table ({ en, es, …, zh })
*/
export function packetScriptureLine(strings, lang = 'en') {
const loc = (strings && (strings[lang] || strings.en)) || {}
const en = (strings && strings.en) || {}
const points = (Array.isArray(loc.points) && loc.points.length ? loc.points : en.points) || []
const p = points.find((x) => x && x.n === MAP_PRINT_POINT) || points[MAP_PRINT_POINT - 1]
if (!p) return ''
const books = loc.books || {}
const refs = (p.refs || []).map((r) => {
const m = String(r).match(/^((?:\d\s)?[A-Za-z]+)\s+(.+)$/)
return m && books[m[1]] ? `${books[m[1]]} ${m[2]}` : String(r)
})
return [p.sub, refs.join(' · ')].filter(Boolean).join(' — ')
}

View file

@ -37,6 +37,8 @@
*/
import { getCurrentInstance, inject, provide, ref, computed, onMounted, onBeforeUnmount, watch, markRaw, toRaw, nextTick, toRef } from 'vue'
import MapPrintProgress from '@map/components/poster/MapPrintProgress.vue'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
import { retryableAsyncComponent } from '@map/composables/retryableAsyncComponent.js'
// ─── Composables (framework) ─────────────────────────────────────────────────
@ -1412,54 +1414,44 @@ function posterLegendRows(preferFlat) {
}
const posterBusy = ref(false)
// The loading screen (MapPrintProgress): the title it names and the renderer's progress while it works.
const printTitle = ref('')
const printProgress = ref(null)
async function openPoster() {
if (posterBusy.value) return
posterBusy.value = true
try {
const [{ useMapPoster }, { defaultPosterSpec }, { printImageBlob }] = await Promise.all([
const [{ useMapPoster }, { mapPrintSpec, packetScriptureLine }, { printImageBlob }, { loadPacketStrings }] = await Promise.all([
import('@map/composables/poster/useMapPoster.js'),
import('@map/constants/posterDefaults.js'),
import('@map/composables/poster/mapPrintTemplate.js'),
import('@map/composables/printInPlace.js'),
import('@map/packet/packetStrings.js'),
])
const exporter = useMapPoster(() => map.value)
// ★ THE PAPER FOLLOWS THE MAP, because a map is usually wider than it is tall and a portrait sheet
// letterboxes it (reported 2026-09-28: the print came out vertical and it should "probably [be] a
// horizontal map"). Same 8.5x11 stock either way — only the ORIENTATION turns, so the sheet is
// still a standard page and not a custom size no printer has.
// Measured from the map's own container rather than from the window: the map may be one pane of a
// composite, and it is the MAP's shape being exported, not the browser's.
const mapEl = map.value?.getContainer?.() || null
const landscape = !!mapEl && mapEl.clientWidth > mapEl.clientHeight
const spec = defaultPosterSpec({
widthIn: landscape ? 11 : 8.5,
heightIn: landscape ? 8.5 : 11,
dpi: 200,
orientation: landscape ? 'landscape' : 'portrait',
})
// The poster is titled by the VIEW on screen ("Prayer", "Religion"…), in the reader's language.
// The legend's title bar is the fallback only: on a flat tab it is the generic word "Legend".
const title = tabLabel(activeTab.value) || legendTitle.value || 'Doxa Map'
// The loading screen follows the renderer's own progress for as long as this print runs.
stopPrintWatch = watch(() => exporter.progress.value, (p) => { printProgress.value = p ? { ...p } : null }, { immediate: true, deep: true })
// ★ THE SHARED MAP-PRINT TEMPLATE (mapPrintTemplate.js): landscape Letter, a header with the MAP's name and
// the view as its subtitle, the legend as a side panel, and a footer line from the prayer packet's final page.
// The map's name comes from the host (profile-config mapTitle, e.g. the gallery's tab label); without one the
// view on screen ("Prayer", "Religion"…) is the title, as before.
const view = tabLabel(activeTab.value) || legendTitle.value || 'Doxa Map'
const mapTitle = String(profileConfig?.value?.mapTitle || '').trim()
const title = mapTitle || view
printTitle.value = title
// Direction follows the MAP's language, not the host page's: an English map on an Arabic page stays LTR.
spec.dir = ['ar', 'fa', 'he', 'ur'].includes(String(uiLocale.value || '').split('-')[0]) ? 'rtl' : 'ltr'
// ★ ASK FOR THE COMPOSED SHEET. Without this the renderer keeps its historical behaviour — the map
// across the whole page with the chrome painted on top, which is what put the legend over the map.
// It is opt-in because country-posters embeds this renderer's output INSIDE its own packet page,
// under the packet's own title, so default-on bands would print a second header there.
spec.layout = 'poster'
spec.slots.title = { ...spec.slots.title, text: title }
// The legend the reader sees: the flat tab's rows (and their sub-rows), else the aggregated ones.
const rows = posterLegendRows(true)
spec.slots.legend = { ...spec.slots.legend, rows, title: legendTitle.value }
// No scale bar or north arrow: the headless renderer's are placeholders (a fixed bar reading
// "scale"), which on a print would be a false statement — and a globe has no single scale.
spec.slots.scaleBar = false
spec.slots.north = false
// Credit the tiles actually drawn: OpenFreeMap on MapLibre, Mapbox otherwise.
// C3: ask the library, not the globals. The explicit === 'maplibre' keeps the original FALSE for
// a map that is neither, so the Mapbox attribution stays the fall-through it always was.
const lang = String(uiLocale.value || 'en').split('-')[0]
const dir = ['ar', 'fa', 'he', 'ur'].includes(lang) ? 'rtl' : 'ltr'
// loadPacketStrings never rejects: a copy it cannot use gives the English table.
const scripture = packetScriptureLine(await loadPacketStrings(resolveAssetsBase(profileConfig?.value)), lang)
// Credit the tiles actually drawn: OpenFreeMap on MapLibre, Mapbox otherwise (C3: ask the library).
const onMaplibre = providerFor(map.value) === 'maplibre'
spec.slots.footer = { ...spec.slots.footer, left: 'pray.doxa.life', right: new Date().toISOString().slice(0, 10),
attribution: onMaplibre ? '© OpenFreeMap © OpenMapTiles © OpenStreetMap' : '© Mapbox © OpenStreetMap' }
const date = new Date().toISOString().slice(0, 10)
const spec = mapPrintSpec({
title, subtitle: mapTitle ? view : '', dir,
legendRows: posterLegendRows(true), legendTitle: legendTitle.value,
footerLeft: scripture || 'pray.doxa.life', footerRight: scripture ? `pray.doxa.life · ${date}` : date,
attribution: onMaplibre ? '© OpenFreeMap © OpenMapTiles © OpenStreetMap' : '© Mapbox © OpenStreetMap',
})
const blob = await exporter.render(spec, 'png')
// ★ THE DIALOG, NOT A DOWNLOAD (ruled 2026-09-28: an export-poster button must open the
// print dialog with a poster preview, never hand over a file). This used to call _downloadBlob,
@ -1481,9 +1473,12 @@ async function openPoster() {
} catch (err) {
console.error('[research-map] poster export failed:', err)
} finally {
if (stopPrintWatch) { stopPrintWatch(); stopPrintWatch = null }
printProgress.value = null
posterBusy.value = false
}
}
let stopPrintWatch = null
// The toolbar's export label, WITH its ellipsis. The ellipsis was stripped while this button handed
// over a file directly; it now opens the print dialog, which is exactly what an ellipsis promises.
const exportPosterLabel = computed(() => String(t('controls.export_poster') || 'Export poster…'))
@ -3177,6 +3172,7 @@ onBeforeUnmount(() => {
<template>
<div ref="rmRoot" :class="['rm-root', { 'rm-dark': isDark, 'dsm-dark': isDark }]">
<MapPrintProgress :visible="posterBusy" :kind="exportPosterLabel" :title="printTitle" :progress="printProgress" />
<!-- 10-tab bar -->
<div v-if="showTabBar" ref="tabBar" class="rm-tab-bar">
<template v-for="tab in tabs" :key="tab.id">

View file

@ -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-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3",
"integrity": "sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj",
"elements": [
"doxa-research-map"
],
@ -435,8 +435,8 @@
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
},
"instances": [
{

File diff suppressed because one or more lines are too long

View file

@ -70,7 +70,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
<script src="../doxa-research-map.js" integrity="sha384-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3" crossorigin="anonymous"></script>
<script src="../doxa-research-map.js" integrity="sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -66,7 +66,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
<script src="../../doxa-research-map.js" integrity="sha384-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3" crossorigin="anonymous"></script>
<script src="../../doxa-research-map.js" integrity="sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -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-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3",
"integrity": "sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj",
"elements": [
"doxa-research-map"
],
@ -435,8 +435,8 @@
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-pjtoXf9NJ7X+jJnRz4LzwtvGcTTSQuv0LZfUebBKWLyYeKlk55wqi/tedXRfgKj3\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-i5IdHpDFia++kYG/9B6TE8DIXBUW8SrlbQ0sDOEI3LrVncie+Rer/VGc+42LQGJj\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
},
"instances": [
{