Merge remote-tracking branch 'refs/remotes/origin/development' into deep-research/manifest-revalidate
# Conflicts: # public/dx-maps/doxa-maps-spa/doxa-maps-spa.js # public/dx-maps/doxa-maps-spa/doxa-maps-spa/app/index.html # public/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html # public/dx-maps/doxa-maps-spa/manifest.json # public/dx-maps/manifest.json
This commit is contained in:
commit
8931f6a28c
81 changed files with 4899 additions and 549 deletions
|
|
@ -13,7 +13,7 @@
|
|||
> bundle `parameterized` (cap-2) no matter how light it looks, and a `meta.json` `type`
|
||||
> declares tiers 3/4/5.
|
||||
|
||||
Generated from 28 bundle(s).
|
||||
Generated from 29 bundle(s).
|
||||
|
||||
### One canonical exemplar per tier (generated — quote these)
|
||||
|
||||
|
|
@ -56,6 +56,7 @@ Generated from 28 bundle(s).
|
|||
| `poster-design` | `poster-sizes` | `single` |
|
||||
| `poster-design` | `poster-styles` | `single` |
|
||||
| `prayer-walk-planner` | `prayer-walk-planner` | `single` |
|
||||
| `regions-dashboard` | `country-poster-sections` | `single` |
|
||||
| `regions-dashboard` | `region-dashboards` | `single` |
|
||||
| `regions-dashboard` | `regions-list` | `single` |
|
||||
|
||||
|
|
@ -67,6 +68,7 @@ Generated from 28 bundle(s).
|
|||
| `doxa-simple-map` | `doxa-simple-map` | `parameterized` |
|
||||
| `doxa-vision-card` | `doxa-vision-card` | `parameterized` |
|
||||
| `people-groups-grid` | `people-groups-grid` | `parameterized` |
|
||||
| `poster-sections` | `poster-sections` | `parameterized` |
|
||||
|
||||
## Capability 3 — multi-view-element (monolithic)
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ import { getApiBaseUrl } from '../utils/apiBaseUrl.js';
|
|||
const { t, locale } = useI18n();
|
||||
|
||||
useShadowStyles(`
|
||||
.people-group-detail{display:flex;flex-direction:column;height:100%;overflow-y:auto;}
|
||||
.people-group-detail{display:flex;flex-direction:column;height:100%;overflow-y:auto;overscroll-behavior:contain;}
|
||||
.people-group-detail.dark-mode{background:#1a1a2e;color:#eee;}
|
||||
.detail-header{display:flex;justify-content:space-between;align-items:flex-start;padding:16px;border-bottom:1px solid #e0e0e0;background:white;position:sticky;top:0;z-index:10;}
|
||||
.dark-mode .detail-header{background:#16213e;border-bottom-color:#2d2d4e;}
|
||||
|
|
@ -133,7 +133,7 @@ useShadowStyles(`
|
|||
.dark-mode .close-button{color:#aaa;}
|
||||
.close-button:hover{color:#333;}
|
||||
.dark-mode .close-button:hover{color:#fff;}
|
||||
.detail-content{padding:6px 12px 12px;flex:1;overflow-y:auto;}
|
||||
.detail-content{padding:6px 12px 12px;flex:1;overflow-y:auto;overscroll-behavior:contain;}
|
||||
.dark-mode .detail-content{background:#1a1a2e;}
|
||||
.quick-stats{display:flex;flex-direction:column;gap:4px;margin-bottom:8px;}
|
||||
.stat-item{display:flex;justify-content:space-between;align-items:baseline;font-size:13px;}
|
||||
|
|
@ -467,6 +467,8 @@ function handleImageError(event) {
|
|||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
/* a scroll that reaches this panel's end never chains to the page (the mobile sheet's detail view) */
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
/* Header Section */
|
||||
|
|
@ -540,6 +542,7 @@ function handleImageError(event) {
|
|||
padding: 6px 12px 12px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
/* Quick Stats */
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ useShadowStyles(`
|
|||
mobile chrome (pull tab, collapse/expand caret, touch drag). ── */
|
||||
/* bottom = the sheet's height (it shows only while the sheet is open, whose height fits its content)
|
||||
+ a 16px gap. */
|
||||
.floating-mini-image{position:absolute;inset-inline-start:16px;bottom:calc(var(--legend-img-seat,var(--legend-sheet-h,30%)) + 16px);z-index:999;pointer-events:auto;cursor:pointer;}
|
||||
.floating-mini-image{position:absolute;inset-inline-start:16px;bottom:calc(var(--legend-img-seat,var(--legend-sheet-h,30%)) + 16px);z-index:999;pointer-events:auto;cursor:pointer;touch-action:none;}
|
||||
/* The image SITS on --legend-img-seat, a copy of the sheet's height that moves only while the image shows (the
|
||||
sheet open, no finger on it; script: IMG_SEAT_VAR). Dragged up, and through the snap to full open, the sheet
|
||||
(z 1000) slides over the still image (z 999), so it goes INTO the legend instead of riding its top edge; dragged
|
||||
|
|
@ -118,7 +118,7 @@ useShadowStyles(`
|
|||
max-height reads --legend-sheet-top-reserve, set on this element from the
|
||||
TOP_RESERVE_PX constant in the script — the SAME number the touch-drag clamp
|
||||
uses, so CSS and JS can never disagree about how tall the sheet may get. */
|
||||
.legend-mobile-sheet{position:absolute;bottom:0;inset-inline-start:0;inset-inline-end:0;z-index:1000;background:white;border-top-left-radius:var(--map-radius,16px);border-top-right-radius:var(--map-radius,16px);box-shadow:0 -4px 20px rgba(0,0,0,0.2);transition:height 0.3s ease-out;display:flex;flex-direction:column;overflow:hidden;max-height:calc(100% - var(--legend-sheet-top-reserve,80px));}
|
||||
.legend-mobile-sheet{overscroll-behavior:contain;position:absolute;bottom:0;inset-inline-start:0;inset-inline-end:0;z-index:1000;background:white;border-top-left-radius:var(--map-radius,16px);border-top-right-radius:var(--map-radius,16px);box-shadow:0 -4px 20px rgba(0,0,0,0.2);transition:height 0.3s ease-out;display:flex;flex-direction:column;overflow:hidden;max-height:calc(100% - var(--legend-sheet-top-reserve,80px));}
|
||||
/* While a finger is resizing the sheet the height must be applied INSTANTLY —
|
||||
with the 0.3s transition left on, the measured height lagged the finger by
|
||||
14-30px (mobile-sheet-probe baseline 2026-09-16) and the release logic read a
|
||||
|
|
@ -964,12 +964,20 @@ onBeforeUnmount(() => { if (_creditTimer) { clearTimeout(_creditTimer); _creditT
|
|||
beside the legend, so it shows in no resting state (collapsed, or open with nothing selected). A
|
||||
tapped dot's detail view keeps its note inside the sheet, where it explains shared dots; the tree's
|
||||
own copy stays off (:pin-note="false" below). Desktop is unchanged. -->
|
||||
<!-- Floating mini-image: 16px above the open sheet's SEAT (it shows only in that state; see .floating-mini-image). -->
|
||||
<!-- Floating mini-image: 16px above the open sheet's SEAT (it shows only in that state; see .floating-mini-image).
|
||||
★ IT IS A HANDLE TOO (measured 2026-10-02: a drag up from the picture scrolled the PAGE, scrollY 0 → 135; it sits
|
||||
outside the sheet and the lip, so it had none of their handlers). It takes the sheet's own capture handlers
|
||||
and touch-action:none: a drag from it resizes the sheet (the grab-zone test holds above the sheet, as for the
|
||||
lip), and a tap is handed back as a click, which reaches @click below as before. -->
|
||||
<Transition name="lm-img">
|
||||
<div
|
||||
v-if="legendMode === 'detail' && selectedPeopleGroup && legendState === 'open' && miniImageUrl"
|
||||
class="floating-mini-image"
|
||||
@click="uiStore.fullyOpenLegend()"
|
||||
@touchstart.capture="onSheetTouchStart"
|
||||
@touchmove.capture="onSheetTouchMove"
|
||||
@touchend.capture="onSheetTouchEnd"
|
||||
@touchcancel.capture="onSheetTouchEnd"
|
||||
>
|
||||
<img
|
||||
:src="miniImageUrl"
|
||||
|
|
@ -984,12 +992,15 @@ onBeforeUnmount(() => { if (_creditTimer) { clearTimeout(_creditTimer); _creditT
|
|||
v-if="legendState !== 'collapsed'"
|
||||
class="sheet-grab-lip"
|
||||
aria-hidden="true"
|
||||
@touchstart="onSheetTouchStart"
|
||||
@touchmove="onSheetTouchMove"
|
||||
@touchend="onSheetTouchEnd"
|
||||
@touchcancel="onSheetTouchEnd"
|
||||
@touchstart.capture="onSheetTouchStart"
|
||||
@touchmove.capture="onSheetTouchMove"
|
||||
@touchend.capture="onSheetTouchEnd"
|
||||
@touchcancel.capture="onSheetTouchEnd"
|
||||
></div>
|
||||
<!-- Touch resize handlers sit on the SHEET: a drag may start anywhere in the
|
||||
<!-- (.capture: the drag sees every touch FIRST, so no child's handler can stop one reaching it; and the sheet
|
||||
contains overscroll, so a list inside it can never chain a scroll to the page: 2026-10-02, a phone shrinking the
|
||||
detail sheet past its content scrolled the page.)
|
||||
Touch resize handlers sit on the SHEET: a drag may start anywhere in the
|
||||
top GRAB_ZONE_PX (strip + title row) — and, in detail mode, on the content
|
||||
when it can't scroll that way. See onSheetTouchStart for the model. -->
|
||||
<div
|
||||
|
|
@ -997,10 +1008,10 @@ onBeforeUnmount(() => { if (_creditTimer) { clearTimeout(_creditTimer); _creditT
|
|||
class="legend-mobile-sheet"
|
||||
:class="[legendState, { 'sheet-dark': isDark, dragging: isDragging, fit: fitOpen }]"
|
||||
:style="{ height: fitOpen ? 'auto' : legendHeight, '--legend-sheet-top-reserve': TOP_RESERVE_PX + 'px' }"
|
||||
@touchstart="onSheetTouchStart"
|
||||
@touchmove="onSheetTouchMove"
|
||||
@touchend="onSheetTouchEnd"
|
||||
@touchcancel="onSheetTouchEnd"
|
||||
@touchstart.capture="onSheetTouchStart"
|
||||
@touchmove.capture="onSheetTouchMove"
|
||||
@touchend.capture="onSheetTouchEnd"
|
||||
@touchcancel.capture="onSheetTouchEnd"
|
||||
>
|
||||
<!-- Thin drag strip at the very top: pull-tab visual + tap-to-toggle. -->
|
||||
<div class="sheet-drag-strip" @click="handleCaretClick">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
/**
|
||||
* PosterSectionView.vue — ONE poster section on its own: the profile behind <dxm-poster-section>.
|
||||
* It reads its settings from profile-config (type, scope, collapsible, collapsed, locale, assetsBase, columns), finds the
|
||||
* It reads its settings from profile-config (type, scope, collapsible, collapsed, locale, assetsBase, columns, minCardPx, showHeader), finds the
|
||||
* poster TYPE in posterTypes.js, and draws a PosterWall in section-only mode: the same section a wall draws (the
|
||||
* same PosterSection, the same print actions and overlay), with no wall head around it.
|
||||
*/
|
||||
|
|
@ -38,8 +38,11 @@ const title = computed(() => (adapter.value ? adapter.value.title(cfg.value.scop
|
|||
const actions = computed(() => (adapter.value
|
||||
? adapter.value.actions({ assetsBase: () => assetsBase.value, apiBase: () => cfg.value.apiBaseUrl || getApiBaseUrl(), lang, profileConfig, t })
|
||||
: null))
|
||||
const collapsible = computed(() => cfg.value.collapsible !== false)
|
||||
const startCollapsed = computed(() => cfg.value.collapsed === true)
|
||||
// show-header="false" hides the section's head so a host's own layout is its header. The head is also the collapse
|
||||
// toggle, so a headless section can neither collapse nor start shut (it would have no way back open).
|
||||
const showHeader = computed(() => cfg.value.showHeader !== false)
|
||||
const collapsible = computed(() => cfg.value.collapsible !== false && showHeader.value)
|
||||
const startCollapsed = computed(() => cfg.value.collapsed === true && showHeader.value)
|
||||
const groupBy = () => title.value
|
||||
const collapsedBy = () => startCollapsed.value
|
||||
const instanceId = computed(() => `poster-section-${typeName.value}-${String(cfg.value.scope || 'all')}`)
|
||||
|
|
@ -56,11 +59,13 @@ const noneLine = computed(() => t('poster.sectionNone', { scope: String(cfg.valu
|
|||
embedded
|
||||
:items="items"
|
||||
:group-by="groupBy"
|
||||
:section-heads="showHeader"
|
||||
:collapsible="collapsible"
|
||||
:collapsed-by="collapsedBy"
|
||||
:instance-id="instanceId"
|
||||
:is-dark="cfg.isDark === true"
|
||||
:columns="cfg.columns || 0"
|
||||
:min-card-px="cfg.minCardPx || 0"
|
||||
:assets-base="assetsBase"
|
||||
:event-name="actions.eventName"
|
||||
:unit="actions.unit"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// <dxm-poster-section>'s attributes → its profile-config. Pure (no Vue), so it is testable on its own.
|
||||
export const ATTRS = ['type', 'scope', 'collapsible', 'collapsed', 'locale', 'assets-base', 'api-base-url', 'is-dark', 'columns']
|
||||
export const ATTRS = ['type', 'scope', 'collapsible', 'collapsed', 'locale', 'assets-base', 'api-base-url', 'is-dark', 'columns', 'min-card-px', 'show-header']
|
||||
export const TAG = 'dxm-poster-section'
|
||||
|
||||
export function posterSectionConfig(el) {
|
||||
|
|
@ -12,6 +12,13 @@ export function posterSectionConfig(el) {
|
|||
// posters per row (1-5); absent = the wall's own default. A reader's saved choice still wins (PosterWall).
|
||||
const cols = Number(a('columns'))
|
||||
if (el.hasAttribute('columns') && Number.isInteger(cols) && cols >= 1 && cols <= 5) cfg.columns = cols
|
||||
// the narrowest a card may get before a row holds one fewer (PosterWall's minCardPx); absent = the wall's own default.
|
||||
// A host that wants two posters a row on a phone sets it low enough to fit (the Regions list: 150).
|
||||
const minCard = Number(a('min-card-px'))
|
||||
if (el.hasAttribute('min-card-px') && Number.isInteger(minCard) && minCard >= 80 && minCard <= 600) cfg.minCardPx = minCard
|
||||
// show-header="false": no section head, so the HOST's own layout is the header (a key only then: every other host's
|
||||
// config is unchanged). The head is also the collapse toggle, so a headless section never collapses (PosterSectionView).
|
||||
if (flag('show-header', true) === false) cfg.showHeader = false
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
*
|
||||
* Attributes: type (default "country"), scope (a region / continent / list key), collapsible (default on;
|
||||
* collapsible="false" turns it off), collapsed (start shut), locale, assets-base, api-base-url, is-dark, columns
|
||||
* (posters per row, 1-5).
|
||||
* (posters per row, 1-5), min-card-px (the narrowest a card gets before a row holds one fewer), show-header (default
|
||||
* on; show-header="false" hides the section's head so the host's layout is its header).
|
||||
* They are mapped into the standard element's profile-config, so the element keeps defineMapElement's per-instance
|
||||
* i18n and store, and the profile (PosterSectionView.vue) reads them like every other profile reads its config.
|
||||
* Any bundle can call registerPosterSectionElement(); a second call is a no-op.
|
||||
|
|
|
|||
14
library/i18n/locales/ar/posterSections.json
Normal file
14
library/i18n/locales/ar/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "أقسام الملصقات",
|
||||
"intro": "اختر الملصقات وشاهدها أدناه، ثم انسخ الرمز لوضعها على أي موقع.",
|
||||
"type": "نوع الملصق",
|
||||
"typeCountry": "الدول",
|
||||
"region": "المنطقة",
|
||||
"allRegions": "كل المناطق",
|
||||
"language": "اللغة",
|
||||
"startCollapsed": "تبدأ مطويّة",
|
||||
"embed": "انسخ رمز التضمين",
|
||||
"embedNote": "الصق هذا في أي صفحة ويب.",
|
||||
"copy": "نسخ",
|
||||
"copied": "تم النسخ"
|
||||
}
|
||||
14
library/i18n/locales/de/posterSections.json
Normal file
14
library/i18n/locales/de/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Plakat-Abschnitte",
|
||||
"intro": "Wähle die Plakate, sieh sie dir unten an und kopiere dann den Code für eine beliebige Website.",
|
||||
"type": "Plakattyp",
|
||||
"typeCountry": "Länder",
|
||||
"region": "Region",
|
||||
"allRegions": "Alle Regionen",
|
||||
"language": "Sprache",
|
||||
"startCollapsed": "Eingeklappt beginnen",
|
||||
"embed": "Einbettung kopieren",
|
||||
"embedNote": "Füge dies in eine beliebige Webseite ein.",
|
||||
"copy": "Kopieren",
|
||||
"copied": "Kopiert"
|
||||
}
|
||||
14
library/i18n/locales/en/posterSections.json
Normal file
14
library/i18n/locales/en/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Poster sections",
|
||||
"intro": "Pick the posters, see them below, then copy the tag to put them on any website.",
|
||||
"type": "Poster type",
|
||||
"typeCountry": "Countries",
|
||||
"region": "Region",
|
||||
"allRegions": "All regions",
|
||||
"language": "Language",
|
||||
"startCollapsed": "Start collapsed",
|
||||
"embed": "Copy embed",
|
||||
"embedNote": "Paste this into any web page.",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied"
|
||||
}
|
||||
14
library/i18n/locales/es/posterSections.json
Normal file
14
library/i18n/locales/es/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Secciones de carteles",
|
||||
"intro": "Elige los carteles, míralos abajo y copia la etiqueta para ponerlos en cualquier sitio web.",
|
||||
"type": "Tipo de cartel",
|
||||
"typeCountry": "Países",
|
||||
"region": "Región",
|
||||
"allRegions": "Todas las regiones",
|
||||
"language": "Idioma",
|
||||
"startCollapsed": "Empezar contraído",
|
||||
"embed": "Copiar código para insertar",
|
||||
"embedNote": "Pega esto en cualquier página web.",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado"
|
||||
}
|
||||
14
library/i18n/locales/fr/posterSections.json
Normal file
14
library/i18n/locales/fr/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Sections d’affiches",
|
||||
"intro": "Choisissez les affiches, regardez-les ci-dessous, puis copiez la balise pour les placer sur n’importe quel site.",
|
||||
"type": "Type d’affiche",
|
||||
"typeCountry": "Pays",
|
||||
"region": "Région",
|
||||
"allRegions": "Toutes les régions",
|
||||
"language": "Langue",
|
||||
"startCollapsed": "Commencer replié",
|
||||
"embed": "Copier le code d’intégration",
|
||||
"embedNote": "Collez ceci dans n’importe quelle page web.",
|
||||
"copy": "Copier",
|
||||
"copied": "Copié"
|
||||
}
|
||||
14
library/i18n/locales/hi/posterSections.json
Normal file
14
library/i18n/locales/hi/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "पोस्टर अनुभाग",
|
||||
"intro": "पोस्टर चुनें, नीचे देखें, फिर किसी भी वेबसाइट पर लगाने के लिए टैग कॉपी करें।",
|
||||
"type": "पोस्टर का प्रकार",
|
||||
"typeCountry": "देश",
|
||||
"region": "क्षेत्र",
|
||||
"allRegions": "सभी क्षेत्र",
|
||||
"language": "भाषा",
|
||||
"startCollapsed": "बंद रूप में शुरू करें",
|
||||
"embed": "एम्बेड कॉपी करें",
|
||||
"embedNote": "इसे किसी भी वेब पेज में चिपकाएँ।",
|
||||
"copy": "कॉपी करें",
|
||||
"copied": "कॉपी हो गया"
|
||||
}
|
||||
14
library/i18n/locales/it/posterSections.json
Normal file
14
library/i18n/locales/it/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Sezioni di poster",
|
||||
"intro": "Scegli i poster, guardali qui sotto, poi copia il tag per metterli su qualsiasi sito.",
|
||||
"type": "Tipo di poster",
|
||||
"typeCountry": "Paesi",
|
||||
"region": "Regione",
|
||||
"allRegions": "Tutte le regioni",
|
||||
"language": "Lingua",
|
||||
"startCollapsed": "Inizia chiuso",
|
||||
"embed": "Copia il codice da incorporare",
|
||||
"embedNote": "Incolla questo in qualsiasi pagina web.",
|
||||
"copy": "Copia",
|
||||
"copied": "Copiato"
|
||||
}
|
||||
14
library/i18n/locales/pt/posterSections.json
Normal file
14
library/i18n/locales/pt/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Seções de cartazes",
|
||||
"intro": "Escolha os cartazes, veja-os abaixo e copie a etiqueta para colocá-los em qualquer site.",
|
||||
"type": "Tipo de cartaz",
|
||||
"typeCountry": "Países",
|
||||
"region": "Região",
|
||||
"allRegions": "Todas as regiões",
|
||||
"language": "Idioma",
|
||||
"startCollapsed": "Começar recolhido",
|
||||
"embed": "Copiar código de incorporação",
|
||||
"embedNote": "Cole isto em qualquer página da web.",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado"
|
||||
}
|
||||
14
library/i18n/locales/ro/posterSections.json
Normal file
14
library/i18n/locales/ro/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Secțiuni de postere",
|
||||
"intro": "Alege posterele, vezi-le mai jos, apoi copiază eticheta ca să le pui pe orice site.",
|
||||
"type": "Tipul posterului",
|
||||
"typeCountry": "Țări",
|
||||
"region": "Regiune",
|
||||
"allRegions": "Toate regiunile",
|
||||
"language": "Limbă",
|
||||
"startCollapsed": "Începe restrâns",
|
||||
"embed": "Copiază codul de încorporare",
|
||||
"embedNote": "Lipește asta în orice pagină web.",
|
||||
"copy": "Copiază",
|
||||
"copied": "Copiat"
|
||||
}
|
||||
14
library/i18n/locales/ru/posterSections.json
Normal file
14
library/i18n/locales/ru/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "Разделы плакатов",
|
||||
"intro": "Выберите плакаты, посмотрите их ниже, затем скопируйте тег, чтобы разместить их на любом сайте.",
|
||||
"type": "Тип плаката",
|
||||
"typeCountry": "Страны",
|
||||
"region": "Регион",
|
||||
"allRegions": "Все регионы",
|
||||
"language": "Язык",
|
||||
"startCollapsed": "Начинать свёрнутым",
|
||||
"embed": "Скопировать код для вставки",
|
||||
"embedNote": "Вставьте это на любую веб-страницу.",
|
||||
"copy": "Копировать",
|
||||
"copied": "Скопировано"
|
||||
}
|
||||
14
library/i18n/locales/zh/posterSections.json
Normal file
14
library/i18n/locales/zh/posterSections.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"heading": "海报分区",
|
||||
"intro": "选择海报,在下方查看,然后复制标签,放到任何网站上。",
|
||||
"type": "海报类型",
|
||||
"typeCountry": "国家",
|
||||
"region": "地区",
|
||||
"allRegions": "所有地区",
|
||||
"language": "语言",
|
||||
"startCollapsed": "默认折叠",
|
||||
"embed": "复制嵌入代码",
|
||||
"embedNote": "将其粘贴到任何网页中。",
|
||||
"copy": "复制",
|
||||
"copied": "已复制"
|
||||
}
|
||||
|
|
@ -354,7 +354,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",
|
||||
"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: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-design-catalogue 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",
|
||||
"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: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: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",
|
||||
"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",
|
||||
|
|
@ -423,6 +423,9 @@
|
|||
"check:side-lanes:self-test": "node plugin-designs/country-posters/scripts/check-side-lanes.mjs --self-test",
|
||||
"check:poster-section": "node plugin-designs/country-posters/scripts/check-poster-section.mjs",
|
||||
"check:poster-section:self-test": "node plugin-designs/country-posters/scripts/check-poster-section.mjs --self-test",
|
||||
"check:poster-sections": "node plugin-designs/poster-sections/scripts/check-poster-sections.mjs",
|
||||
"check:poster-sections:self-test": "node plugin-designs/poster-sections/scripts/check-poster-sections.mjs --self-test",
|
||||
"gen:poster-sections-presets": "node plugin-designs/poster-sections/scripts/gen-presets.mjs",
|
||||
"check:side-lane-photo-pixels": "node plugin-designs/country-posters/scripts/check-side-lane-photo-pixels.mjs",
|
||||
"check:side-lane-photo-pixels:self-test": "node plugin-designs/country-posters/scripts/check-side-lane-photo-pixels.mjs --self-test",
|
||||
"check:poster-type-registry": "node internal/check-poster-type-registry.mjs",
|
||||
|
|
@ -700,6 +703,9 @@
|
|||
"check:side-lanes:self-test": "a poster-render.js copy WITHOUT the lane clamp must make check:side-lanes' crossing arm FAIL on the same seeded data (it does: 36 crossings), so the arm is shown able to fail.",
|
||||
"check:poster-section": "the reusable poster section (<dxm-poster-section>): the type registry has 'country'; DX Life's /regions slugs (and every other spelling of a region) each show their countries, every country once; a withheld whole-country poster stays withheld through the type; the element's attributes map to its config; PosterSection.vue injects every name its template uses.",
|
||||
"check:poster-section:self-test": "a registry without 'country', a region key without \"&\" → \"and\", and a section that uses a name it does not inject must each make check:poster-section fail.",
|
||||
"check:poster-sections": "the Poster Sections page (plugin-designs/poster-sections): every panel choice (each region or All regions, each language, open or collapsed, 1-5 per row) builds <dxm-poster-section> tags that the element's own attribute reader turns back into exactly those choices; All regions is one section per region; Copy embed is the bundle script plus those same tags. The presets file holds, per poster type, exactly an all-regions preset plus one per region its adapter's scopes() returns, so a region the data drops (or gains) cannot drift from the presets; a poster type not on the tree has no presets (its pages could show no posters); and the built preset pages are exactly the presets (no stale page).",
|
||||
"check:poster-sections:self-test": "a tag builder that drops columns, one that drops collapsed, one that repeats a region under All regions, a preset for a region the type does not have, and a built page for a preset that no longer exists must each make check:poster-sections fail.",
|
||||
"gen:poster-sections-presets": "writes plugin-designs/poster-sections/poster-sections.instances.json from each poster type's own scopes() (all regions + one preset per region, titled by the type's own section title, then configure); the list check:poster-sections holds the file to.",
|
||||
"check:side-lane-photo-pixels": "BROWSER gate (browser seat; elsewhere 75 NOT MEASURED): the 60x96 side-lane sheet rasterised by poster-raster.js, measured in pixels at every photo slot. A data: photo draws; every photo on its own host stays flat, never the broken-image glyph an external <image> rasterises as.",
|
||||
"check:side-lane-photo-pixels:self-test": "the same raster WITHOUT stripExternalImages must leave the external photo slots NOT flat (the glyph), else the gate proves nothing.",
|
||||
"check:host-floor": "the profile floor (min-height 100svh) is decided against the HOST without disturbing live content (library/elements/hostFloor.js, used by ProfileLoader): measured ONCE at mount; after that host resizes never write the loader's min-height nor move a scrolled child's scrollTop (the regression: a per-resize probe reset the embedded UPG-100 app from Map to its first view); a sized host is filled; a host sized after mount flips once; a max-height cap never loops; a collapsed host gets the floor back; an inline host is left alone. Stub DOM, headless. Run the `check:host-floor:self-test` script for the NEGATIVE test (the old per-resize probe and a missing latch).",
|
||||
|
|
|
|||
|
|
@ -289,7 +289,9 @@ async function main() {
|
|||
['India offered 66x66 again', () => armB(seriesFor, countries, (it, f) => (it?.iso3 === 'IND' && f === '66x66' ? '66x66' : sheetFormatFor(it, f)))],
|
||||
['a packet page becomes a large sheet', () => armB(seriesFor, countries, (it, f) => (it?.iso3 === 'IND' ? '60x96' : f))],
|
||||
['the wall resolves [] for a phone-blocked card', () => armF({ wall: files[wall].replace("const desktopOnly = () => Promise.reject(keyedError('poster.phoneLimitAll'", "const desktopOnly = () => Promise.resolve([]) || (keyedError('poster.phoneLimitAll'"), regions: REG })],
|
||||
['the Regions list resolves [] for a phone-blocked card', () => armF(lists(files[wall], REG.replace("phoneBlocked(item) ? Promise.reject(keyedError('poster.phoneLimitAll'", "phoneBlocked(item) ? Promise.resolve([]) || (keyedError('poster.phoneLimitAll'")))],
|
||||
// Only while the Regions list draws its own sheets (regIsCaller): a list that delegates to <dxm-poster-section> has
|
||||
// no phone-block code of its own to poison, and lists() leaves it out of arm F, so this case would read as MISSED.
|
||||
...(regIsCaller ? [['the Regions list resolves [] for a phone-blocked card', () => armF(lists(files[wall], REG.replace("phoneBlocked(item) ? Promise.reject(keyedError('poster.phoneLimitAll'", "phoneBlocked(item) ? Promise.resolve([]) || (keyedError('poster.phoneLimitAll'")))]] : []),
|
||||
['a locale loses a state name', () => armD(states, { ...areas, ro: { ...areas.ro, stateNames: Object.fromEntries(Object.entries(areas.ro.stateNames).filter(([k]) => k !== 'IN-GJ')) } }, meta)],
|
||||
['the names lose their licence line', () => armD(states, areas, { ...meta, licence: '' })],
|
||||
['India\'s poster released by default', async () => armG({ ...CS, posterWithheld: () => false, largeSheet: () => CS.LARGE_SHEETS.IND }, lists(files[wall]), MFE)],
|
||||
|
|
@ -303,6 +305,7 @@ async function main() {
|
|||
['a helper that mutates the shared ADM1', () => armC({ ...H, stateGeometry: (z, a, n) => { (a.features || []).forEach((f) => { if (f.properties.shapeName === n) f.properties.listId = z.id }); return H.stateGeometry(z, a, n) } }, states, JSON.parse(JSON.stringify(adm1Ind)))],
|
||||
]
|
||||
let bad = 0
|
||||
if (!regIsCaller) console.log('n/a the Regions list resolves [] for a phone-blocked card (the list delegates to <dxm-poster-section>; its withholding is arm H, the library type)')
|
||||
for (const [name, run] of cases) {
|
||||
const f = await run()
|
||||
console.log(`${f.length ? '✓ caught' : '✗ MISSED'} ${name}${f.length ? ` -> ${f[0]}` : ''}`)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
|||
// real region), so the /regions/other scope resolves to zero countries and is no longer one of the covering scopes.
|
||||
const DXL = ['africa', 'asia', 'europe', 'latin-america-and-caribbean', 'middle-east', 'north-america-and-non-spanish-caribbean', 'oceania']
|
||||
|
||||
async function arms({ types, country, element, sectionSrc, cs }) {
|
||||
async function arms({ types, country, element, sectionSrc, viewSrc, cs }) {
|
||||
const fails = []
|
||||
// A
|
||||
if (!types.posterTypeNames().includes('country')) fails.push("A: the registry has no 'country' type")
|
||||
|
|
@ -68,6 +68,19 @@ async function arms({ types, country, element, sectionSrc, cs }) {
|
|||
if (!(d0.type === 'country' && d0.scope === 'africa' && d0.collapsible === true && d0.collapsed === false)) fails.push(`D: defaults wrong ${JSON.stringify(d0)}`)
|
||||
if (element.posterSectionConfig(fake({ collapsible: 'false' })).collapsible !== false) fails.push('D: collapsible="false" does not turn collapsing off')
|
||||
if (element.posterSectionConfig(fake({ collapsed: '' })).collapsed !== true) fails.push('D: collapsed does not start the section shut')
|
||||
// min-card-px: absent = no key (the wall's own default); an integer 80..600 is kept; anything else is dropped.
|
||||
if ('minCardPx' in d0) fails.push('D: no min-card-px still sets minCardPx')
|
||||
if (element.posterSectionConfig(fake({ 'min-card-px': '150' })).minCardPx !== 150) fails.push('D: min-card-px="150" is not kept')
|
||||
if ('minCardPx' in element.posterSectionConfig(fake({ 'min-card-px': '50' }))) fails.push('D: min-card-px="50" (below 80) is kept')
|
||||
// show-header: absent = no key (shown, every current host unchanged); "false" = showHeader false; any other value = shown.
|
||||
if ('showHeader' in d0) fails.push('D: no show-header still sets showHeader')
|
||||
if (element.posterSectionConfig(fake({ 'show-header': 'false' })).showHeader !== false) fails.push('D: show-header="false" does not hide the head')
|
||||
if ('showHeader' in element.posterSectionConfig(fake({ 'show-header': '' }))) fails.push('D: show-header="" hides the head')
|
||||
// F: the view passes show-header to the wall, and a headless section can neither collapse nor start shut.
|
||||
const view = String(viewSrc || '').replace(/<!--[\s\S]*?-->/g, '').replace(/\/\/[^\n]*/g, '')
|
||||
if (!/:section-heads="showHeader"/.test(view)) fails.push('F: PosterSectionView does not pass showHeader to the wall (section-heads)')
|
||||
if (!/const collapsible\s*=\s*computed\([^\n]*showHeader/.test(view)) fails.push('F: a headless section can still collapse')
|
||||
if (!/const startCollapsed\s*=\s*computed\([^\n]*showHeader/.test(view)) fails.push('F: a headless section can still start shut')
|
||||
// E
|
||||
const tpl = (sectionSrc.match(/<template>([\s\S]*)<\/template>\s*$/) || ['', ''])[1].replace(/<!--[\s\S]*?-->/g, '')
|
||||
const injected = new Set(((sectionSrc.match(/const \{([\s\S]*?)\} = inject\(POSTER_WALL_CTX\)/) || ['', ''])[1]).split(/[\s,]+/).filter(Boolean))
|
||||
|
|
@ -91,14 +104,18 @@ try {
|
|||
const country = (await load('library/components/poster/types/countryPosterType.js')).default
|
||||
const element = await load('library/elements/posterSectionConfig.js')
|
||||
const sectionSrc = readFileSync(join(ROOT, 'library/components/poster/PosterSection.vue'), 'utf8')
|
||||
const viewSrc = readFileSync(join(ROOT, 'library/components/poster/PosterSectionView.vue'), 'utf8')
|
||||
const cs = await load('library/packet/countrySheet.js')
|
||||
const real = { types, country, element, sectionSrc, cs }
|
||||
const real = { types, country, element, sectionSrc, viewSrc, cs }
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const poisons = [
|
||||
["a registry without 'country'", { ...real, types: { ...types, posterTypeNames: () => [] } }, /^A:/],
|
||||
['a region key without "&" → "and"', { ...real, country: { ...country, items: (s, l) => (/-and-/.test(String(s)) ? [] : country.items(s, l)) } }, /^B:/],
|
||||
['the preview ignored under DEV', { ...real, cs: { ...cs, posterWithheld: () => true, largeSheet: () => null } }, /^C2:/],
|
||||
['a section that uses a name it does not inject', { ...real, sectionSrc: sectionSrc.replace('</template>', '{{ notProvided }}</template>') }, /^E:/],
|
||||
['an element config that drops show-header', { ...real, element: { posterSectionConfig: (el) => { const c = element.posterSectionConfig(el); delete c.showHeader; return c } } }, /^D: show-header="false"/],
|
||||
['a view that does not pass show-header to the wall', { ...real, viewSrc: viewSrc.replace(':section-heads="showHeader"', '') }, /^F:/],
|
||||
['a headless section that can still start shut', { ...real, viewSrc: viewSrc.replace(/(const startCollapsed = computed\(\(\) => cfg\.value\.collapsed === true)[^\n]*/, '$1)') }, /^F: a headless section can still start shut/],
|
||||
]
|
||||
for (const [name, inp, rx] of poisons) {
|
||||
const f = await arms(inp); const ok = f.some((x) => rx.test(x))
|
||||
|
|
|
|||
|
|
@ -377,7 +377,10 @@ const NOT_EXPERIMENTAL = new Set([
|
|||
'posterverse',
|
||||
// bible-posters lives on POSTERVERSE's Experimental shelf, and only there: one poster plugin, shown
|
||||
// once, in the shell that gathers the poster plugins.
|
||||
'bible-posters'
|
||||
'bible-posters',
|
||||
// poster-sections is a TEMPLATE plugin: the <dxm-poster-section> element other plugins and sites embed
|
||||
// (the Regions list, DX Life's region pages). It stays in Plugin Studio, not on a reader's shelf.
|
||||
'poster-sections'
|
||||
])
|
||||
|
||||
// ── EXPERIMENTAL ORDER = KINGDOM IMPACT, not the alphabet ────────────────────────────────
|
||||
|
|
|
|||
34
plugin-designs/poster-sections/index.html
Normal file
34
plugin-designs/poster-sections/index.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>doxa-maps · poster-sections staging</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { background: #f3f3f1; color: #3b463d; min-height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; }
|
||||
.wrap { max-width: 1280px; margin: 0 auto; padding: 32px 20px; }
|
||||
h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.sub { color: #6d766e; font-size: 13px; margin: 6px 0 24px; }
|
||||
.slot { border: 1px solid #d1d4cd; border-radius: 12px; overflow: hidden; background: #fff; margin-bottom: 24px; }
|
||||
.slot .label { font: 12px/1 ui-monospace, monospace; color: #6d766e; padding: 8px 12px;
|
||||
background: #f3f3f1; border-bottom: 1px solid #d1d4cd; }
|
||||
.frame { height: 900px; position: relative; }
|
||||
poster-sections { display: block; width: 100%; height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>poster-sections · staging</h1>
|
||||
<p class="sub">One bundle, one profile: the Poster Sections page over the library's
|
||||
<code><dxm-poster-section></code>. Compiles into <code>poster-sections.js</code>.</p>
|
||||
<div class="slot">
|
||||
<div class="label">profile-config.profile = "poster-sections"</div>
|
||||
<div class="frame"><poster-sections profile-config='{"profile":"poster-sections"}'></poster-sections></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
24
plugin-designs/poster-sections/index.js
Normal file
24
plugin-designs/poster-sections/index.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { defineMapElement } from '@map/elements/defineMapElement.js'
|
||||
import { registerPosterSectionElement } from '@map/elements/posterSectionElement.js'
|
||||
/**
|
||||
* plugin-designs/poster-sections/index.js
|
||||
*
|
||||
* Bundle entry. Registers TWO tags:
|
||||
* - <poster-sections> the Poster Sections page: a panel of choices over the live sections, plus the tag to copy
|
||||
* - <dxm-poster-section> the library's poster section element itself, so a website that loads ONLY this script
|
||||
* can place the tags the page hands out:
|
||||
*
|
||||
* <script src="…/dx-maps/poster-sections/poster-sections.js"></script>
|
||||
* <dxm-poster-section type="country" scope="africa" locale="ar" columns="3"></dxm-poster-section>
|
||||
*/
|
||||
const profileModules = import.meta.glob('./*.vue')
|
||||
|
||||
const PosterSectionsElement = defineMapElement({ profileModules })
|
||||
|
||||
if (typeof customElements !== 'undefined' && !customElements.get('poster-sections')) {
|
||||
customElements.define('poster-sections', PosterSectionsElement)
|
||||
}
|
||||
|
||||
registerPosterSectionElement()
|
||||
|
||||
export default PosterSectionsElement
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"//": "Presets of the poster-sections parameterized profile (capability 2, the same shape as doxa-simple-map's simple-map.instances.json). ONE profile, one preset per row: a poster type × a region (scope; 'all' = one section per region). The build emits a standalone page and embed snippet per preset at poster-sections/poster-sections/<id>/index.html and lists them in manifest.json. GENERATED by `bun run gen:poster-sections-presets` from each poster type's own scopes(), and check:poster-sections holds it to them: country posters = DX Life's /regions regions; a type joins once its type file is on the tree and its page can load it. 'configure' adds the panel of choices and Copy embed, a tool for making new presets.",
|
||||
"profile": "poster-sections",
|
||||
"instances": [
|
||||
{
|
||||
"id": "countries-all",
|
||||
"title": "Country posters — all regions",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "all"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-asia",
|
||||
"title": "Country posters — Asia",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "asia"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-middle-east",
|
||||
"title": "Country posters — Middle East",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "middle-east"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-latin-america-and-caribbean",
|
||||
"title": "Country posters — Latin America & Caribbean",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "latin-america-and-caribbean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-africa",
|
||||
"title": "Country posters — Africa",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "africa"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-europe",
|
||||
"title": "Country posters — Europe",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "europe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-north-america-and-non-spanish-caribbean",
|
||||
"title": "Country posters — North America & Non-Spanish Caribbean",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "north-america-and-non-spanish-caribbean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-oceania",
|
||||
"title": "Country posters — Oceania",
|
||||
"props": {
|
||||
"type": "country",
|
||||
"scope": "oceania"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "configure",
|
||||
"title": "Configure — make a new preset",
|
||||
"props": {
|
||||
"configure": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugin-designs/poster-sections/poster-sections.meta.json
Normal file
4
plugin-designs/poster-sections/poster-sections.meta.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"label": "Poster Sections — presets",
|
||||
"note": "One profile, many presets (capability 2): each region's poster section (country posters by DX Life's /regions regions), plus all regions, each with its own page and embed snippet in manifest.json. Other poster types join when their type can load on the page. The Configure preset adds a panel of choices and Copy embed for making new presets."
|
||||
}
|
||||
182
plugin-designs/poster-sections/poster-sections.vue
Normal file
182
plugin-designs/poster-sections/poster-sections.vue
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
<script setup>
|
||||
/**
|
||||
* @description Poster Sections: one region's posters (or every region's), as ready-made presets with their own page and embed code; the Configure preset adds a panel of choices and Copy embed for making new ones.
|
||||
*
|
||||
* poster-sections.vue — ONE parameterized profile (capability 2, like doxa-simple-map): its props pick the poster
|
||||
* type, the region ('all' = one section per region), the language, posters per row and collapsed, and it renders the
|
||||
* library's <dxm-poster-section> element for them. poster-sections.instances.json lists the presets; the build gives
|
||||
* each its own page and embed snippet. The `configure` preset adds the panel of choices and "Copy embed", a tool for
|
||||
* making new presets. Nothing here draws a poster: the sections ARE the element (registered by index.js), and Copy
|
||||
* embed shows the same attributes as text (src/embed.js, one function for both).
|
||||
*/
|
||||
import { computed, h, inject, ref, watch } from 'vue'
|
||||
import { useOptionalI18n } from '@map/composables/useOptionalT.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { SUPPORTED_LOCALES, RTL_LOCALES, getRuntimeLocale, detectLocale } from '@map/i18n/index.js'
|
||||
import { posterType, posterTypeNames } from '@map/components/poster/posterTypes.js'
|
||||
import { absoluteEmbedUrl, resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { TAG, sectionAttrs, embedSnippet } from './src/embed.js'
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
const cfg = computed(() => profileConfig.value || {})
|
||||
const { t, locale: pageLocale } = useOptionalI18n('')
|
||||
const pageLang = computed(() => String(pageLocale?.value || cfg.value.locale || getRuntimeLocale() || detectLocale() || 'en'))
|
||||
const dir = computed(() => (RTL_LOCALES.has(pageLang.value.split('-')[0]) ? 'rtl' : 'ltr'))
|
||||
|
||||
// ── the choices: seeded from the preset's props, then the panel's (configure) ──────────────────────────────
|
||||
const configure = computed(() => cfg.value.configure === true)
|
||||
const ALL = (v) => (!v || v === 'all' ? '' : String(v)) // '' = All regions
|
||||
const type = ref('country')
|
||||
const scope = ref('')
|
||||
const lang = ref('en')
|
||||
const collapsed = ref(false)
|
||||
const columns = ref(2)
|
||||
watch(cfg, (c) => {
|
||||
type.value = c.type || 'country'
|
||||
scope.value = ALL(c.scope)
|
||||
const l = c.locale || pageLang.value
|
||||
lang.value = SUPPORTED_LOCALES.includes(l) ? l : 'en'
|
||||
collapsed.value = c.collapsed === true
|
||||
columns.value = Number(c.columns) || 2
|
||||
}, { immediate: true })
|
||||
const COLUMN_CHOICES = [1, 2, 3, 4, 5]
|
||||
|
||||
// The poster types the registry knows, read at runtime and again whenever the picker is opened: another bundle on
|
||||
// the page may register its type later (each type's own bundle registers it). Named in the reader's language when the
|
||||
// catalogue has a name for it, else by its registry name.
|
||||
const types = ref(posterTypeNames())
|
||||
const refreshTypes = () => { const now = posterTypeNames(); if (now.join() !== types.value.join()) types.value = now }
|
||||
const typeLabel = (name) => {
|
||||
const key = `posterSections.type${name.charAt(0).toUpperCase()}${name.slice(1)}`
|
||||
const v = t(key)
|
||||
return v && v !== key ? v : name
|
||||
}
|
||||
// A language in its OWN name (العربية, Deutsch …), so a reader finds theirs whatever the page's language.
|
||||
const langLabel = (code) => {
|
||||
try { return new Intl.DisplayNames([code], { type: 'language' }).of(code) || code } catch { return code }
|
||||
}
|
||||
|
||||
// The regions the chosen type offers (its adapter's scopes), each named by the type's own section title.
|
||||
const adapter = ref(null)
|
||||
watch(type, async (name) => { adapter.value = await posterType(name) }, { immediate: true })
|
||||
const scopes = computed(() => {
|
||||
const a = adapter.value
|
||||
const list = a && typeof a.scopes === 'function' ? a.scopes() : []
|
||||
const named = list.map((key) => ({ key, label: a.title(key, t) }))
|
||||
// alphabetical in the page's language, with the catch-all last
|
||||
return named.sort((x, y) => (x.key === 'other') - (y.key === 'other') || x.label.localeCompare(y.label, pageLang.value))
|
||||
})
|
||||
// In the panel, a region the newly chosen type does not have falls back to All regions. A preset keeps its scope even
|
||||
// before its type is registered (the element then says it has no posters, rather than silently showing another region).
|
||||
watch(scopes, (list) => { if (configure.value && scope.value && list.length && !list.some((s) => s.key === scope.value)) scope.value = '' })
|
||||
|
||||
// ── the tags ─────────────────────────────────────────────────────────────────
|
||||
// Absolute, so the pasted tags work on another website (a relative path would resolve against THAT site).
|
||||
const absolute = (u) => { try { return new URL(u, document.baseURI).href } catch { return u } }
|
||||
const assetsBase = computed(() => absolute(resolveAssetsBase(cfg.value)))
|
||||
const scriptUrl = computed(() => absoluteEmbedUrl('poster-sections/poster-sections.js', cfg.value))
|
||||
const choices = computed(() => ({
|
||||
type: type.value, scope: scope.value, scopes: scopes.value.map((s) => s.key),
|
||||
locale: lang.value, collapsed: collapsed.value, columns: columns.value, assetsBase: assetsBase.value,
|
||||
}))
|
||||
const snippet = computed(() => embedSnippet(choices.value, scriptUrl.value))
|
||||
|
||||
// The live sections: the element itself, with exactly the attributes above. Keyed by them, so a new choice
|
||||
// mounts a fresh section (a language change rebuilds its catalogue rather than patching it).
|
||||
const Sections = () => h('div', { class: 'pss__sections' },
|
||||
sectionAttrs(choices.value).map((a) => h(TAG, { key: JSON.stringify(a), ...a })))
|
||||
|
||||
const copied = ref(false)
|
||||
async function copy() {
|
||||
try { await navigator.clipboard.writeText(snippet.value) } catch {
|
||||
const ta = document.createElement('textarea'); ta.value = snippet.value; document.body.appendChild(ta); ta.select()
|
||||
try { document.execCommand('copy') } finally { ta.remove() }
|
||||
}
|
||||
copied.value = true; setTimeout(() => { copied.value = false }, 1600)
|
||||
}
|
||||
|
||||
useShadowStyles(`
|
||||
.pss { box-sizing: border-box; height: 100%; overflow: auto; padding: 20px clamp(12px, 3vw, 32px) 40px; color: #3b463d;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #f3f3f1; }
|
||||
.pss *, .pss *::before, .pss *::after { box-sizing: border-box; }
|
||||
.pss__head h2 { margin: 0; font-size: 22px; font-weight: 700; }
|
||||
.pss__head p { margin: 6px 0 16px; color: #6d766e; font-size: 14px; max-width: 60ch; }
|
||||
.pss__panel { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px 16px; align-items: end;
|
||||
background: #fff; border: 1px solid #d1d4cd; border-radius: 12px; padding: 14px 16px; }
|
||||
.pss__field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
||||
.pss__field > span { font-size: 13px; font-weight: 600; }
|
||||
.pss__field select { font: inherit; font-size: 14px; padding: 7px 8px; border: 1px solid #c3c8bf; border-radius: 8px; background: #fff;
|
||||
color: inherit; min-height: 38px; width: 100%; }
|
||||
.pss__check { flex-direction: row; align-items: center; gap: 8px; min-height: 38px; }
|
||||
.pss__check input { width: 18px; height: 18px; margin: 0; }
|
||||
.pss__embed { margin-top: 12px; background: #fff; border: 1px solid #d1d4cd; border-radius: 12px; padding: 12px 16px; }
|
||||
.pss__embed-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
||||
.pss__embed h3 { margin: 0; font-size: 15px; }
|
||||
.pss__embed p { margin: 2px 0 0; font-size: 13px; color: #6d766e; }
|
||||
.pss__copy { font: inherit; font-size: 14px; font-weight: 600; padding: 7px 16px; border-radius: 999px; border: 1px solid #3b463d;
|
||||
background: #3b463d; color: #fff; cursor: pointer; min-height: 38px; }
|
||||
.pss__copy:focus-visible, .pss__field select:focus-visible, .pss__check input:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
|
||||
.pss__code { display: block; width: 100%; margin-top: 10px; min-height: 84px; max-height: 220px; resize: vertical; font: 12px/1.5 ui-monospace, monospace;
|
||||
direction: ltr; text-align: left; white-space: pre; overflow: auto; border: 1px solid #d1d4cd; border-radius: 8px; padding: 8px; background: #f8f8f6; color: #1f2937; }
|
||||
.pss--preset { padding-top: 12px; }
|
||||
.pss--preset .pss__sections { margin-top: 0; }
|
||||
.pss__sections { margin-top: 20px; display: flex; flex-direction: column; gap: 16px; }
|
||||
.pss__sections > ${TAG} { display: block; }
|
||||
`, 'poster-sections')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pss" :class="{ 'pss--preset': !configure }" :dir="dir" :lang="pageLang">
|
||||
<template v-if="configure">
|
||||
<header class="pss__head">
|
||||
<h2>{{ t('posterSections.heading') }}</h2>
|
||||
<p>{{ t('posterSections.intro') }}</p>
|
||||
</header>
|
||||
|
||||
<form class="pss__panel" @submit.prevent>
|
||||
<label class="pss__field">
|
||||
<span>{{ t('posterSections.type') }}</span>
|
||||
<select v-model="type" data-param="type" @focus="refreshTypes" @pointerdown="refreshTypes">
|
||||
<option v-for="n in types" :key="n" :value="n">{{ typeLabel(n) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pss__field">
|
||||
<span>{{ t('posterSections.region') }}</span>
|
||||
<select v-model="scope" data-param="scope">
|
||||
<option value="">{{ t('posterSections.allRegions') }}</option>
|
||||
<option v-for="s in scopes" :key="s.key" :value="s.key">{{ s.label }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pss__field">
|
||||
<span>{{ t('posterSections.language') }}</span>
|
||||
<select v-model="lang" data-param="locale">
|
||||
<option v-for="c in SUPPORTED_LOCALES" :key="c" :value="c" :lang="c">{{ langLabel(c) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pss__field">
|
||||
<span>{{ t('buttons.postersPerRow') }}</span>
|
||||
<select v-model.number="columns" data-param="columns">
|
||||
<option v-for="n in COLUMN_CHOICES" :key="n" :value="n">{{ n }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pss__field pss__check">
|
||||
<input v-model="collapsed" type="checkbox" data-param="collapsed" />
|
||||
<span>{{ t('posterSections.startCollapsed') }}</span>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
<section class="pss__embed">
|
||||
<div class="pss__embed-row">
|
||||
<div>
|
||||
<h3>{{ t('posterSections.embed') }}</h3>
|
||||
<p>{{ t('posterSections.embedNote') }}</p>
|
||||
</div>
|
||||
<button type="button" class="pss__copy" @click="copy">{{ copied ? t('posterSections.copied') : t('posterSections.copy') }}</button>
|
||||
</div>
|
||||
<textarea class="pss__code" readonly :value="snippet" :aria-label="t('posterSections.embed')" spellcheck="false"></textarea>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<Sections />
|
||||
</div>
|
||||
</template>
|
||||
133
plugin-designs/poster-sections/scripts/check-poster-sections.mjs
Normal file
133
plugin-designs/poster-sections/scripts/check-poster-sections.mjs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env node
|
||||
// check:poster-sections — the Poster Sections panel's choices reach the element unchanged.
|
||||
// For every choice the panel offers (type, one region or All regions, each language, collapsed or open, 1-5 per
|
||||
// row), the tags src/embed.js builds, read back through the ELEMENT's own attribute reader (posterSectionConfig),
|
||||
// give exactly those choices; "All regions" is one section per region, each once; the snippet's script and tags
|
||||
// are the same tags the page renders.
|
||||
// PRESETS: poster-sections.instances.json has, per poster type, exactly "<prefix>-all" + one preset per region its
|
||||
// adapter's scopes() returns, no more, no fewer. So a region the data no longer has (a stale "other") or a new one
|
||||
// cannot drift from the presets. A type whose file is NOT on the tree must have no presets at all (its pages would
|
||||
// only say "No posters"): 'upg100-list' is listed once its type file is on the tree.
|
||||
// PAGES: the built preset pages (public/<tree>/poster-sections/poster-sections/<id>/) are exactly the presets: the tree
|
||||
// generator writes a page per preset but never deletes one, so a dropped preset would leave a stale page behind.
|
||||
// --self-test: a builder that drops `columns`, one that drops `collapsed`, one that repeats a region under All, and a
|
||||
// presets file with a region the type does not have must each FAIL.
|
||||
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
// the site's languages = the catalogue's locale folders (library/i18n/index.js needs Vite's import.meta.glob)
|
||||
const LOC = join(dirname(fileURLToPath(import.meta.url)), '../../../library/i18n/locales')
|
||||
const SUPPORTED_LOCALES = readdirSync(LOC).filter((d) => statSync(join(LOC, d)).isDirectory()).sort()
|
||||
import { posterSectionConfig } from '../../../library/elements/posterSectionConfig.js'
|
||||
import * as real from '../src/embed.js'
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '../../..')
|
||||
const PRESETS = JSON.parse(readFileSync(join(ROOT, 'plugin-designs/poster-sections/poster-sections.instances.json'), 'utf8'))
|
||||
// poster type → [preset id prefix, the file that defines the type's adapter (its default export has scopes())]
|
||||
const TYPES = { country: ['countries', 'library/components/poster/types/countryPosterType.js'], 'upg100-list': ['upg100', 'plugin-designs/my-upg-100-list/data/upg100-poster-type.js'] }
|
||||
|
||||
// Each type's scopes(), loaded through Vite (the adapters import through the @map alias).
|
||||
async function loadScopes() {
|
||||
const { createServer } = await import('vite')
|
||||
const server = await createServer({ root: ROOT, configFile: false, logLevel: 'error', appType: 'custom', server: { middlewareMode: true, hmr: false, ws: false }, resolve: { alias: { '@map': join(ROOT, 'library') } } })
|
||||
const out = {}
|
||||
try {
|
||||
for (const [type, [, file]] of Object.entries(TYPES)) {
|
||||
if (!existsSync(join(ROOT, file))) { out[type] = null; continue }
|
||||
const a = (await server.ssrLoadModule(join(ROOT, file))).default
|
||||
out[type] = a.scopes()
|
||||
}
|
||||
} finally { await server.close() }
|
||||
return out
|
||||
}
|
||||
|
||||
function presetArm(presets, scopesByType) {
|
||||
const fails = []
|
||||
for (const [type, [prefix]] of Object.entries(TYPES)) {
|
||||
const scopes = scopesByType[type]
|
||||
const ids = presets.instances.filter((i) => i.props && i.props.type === type).map((i) => i.id)
|
||||
if (scopes == null) { // the type's file is not on this tree: a preset of it could only say "No posters"
|
||||
if (ids.length) fails.push(`presets for "${type}" exist (${ids.join(', ')}) but its type is not on this tree, so their pages would show no posters`)
|
||||
continue
|
||||
}
|
||||
const want = [`${prefix}-all`, ...scopes.map((s) => `${prefix}-${s}`)].sort()
|
||||
const have = [...ids].sort()
|
||||
const extra = have.filter((x) => !want.includes(x)), missing = want.filter((x) => !have.includes(x))
|
||||
if (extra.length || missing.length) fails.push(`presets for "${type}" differ from its scopes(): extra ${extra.join(', ') || '-'}; missing ${missing.join(', ') || '-'}`)
|
||||
for (const i of presets.instances.filter((x) => x.props && x.props.type === type && x.id !== `${prefix}-all`)) {
|
||||
if (i.id !== `${prefix}-${i.props.scope}`) fails.push(`preset "${i.id}" shows scope "${i.props.scope}"`)
|
||||
}
|
||||
}
|
||||
if (!presets.instances.some((i) => i.id === 'configure' && i.props && i.props.configure === true)) fails.push('no "configure" preset')
|
||||
return fails
|
||||
}
|
||||
|
||||
// The built preset pages, when a tree is built: every folder under poster-sections/poster-sections/ that holds a page.
|
||||
function builtPages() {
|
||||
const pub = join(ROOT, 'public')
|
||||
if (!existsSync(pub)) return null
|
||||
for (const tree of readdirSync(pub)) {
|
||||
const dir = join(pub, tree, 'poster-sections', 'poster-sections')
|
||||
if (existsSync(dir)) return readdirSync(dir).filter((d) => existsSync(join(dir, d, 'index.html')))
|
||||
}
|
||||
return null
|
||||
}
|
||||
function pagesArm(presets, pages) {
|
||||
if (pages == null) return []
|
||||
const ids = presets.instances.map((i) => i.id)
|
||||
const stale = pages.filter((p) => !ids.includes(p)), missing = ids.filter((i) => !pages.includes(i))
|
||||
return stale.length || missing.length ? [`built preset pages differ from the presets: stale ${stale.join(', ') || '-'}; missing ${missing.join(', ') || '-'} (delete a dropped preset's page; rebuild the tree for a new one)`] : []
|
||||
}
|
||||
|
||||
const SCOPES = ['africa', 'asia', 'europe', 'latin-america-and-caribbean', 'middle-east', 'north-america-and-non-spanish-caribbean', 'oceania', 'other']
|
||||
const fake = (attrs) => ({ getAttribute: (n) => (n in attrs ? attrs[n] : null), hasAttribute: (n) => n in attrs })
|
||||
|
||||
function arms(embed) {
|
||||
const fails = []
|
||||
for (const scope of ['', ...SCOPES]) for (const locale of SUPPORTED_LOCALES) for (const collapsed of [false, true]) for (const columns of [1, 2, 3, 4, 5]) {
|
||||
const choices = { type: 'country', scope, scopes: SCOPES, locale, collapsed, columns, assetsBase: 'https://example.org/dx-maps/assets' }
|
||||
const tags = embed.sectionAttrs(choices)
|
||||
const want = scope ? [scope] : SCOPES
|
||||
if (tags.map((a) => a.scope).join() !== want.join()) { fails.push(`scope "${scope || 'All'}": sections ${tags.map((a) => a.scope).join()}`); continue }
|
||||
for (const a of tags) {
|
||||
const c = posterSectionConfig(fake(a))
|
||||
const bad = [c.type !== 'country' && 'type', c.locale !== locale && 'locale', c.collapsed !== collapsed && 'collapsed',
|
||||
c.columns !== columns && 'columns', c.assetsBase !== choices.assetsBase && 'assets-base', c.collapsible !== true && 'collapsible'].filter(Boolean)
|
||||
if (bad.length) { fails.push(`${a.scope}/${locale}/${collapsed ? 'shut' : 'open'}/${columns}: the element reads back a different ${bad.join(', ')}`); break }
|
||||
}
|
||||
if (fails.length > 3) return fails
|
||||
}
|
||||
const snip = embed.embedSnippet({ type: 'country', scope: '', scopes: SCOPES, locale: 'ar', collapsed: true, columns: 3 }, 'https://example.org/dx-maps/poster-sections/poster-sections.js')
|
||||
const lines = snip.split('\n')
|
||||
if (lines[0] !== '<script src="https://example.org/dx-maps/poster-sections/poster-sections.js"></script>') fails.push(`snippet: first line is not the bundle script: ${lines[0]}`)
|
||||
if (lines.length !== SCOPES.length + 1 || !lines.slice(1).every((l, i) => l === `<dxm-poster-section type="country" scope="${SCOPES[i]}" locale="ar" collapsed columns="3"></dxm-poster-section>`)) fails.push(`snippet: the tags are not one per region with the chosen attributes:\n${snip}`)
|
||||
return fails
|
||||
}
|
||||
|
||||
const SCOPES_BY_TYPE = await loadScopes()
|
||||
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const mutants = {
|
||||
'drops columns': { ...real, sectionAttrs: (c) => real.sectionAttrs(c).map(({ columns, ...a }) => a) },
|
||||
'drops collapsed': { ...real, sectionAttrs: (c) => real.sectionAttrs(c).map(({ collapsed, ...a }) => a) },
|
||||
'repeats a region under All': { ...real, sectionAttrs: (c) => { const r = real.sectionAttrs(c); return c.scope ? r : [...r, r[0]] } },
|
||||
}
|
||||
let ok = true
|
||||
const stale = { ...PRESETS, instances: [...PRESETS.instances, { id: 'countries-atlantis', props: { type: 'country', scope: 'atlantis' } }] }
|
||||
const pg = pagesArm(PRESETS, [...PRESETS.instances.map((i) => i.id), 'countries-atlantis'])
|
||||
console.log(` mutant "a built page for a preset that no longer exists": ${pg.length ? 'FAILS (good)' : 'PASSES (bad)'}`)
|
||||
if (!pg.length) ok = false
|
||||
const pf = presetArm(stale, SCOPES_BY_TYPE)
|
||||
console.log(` mutant "a preset for a region the type does not have": ${pf.length ? 'FAILS (good)' : 'PASSES (bad)'}`)
|
||||
if (!pf.length) ok = false
|
||||
for (const [name, m] of Object.entries(mutants)) {
|
||||
const f = arms(m)
|
||||
console.log(` mutant "${name}": ${f.length ? 'FAILS (good)' : 'PASSES (bad)'}`)
|
||||
if (!f.length) ok = false
|
||||
}
|
||||
console.log(ok ? 'POSTER-SECTIONS SELF-TEST: PASS — every mutant fails the check.' : 'POSTER-SECTIONS SELF-TEST: FAIL')
|
||||
process.exit(ok ? 0 : 1)
|
||||
}
|
||||
const fails = [...arms(real), ...presetArm(PRESETS, SCOPES_BY_TYPE), ...pagesArm(PRESETS, builtPages())]
|
||||
const measured = Object.entries(SCOPES_BY_TYPE).filter(([, v]) => v).map(([k, v]) => `${k} ${v.length}`).join(', ')
|
||||
if (fails.length) { console.log(`POSTER-SECTIONS: FAIL (${fails.length})`); fails.forEach((f) => console.log(` - ${f}`)); process.exit(1) }
|
||||
console.log(`POSTER-SECTIONS: PASS — every panel choice (${SCOPES.length + 1} region choices × ${SUPPORTED_LOCALES.length} languages × open/collapsed × 5 per row) reads back through the element unchanged; All regions = one section per region; the snippet is the script + those tags; presets = each type's scopes() + all (${measured}).`)
|
||||
42
plugin-designs/poster-sections/scripts/gen-presets.mjs
Normal file
42
plugin-designs/poster-sections/scripts/gen-presets.mjs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env node
|
||||
// gen-presets.mjs (bun run gen:poster-sections-presets) — writes poster-sections.instances.json from each poster type's
|
||||
// OWN regions: "<prefix>-all" plus one preset per scope its adapter's scopes() returns, titled by the type's own
|
||||
// (English) section title, then "configure". The same source check:poster-sections holds the file to, so when the
|
||||
// data's regions change, running this is the whole fix. A type whose file is not on the tree gets NO presets: its
|
||||
// pages could only say "No posters".
|
||||
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '../../..')
|
||||
const FILE = join(ROOT, 'plugin-designs/poster-sections/poster-sections.instances.json')
|
||||
const TYPES = [
|
||||
{ type: 'country', prefix: 'countries', noun: 'Country posters', file: 'library/components/poster/types/countryPosterType.js' },
|
||||
{ type: 'upg100-list', prefix: 'upg100', noun: 'UPG 100 posters', file: 'plugin-designs/my-upg-100-list/data/upg100-poster-type.js' },
|
||||
]
|
||||
const old = JSON.parse(readFileSync(FILE, 'utf8'))
|
||||
const { createServer } = await import('vite')
|
||||
const server = await createServer({ root: ROOT, configFile: false, logLevel: 'error', appType: 'custom', server: { middlewareMode: true, hmr: false, ws: false }, resolve: { alias: { '@map': join(ROOT, 'library') } } })
|
||||
// English from the real catalogue (library/i18n/locales/en/<area>.json), so a title the type translates reads as the
|
||||
// site's English; a key the catalogue lacks comes back as itself, which every adapter treats as "use the region name".
|
||||
const english = (key) => {
|
||||
const [area, ...parts] = String(key).split('.')
|
||||
const f = join(ROOT, `library/i18n/locales/en/${area}.json`)
|
||||
if (!parts.length || !existsSync(f)) return key
|
||||
let o = JSON.parse(readFileSync(f, 'utf8'))
|
||||
for (const p of parts) { if (!o || typeof o !== 'object' || !(p in o)) return key; o = o[p] }
|
||||
return typeof o === 'string' ? o : key
|
||||
}
|
||||
const instances = []
|
||||
try {
|
||||
for (const { type, prefix, noun, file } of TYPES) {
|
||||
if (!existsSync(join(ROOT, file))) continue
|
||||
const a = (await server.ssrLoadModule(join(ROOT, file))).default
|
||||
instances.push({ id: `${prefix}-all`, title: `${noun} — all regions`, props: { type, scope: 'all' } })
|
||||
for (const scope of a.scopes()) instances.push({ id: `${prefix}-${scope}`, title: `${noun} — ${a.title(scope, english)}`, props: { type, scope } })
|
||||
}
|
||||
} finally { await server.close() }
|
||||
instances.push({ id: 'configure', title: 'Configure — make a new preset', props: { configure: true } })
|
||||
writeFileSync(FILE, JSON.stringify({ ...old, instances }, null, 2) + '\n')
|
||||
const before = new Set(old.instances.map((i) => i.id)), after = new Set(instances.map((i) => i.id))
|
||||
console.log(`poster-sections presets: ${instances.length} (removed: ${[...before].filter((x) => !after.has(x)).join(', ') || 'none'}; added: ${[...after].filter((x) => !before.has(x)).join(', ') || 'none'})`)
|
||||
31
plugin-designs/poster-sections/src/embed.js
Normal file
31
plugin-designs/poster-sections/src/embed.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* embed.js — the Poster Sections panel's choices → the <dxm-poster-section> tags that show them. Pure (no Vue, no
|
||||
* DOM), so the page and scripts/check-poster-sections.mjs use the SAME function: what the live sections render is
|
||||
* exactly what "Copy embed" hands a website.
|
||||
*
|
||||
* choices = { type, scope, scopes, locale, collapsed, columns, assetsBase }
|
||||
* scope '' = "All regions": one section per scope in `scopes`, in that order
|
||||
* columns 0 = leave it to the wall (no attribute)
|
||||
*/
|
||||
export const TAG = 'dxm-poster-section'
|
||||
|
||||
export function sectionAttrs(choices) {
|
||||
const { type = 'country', scope = '', scopes = [], locale = '', collapsed = false, columns = 0, assetsBase = '' } = choices || {}
|
||||
const list = scope ? [scope] : scopes
|
||||
return list.map((s) => {
|
||||
const a = { type, scope: s }
|
||||
if (locale) a.locale = locale
|
||||
if (collapsed) a.collapsed = ''
|
||||
if (columns) a.columns = String(columns)
|
||||
if (assetsBase) a['assets-base'] = assetsBase
|
||||
return a
|
||||
})
|
||||
}
|
||||
|
||||
const esc = (v) => String(v).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<')
|
||||
|
||||
/** The text a website pastes: the bundle's script once, then one tag per section. */
|
||||
export function embedSnippet(choices, scriptUrl) {
|
||||
const tags = sectionAttrs(choices).map((a) => `<${TAG}${Object.entries(a).map(([k, v]) => (v === '' ? ` ${k}` : ` ${k}="${esc(v)}"`)).join('')}></${TAG}>`)
|
||||
return [`<script src="${esc(scriptUrl)}"></script>`, ...tags].join('\n')
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
* GATED. The allow-list lives in src/tabs.js; this gate is what keeps it one.
|
||||
*
|
||||
* WHAT IT ASSERTS (each arm is poisoned by --self-test before a pass is trusted):
|
||||
* A. the rail's rows are exactly the named sets: Posters = upg-100, country-posters, poster-design;
|
||||
* A. the Experimental folder starts open, and the rail's rows are exactly the named sets: Posters = upg-100, country-posters, poster-design;
|
||||
* Experimental = wall-of-people-groups, bible-posters (poster plugins only). Ids unique,
|
||||
* every row in a declared section.
|
||||
* B. no row names a NEVER bundle (key-cities, the shells themselves), and resolving the built
|
||||
|
|
@ -52,6 +52,9 @@ export function checkShelf({ SECTIONS, TABS, NEVER, resolveTabs, manifest, meta,
|
|||
const got = TABS.filter((t) => t.section === sec).map((t) => t.id)
|
||||
if (!sameList(got, want)) errs.push(`A: section ${sec} rows are [${got.join(', ')}], expected [${want.join(', ')}]`)
|
||||
}
|
||||
// The Experimental folder starts OPEN: closed, under one short section, it read as an empty caption.
|
||||
const exp = SECTIONS.find((g) => g.key === 'experimental')
|
||||
if (!exp || !exp.folder || exp.open !== true) errs.push('A: the Experimental folder must be declared folder:true, open:true (closed, it is easy to miss)')
|
||||
const extra = TABS.filter((t) => !EXPECT[t.section]).map((t) => t.id)
|
||||
if (extra.length) errs.push(`A: rows outside the allow-listed sections: ${extra.join(', ')}`)
|
||||
// B. never a gated bundle — by declaration and by resolution over the real manifest
|
||||
|
|
@ -114,6 +117,7 @@ function selfTest(base) {
|
|||
const clone = (o) => JSON.parse(JSON.stringify(o))
|
||||
const cases = [
|
||||
['A: key-cities added to Experimental', (c) => { c.TABS = [...c.TABS, { id: 'key-cities', section: 'experimental', bundle: 'key-cities', profile: 'key-cities' }] }, /^A: section experimental|^B: row key-cities/],
|
||||
['A: the Experimental folder closed again', (c) => { c.SECTIONS = c.SECTIONS.map((g) => g.key === 'experimental' ? { ...g, open: false } : g) }, /^A: the Experimental folder must be/],
|
||||
['A: a row dropped', (c) => { c.TABS = c.TABS.filter((t) => t.id !== 'wall-of-people-groups') }, /^A: section experimental/],
|
||||
['B: resolver that shows whatever the manifest carries', (c) => { c.resolveTabs = (m) => (m.bundles || []).map((b) => ({ id: b.bundle, bundle: b.bundle, profile: b.bundle, tag: b.bundle, scriptUrl: b.scriptUrl })) }, /^B: resolution produced a key-cities row/],
|
||||
['C: a composite row falls back to a leaf', (c) => { c.manifest = clone(c.manifest); const b = c.manifest.bundles.find((x) => x.bundle === 'my-upg-100-list'); b.profiles = b.profiles.filter((p) => p.profile !== 'upg-100-mfe') }, /^C: row upg-100 opens/],
|
||||
|
|
|
|||
|
|
@ -20,10 +20,14 @@
|
|||
* composite is the plugin as its owner designed it (and it is what makes Posterverse capability 5).
|
||||
*/
|
||||
|
||||
// The rail's sections, in order. `folder` renders collapsible and starts closed, like the gallery's.
|
||||
// The rail's sections, in order. Experimental is a collapsible folder, like the gallery's, but it starts
|
||||
// OPEN: the gallery's closed folder sits at the foot of a long rail, while Posterverse has one short
|
||||
// section above it, so a closed folder sat mid-drawer with empty space below and read as a caption with
|
||||
// nothing under it (measured at 390: the heading at y 335 in an 800px drawer, ~500px empty below).
|
||||
// Open, its plugins show like any section's rows; a reader can still collapse it.
|
||||
export const SECTIONS = [
|
||||
{ key: 'posters', i18n: 'sec.posters', icon: 'poster' },
|
||||
{ key: 'experimental', i18n: 'sec.experimental', icon: 'flask', folder: true, open: false },
|
||||
{ key: 'experimental', i18n: 'sec.experimental', icon: 'flask', folder: true, open: true },
|
||||
]
|
||||
|
||||
// Every row Posterverse can show, in rail order.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "single",
|
||||
"label": "Regions — country posters by region",
|
||||
"note": "The Regions dashboard's third view: one collapsible <dxm-poster-section type=\"country\"> per region (the library's element, the same cards and print options as the Country Posters wall), starting shut, in the page's locale. The regions are the ones the poster data files its countries under, in the dashboard's region order."
|
||||
}
|
||||
66
plugin-designs/regions-dashboard/country-poster-sections.vue
Normal file
66
plugin-designs/regions-dashboard/country-poster-sections.vue
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<script setup>
|
||||
/**
|
||||
* @description Country posters by region: one collapsible poster section per region (the library's <dxm-poster-section>), the Regions dashboard's third view.
|
||||
*
|
||||
* country-poster-sections.vue — the Regions dashboard's THIRD view, "Country Posters", beside the regions list and the
|
||||
* region dashboards. One <dxm-poster-section type="country"> per region, collapsible and starting shut, in the page's
|
||||
* locale.
|
||||
*
|
||||
* ★ THE ELEMENT IS THE LIBRARY'S (library/elements/posterSectionElement.js): the same section, cards and print options as
|
||||
* the Country Posters wall. This bundle only registers it (a second registration on one page is a no-op) and places it.
|
||||
*
|
||||
* ★ THE SECTIONS ARE THE REGIONS THE POSTERS ARE FILED UNDER. The country type groups its cards by the poster data's own
|
||||
* region (library/data/country-posters.json), so the list of sections comes from that same data, in the dashboard's
|
||||
* fixed region order (REGION_ORDER). That way every country with a poster lands in exactly one section; a country the
|
||||
* data files under no WAGF region gets the type's "Other" section instead of disappearing. The scope keys go through
|
||||
* the type's own regionKey, so this page and the element can never spell a region two ways.
|
||||
*/
|
||||
import { computed, h, inject, ref } from 'vue'
|
||||
import { registerPosterSectionElement } from '@map/elements/posterSectionElement.js'
|
||||
import { regionKey } from '@map/components/poster/types/countryPosterType.js'
|
||||
import posterData from '@map/data/country-posters.json'
|
||||
import { REGION_ORDER } from '@map/colors/wagf-region.js'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { getRuntimeLocale, detectLocale } from '@map/i18n/index.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
||||
registerPosterSectionElement()
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
const cfg = computed(() => profileConfig.value || {})
|
||||
const locale = computed(() => cfg.value.locale || cfg.value.lang || cfg.value.languageCode || getRuntimeLocale() || detectLocale() || 'en')
|
||||
// The people-groups service the Regions list prints from (regions-list.vue's prayBaseUrl), so a section's prayer list
|
||||
// and cards come from the same records as the list's own country posters.
|
||||
const apiBase = computed(() => cfg.value.prayBaseUrl || cfg.value.apiBaseUrl || 'https://pray.doxa.life')
|
||||
|
||||
const scopes = computed(() => {
|
||||
const inData = new Set((posterData.countries || []).map((c) => regionKey(c.region)))
|
||||
const ordered = REGION_ORDER.map(regionKey).filter((k) => inData.has(k))
|
||||
return [...ordered, ...[...inData].filter((k) => !ordered.includes(k)).sort()]
|
||||
})
|
||||
|
||||
const section = (scope) => h('dxm-poster-section', {
|
||||
key: scope,
|
||||
type: 'country',
|
||||
scope,
|
||||
locale: locale.value,
|
||||
collapsed: '',
|
||||
'assets-base': resolveAssetsBase(cfg.value),
|
||||
'api-base-url': apiBase.value,
|
||||
...(cfg.value.isDark === true ? { 'is-dark': '' } : {}),
|
||||
})
|
||||
const Page = () => h('div', { class: 'cps' }, h('div', { class: 'cps__inner' }, scopes.value.map(section)))
|
||||
|
||||
useShadowStyles(
|
||||
// The pane gives this element its whole box; the page scrolls here, and the sections stack in it.
|
||||
':host{ display:block; height:100%; }' +
|
||||
'.cps{ height:100%; overflow-y:auto; -webkit-overflow-scrolling:touch; box-sizing:border-box; padding:16px 12px 32px; }' +
|
||||
'.cps__inner{ max-width:1180px; margin:0 auto; display:flex; flex-direction:column; gap:12px; }' +
|
||||
'.cps__inner > *{ display:block; }',
|
||||
'country-poster-sections'
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page />
|
||||
</template>
|
||||
|
|
@ -44,7 +44,6 @@ import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js'
|
|||
import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js'
|
||||
import { splitLastWord } from './src/useLastWordHighlight.js'
|
||||
import { useRegionsI18n } from '@map/i18n/useRegionsI18n.js'
|
||||
import { countryName } from '@map/utils/countryName.js'
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
// ar renders right-to-left. RTL_LOCALES is the shared set and :dir on our own root is the
|
||||
// house pattern (doxa-simple-map.vue:18, :246, :1346) — an EMBEDDED element must not reach for
|
||||
|
|
@ -60,20 +59,14 @@ import { DXLIFE_TOKENS } from './dxlife-tokens.js'
|
|||
// and the no-selection chooser. The requirement: a toggle at the bottom of each region section
|
||||
// that opens that region's country posters.
|
||||
//
|
||||
// NOTHING HERE IS REBUILT. PosterWall is already the shared, fully parameterised grid that
|
||||
// my-upg-100-list/poster-wall.vue and country-posters/country-poster-wall.vue both render, and
|
||||
// it already owns the card layout, the "Show on map" header button, the Print Options footer
|
||||
// and the client-side raster. This file supplies only data and a toggle — the same division of
|
||||
// labour country-poster-wall.vue states in its own header ("this profile supplies only its
|
||||
// data and taxonomy").
|
||||
import PosterWall from '@map/components/poster/PosterWall.vue'
|
||||
// NOTHING HERE IS REBUILT. Each region's posters are the library's <dxm-poster-section type="country">
|
||||
// (library/elements/posterSectionElement.js), the same section, cards and print options as the Country Posters
|
||||
// wall and the Regions dashboard's third view, so the list is composed from the shared element rather than
|
||||
// drawing a wall of its own. This file supplies only the toggle and the region.
|
||||
import RegionSectionCard from '@map/components/regions/RegionSectionCard.vue'
|
||||
import posterData from '@map/data/country-posters.json'
|
||||
import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js'
|
||||
import { makeCountrySheet, largeSheet, posterWithheld, rowsWithheld } from '@map/packet/countrySheet.js'
|
||||
import { isPhonePrint } from '@map/composables/printInPlace.js'
|
||||
import { keyedError } from '@map/i18n/keyedError.js'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { registerPosterSectionElement } from '@map/elements/posterSectionElement.js'
|
||||
import { regionKey } from '@map/components/poster/types/countryPosterType.js'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { cyrillicHeading } from '@map/styles/portedGlobals.js'
|
||||
import { hostElement } from '@map/utils/hostElement.js'
|
||||
|
|
@ -276,44 +269,32 @@ async function loadRegions() {
|
|||
}
|
||||
}
|
||||
|
||||
// ─── COUNTRY POSTERS, per region ─────────────────────────────────────────────────────────
|
||||
// One wall per region, opened by the footer toggle. instanceId is suffixed with the region
|
||||
// slug because PosterWall scopes its CustomEvent by instanceId so several embeds on one page
|
||||
// never cross-talk (PosterWall.vue:40) — and here there genuinely are several.
|
||||
const POSTER_EVENT = 'doxa-regions:show-country-on-map'
|
||||
// ─── COUNTRY POSTERS, per region ─────────────────────────────────────────────
|
||||
// One poster section per region, opened by the footer toggle: the library's <dxm-poster-section type="country">.
|
||||
// Its scope is the region's key through the country type's own regionKey, so the list and the element can never
|
||||
// spell a region two ways. It is not collapsible (the toggle already opens and shuts it) and shows two cards a row,
|
||||
// on a phone as well.
|
||||
// ⚠ The element files countries under the poster data's region (library/data/country-posters.json), so this list
|
||||
// shows exactly that data's membership for each region.
|
||||
registerPosterSectionElement()
|
||||
const SECTION_EVENT = 'doxa-country-posters:show-on-map'
|
||||
// Read off the injected config the same way every other value in this file is (see the note
|
||||
// above cfg) — a bare default here would make two embeds on one page answer each other's
|
||||
// poster events.
|
||||
const instanceId = computed(() => cfg.value.instanceId || 'regions-list')
|
||||
const posterAssetsBase = computed(() => resolveAssetsBase(cfg.value))
|
||||
|
||||
// ★ THE PRINT ROWS, AND THE SHEET DRAWN FROM THEM. Supplying `records` is what makes the Prayer
|
||||
// List, Card Deck and Prayer Packet buttons appear on each country (PosterWall hides them when a
|
||||
// wall cannot supply rows, rather than failing at click time). One fetch per language, cached in
|
||||
// the provider and shared by every country in every region, so opening five regions is still one
|
||||
// request. The same provider feeds the sheet, so the printed map and the printed list can never
|
||||
// disagree about which groups are in a country.
|
||||
const countryRecords = createPeopleGroupRecords({
|
||||
apiBase: () => prayBaseUrl.value,
|
||||
lang: () => languageCode.value,
|
||||
profileConfig: injectedCfg,
|
||||
const sectionAttrs = (region) => ({
|
||||
type: 'country',
|
||||
scope: regionKey(region.value || region.label),
|
||||
locale: i18nLocale.value || languageCode.value || 'en',
|
||||
collapsible: 'false',
|
||||
columns: '2',
|
||||
// Two posters a row on a phone too, as this list has always shown them: 150 px lets two cards fit at 390.
|
||||
'min-card-px': '150',
|
||||
'assets-base': posterAssetsBase.value,
|
||||
// The same people-groups service this list reads its regions from, so a card's list and deck match it.
|
||||
'api-base-url': prayBaseUrl.value,
|
||||
})
|
||||
// Page 1 of a country packet, and the country poster, drawn at the click. Nothing is stored: no
|
||||
// country sheet ships, and the packet's default lift would ask for posters/country-<iso3>.html and
|
||||
// 404 on all 175.
|
||||
const sheetForRecord = makeCountrySheet({
|
||||
records: countryRecords,
|
||||
assetsBase: () => posterAssetsBase.value,
|
||||
// The same list the Country Posters wall computes its series from, so this tab's printed
|
||||
// "map N of M" agrees with the wall's print and with the country's thumbnail.
|
||||
countries: posterData.countries || [],
|
||||
})
|
||||
// ★ THE POSTER IS ENGLISH BY RECORDED DECISION (series.json english_only_note), so the generator gets
|
||||
// the DATA's own record, never the card: the card's label is localised (localisedPoster below), and
|
||||
// handing it over printed "Pray for السودان" beside an English "Countries: Sudan" in Arabic. The
|
||||
// Country Posters wall resolves the record by id the same way (country-poster-wall.vue, byId).
|
||||
const POSTER_BY_ID = new Map((posterData.countries || []).map(c => [c.id, c]))
|
||||
const countrySheet = (card, format) => sheetForRecord(POSTER_BY_ID.get(card && card.id) || card, format)
|
||||
const openPosters = ref(new Set())
|
||||
|
||||
function togglePosters(region) {
|
||||
|
|
@ -323,74 +304,17 @@ function togglePosters(region) {
|
|||
}
|
||||
const postersOpen = (region) => openPosters.value.has(region.slug)
|
||||
|
||||
// The poster dataset is keyed by ISO-3 (`iso3`); the regions rollup keys by `code`. Same
|
||||
// codes, so the join is direct.
|
||||
const POSTER_BY_ISO3 = new Map((posterData.countries || []).map(c => [String(c.iso3 || '').toUpperCase(), c]))
|
||||
|
||||
// Every country in the region gets a card, in PosterWall's own shape. A country with no
|
||||
// generated poster is NOT dropped — it is passed with a reason, which is what the wall's
|
||||
// `unsupported` slot is for, so the grid never silently loses a country.
|
||||
// ★ EACH CARD NAMED IN THE READER'S LANGUAGE. item.label is the card's accessible name, the print
|
||||
// HUD's title, and the title on the printed list, deck and packet — and country-posters.json carries
|
||||
// it in English only, so every card was English in every locale. Same fix as the country-posters
|
||||
// wall, and the same two caveats travel with it:
|
||||
// · ENGLISH KEEPS THE DATA'S OWN NAMES. Intl's English differs from them for 32 of these 175
|
||||
// countries, and countryName.js records that swap as a product decision rather than a refactor.
|
||||
// · THE SHEET GENERATOR STILL GETS THE ORIGINAL RECORD. The poster itself is English by recorded
|
||||
// decision, so only the card's label is localised — postersFor's result feeds the wall, while
|
||||
// countrySheet (above) swaps the card back for the json's own record, by id, before generating.
|
||||
// The i18n locale REF is read, not a snapshot, so a runtime language switch renames the cards.
|
||||
function localisedPoster(rec) {
|
||||
const locale = i18nLocale.value || 'en'
|
||||
if (/^en\b/i.test(locale)) return rec
|
||||
const name = countryName(rec.iso3, locale)
|
||||
return name && name !== rec.iso3 ? { ...rec, label: name } : rec
|
||||
}
|
||||
|
||||
function postersFor(region) {
|
||||
return (region.countries || []).map((c) => {
|
||||
const rec = POSTER_BY_ISO3.get(String(c.code || '').toUpperCase())
|
||||
if (rec) return localisedPoster(rec)
|
||||
return {
|
||||
id: `country-${String(c.code || '').toLowerCase()}`,
|
||||
iso3: c.code,
|
||||
label: c.name,
|
||||
count: c.count,
|
||||
unsupportedReason: t('No poster has been generated for this country yet.'),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ★ THE REASON IS TRANSLATED, THE PRESENCE OF ONE IS STILL THE DATA'S. The record carries its own
|
||||
// English note ("India has 855 people groups — too many…"), and returning it put one untranslated
|
||||
// sentence on the wall in every locale. The record still DECIDES whether a country is unsupported;
|
||||
// only the wording comes from the catalogue now. messages.countryPosterTooMany exists in all
|
||||
// eleven, and needs no number in it: the card already shows the count directly above.
|
||||
// India is the only country in the dataset carrying one (855 people groups, no sheet); China is
|
||||
// fine at 3x4. My own not-yet reason, for a country with no record at all, is already through t().
|
||||
// ★ A COUNTRY TOO LARGE FOR PAPER (countrySheet.js's LARGE_SHEETS; India) prints as one large sheet through the same
|
||||
// makeCountrySheet, so its record's "too many" note no longer makes it unsupported here. It is DESKTOP-ONLY: on a phone
|
||||
// its card says so and offers no rows, rather than failing (the Country Posters wall applies the same rule, same table).
|
||||
const ON_PHONE = isPhonePrint()
|
||||
const phoneBlocked = (c) => ON_PHONE && Boolean(largeSheet(c)?.desktopOnly)
|
||||
const posterUnsupported = (c) => (posterWithheld(c) ? tKey('messages.countryPosterTooMany') : phoneBlocked(c) ? tKey('poster.phoneLimitAll')
|
||||
: c.unsupportedReason && !largeSheet(c) ? tKey('messages.countryPosterTooMany') : null)
|
||||
// A blocked card's rows REJECT with the notice, never [] ("no people groups" on a card that has hundreds).
|
||||
// A withheld whole-country poster (countrySheet.js WITHHELD: India) withholds its list, packet and cards too.
|
||||
const posterRows = (item) => (rowsWithheld(item) ? Promise.reject(keyedError('messages.countryPosterTooMany', 'Too many people groups to fit on one poster legibly — a solution is in progress.')) : phoneBlocked(item) ? Promise.reject(keyedError('poster.phoneLimitAll', 'This needs a desktop or laptop computer.')) : countryRecords({ type: 'country', values: [item.iso3] }))
|
||||
|
||||
// One region's wall is already inside a region section, so it needs no second level of
|
||||
// collapsing and no grouping: everything in it is that one region.
|
||||
const posterGroupBy = () => 'countries'
|
||||
|
||||
// PosterWall's "Show on map" dispatches a window CustomEvent carrying listId (the poster id,
|
||||
// A poster card's "Show on map" dispatches a window CustomEvent carrying listId (the poster id,
|
||||
// e.g. "country-chn") or null when the user toggles the same card off. Mapping that id back to
|
||||
// the country record and reusing selectCountry() is what makes the button open the dashboard
|
||||
// tab for that country: the requirement is that the view-on-map button opens the country
|
||||
// dashboard page for that country.
|
||||
// The library's poster sections name themselves 'poster-section-country-<region>' (PosterSectionView.vue). In the
|
||||
// Regions composite this list is always mounted, so it also answers the third view's sections (Country Posters):
|
||||
// ONE handler for both, which keeps the region context in the selection.
|
||||
function onPosterShowOnMap(ev) {
|
||||
const d = ev?.detail || {}
|
||||
if (!String(d.instanceId || '').startsWith(instanceId.value)) return
|
||||
if (!String(d.instanceId || '').startsWith('poster-section-country-')) return
|
||||
if (!d.listId) return // a deselect is not a navigation
|
||||
const iso3 = String(d.listId).replace(/^country-/, '').toUpperCase()
|
||||
for (const region of regions.value) {
|
||||
|
|
@ -407,8 +331,8 @@ function onPosterShowOnMap(ev) {
|
|||
// already had this watcher (line 541); this tab was simply missed.
|
||||
watch(languageCode, () => { loadRegions() })
|
||||
|
||||
onMounted(() => { if (typeof window !== 'undefined') window.addEventListener(POSTER_EVENT, onPosterShowOnMap) })
|
||||
onBeforeUnmount(() => { if (typeof window !== 'undefined') window.removeEventListener(POSTER_EVENT, onPosterShowOnMap) })
|
||||
onMounted(() => { if (typeof window !== 'undefined') window.addEventListener(SECTION_EVENT, onPosterShowOnMap) })
|
||||
onBeforeUnmount(() => { if (typeof window !== 'undefined') window.removeEventListener(SECTION_EVENT, onPosterShowOnMap) })
|
||||
|
||||
onMounted(loadRegions)
|
||||
|
||||
|
|
@ -583,32 +507,7 @@ useShadowStyles(
|
|||
@select-country="(c) => selectCountry(c, region)"
|
||||
>
|
||||
<template #extra>
|
||||
<PosterWall
|
||||
:items="postersFor(region)"
|
||||
:group-by="posterGroupBy"
|
||||
:instance-id="`${instanceId}-${region.slug}`"
|
||||
:assets-base="posterAssetsBase"
|
||||
:event-name="POSTER_EVENT"
|
||||
:heading="''"
|
||||
:intro="''"
|
||||
unit="UPG"
|
||||
:item-noun="t('Country')"
|
||||
:min-card-px="150"
|
||||
:item-noun-plural="t('Countries')"
|
||||
item-count-key="regions.countryCount"
|
||||
:records="countryRecords ? posterRows : null"
|
||||
:sheet="countrySheet"
|
||||
:card-stocks="true"
|
||||
:collapsible="false"
|
||||
:embedded="true"
|
||||
:section-heads="false"
|
||||
:columns="2"
|
||||
:columns-control="true"
|
||||
:unsupported="posterUnsupported"
|
||||
download-prefix="doxa-country"
|
||||
poster-action="print"
|
||||
:prayer-list-soon-message="t('Coming soon — a prayer list will be generated for each country, naming every unengaged people group within it.')"
|
||||
/>
|
||||
<dxm-poster-section v-bind="sectionAttrs(region)" />
|
||||
</template>
|
||||
</RegionSectionCard>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
* listens for, and scrolls to tab 2. That is the same seam upg-100-mfe uses for the poster
|
||||
* wall's 'show-on-map', and it keeps each pane under its own ProfileLoader/Pinia.
|
||||
*/
|
||||
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ref, computed, inject, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import { usePaneConfig } from '@map/composables/usePaneConfig.js'
|
||||
import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -47,8 +47,11 @@ import MapInfoButton from '@map/components/map-controls/MapInfoButton.vue'
|
|||
// them to 97 and fail the gate. The moment someone adds these three strings to all locales,
|
||||
// the strip localises with no code change here.
|
||||
import { useRegionsI18n } from '@map/i18n/useRegionsI18n.js'
|
||||
// The third view's label is the gallery's own tab name for country posters (tabs.countryPosters, all eleven locales).
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
|
||||
const { t } = useRegionsI18n()
|
||||
const tKey = useOptionalT('')
|
||||
const cfg = inject('profileConfig', ref(null))
|
||||
const uiStore = inject('uiStore', null)
|
||||
const isDark = computed(() => !!uiStore?.isDark)
|
||||
|
|
@ -70,6 +73,8 @@ const spec = computed(() => ({
|
|||
panes: [
|
||||
{ id: 'list', label: t('Regions List'), at: [0, 0], config: { profile: 'regions-list' } },
|
||||
{ id: 'dashboard', label: t('Region Dashboards'), at: [1, 0], config: { profile: 'region-dashboards' } },
|
||||
// THE THIRD VIEW: country posters, one collapsible section per region (the library's <dxm-poster-section>).
|
||||
{ id: 'posters', label: tKey('tabs.countryPosters'), at: [2, 0], config: { profile: 'country-poster-sections' } },
|
||||
],
|
||||
// The View switcher lives in this element's own header, so the layout's "… below ↓" pill
|
||||
// would be a second, redundant control (same call upg-100-mfe makes).
|
||||
|
|
@ -146,6 +151,13 @@ function go(id) {
|
|||
column?.scrollTo(id)
|
||||
}
|
||||
|
||||
// On a phone the three tabs scroll sideways (see the 600px rule below): keep the current one in view.
|
||||
const hdr = ref(null)
|
||||
watch(view, () => nextTick(() => {
|
||||
const b = hdr.value && hdr.value.querySelector('.seg button[aria-pressed="true"]')
|
||||
if (b && b.scrollIntoView) b.scrollIntoView({ inline: 'center', block: 'nearest' })
|
||||
}))
|
||||
|
||||
// ── tab 1 -> tab 2: forward the selection, then swap the visible pane ───────
|
||||
const SELECT_EVENT = 'doxa-regions-dashboard:select'
|
||||
|
||||
|
|
@ -169,6 +181,10 @@ function onCountrySelected(e) {
|
|||
forwardSelection({ regionSlug: d.region?.slug || '', countrySlug: d.slug || '' })
|
||||
}
|
||||
|
||||
// A card's "Show on map" in the third view (Country Posters) opens that country's dashboard: the Regions list pane
|
||||
// (always mounted here) answers every poster section's event with its own selectCountry(), which arrives above as
|
||||
// 'country-selected'. One handler for both views (regions-list.vue onPosterShowOnMap).
|
||||
|
||||
onMounted(() => {
|
||||
if (!stage.value) return
|
||||
column = buildPaneColumn(spec.value, { host: stage.value })
|
||||
|
|
@ -234,6 +250,14 @@ useShadowStyles(SHADOW_BOX_SIZING_RESET + PANE_GRID_STYLES + '\n' +
|
|||
// ON A PHONE the centred view switcher (about 290 px at 390) leaves no room for two buttons at one end, so the
|
||||
// ⓘ takes the header's START edge, where the floating ⓘ sat before; Share keeps the end.
|
||||
'@media (max-width: 600px){ .rmfe .hdr .hdr-btns > :first-child{ position:absolute; inset-inline-start:12px; top:50%; transform:translateY(-50%); } }' +
|
||||
// THREE VIEWS DO NOT FIT A PHONE'S HEADER (measured at 390: the tabs need about 390 px and ran under the corner
|
||||
// buttons). The switcher keeps the room between the two 36 px buttons (12 + 36 + 8 on each side) and its tab row
|
||||
// scrolls sideways, every label whole; the current tab is scrolled into view on each change. A fieldset is
|
||||
// min-content wide by default, hence min-width:0. Desktop is unchanged.
|
||||
'@media (max-width: 600px){ .rmfe .hdr .viewsel{ min-width:0; max-width:calc(100% - 112px); }' +
|
||||
' .rmfe .hdr .viewsel .seg{ overflow-x:auto; scrollbar-width:none; scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }' +
|
||||
' .rmfe .hdr .viewsel .seg::-webkit-scrollbar{ display:none; }' +
|
||||
' .rmfe .hdr .viewsel .seg button{ flex:0 0 auto; scroll-snap-align:center; } }' +
|
||||
// Stage: the pane-wrapper column mounts here (its .pane-col is absolute).
|
||||
'.rmfe .stage{ position:relative; flex:1 1 auto; min-height:0; }' +
|
||||
'.pane > .pane-mount > *{ position:absolute; inset:0; width:100%; height:100%; display:block; }',
|
||||
|
|
@ -243,7 +267,7 @@ useShadowStyles(SHADOW_BOX_SIZING_RESET + PANE_GRID_STYLES + '\n' +
|
|||
|
||||
<template>
|
||||
<div class="rmfe">
|
||||
<header class="hdr">
|
||||
<header ref="hdr" class="hdr">
|
||||
<ViewSelect
|
||||
:views="spec.panes"
|
||||
:current="view"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This folder is GENERATED by `bun run build`; do not edit it by hand. It holds exactly one thing:
|
||||
|
||||
- **`dx-maps/`** — the output folder: 28 plugin bundles and 43 pages, self-contained.
|
||||
- **`dx-maps/`** — the output folder: 29 plugin bundles and 45 pages, self-contained.
|
||||
Copy this one folder to any web host (or open it from disk) and it works.
|
||||
- `dx-maps/index.html` — the front page: the Plugin Studio, every plugin in one place.
|
||||
- `dx-maps/<plugin>/<plugin>.js` — a plugin's bundle (a custom element; load it with a `<script>` tag).
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
|
||||
<script src="../../doxa-maps-spa.js" integrity="sha384-pCf6xV6CeIBOMglLrBqUX/q+TRHtkbDGehwZOfuVSNJTVDEF84UBWw1ec/1mGLyO" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-maps-spa.js" integrity="sha384-6LiR6vjB5GrQ6TfcAVuCU0pon7+MTANx5bWgB0a9RiWqYqBvFZ1DS9XX7/hg07iO" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
|
||||
<script src="../doxa-maps-spa.js" integrity="sha384-pCf6xV6CeIBOMglLrBqUX/q+TRHtkbDGehwZOfuVSNJTVDEF84UBWw1ec/1mGLyO" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-maps-spa.js" integrity="sha384-6LiR6vjB5GrQ6TfcAVuCU0pon7+MTANx5bWgB0a9RiWqYqBvFZ1DS9XX7/hg07iO" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
"schema": "doxa-maps-manifest@1",
|
||||
"generatedFrom": "plugin-designs/*/*.vue",
|
||||
"embedBase": ".",
|
||||
"bundleCount": 28,
|
||||
"profileCount": 43,
|
||||
"bundleCount": 29,
|
||||
"profileCount": 45,
|
||||
"runtime": {
|
||||
"apiBaseUrl": "https://pray.doxa.life"
|
||||
},
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
"bundle": "cities-internet",
|
||||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"output": "dx-maps/cities-internet/cities-internet.js",
|
||||
"integrity": "sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C",
|
||||
"integrity": "sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5",
|
||||
"elements": [
|
||||
"cities-internet-map"
|
||||
],
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -177,8 +177,8 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" 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<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" 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<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -232,7 +232,7 @@
|
|||
"bundle": "country-posters",
|
||||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"output": "dx-maps/country-posters/country-posters.js",
|
||||
"integrity": "sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2",
|
||||
"integrity": "sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V",
|
||||
"elements": [
|
||||
"country-posters-map"
|
||||
],
|
||||
|
|
@ -255,8 +255,8 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/countries-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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" 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<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" 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<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -272,7 +272,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -288,8 +288,8 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" 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<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" 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<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -305,7 +305,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/poster-section/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -409,7 +409,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-pCf6xV6CeIBOMglLrBqUX/q+TRHtkbDGehwZOfuVSNJTVDEF84UBWw1ec/1mGLyO",
|
||||
"integrity": "sha384-6LiR6vjB5GrQ6TfcAVuCU0pon7+MTANx5bWgB0a9RiWqYqBvFZ1DS9XX7/hg07iO",
|
||||
"elements": [
|
||||
"doxa-maps-spa"
|
||||
],
|
||||
|
|
@ -439,7 +439,7 @@
|
|||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-pCf6xV6CeIBOMglLrBqUX/q+TRHtkbDGehwZOfuVSNJTVDEF84UBWw1ec/1mGLyO\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-6LiR6vjB5GrQ6TfcAVuCU0pon7+MTANx5bWgB0a9RiWqYqBvFZ1DS9XX7/hg07iO\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -466,7 +466,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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn",
|
||||
"integrity": "sha384-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4",
|
||||
"elements": [
|
||||
"doxa-research-map"
|
||||
],
|
||||
|
|
@ -485,8 +485,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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn\" 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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn\" 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-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4\" 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-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4\" 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": [
|
||||
{
|
||||
|
|
@ -512,7 +512,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-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN",
|
||||
"integrity": "sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu",
|
||||
"elements": [
|
||||
"dxm-simple-map"
|
||||
],
|
||||
|
|
@ -531,8 +531,8 @@
|
|||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN\" 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<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu\" 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<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -929,7 +929,7 @@
|
|||
"bundle": "my-upg-100-list",
|
||||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
|
||||
"integrity": "sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw",
|
||||
"integrity": "sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -952,7 +952,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -968,8 +968,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -985,8 +985,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1177,11 +1177,175 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bundle": "poster-sections",
|
||||
"scriptUrl": "./poster-sections/poster-sections.js",
|
||||
"output": "dx-maps/poster-sections/poster-sections.js",
|
||||
"integrity": "sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ",
|
||||
"elements": [
|
||||
"poster-sections"
|
||||
],
|
||||
"stage": "experimental",
|
||||
"profiles": [
|
||||
{
|
||||
"profile": "poster-sections",
|
||||
"stage": "experimental",
|
||||
"label": "Poster Sections — presets",
|
||||
"file": "poster-sections.vue",
|
||||
"description": "Poster Sections: one region's posters (or every region's), as ready-made presets with their own page and embed code; the Configure preset adds a panel of choices and Copy embed for making new ones.",
|
||||
"element": "poster-sections",
|
||||
"type": "parameterized",
|
||||
"isMap": false,
|
||||
"iframeUrl": "./poster-sections/poster-sections/index.html",
|
||||
"scriptUrl": "./poster-sections/poster-sections.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-sections/poster-sections.js\" integrity=\"sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-sections profile-config='{\"profile\":\"poster-sections\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-sections>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
"id": "countries-all",
|
||||
"title": "Country posters — all regions",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "all"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-all/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-all/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-asia",
|
||||
"title": "Country posters — Asia",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "asia"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-asia/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-asia/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-middle-east",
|
||||
"title": "Country posters — Middle East",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "middle-east"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-middle-east/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-middle-east/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-latin-america-and-caribbean",
|
||||
"title": "Country posters — Latin America & Caribbean",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "latin-america-and-caribbean"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-latin-america-and-caribbean/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-latin-america-and-caribbean/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-africa",
|
||||
"title": "Country posters — Africa",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "africa"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-africa/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-africa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-europe",
|
||||
"title": "Country posters — Europe",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "europe"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-europe/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-europe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-north-america-and-non-spanish-caribbean",
|
||||
"title": "Country posters — North America & Non-Spanish Caribbean",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "north-america-and-non-spanish-caribbean"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-north-america-and-non-spanish-caribbean/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-north-america-and-non-spanish-caribbean/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-oceania",
|
||||
"title": "Country posters — Oceania",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "oceania"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-oceania/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-oceania/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "configure",
|
||||
"title": "Configure — make a new preset",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"configure": true
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/configure/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/configure/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bundle": "posterverse",
|
||||
"scriptUrl": "./posterverse/posterverse.js",
|
||||
"output": "dx-maps/posterverse/posterverse.js",
|
||||
"integrity": "sha384-ymAlpF3AiStPvnMzBJXPb3e5cBzsnNEGAlcy8Q+FwJhFrT5McyPEjPHFLjuuXWTn",
|
||||
"integrity": "sha384-Eu6xYVd6sm1N+Jmlt9wMiWk/f3TdS/KyyTmkKu6ClKdwwFzRGenJTXpLGgFxGHrV",
|
||||
"elements": [
|
||||
"posterverse-app"
|
||||
],
|
||||
|
|
@ -1207,7 +1371,7 @@
|
|||
"scriptUrl": "./posterverse/posterverse.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./posterverse/posterverse/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-ymAlpF3AiStPvnMzBJXPb3e5cBzsnNEGAlcy8Q+FwJhFrT5McyPEjPHFLjuuXWTn\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-Eu6xYVd6sm1N+Jmlt9wMiWk/f3TdS/KyyTmkKu6ClKdwwFzRGenJTXpLGgFxGHrV\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -1292,7 +1456,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV",
|
||||
"integrity": "sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -1302,6 +1466,22 @@
|
|||
],
|
||||
"stage": "production",
|
||||
"profiles": [
|
||||
{
|
||||
"profile": "country-poster-sections",
|
||||
"stage": "experimental",
|
||||
"label": "Regions — country posters by region",
|
||||
"file": "country-poster-sections.vue",
|
||||
"description": "Country posters by region: one collapsible poster section per region (the library's <dxm-poster-section>), the Regions dashboard's third view.",
|
||||
"element": "regions-dashboard",
|
||||
"type": "single",
|
||||
"isMap": false,
|
||||
"iframeUrl": "./regions-dashboard/country-poster-sections/index.html",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/country-poster-sections/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"country-poster-sections\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile": "region-dashboards",
|
||||
"stage": "experimental",
|
||||
|
|
@ -1315,8 +1495,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" 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<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" 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<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1332,7 +1512,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1348,8 +1528,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" 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<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" 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<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -76,7 +76,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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-research-map.js" integrity="sha384-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -72,7 +72,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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-research-map.js" integrity="sha384-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dxm-simple-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dxm-simple-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dxm-simple-map> custom element. -->
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <dxm-simple-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
"schema": "doxa-maps-manifest@1",
|
||||
"generatedFrom": "plugin-designs/*/*.vue",
|
||||
"embedBase": ".",
|
||||
"bundleCount": 28,
|
||||
"profileCount": 43,
|
||||
"bundleCount": 29,
|
||||
"profileCount": 45,
|
||||
"runtime": {
|
||||
"apiBaseUrl": "https://pray.doxa.life"
|
||||
},
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
"bundle": "cities-internet",
|
||||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"output": "dx-maps/cities-internet/cities-internet.js",
|
||||
"integrity": "sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C",
|
||||
"integrity": "sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5",
|
||||
"elements": [
|
||||
"cities-internet-map"
|
||||
],
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -177,8 +177,8 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" 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<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" 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<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-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=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-kUGhXBMOJap+zSgt/uSycCzzaJdtMzx0wYdhBudXxjjUIMVclBaYP6c8jF82zu0C\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-/kNcRxqLX/p7+cFvQ7Ft1uhwBYfW28D82LEHcvRBZM+TvTIKvZ12R60ujHB4v4/5\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -232,7 +232,7 @@
|
|||
"bundle": "country-posters",
|
||||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"output": "dx-maps/country-posters/country-posters.js",
|
||||
"integrity": "sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2",
|
||||
"integrity": "sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V",
|
||||
"elements": [
|
||||
"country-posters-map"
|
||||
],
|
||||
|
|
@ -255,8 +255,8 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/countries-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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" 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<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" 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<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -272,7 +272,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -288,8 +288,8 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" 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<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" 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<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -305,7 +305,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/poster-section/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-5OPrLQNYx8cPKfvwpoEz6SHiO1YKYUsJbFPQcTtwC8QUQTWkOEV+Reqr+7fxDkC2\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-Pj/v7sB3Yn4vz5kdjBBl3QpsUbRVdba/HfbXSu6wJjiIfadRaKVd0I1Sh2P/ze8V\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"poster-section\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -409,7 +409,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-pCf6xV6CeIBOMglLrBqUX/q+TRHtkbDGehwZOfuVSNJTVDEF84UBWw1ec/1mGLyO",
|
||||
"integrity": "sha384-6LiR6vjB5GrQ6TfcAVuCU0pon7+MTANx5bWgB0a9RiWqYqBvFZ1DS9XX7/hg07iO",
|
||||
"elements": [
|
||||
"doxa-maps-spa"
|
||||
],
|
||||
|
|
@ -439,7 +439,7 @@
|
|||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-pCf6xV6CeIBOMglLrBqUX/q+TRHtkbDGehwZOfuVSNJTVDEF84UBWw1ec/1mGLyO\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-6LiR6vjB5GrQ6TfcAVuCU0pon7+MTANx5bWgB0a9RiWqYqBvFZ1DS9XX7/hg07iO\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -466,7 +466,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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn",
|
||||
"integrity": "sha384-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4",
|
||||
"elements": [
|
||||
"doxa-research-map"
|
||||
],
|
||||
|
|
@ -485,8 +485,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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn\" 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-XrPeGIB26yCMS1OWjB8c44HqtVbSCC9vyep2fEoqzh34ZI7kwZd1BebXIUcTxfLn\" 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-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4\" 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-WeOQrnujUZ4A942d6Y3k8d2TxlVowsYxSrjxoD2XOTJtTUX0c2C4ntp6oi5wQDN4\" 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": [
|
||||
{
|
||||
|
|
@ -512,7 +512,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-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN",
|
||||
"integrity": "sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu",
|
||||
"elements": [
|
||||
"dxm-simple-map"
|
||||
],
|
||||
|
|
@ -531,8 +531,8 @@
|
|||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN\" 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<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-nHRJFyHIqimO7KdT8Gx/BksX36tY1YHFWW1jb70F3ZtlldeswE9y7IQNyY4ic2BN\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu\" 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<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-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-simple-map/doxa-simple-map.js\" integrity=\"sha384-jk+quEe8NnQEMtsEM71OE5IquBg4vpRktGEQueboxU0+/HQimXSbsyCeUrs/Rzmu\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<dxm-simple-map profile-config='{\"profile\":\"doxa-simple-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></dxm-simple-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -929,7 +929,7 @@
|
|||
"bundle": "my-upg-100-list",
|
||||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
|
||||
"integrity": "sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw",
|
||||
"integrity": "sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -952,7 +952,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -968,8 +968,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -985,8 +985,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" 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<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1177,11 +1177,175 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bundle": "poster-sections",
|
||||
"scriptUrl": "./poster-sections/poster-sections.js",
|
||||
"output": "dx-maps/poster-sections/poster-sections.js",
|
||||
"integrity": "sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ",
|
||||
"elements": [
|
||||
"poster-sections"
|
||||
],
|
||||
"stage": "experimental",
|
||||
"profiles": [
|
||||
{
|
||||
"profile": "poster-sections",
|
||||
"stage": "experimental",
|
||||
"label": "Poster Sections — presets",
|
||||
"file": "poster-sections.vue",
|
||||
"description": "Poster Sections: one region's posters (or every region's), as ready-made presets with their own page and embed code; the Configure preset adds a panel of choices and Copy embed for making new ones.",
|
||||
"element": "poster-sections",
|
||||
"type": "parameterized",
|
||||
"isMap": false,
|
||||
"iframeUrl": "./poster-sections/poster-sections/index.html",
|
||||
"scriptUrl": "./poster-sections/poster-sections.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-sections/poster-sections.js\" integrity=\"sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-sections profile-config='{\"profile\":\"poster-sections\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-sections>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
"id": "countries-all",
|
||||
"title": "Country posters — all regions",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "all"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-all/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-all/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-asia",
|
||||
"title": "Country posters — Asia",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "asia"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-asia/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-asia/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-middle-east",
|
||||
"title": "Country posters — Middle East",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "middle-east"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-middle-east/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-middle-east/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-latin-america-and-caribbean",
|
||||
"title": "Country posters — Latin America & Caribbean",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "latin-america-and-caribbean"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-latin-america-and-caribbean/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-latin-america-and-caribbean/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-africa",
|
||||
"title": "Country posters — Africa",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "africa"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-africa/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-africa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-europe",
|
||||
"title": "Country posters — Europe",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "europe"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-europe/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-europe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-north-america-and-non-spanish-caribbean",
|
||||
"title": "Country posters — North America & Non-Spanish Caribbean",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "north-america-and-non-spanish-caribbean"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-north-america-and-non-spanish-caribbean/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-north-america-and-non-spanish-caribbean/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "countries-oceania",
|
||||
"title": "Country posters — Oceania",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"type": "country",
|
||||
"scope": "oceania"
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/countries-oceania/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/countries-oceania/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "configure",
|
||||
"title": "Configure — make a new preset",
|
||||
"apiBaseUrl": "",
|
||||
"props": {
|
||||
"profile": "poster-sections",
|
||||
"configure": true
|
||||
},
|
||||
"element": "poster-sections",
|
||||
"iframeUrl": "./poster-sections/poster-sections/configure/index.html",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-sections/poster-sections/configure/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bundle": "posterverse",
|
||||
"scriptUrl": "./posterverse/posterverse.js",
|
||||
"output": "dx-maps/posterverse/posterverse.js",
|
||||
"integrity": "sha384-ymAlpF3AiStPvnMzBJXPb3e5cBzsnNEGAlcy8Q+FwJhFrT5McyPEjPHFLjuuXWTn",
|
||||
"integrity": "sha384-Eu6xYVd6sm1N+Jmlt9wMiWk/f3TdS/KyyTmkKu6ClKdwwFzRGenJTXpLGgFxGHrV",
|
||||
"elements": [
|
||||
"posterverse-app"
|
||||
],
|
||||
|
|
@ -1207,7 +1371,7 @@
|
|||
"scriptUrl": "./posterverse/posterverse.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./posterverse/posterverse/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-ymAlpF3AiStPvnMzBJXPb3e5cBzsnNEGAlcy8Q+FwJhFrT5McyPEjPHFLjuuXWTn\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./posterverse/posterverse.js\" integrity=\"sha384-Eu6xYVd6sm1N+Jmlt9wMiWk/f3TdS/KyyTmkKu6ClKdwwFzRGenJTXpLGgFxGHrV\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<posterverse-app profile-config='{\"profile\":\"posterverse\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></posterverse-app>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -1292,7 +1456,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV",
|
||||
"integrity": "sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -1302,6 +1466,22 @@
|
|||
],
|
||||
"stage": "production",
|
||||
"profiles": [
|
||||
{
|
||||
"profile": "country-poster-sections",
|
||||
"stage": "experimental",
|
||||
"label": "Regions — country posters by region",
|
||||
"file": "country-poster-sections.vue",
|
||||
"description": "Country posters by region: one collapsible poster section per region (the library's <dxm-poster-section>), the Regions dashboard's third view.",
|
||||
"element": "regions-dashboard",
|
||||
"type": "single",
|
||||
"isMap": false,
|
||||
"iframeUrl": "./regions-dashboard/country-poster-sections/index.html",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/country-poster-sections/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"country-poster-sections\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"profile": "region-dashboards",
|
||||
"stage": "experimental",
|
||||
|
|
@ -1315,8 +1495,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" 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<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" 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<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1332,7 +1512,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1348,8 +1528,8 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" 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<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" 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<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
|
||||
"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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-P3qyu7MegojDl8iaorzqXxW52wTX0K++PKsw5t5ocq/ZytZmgMqWVnSsGjDB2HSw" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-NI+A8OCrOoKb53elAZUtoKYfH1+fT0CyF8htPQGnHKpZvJAckCPNoUBUAohAp9eR" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
499
public/dx-maps/poster-sections/poster-sections.js
Normal file
499
public/dx-maps/poster-sections/poster-sections.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Configure — make a new preset · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","configure":true};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — Africa · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"africa"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — all regions · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"all"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — Asia · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"asia"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — Europe · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"europe"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — Latin America & Caribbean · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"latin-america-and-caribbean"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — Middle East · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"middle-east"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — North America & Non-Spanish Caribbean · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"north-america-and-non-spanish-caribbean"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>Country posters — Oceania · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"assetsBase":"../../../assets","profile":"poster-sections","type":"country","scope":"oceania"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
250
public/dx-maps/poster-sections/poster-sections/index.html
Normal file
250
public/dx-maps/poster-sections/poster-sections/index.html
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>poster-sections · poster-sections</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, poster-sections { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, poster-sections { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-sections> custom element. -->
|
||||
<script src="../poster-sections.js" integrity="sha384-4b2SaYvCUEa0bl9AGmcfuHGluYCZ+pq8UdG/N0APcOnILOAtIgC9NP/zY5lM1eOJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"profile":"poster-sections","dataSource":"pray-tools","instanceId":"poster-sections-poster-sections","assetsBase":"../../assets"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("poster-sections");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <posterverse-app> custom element. -->
|
||||
<script src="../../posterverse.js" integrity="sha384-ymAlpF3AiStPvnMzBJXPb3e5cBzsnNEGAlcy8Q+FwJhFrT5McyPEjPHFLjuuXWTn" crossorigin="anonymous"></script>
|
||||
<script src="../../posterverse.js" integrity="sha384-Eu6xYVd6sm1N+Jmlt9wMiWk/f3TdS/KyyTmkKu6ClKdwwFzRGenJTXpLGgFxGHrV" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <posterverse-app> custom element. -->
|
||||
<script src="../posterverse.js" integrity="sha384-ymAlpF3AiStPvnMzBJXPb3e5cBzsnNEGAlcy8Q+FwJhFrT5McyPEjPHFLjuuXWTn" crossorigin="anonymous"></script>
|
||||
<script src="../posterverse.js" integrity="sha384-Eu6xYVd6sm1N+Jmlt9wMiWk/f3TdS/KyyTmkKu6ClKdwwFzRGenJTXpLGgFxGHrV" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,250 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- THIS PAGE IS OURS. The only thing that knows a document belongs to the plugin is the page
|
||||
the generator writes, so it says so here. library/i18n reads it before reflecting an RTL
|
||||
locale on <html dir>; nothing else sets it, so a PARTNER page defaults to "not ours" and
|
||||
is never touched. Absence must mean restraint — an embedded plugin flipping a partner's
|
||||
whole <html> because a reader followed a ?lang=ar link is the bug this prevents. -->
|
||||
<script>window.MAP_OWNS_PAGE = true;</script>
|
||||
<title>country-poster-sections · regions-dashboard</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- NON-MAP leaf (app-shell / composite / grid / card / chart-table): NO map peer.
|
||||
This element draws no map tiles, so it needs no Mapbox/MapLibre engine and MUST NOT
|
||||
gate on a token. Any nested MAP (in a composite/shell) renders in its own iframe leaf,
|
||||
which loads its own map peer + token. A peer here would be dead weight + imply a gate. -->
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
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, regions-dashboard { 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; }
|
||||
#status.err { color: #f87171; }
|
||||
|
||||
/* ── PRINT, BOTH LEAF SHAPES ────────────────────────────────────────────────────────────
|
||||
Two things every generated leaf got wrong on paper, because no generated page has ever
|
||||
carried an @media print rule (measured: 0 across the tree).
|
||||
|
||||
The background is #0a0e15 — a dark page, printed. Browsers drop background colours by
|
||||
default, but a reader who ticks "Background graphics" to get the MAP also gets the page
|
||||
flooded black, and that is the reader who most wants the printout.
|
||||
|
||||
#status is the LOADING OVERLAY: position:fixed, inset:0, over everything. On paper it is
|
||||
a full sheet reading "loading…" above the content that did in fact load. It is hidden
|
||||
only while it is NOT an error — an error still prints, because a blank sheet with the
|
||||
explanation suppressed is worse than a sheet that says what went wrong. */
|
||||
@media print {
|
||||
html, body { height: auto; background: #fff; }
|
||||
#status:not(.err) { display: none; }
|
||||
/* ★ RELEASE THE FIXED HOST, MEASURED (a print check on a035408e2). The leaf pins both the
|
||||
slot and the element with position:fixed; inset:0. A fixed element is painted once, on the
|
||||
first page, so the printed document was exactly ONE viewport: 900px tall, while the content
|
||||
continued to y=1753. Every plugin whose leaf is taller than a screen has been printing its
|
||||
top screenful and silently dropping the rest — the dashboard printed its stats and its map,
|
||||
then blank to the bottom of the sheet.
|
||||
min-height, not a released height alone: a full-bleed map leaf takes its SIZE from this rule,
|
||||
so height:auto with nothing else can leave the element a zero-height box and print an empty
|
||||
page — a worse failure than the one being fixed, and the discriminator is not isMap
|
||||
(region-dashboards is isMap:true and is a scrolling dashboard). The floor keeps a definite
|
||||
box for the map case while letting a taller document grow onto page two. */
|
||||
#slot, regions-dashboard { position: static; inset: auto; width: 100%; height: auto; min-height: 14cm; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading…</div>
|
||||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
// 1. prop — a token hardcoded into this page's profile-config (config.tk)
|
||||
// 2. ?tk= URL param — token carried in the shared link
|
||||
// 3. baked token — opt-in literal token inlined at build time (build-config.token)
|
||||
// A token from rungs 1-3 is used DIRECTLY (no network). Otherwise a token
|
||||
// *endpoint* is resolved and fetched:
|
||||
// 4. window.MAP_TOKEN_URL override 5. configured TOKEN_SRC URL 6. same-origin /api/maps/token
|
||||
// TOKEN_SRC is treated as DATA, never eval'd: the literal "window.MAP_TOKEN_URL"
|
||||
// means "read that global"; anything else is used as a URL — so both a global
|
||||
// name and a plain URL are safe (no SyntaxError footgun).
|
||||
(function () {
|
||||
var slot = document.getElementById('slot');
|
||||
var status = document.getElementById('status');
|
||||
var BASE_CONFIG = {"profile":"country-poster-sections","dataSource":"pray-tools","instanceId":"regions-dashboard-country-poster-sections","assetsBase":"../../assets"};
|
||||
// MapLibre map pages AND shell pages (app-shell / nested-mfe-spa) need NO token:
|
||||
// the ladder below is skipped entirely and the element mounts immediately.
|
||||
// (A leaf whose engine is MapLibre at RUNTIME joins them — see PROVIDER below.)
|
||||
var TOKEN_FREE = true;
|
||||
// A token-free COMPOSITE still nests plugins, and a nested Mapbox map reads window.MAP_TOKEN
|
||||
// rather than this page's profile-config. With no baked token (the public copy), it must get
|
||||
// one from ?tk= or a pasted token, or be asked for one — see the rungs below.
|
||||
var NESTS_MAPS = false;
|
||||
var TOKEN_SRC = "window.MAP_TOKEN_URL";
|
||||
var BAKED_TOKEN = "";
|
||||
// Expose the baked token as window.MAP_TOKEN so a BAKED composite (a compiled element that mounts
|
||||
// a map component directly, with no ProfileLoader/profile-config wrapper) still finds a token —
|
||||
// useMapInstance falls back to window.MAP_TOKEN. No effect when BAKED_TOKEN is empty.
|
||||
// A token the HOST set before this page ran outranks the baked one (measured: it used to lose).
|
||||
var HOST_TOKEN = (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN) || '';
|
||||
if (BAKED_TOKEN && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = BAKED_TOKEN; }
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
|
||||
function fail(msg) { if (status) { status.className = 'err'; status.textContent = '⚠ ' + msg; } }
|
||||
// No token anywhere on the ladder → ask ONCE, store locally, remount. No dead ends.
|
||||
function askToken() {
|
||||
if (!status) return;
|
||||
status.className = 'err';
|
||||
status.innerHTML = 'map needs a Mapbox token — ' +
|
||||
'<input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)" ' +
|
||||
'style="font:12px ui-monospace,monospace;padding:6px 8px;border-radius:6px;border:1px solid #223047;background:#0e1623;color:#eef2f7;width:min(320px,70vw)"> ' +
|
||||
'<button id="tkGo" style="font:700 11px ui-monospace,monospace;padding:7px 12px;border-radius:999px;border:0;cursor:pointer;background:linear-gradient(90deg,#46d4ff,#b9a6f5);color:#0a0e15">use it</button>';
|
||||
var go = document.getElementById('tkGo');
|
||||
if (go) go.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (!v) return;
|
||||
try { localStorage.setItem('MAPS_STAGING_TK', v); } catch (e) {}
|
||||
mount(v);
|
||||
});
|
||||
}
|
||||
/* ★ RELEASE THE LOAD BACKDROP WHEN THE ELEMENT MOUNTS (BUG-GOSPEL-MAP-BLACK-RECTANGLE).
|
||||
html/body carry background:#0a0e15 so the light #status text (#9aa7b8) is readable while the
|
||||
bundle loads. That backdrop is LOAD-STATE CHROME, but it outlived its moment: any gap the
|
||||
mounted element does not cover shows it through, because every layer above body is
|
||||
transparent. On gospel-resources/resources-map that produced a 352x126 DARK RECTANGLE between
|
||||
the legend card and the detail panel — MEASURED rgb(8,12,18), i.e. the page background, NOT an
|
||||
unpainted canvas as first supposed. Clearing it here (the one place we already know the
|
||||
element is up) is a one-shot style write — no resize, no repaint loop. Keep it a one-shot
|
||||
write: do NOT turn this into a per-frame or per-event handler (a resize loop strobes the screen). */
|
||||
function done() {
|
||||
if (status && status.parentNode) { status.parentNode.removeChild(status); }
|
||||
document.documentElement.style.background = '#f3f3f1';
|
||||
document.body.style.background = '#f3f3f1';
|
||||
}
|
||||
|
||||
function mount(token) {
|
||||
if (!token && !TOKEN_FREE) { return askToken(); }
|
||||
// Nested maps (a composite's children) read window.MAP_TOKEN, not this element's config.
|
||||
if (token && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = token; }
|
||||
try {
|
||||
var cfg = token ? Object.assign({}, BASE_CONFIG, { tk: token }) : BASE_CONFIG;
|
||||
var el = document.createElement("regions-dashboard");
|
||||
el.setAttribute('profile-config', JSON.stringify(cfg));
|
||||
slot.innerHTML = '';
|
||||
slot.appendChild(el);
|
||||
done();
|
||||
} catch (e) { fail(e && e.message ? e.message : String(e)); }
|
||||
}
|
||||
|
||||
function qp(name) {
|
||||
try { return new URLSearchParams(location.search).get(name) || ''; }
|
||||
catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function extractToken(data) {
|
||||
if (typeof data === 'string') return data.trim();
|
||||
if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || '';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Rungs 1-3b: an actual token → mount directly, no fetch.
|
||||
// 2b = localStorage: the paste-once store shared with the staging pages
|
||||
// (MAPS_STAGING_TK) — paste a token ONCE anywhere in the tree, every page uses it.
|
||||
var stored = '';
|
||||
try { stored = localStorage.getItem('MAPS_STAGING_TK') || ''; } catch (e) {}
|
||||
var urlTk = qp('tk');
|
||||
if (urlTk) { try { localStorage.setItem('MAPS_STAGING_TK', urlTk); } catch (e) {} }
|
||||
var directToken = (BASE_CONFIG && BASE_CONFIG.tk) || urlTk || stored || HOST_TOKEN || BAKED_TOKEN;
|
||||
// PROVIDER — the same ladder as resolveMapProvider (library/composables/useMapProvider.js):
|
||||
// profile-config.provider → ?provider= → window.MAP_PROVIDER → 'auto'. An explicit engine wins,
|
||||
// and 'auto' draws with MapLibre whether or not a token is available (the element decides that).
|
||||
// This page only decides whether to FIND a token first: when one is on offer (a rung above, a
|
||||
// host-set window.MAP_TOKEN, or a token endpoint) it resolves it, since place search uses it on
|
||||
// either engine; with none, it mounts at once, keyless: no token prompt, no request to Mapbox.
|
||||
var ASKED = String((BASE_CONFIG && BASE_CONFIG.provider) || qp('provider') || window.MAP_PROVIDER || '').toLowerCase().trim();
|
||||
var EXPLICIT = (ASKED === 'mapbox' || ASKED === 'maplibre') ? ASKED : '';
|
||||
var HAS_TOKEN = !!(directToken || (typeof window.MAP_TOKEN === 'string' && window.MAP_TOKEN));
|
||||
var HAS_ENDPOINT = !!(window.MAP_TOKEN_URL || (TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL'));
|
||||
if (EXPLICIT === 'maplibre' || (!EXPLICIT && !HAS_TOKEN && !HAS_ENDPOINT)) { TOKEN_FREE = true; }
|
||||
if (TOKEN_FREE) {
|
||||
if (directToken && typeof window !== 'undefined' && !window.MAP_TOKEN) { window.MAP_TOKEN = directToken; }
|
||||
mount('');
|
||||
// A token-free composite with no token anywhere: its content renders, but its nested maps
|
||||
// cannot. Ask once; the token is stored for the whole tree and the page reloads with it.
|
||||
// Only when Mapbox was ASKED for: under 'auto' the nested maps draw with MapLibre instead.
|
||||
if (NESTS_MAPS && !directToken && EXPLICIT === 'mapbox') {
|
||||
var bar = document.createElement('div');
|
||||
bar.id = 'status';
|
||||
bar.setAttribute('style', 'position:fixed;left:12px;right:12px;bottom:12px;z-index:2147483000;padding:10px 12px;border-radius:10px;background:#0e1623;color:#eef2f7;font:13px system-ui,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.25)');
|
||||
document.body.appendChild(bar);
|
||||
status = bar;
|
||||
askToken();
|
||||
var goBtn = document.getElementById('tkGo');
|
||||
if (goBtn) goBtn.addEventListener('click', function () {
|
||||
var v = (document.getElementById('tkIn').value || '').trim();
|
||||
if (v) { location.reload(); }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve the token ENDPOINT candidates and try them IN ORDER — the first
|
||||
// one that answers with a token wins. Order: window.MAP_TOKEN_URL override → the
|
||||
// configured TOKEN_SRC (an ABSOLUTE url, so a tree hosted on a CDN away from the API
|
||||
// still finds it) → same-origin /api/maps/token (dev, or the site hosting both).
|
||||
// A single hard-coded pick used to kill the page when its one url 404'd or was
|
||||
// blocked by CORS; a shared link must work wherever the tree is hosted.
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u); }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) {
|
||||
// Every endpoint failed. Under 'auto' that means no token: draw with MapLibre, keyless.
|
||||
if (!EXPLICIT) { TOKEN_FREE = true; return mount(''); }
|
||||
// Mapbox was asked for: say which endpoints failed, and still offer the paste-once box.
|
||||
askToken();
|
||||
if (status) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = '⚠ Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')';
|
||||
status.insertBefore(d, status.firstChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var url = candidates[i];
|
||||
return fetch(url)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) {
|
||||
var tk = extractToken(data);
|
||||
if (!tk) throw new Error(url + ' → no token in response');
|
||||
mount(tk);
|
||||
})
|
||||
.catch(function (e) {
|
||||
errors.push(e && e.message ? e.message : String(e));
|
||||
return fetchToken(i + 1, errors);
|
||||
});
|
||||
}
|
||||
fetchToken(0, []);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-wFK1JUeWwZE9pshq0QjRlB3W/qlfMfOvaiTxuy6v5/KswtcCnEu5yuZxExZTO8GV" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-wz3n3/56pcxJdV3kwIQzRnHFbjBrh0CkKHjI2SW6KWz2u23zZrXEgbVZDw1qma+M" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue