revert: remove the vibe-coded prayer-map profile entirely (bundle, output, SPA nav/PROFILES) — the real prayer map is the doxa-simple-map prayer instance; rebuild tree+shell (9 bundles)
This commit is contained in:
parent
35c7b2e4ba
commit
bd3c1eb2f5
28 changed files with 12 additions and 1199 deletions
|
|
@ -1,55 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>doxa-maps · prayer-map staging</title>
|
||||
<link rel="icon" href="data:,">
|
||||
|
||||
<!-- Mapbox peer (loaded once per page, before the bundle) -->
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css" rel="stylesheet" />
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js"></script>
|
||||
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { background: #0b0f14; color: #e5e7eb; min-height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; }
|
||||
.wrap { max-width: 1100px; margin: 0 auto; padding: 32px 20px; }
|
||||
h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
|
||||
.sub { color: #9ca3af; font-size: 13px; margin: 6px 0 24px; }
|
||||
.slot { border: 1px solid #1f2937; border-radius: 12px; overflow: hidden; }
|
||||
.slot .label { font: 12px/1 ui-monospace, monospace; color: #9ca3af; padding: 8px 12px;
|
||||
background: #111827; border-bottom: 1px solid #1f2937; }
|
||||
.frame { position: relative; height: 70vh; min-height: 420px; }
|
||||
prayer-map { display: block; position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>prayer-map · staging</h1>
|
||||
<p class="sub">One bundle, one profile, two in-map tabs: <strong>By Religion</strong>
|
||||
(shared <code>religion</code> strategy) and <strong>Purple Pins</strong>
|
||||
(bundle-local <code>single-color</code> strategy — all pins <code>#8b5cf6</code>).</p>
|
||||
|
||||
<div class="slot">
|
||||
<div class="label">profile-config.profile = "prayer-map"</div>
|
||||
<div class="frame">
|
||||
<prayer-map id="prayer-map-slot"></prayer-map>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Pulled from .env at dev-server start. Vite replaces %VITE_MAPBOX_TOKEN%
|
||||
// with the value at request time; the .env file is gitignored.
|
||||
window.DOXA_TOKEN = '%VITE_MAPBOX_TOKEN%';
|
||||
document.getElementById('prayer-map-slot').setAttribute('profile-config',
|
||||
JSON.stringify({ profile: 'prayer-map', tk: window.DOXA_TOKEN, instanceId: 'dev-prayer-map' }));
|
||||
</script>
|
||||
<script>window.MAP_APP_BASE_URL = '/';</script>
|
||||
|
||||
<!-- Vite multi-page entry. In dev, served transformed; in build, Vite emits
|
||||
the prayer-map IIFE and rewrites this script tag. -->
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* app-profiles/prayer-map/index.js
|
||||
*
|
||||
* Bundle entry — registers the <prayer-map> web component.
|
||||
* Adapted from app-profiles/template-bundle/index.js (the copy-me reference).
|
||||
*
|
||||
* Folder name = bundle name. This folder builds to exactly one output file:
|
||||
* app/doxa-maps/prayer-map/prayer-map.js
|
||||
*
|
||||
* Profiles live as FLAT .vue files DIRECTLY in this folder (prayer-map.vue) —
|
||||
* no profiles/ subfolder. Each `*.vue` here = one profile.
|
||||
*
|
||||
* Usage from a host page:
|
||||
* <script src=".../prayer-map/prayer-map.js"></script>
|
||||
* <prayer-map profile-config='{"profile":"prayer-map","tk":"pk.eyJ..."}'></prayer-map>
|
||||
*/
|
||||
|
||||
import { defineCustomElement } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ProfileLoader from '@map/ProfileLoader.vue'
|
||||
import { createAppI18n } from '@map/i18n/index.js'
|
||||
import { registerStrategies } from '@map/colors/_registry.js'
|
||||
|
||||
// ─── Bundle-private profile registry ─────────────────────────────────────────
|
||||
// import.meta.glob is evaluated by Vite at build time relative to THIS file, so
|
||||
// it captures only THIS bundle's profiles. ProfileLoader (in @map) cannot run
|
||||
// this glob itself, so the bundle hands the result over via app.provide().
|
||||
const profileModules = import.meta.glob('./*.vue')
|
||||
|
||||
// ─── Bundle-private color strategies ─────────────────────────────────────────
|
||||
// Merges ./src/colors/*.js OVER the shared library set for THIS bundle
|
||||
// only. prayer-map ships `single-color.js` → mode `singleColor` — the strategy
|
||||
// its "Purple" tab uses. The shared `religion` strategy stays untouched. The
|
||||
// glob is `*.js`, so `src/colors/colors.json` (plain values) is ignored by design.
|
||||
registerStrategies(import.meta.glob('./src/colors/*.js', { eager: true }))
|
||||
|
||||
// ─── Register the custom element ─────────────────────────────────────────────
|
||||
// One tag per bundle, named after the bundle. The guard prevents an
|
||||
// "already registered" error if the IIFE is ever loaded twice on one page.
|
||||
const DemoMapElement = defineCustomElement(ProfileLoader, {
|
||||
configureApp(app) {
|
||||
app.use(createPinia())
|
||||
// Install i18n AND hand the instance to ProfileLoader by reference. The
|
||||
// defineCustomElement root cannot use useI18n() (see ProfileLoader.vue);
|
||||
// it drives locale through this provided instance instead.
|
||||
const i18n = createAppI18n()
|
||||
app.use(i18n)
|
||||
app.provide('appI18n', i18n)
|
||||
app.provide('profileModules', profileModules)
|
||||
}
|
||||
})
|
||||
|
||||
if (typeof customElements !== 'undefined' && !customElements.get('prayer-map')) {
|
||||
customElements.define('prayer-map', DemoMapElement)
|
||||
}
|
||||
|
||||
export default DemoMapElement
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
<script setup>
|
||||
/**
|
||||
* prayer-map.vue — Application Profile: Prayer Map
|
||||
*
|
||||
* @description The flagship prayer map: one clean, search-free embed for partner sites.
|
||||
* Colors people-group pins by prayer progress. Runs on MapLibre by default
|
||||
* (token-free) so no Mapbox-token outage can take down thousands of partner
|
||||
* embeds at once; pass profile-config.provider="mapbox" to opt back in, and
|
||||
* profile-config.style to swap the basemap with no code.
|
||||
* @profile prayer-map
|
||||
* @bundle prayer-map
|
||||
* @element prayer-map
|
||||
*
|
||||
* Built by composing reusable @map/* library pieces (see docs/reference.md):
|
||||
* useMapInstance — GL engine boot/teardown (mapbox|maplibre)
|
||||
* useMapProvider — resolve the engine (profile-config → ?provider → window → default)
|
||||
* useMapData — data load via DataSourceManager
|
||||
* useMapLayers — the 'language-family-pins' circle layer + popups
|
||||
* useShadowStyles — Shadow-DOM-safe CSS injection
|
||||
* No geocoder/search bar by design. Modifies nothing in library/ or internal/.
|
||||
*/
|
||||
|
||||
import { inject, provide, ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { resolveMapProvider, providerLabel } from '@map/composables/useMapProvider.js'
|
||||
import { useMapData } from '@map/composables/useMapData.js'
|
||||
import { useMapLayers } from '@map/composables/useMapLayers.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { DataSourceManager } from '@map/utils/DataSourceManager.js'
|
||||
import { getLanguageFamilyColor } from '@map/colors/language-family.js'
|
||||
import { buildColorExpression, getColorStrategy, COLOR_MODES } from '@map/colors/_registry.js'
|
||||
|
||||
// ─── Shadow DOM style injection ──────────────────────────────────────────────
|
||||
useShadowStyles(`
|
||||
.dm-root { position:absolute;inset:0;display:flex;flex-direction:column;overflow:hidden;
|
||||
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif; }
|
||||
.dm-tab-bar { flex:0 0 auto;display:flex;align-items:center;background:#fff;
|
||||
border-bottom:2px solid #e0e0e0;box-shadow:0 2px 4px rgba(0,0,0,.08);height:48px;
|
||||
padding:0 12px;gap:4px;z-index:10; }
|
||||
.dm-tab { background:none;border:none;padding:0 20px;height:100%;font-size:14px;
|
||||
font-weight:500;color:#555;cursor:pointer;border-bottom:3px solid transparent;
|
||||
transition:color .15s,border-color .15s,background .15s;white-space:nowrap; }
|
||||
.dm-tab:hover { background:rgba(139,92,246,.06);color:#8b5cf6; }
|
||||
.dm-tab.active { color:#8b5cf6;border-bottom-color:#8b5cf6;background:rgba(139,92,246,.06); }
|
||||
.dm-map-area { flex:1 1 0;position:relative;overflow:hidden; }
|
||||
.dm-map-canvas { position:absolute;inset:0; }
|
||||
.dm-loading { position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
|
||||
background:#f8f9fa;color:#555;font-size:14px;z-index:5; }
|
||||
/* Failure state, NOT a cover: modelled on cities-internet's .gm-err so anything the
|
||||
map did manage to render stays visible around it. A spinner that outlives the
|
||||
failure reads as "slow" and the user just waits; this says the map is gone. */
|
||||
.dm-error { position:absolute;top:50%;left:12px;right:12px;z-index:7;transform:translateY(-50%);
|
||||
text-align:center;font-size:13px;line-height:1.5;color:#7f1d1d;background:rgba(255,255,255,.94);
|
||||
border:1px solid rgba(127,29,29,.25);border-radius:10px;padding:12px 14px;
|
||||
box-shadow:0 2px 10px rgba(0,0,0,.16); }
|
||||
.dm-legend { position:absolute;left:10px;bottom:26px;z-index:6;background:rgba(255,255,255,.92);
|
||||
border:1px solid #e5e7eb;border-radius:10px;box-shadow:0 2px 8px rgba(0,0,0,.12);
|
||||
padding:10px 14px;max-width:220px; }
|
||||
.dm-legend-title { font-size:12px;font-weight:700;color:#374151;margin-bottom:6px;
|
||||
text-transform:uppercase;letter-spacing:.04em; }
|
||||
.dm-legend-row { display:flex;align-items:center;gap:8px;font-size:12px;color:#4b5563;
|
||||
line-height:1.7; }
|
||||
.dm-dot { flex:none;width:10px;height:10px;border-radius:50%;
|
||||
border:1px solid rgba(0,0,0,.25); }
|
||||
`, 'prayer-map')
|
||||
|
||||
// ─── Config from ProfileLoader ────────────────────────────────────────────────
|
||||
const profileConfig = inject('profileConfig')
|
||||
const mapboxToken = inject('mapboxToken')
|
||||
const dataSource = inject('dataSource')
|
||||
|
||||
// Engine: profile-config.provider → ?provider → window.MAP_PROVIDER → 'maplibre' (this
|
||||
// profile's default — the prayer map runs token-free so no Mapbox-token outage can take
|
||||
// down thousands of partner embeds at once). Pass provider='mapbox' to opt back in.
|
||||
const cfgVal = () => (profileConfig?.value ?? profileConfig) || {}
|
||||
const mapProvider = resolveMapProvider({ ...cfgVal(), provider: cfgVal().provider || 'maplibre' })
|
||||
const mapProviderName = providerLabel(mapProvider)
|
||||
// style-by-props: a host may pass profile-config.style to swap the basemap with no code.
|
||||
const mapStyleOverride = cfgVal().style || null
|
||||
|
||||
// ─── Tabs ─────────────────────────────────────────────────────────────────────
|
||||
// Two fixed tabs (a host page may still override via profile-config.tabs,
|
||||
// mirroring the doxa-simple-map pattern):
|
||||
// religion — shared library strategy (COLOR_MODES.RELIGION)
|
||||
// purple — this bundle's local single-color strategy (all pins #8b5cf6)
|
||||
// Single view: prayer progress. A host MAY still override via profile-config.tabs,
|
||||
// but the flagship prayer map ships as ONE clean, search-free embed.
|
||||
const DEFAULT_TABS = [
|
||||
{ id: 'prayer', label: 'Prayer', colorStrategy: COLOR_MODES.PRAYER_PROGRESS },
|
||||
]
|
||||
const tabs = computed(() => profileConfig?.value?.tabs ?? DEFAULT_TABS)
|
||||
const activeTabId = ref(null)
|
||||
const activeTab = computed(() => tabs.value.find(t => t.id === activeTabId.value) ?? tabs.value[0])
|
||||
|
||||
function switchTab(tabId) {
|
||||
if (tabId === activeTabId.value) return
|
||||
activeTabId.value = tabId
|
||||
const tab = tabs.value.find(t => t.id === tabId)
|
||||
const m = map.value
|
||||
if (!tab || !m || !m.getLayer('language-family-pins')) return
|
||||
m.setPaintProperty('language-family-pins', 'circle-color',
|
||||
buildColorExpression(tab.colorStrategy))
|
||||
}
|
||||
|
||||
// ─── Mini legend (local to this profile — values come from the strategies) ───
|
||||
const legendRows = computed(() => {
|
||||
const strat = getColorStrategy(activeTab.value?.colorStrategy)
|
||||
if (!strat) return []
|
||||
// Religion strategy exposes `families` (letter → label) + `palette` (letter → hex)
|
||||
if (strat.families) {
|
||||
return Object.entries(strat.families).map(([letter, label]) => ({
|
||||
label,
|
||||
color: strat.palette?.[letter] ?? strat.fallback,
|
||||
}))
|
||||
}
|
||||
// Prayer-progress: fixed 3 tiers (palette keys from prayer-progress.js)
|
||||
const pal = strat.palette || strat.PALETTE || {}
|
||||
return [
|
||||
{ label: 'Needs prayer', color: pal.none ?? pal.low ?? '#e74c3c' },
|
||||
{ label: 'Has prayer', color: pal.some ?? pal.mid ?? '#f39c12' },
|
||||
{ label: 'Full prayer coverage',color: pal.full ?? pal.high ?? '#2ecc71' },
|
||||
]
|
||||
})
|
||||
const legendTitle = computed(() =>
|
||||
getColorStrategy(activeTab.value?.colorStrategy)?.name ?? 'Legend')
|
||||
|
||||
// ─── Map ID ───────────────────────────────────────────────────────────────────
|
||||
const injectedInstanceId = inject('instanceId', null)
|
||||
const mapId = (injectedInstanceId?.value || injectedInstanceId) ||
|
||||
('prayer-map-' + Math.random().toString(36).slice(2, 7))
|
||||
provide('mapId', mapId)
|
||||
|
||||
// ─── Stores (provided by ProfileLoader) ──────────────────────────────────────
|
||||
const mapStore = inject('mapStore')
|
||||
const dataStore = inject('dataStore')
|
||||
const uiStore = inject('uiStore')
|
||||
|
||||
// ─── Data + map + layers (all reused from @map) ──────────────────────────────
|
||||
const mapContainer = ref(null)
|
||||
const appReady = ref(false)
|
||||
// Set when map init concludes it has failed. appReady alone cannot express this:
|
||||
// it is false both while loading AND forever after a failure, so the loading
|
||||
// overlay outlived every failure. Same `err` shape cities-internet already uses.
|
||||
const mapError = ref(null)
|
||||
const dsm = new DataSourceManager()
|
||||
|
||||
const { map, initializeMap, destroy } = useMapInstance({
|
||||
containerRef: mapContainer,
|
||||
provider: mapProvider,
|
||||
accessToken: mapboxToken?.value,
|
||||
...(mapStyleOverride ? { style: mapStyleOverride } : {}),
|
||||
center: [20, 10],
|
||||
zoom: 1.8,
|
||||
})
|
||||
|
||||
const mapData = useMapData({
|
||||
mapId,
|
||||
dataSourceId: dataSource.value,
|
||||
dataSourceManager: dsm,
|
||||
dataStore,
|
||||
markRaw: (v) => v,
|
||||
})
|
||||
|
||||
const mapLayers = useMapLayers({
|
||||
getMap: () => map.value,
|
||||
mapId,
|
||||
getLanguageFamilyColor,
|
||||
getNormalizedPeopleGroups: () => mapData.normalizedPeopleGroups.value || [],
|
||||
})
|
||||
|
||||
// ─── Lifecycle ────────────────────────────────────────────────────────────────
|
||||
onMounted(async () => {
|
||||
activeTabId.value = tabs.value[0]?.id ?? null
|
||||
uiStore?.updateBreakpoint?.(window.innerWidth)
|
||||
|
||||
// 1. Start the data load immediately (parallel with map init)
|
||||
const dataPromise = mapData.loadData().catch((err) => {
|
||||
console.error('[demo-map] Data load failed:', err)
|
||||
return { normalizedPeopleGroups: [] }
|
||||
})
|
||||
|
||||
// 2. Boot the map
|
||||
try {
|
||||
await initializeMap()
|
||||
} catch (err) {
|
||||
// `new mapboxgl.Map()` throws outright when the device has no WebGL context.
|
||||
console.error('[demo-map] Map failed to initialize —', err)
|
||||
mapError.value = String(err?.message || err)
|
||||
return
|
||||
}
|
||||
if (!map.value) {
|
||||
console.error('[demo-map] Map failed to initialize — check token and container')
|
||||
mapError.value = 'Map failed to initialize — check token and container'
|
||||
return
|
||||
}
|
||||
|
||||
// 3. After style.load: add the pins colored by the ACTIVE tab's strategy
|
||||
map.value.once('style.load', async () => {
|
||||
try {
|
||||
const { normalizedPeopleGroups } = await dataPromise
|
||||
mapStore?.registerMap?.(mapId, map.value)
|
||||
try { map.value.setProjection('mercator') } catch (e) { /* optional */ }
|
||||
if (normalizedPeopleGroups?.length) {
|
||||
mapLayers.addLanguageFamilyLayer(normalizedPeopleGroups, activeTab.value?.colorStrategy)
|
||||
}
|
||||
mapStore?.setMapReady?.(mapId)
|
||||
appReady.value = true
|
||||
} catch (err) {
|
||||
console.error('[demo-map] Map ready failed:', err)
|
||||
mapError.value = String(err?.message || err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
mapStore?.unregisterMap?.(mapId)
|
||||
destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dm-root">
|
||||
<!-- Tab bar hidden for the single-view flagship embed; shown only if a host passes >1 tab -->
|
||||
<div class="dm-tab-bar" v-if="tabs.length > 1">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="dm-tab"
|
||||
:class="{ active: activeTab?.id === tab.id }"
|
||||
@click="switchTab(tab.id)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Map area -->
|
||||
<div class="dm-map-area">
|
||||
<div v-if="!appReady && !mapError" class="dm-loading">Loading map…</div>
|
||||
<div v-if="mapError" class="dm-error">The map could not be loaded. {{ mapError }}</div>
|
||||
<div ref="mapContainer" class="dm-map-canvas" />
|
||||
|
||||
<!-- Mini legend, driven by the active tab's strategy values -->
|
||||
<div v-if="appReady" class="dm-legend">
|
||||
<div class="dm-legend-title">{{ legendTitle }}</div>
|
||||
<div v-for="row in legendRows" :key="row.label" class="dm-legend-row">
|
||||
<span class="dm-dot" :style="{ background: row.color }"></span>{{ row.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# src/ — this map's sandbox
|
||||
|
||||
Everything this map needs that isn't already in the shared `library/` gets built here. Nothing in
|
||||
this folder leaks to another map — build freely.
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/ which API/CSV/endpoint this map reads
|
||||
├── colors/ this map's colors: values (colors.json) + strategy .js files
|
||||
├── components/ this map's own .vue pieces (not reusable)
|
||||
├── composables/ this map's own logic hooks (useThing.js)
|
||||
└── utils/ this map's own pure helpers
|
||||
```
|
||||
|
||||
Two import styles, two meanings:
|
||||
|
||||
```js
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js' // reuse the shared library
|
||||
import colors from './src/colors/colors.json' // your own sandbox (relative)
|
||||
```
|
||||
|
||||
There is no `@map/`-style alias for your sandbox — your code is always relative. That boundary keeps
|
||||
it private. **Reusable + parameterized → `library/` (an expert move); specific to this map → stays
|
||||
here.** Local by default.
|
||||
|
||||
Each subfolder has its own README. See also:
|
||||
[`../../README.md`](../../README.md) (authoring guide) ·
|
||||
[`../../../docs/reference.md`](../../../docs/reference.md) (the `@map/…` menu).
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# src/api/ — this map's data wiring
|
||||
|
||||
Declare **which** data source this map reads. The library owns the *mechanism* (how to fetch and
|
||||
parse); you own the *declaration*.
|
||||
|
||||
- Mechanism (`DataSourceManager`, the source registry) → `library/api/`, reused via `@map/api/…`.
|
||||
Never edited.
|
||||
- Which source, endpoint, and field mappings → **here**, in your own `sources.json`.
|
||||
|
||||
Data usually arrives at runtime from the host page's `profile-config` (`inject('dataSource')`), so
|
||||
the same bundle can point at different data with no rebuild. Ship a `sources.json` here only for a
|
||||
fixed default source. Prefer a real API over inlining large JSON.
|
||||
|
||||
- Source-entry contract (types + fields): [`../../../../library/api/README.md`](../../../../library/api/README.md)
|
||||
- The `@map/api` mechanism: [`../../../../docs/reference.md`](../../../../docs/reference.md)
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# src/colors/ — this map's colors
|
||||
|
||||
All of one map's colors live here, and it's **bundle-private** — editing it can never affect
|
||||
another map. Two surfaces sit side by side:
|
||||
|
||||
```
|
||||
src/colors/
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ colors.json fixed colors (hex only, no code)│ ← non-coders edit this
|
||||
│ <mode>.js data-driven color strategies │ ← green if praying, red if not
|
||||
└──────────────────────────────────────────────┘
|
||||
index.js globs *.js as strategies · colors.json is a plain import
|
||||
```
|
||||
|
||||
- **`colors.json`** — pins/polygons/fallback as plain hex. Import it relatively:
|
||||
`import colors from './src/colors/colors.json'` (the sandbox has no `@map/` alias).
|
||||
- **A strategy `.js`** — colors pins by a data value. Drop one in and this bundle's `index.js`
|
||||
auto-registers it, merging **over** the shared library set. Same mode key as a shared strategy →
|
||||
overrides it; a new key → adds one. For this bundle only.
|
||||
- Filename → mode key: `example-mode.js` → `exampleMode`. Files starting with `_`/`.` are skipped.
|
||||
|
||||
**Writing a strategy?** The full contract (required `export default` shape, checklist,
|
||||
profile-local overrides) is in
|
||||
**[../../../../docs/architecture/color-strategies.md](../../../../docs/architecture/color-strategies.md)**.
|
||||
Two places a color can live, in depth: [`../../../../contributing/COLORS.md`](../../../../contributing/COLORS.md).
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"_comment": "Your map's colors as plain VALUES — no code. Edit the hex strings; nothing else. This file is a pure edit surface a non-programmer can safely change. Import it in your profile .vue with a relative path: import colors from './src/colors/colors.json'. For color logic that varies per data value (e.g. color a pin by a field), use a code-based strategy .js file right here in src/colors/ instead — see this folder's README.",
|
||||
|
||||
"pins": {
|
||||
"default": "#2563eb",
|
||||
"selected": "#f59e0b",
|
||||
"muted": "#94a3b8"
|
||||
},
|
||||
|
||||
"polygons": {
|
||||
"fill": "#93c5fd",
|
||||
"fillOpacity": 0.35,
|
||||
"outline": "#1d4ed8"
|
||||
},
|
||||
|
||||
"fallback": "#cbd5e1"
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# src/components/ — this map's own Vue pieces
|
||||
|
||||
Small `.vue` components used by **this map only** — a custom info card, a bespoke overlay, a one-off
|
||||
panel. Reusable, parameterized components (legend, toolbar button, drawer) live in
|
||||
`library/components/` and are reused via `@map/components/…`.
|
||||
|
||||
Before building one, check the library first — it ships legends, map-control buttons, drawers, a
|
||||
geocoder, poster tools, and more (see [`../../../../docs/reference.md`](../../../../docs/reference.md)).
|
||||
|
||||
```js
|
||||
import LegendDesktop from '@map/components/legends/LegendDesktop.vue' // reuse (by reference)
|
||||
import YourThing from './src/components/YourThing.vue' // your own (relative)
|
||||
```
|
||||
|
||||
Your profile `.vue` screens stay **flat at the bundle root** (that's the discovery convention); the
|
||||
pieces they're built from live here.
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# src/composables/ — this map's own logic hooks
|
||||
|
||||
Composition-API hooks (`useSomething.js`) holding **this map's** logic. Reusable map logic already
|
||||
ships in `library/composables/` — `useMapInstance`, `useMapData`, `useMapLayers`,
|
||||
`useMapClustering`, `useMapFly`, `useMapTheme`, `useLegendData`, and more
|
||||
(see [`../../../../docs/reference.md`](../../../../docs/reference.md)). Write here only the glue
|
||||
unique to your map.
|
||||
|
||||
```js
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js' // reuse
|
||||
import { useYourThing } from './src/composables/useYourThing.js' // your own (relative)
|
||||
```
|
||||
|
||||
- One hook per file, named `useThing.js`, exporting a `useThing()`.
|
||||
- Keep it framework-pure — that's what makes a hook easy to promote to `library/` later, *if* a
|
||||
second map ever needs it (local by default).
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# src/utils/ — this map's own helper functions
|
||||
|
||||
Small, pure, stateless helpers (formatters, tiny transforms, lookups) specific to **this map**.
|
||||
General reusable helpers — geo math, cluster helpers, `apiBaseUrl` — live in `library/utils/` and
|
||||
are reused via `@map/utils/…` (see [`../../../../docs/reference.md`](../../../../docs/reference.md)).
|
||||
|
||||
```js
|
||||
import { haversine } from '@map/utils/geoUtils.js' // reuse
|
||||
import { formatLabel } from './src/utils/format.js' // your own (relative)
|
||||
```
|
||||
|
||||
- Keep functions pure (no side effects, no global state).
|
||||
- Named exports, one concern per file.
|
||||
|
|
@ -900,14 +900,6 @@ const PROFILES = {
|
|||
views: ['map'],
|
||||
config: { profile:'upg-neighbors', dataSource:'pray-tools', instanceId:'wrapper-upg-neighbor-lab' },
|
||||
},
|
||||
// Flagship prayer map — MapLibre by default (token-free; no single point of failure for
|
||||
// partner embeds), no search bar. Experimental until blessed to production.
|
||||
'prayer-map-maplibre': {
|
||||
tag: 'prayer-map',
|
||||
src: './doxa-maps/prayer-map/prayer-map.js',
|
||||
views: ['map'],
|
||||
config: { profile:'prayer-map', dataSource:'pray-tools', instanceId:'wrapper-prayer-map', provider:'maplibre' },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -98,9 +98,6 @@ export const NAV = [
|
|||
open: false,
|
||||
children: [
|
||||
{ kind: 'item', id: 'nationmapper', label: 'Nationmapper', icon: 'share', target: 'both' },
|
||||
// Flagship prayer-map (MapLibre-default, token-free, no search) — in Experimental
|
||||
// until blessed to production, per the experimental-profile law.
|
||||
{ kind: 'item', id: 'prayer-map-maplibre', label: 'Prayer Map (MapLibre)', icon: 'prayer', target: 'both' },
|
||||
// Country Posters MOVED here from Prayer Mobilization to Experimental.
|
||||
{ kind: 'item', id: 'countries-map', label: 'Country Posters', icon: 'poster', target: 'both' },
|
||||
// Outreach Planner = geo-steward, renamed + moved out of the Dashboard folder.
|
||||
|
|
|
|||
2
public/doxa-maps/cities-internet/index.html
generated
2
public/doxa-maps/cities-internet/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"cities-internet","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","elements":["cities-internet-map"],"profiles":[{"profile":"connectivity-list","file":"connectivity-list.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-list/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/cities-internet-map>"}},{"profile":"connectivity-map","file":"connectivity-map.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-map/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/cities-internet-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/data-tables/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/data-tables/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/cities-internet-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","elements":["cities-internet-map"],"profiles":[{"profile":"connectivity-list","file":"connectivity-list.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-list/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/cities-internet-map>"}},{"profile":"connectivity-map","file":"connectivity-map.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/connectivity-map/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/connectivity-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/cities-internet-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"cities-internet-map","iframeUrl":"./cities-internet/data-tables/index.html","scriptUrl":"./cities-internet/cities-internet.js","embed":{"iframe":"\u003ciframe src=\"./cities-internet/data-tables/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/cities-internet-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
2
public/doxa-maps/country-posters/index.html
generated
2
public/doxa-maps/country-posters/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"country-posters","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","elements":["country-posters-map"],"profiles":[{"profile":"countries-map","file":"countries-map.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/countries-map/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/countries-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/country-posters-map>"}},{"profile":"country-poster-wall","file":"country-poster-wall.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/country-poster-wall/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/country-poster-wall/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/country-posters-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","elements":["country-posters-map"],"profiles":[{"profile":"countries-map","file":"countries-map.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/countries-map/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/countries-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/country-posters-map>"}},{"profile":"country-poster-wall","file":"country-poster-wall.vue","description":"","element":"country-posters-map","iframeUrl":"./country-posters/country-poster-wall/index.html","scriptUrl":"./country-posters/country-posters.js","embed":{"iframe":"\u003ciframe src=\"./country-posters/country-poster-wall/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/country-posters-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
2
public/doxa-maps/demo-map/index.html
generated
2
public/doxa-maps/demo-map/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"demo-map","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"demo-map","scriptUrl":"./demo-map/demo-map.js","output":"doxa-maps/demo-map/demo-map.js","elements":["demo-map"],"profiles":[{"profile":"demo-map","file":"demo-map.vue","description":"Simple two-tab demo map. Tab 1 colors pins by religion (shared","element":"demo-map","iframeUrl":"./demo-map/demo-map/index.html","scriptUrl":"./demo-map/demo-map.js","embed":{"iframe":"\u003ciframe src=\"./demo-map/demo-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./demo-map/demo-map.js\">\u003c/script>\n\u003cdemo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/demo-map>"},"bundle":"demo-map"}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"demo-map","scriptUrl":"./demo-map/demo-map.js","output":"doxa-maps/demo-map/demo-map.js","elements":["demo-map"],"profiles":[{"profile":"demo-map","file":"demo-map.vue","description":"Simple two-tab demo map. Tab 1 colors pins by religion (shared","element":"demo-map","iframeUrl":"./demo-map/demo-map/index.html","scriptUrl":"./demo-map/demo-map.js","embed":{"iframe":"\u003ciframe src=\"./demo-map/demo-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./demo-map/demo-map.js\">\u003c/script>\n\u003cdemo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/demo-map>"},"bundle":"demo-map"}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
2
public/doxa-maps/doxa-research-map/index.html
generated
2
public/doxa-maps/doxa-research-map/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"doxa-research-map","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"doxa-research-map","scriptUrl":"./doxa-research-map/doxa-research-map.js","output":"doxa-maps/doxa-research-map/doxa-research-map.js","elements":["doxa-research-map"],"profiles":[{"profile":"research-map","file":"research-map.vue","description":"","element":"doxa-research-map","iframeUrl":"./doxa-research-map/research-map/index.html","scriptUrl":"./doxa-research-map/doxa-research-map.js","embed":{"iframe":"\u003ciframe src=\"./doxa-research-map/research-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-research-map/doxa-research-map.js\">\u003c/script>\n\u003cdoxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/doxa-research-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"doxa-research-map","scriptUrl":"./doxa-research-map/doxa-research-map.js","output":"doxa-maps/doxa-research-map/doxa-research-map.js","elements":["doxa-research-map"],"profiles":[{"profile":"research-map","file":"research-map.vue","description":"","element":"doxa-research-map","iframeUrl":"./doxa-research-map/research-map/index.html","scriptUrl":"./doxa-research-map/doxa-research-map.js","embed":{"iframe":"\u003ciframe src=\"./doxa-research-map/research-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-research-map/doxa-research-map.js\">\u003c/script>\n\u003cdoxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/doxa-research-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
2
public/doxa-maps/doxa-simple-map/index.html
generated
2
public/doxa-maps/doxa-simple-map/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"doxa-simple-map","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"doxa-simple-map","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","output":"doxa-maps/doxa-simple-map/doxa-simple-map.js","elements":["doxa-map","doxa-simple-map"],"profiles":[{"profile":"doxa-simple-map","file":"doxa-simple-map.vue","description":"","element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/index.html","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-simple-map/doxa-simple-map.js\">\u003c/script>\n\u003cdoxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/doxa-map>"},"instances":[{"id":"prayer","title":"Prayer Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-prayer","dataSource":"pray-tools","tabs":[{"id":"prayer","colorStrategy":"prayer","legend":"prayer","popup":"prayer"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/prayer/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/prayer/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"engagement","title":"Engagement Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-engagement","dataSource":"pray-tools","tabs":[{"id":"engagement","colorStrategy":"engagement","legend":"engagement","popup":"engagement"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/engagement/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/engagement/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"adoption","title":"Adoption Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-adoption","dataSource":"pray-tools","tabs":[{"id":"adoption","colorStrategy":"adoption","legend":"adoption","popup":"adoption"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/adoption/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/adoption/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}]}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"doxa-simple-map","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","output":"doxa-maps/doxa-simple-map/doxa-simple-map.js","elements":["doxa-map","doxa-simple-map"],"profiles":[{"profile":"doxa-simple-map","file":"doxa-simple-map.vue","description":"","element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/index.html","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-simple-map/doxa-simple-map.js\">\u003c/script>\n\u003cdoxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/doxa-map>"},"instances":[{"id":"prayer","title":"Prayer Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-prayer","dataSource":"pray-tools","tabs":[{"id":"prayer","colorStrategy":"prayer","legend":"prayer","popup":"prayer"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/prayer/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/prayer/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"engagement","title":"Engagement Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-engagement","dataSource":"pray-tools","tabs":[{"id":"engagement","colorStrategy":"engagement","legend":"engagement","popup":"engagement"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/engagement/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/engagement/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}},{"id":"adoption","title":"Adoption Map","apiBaseUrl":"https://pray.doxa.life","props":{"profile":"doxa-simple-map","instanceId":"embed-adoption","dataSource":"pray-tools","tabs":[{"id":"adoption","colorStrategy":"adoption","legend":"adoption","popup":"adoption"}]},"element":"doxa-map","iframeUrl":"./doxa-simple-map/doxa-simple-map/adoption/index.html","embed":{"iframe":"\u003ciframe src=\"./doxa-simple-map/doxa-simple-map/adoption/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}]}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
2
public/doxa-maps/gospel-resources/index.html
generated
2
public/doxa-maps/gospel-resources/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"gospel-resources","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"gospel-resources","scriptUrl":"./gospel-resources/gospel-resources.js","output":"doxa-maps/gospel-resources/gospel-resources.js","elements":["gospel-resources-map"],"profiles":[{"profile":"charts","file":"charts.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/charts/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/charts/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/data-tables/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/data-tables/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-list","file":"resources-list.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-list/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/resources-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-map","file":"resources-map.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-map/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/resources-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"gospel-resources","scriptUrl":"./gospel-resources/gospel-resources.js","output":"doxa-maps/gospel-resources/gospel-resources.js","elements":["gospel-resources-map"],"profiles":[{"profile":"charts","file":"charts.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/charts/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/charts/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/data-tables/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/data-tables/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-list","file":"resources-list.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-list/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/resources-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}},{"profile":"resources-map","file":"resources-map.vue","description":"","element":"gospel-resources-map","iframeUrl":"./gospel-resources/resources-map/index.html","scriptUrl":"./gospel-resources/gospel-resources.js","embed":{"iframe":"\u003ciframe src=\"./gospel-resources/resources-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/gospel-resources-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
2
public/doxa-maps/key-cities/index.html
generated
2
public/doxa-maps/key-cities/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"key-cities","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"key-cities","scriptUrl":"./key-cities/key-cities.js","output":"doxa-maps/key-cities/key-cities.js","elements":["key-cities-map"],"profiles":[{"profile":"cities-list","file":"cities-list.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/cities-list/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/cities-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"cities-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}},{"profile":"cities-map","file":"cities-map.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/cities-map/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/cities-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"cities-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}},{"profile":"city-poster-wall","file":"city-poster-wall.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/city-poster-wall/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/city-poster-wall/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"city-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/data-tables/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/data-tables/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"key-cities","scriptUrl":"./key-cities/key-cities.js","output":"doxa-maps/key-cities/key-cities.js","elements":["key-cities-map"],"profiles":[{"profile":"cities-list","file":"cities-list.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/cities-list/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/cities-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"cities-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}},{"profile":"cities-map","file":"cities-map.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/cities-map/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/cities-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"cities-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}},{"profile":"city-poster-wall","file":"city-poster-wall.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/city-poster-wall/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/city-poster-wall/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"city-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}},{"profile":"data-tables","file":"data-tables.vue","description":"","element":"key-cities-map","iframeUrl":"./key-cities/data-tables/index.html","scriptUrl":"./key-cities/key-cities.js","embed":{"iframe":"\u003ciframe src=\"./key-cities/data-tables/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./key-cities/key-cities.js\">\u003c/script>\n\u003ckey-cities-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/key-cities-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
27
public/doxa-maps/manifest.json
generated
27
public/doxa-maps/manifest.json
generated
|
|
@ -2,8 +2,8 @@
|
|||
"schema": "doxa-maps-manifest@1",
|
||||
"generatedFrom": "app-profiles/*/*.vue",
|
||||
"embedBase": ".",
|
||||
"bundleCount": 10,
|
||||
"profileCount": 21,
|
||||
"bundleCount": 9,
|
||||
"profileCount": 20,
|
||||
"bundles": [
|
||||
{
|
||||
"bundle": "cities-internet",
|
||||
|
|
@ -374,29 +374,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bundle": "prayer-map",
|
||||
"scriptUrl": "./prayer-map/prayer-map.js",
|
||||
"output": "doxa-maps/prayer-map/prayer-map.js",
|
||||
"elements": [
|
||||
"prayer-map"
|
||||
],
|
||||
"profiles": [
|
||||
{
|
||||
"profile": "prayer-map",
|
||||
"file": "prayer-map.vue",
|
||||
"description": "The flagship prayer map: one clean, search-free embed for partner sites.",
|
||||
"element": "prayer-map",
|
||||
"iframeUrl": "./prayer-map/prayer-map/index.html",
|
||||
"scriptUrl": "./prayer-map/prayer-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./prayer-map/prayer-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./prayer-map/prayer-map.js\"></script>\n<prayer-map profile-config='{\"profile\":\"prayer-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'></prayer-map>"
|
||||
},
|
||||
"bundle": "prayer-map"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bundle": "template-bundle",
|
||||
"scriptUrl": "./template-bundle/template-bundle.js",
|
||||
|
|
|
|||
2
public/doxa-maps/my-upg-100-list/index.html
generated
2
public/doxa-maps/my-upg-100-list/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"my-upg-100-list","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"my-upg-100-list","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","output":"doxa-maps/my-upg-100-list/my-upg-100-list.js","elements":["my-upg-100-list-map"],"profiles":[{"profile":"poster-wall","file":"poster-wall.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/poster-wall/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"iframe":"\u003ciframe src=\"./my-upg-100-list/poster-wall/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/my-upg-100-list-map>"}},{"profile":"upg-100-list","file":"upg-100-list.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/upg-100-list/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"iframe":"\u003ciframe src=\"./my-upg-100-list/upg-100-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/my-upg-100-list-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"my-upg-100-list","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","output":"doxa-maps/my-upg-100-list/my-upg-100-list.js","elements":["my-upg-100-list-map"],"profiles":[{"profile":"poster-wall","file":"poster-wall.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/poster-wall/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"iframe":"\u003ciframe src=\"./my-upg-100-list/poster-wall/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/my-upg-100-list-map>"}},{"profile":"upg-100-list","file":"upg-100-list.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/upg-100-list/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"iframe":"\u003ciframe src=\"./my-upg-100-list/upg-100-list/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/my-upg-100-list-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
347
public/doxa-maps/prayer-map/index.html
generated
347
public/doxa-maps/prayer-map/index.html
generated
|
|
@ -1,347 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>doxa-maps</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<style>/* staging-forge.css — the ONE-PAGE semantic staging theme (root + bundle pages).
|
||||
design brief 2026-07-11: tree sidebar left on desktop, tree-top + scroll-to-map
|
||||
on mobile, GEM FRAMES as the hero (each map in its colored jewel frame), quiet
|
||||
architectural dark, zero folder-hopping. doxa-maps design language. */
|
||||
:root {
|
||||
--bg:#0a0e15; --surf:#121a27; --surf2:#0e1623; --line:#223047;
|
||||
--ink:#eef2f7; --mut:#9aa7b8; --dim:#6b7787;
|
||||
--cy:#46d4ff; --vi:#b9a6f5; --gold:#ffc24b; --grn:#5ee0a0;
|
||||
--sans:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
|
||||
--mono:ui-monospace,SFMono-Regular,Menlo,monospace;
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
background:
|
||||
radial-gradient(1100px 480px at 82% -12%, rgba(70,212,255,.10), transparent 60%),
|
||||
radial-gradient(820px 460px at -8% 112%, rgba(185,166,245,.10), transparent 60%),
|
||||
var(--bg);
|
||||
color: var(--ink); font-family: var(--sans); -webkit-font-smoothing: antialiased;
|
||||
}
|
||||
code { font-family: var(--mono); }
|
||||
.hidden { display: none !important; }
|
||||
.empty { color: var(--dim); font: 12px/1.5 var(--mono); padding: 18px; }
|
||||
|
||||
/* ── Shell: header strip, then sidebar|stage grid (desktop) ── */
|
||||
header.top { padding: 14px 18px 10px; border-bottom: 1px solid var(--surf2); }
|
||||
header.top h1 { font: 800 15px/1.2 var(--sans); letter-spacing: -.01em; }
|
||||
header.top h1 .ac { color: var(--cy); font-weight: 600; }
|
||||
header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut); }
|
||||
.strip { padding: 7px 18px; font: 11px/1.4 var(--mono); color: var(--mut);
|
||||
border-bottom: 1px solid var(--surf2); background: rgba(255,255,255,.012); }
|
||||
.strip .pat { color: var(--cy); } .strip .err { color: #f87171; } .strip strong { color: var(--ink); }
|
||||
|
||||
/* token bar — visible only while no token resolves */
|
||||
#tokenBar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
|
||||
padding: 9px 18px; font: 12px/1.4 var(--sans); color: var(--gold);
|
||||
border-bottom: 1px solid var(--surf2); background: rgba(255,194,75,.06); }
|
||||
#tokenBar input { flex: 1 1 220px; max-width: 420px; font: 12px/1 var(--mono);
|
||||
color: var(--ink); background: var(--surf2); border: 1px solid var(--line);
|
||||
border-radius: 8px; padding: 8px 10px; }
|
||||
#tokenBar button { font: 700 11px/1 var(--mono); color: #0a0e15; cursor: pointer;
|
||||
background: linear-gradient(90deg, var(--cy), var(--vi)); border: 0;
|
||||
border-radius: 999px; padding: 8px 14px; }
|
||||
|
||||
/* ── The one-page layout ── */
|
||||
#shell { display: grid; grid-template-columns: minmax(280px, 340px) 1fr;
|
||||
height: calc(100vh - 118px); min-height: 420px; }
|
||||
|
||||
/* sidebar: THE semantic tree — navigation and architecture in one */
|
||||
#treeNav { overflow-y: auto; border-right: 1px solid var(--surf2);
|
||||
padding: 12px 10px 28px; background: rgba(255,255,255,.008); }
|
||||
.bgroup { margin-bottom: 8px; border: 1px solid var(--line); border-radius: 10px;
|
||||
background: var(--surf); overflow: hidden; }
|
||||
.bgroup summary { list-style: none; cursor: pointer; display: flex; align-items: baseline;
|
||||
gap: 8px; padding: 10px 12px; font: 700 13px/1.2 var(--sans); }
|
||||
.bgroup summary::-webkit-details-marker { display: none; }
|
||||
.bgroup summary .bdot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
|
||||
align-self: center; background: var(--gem, var(--cy)); box-shadow: 0 0 8px var(--gem, var(--cy)); }
|
||||
.bgroup summary .bmeta { margin-left: auto; font: 10px/1.3 var(--mono); color: var(--dim); font-weight: 400; }
|
||||
.bgroup[open] summary { border-bottom: 1px solid var(--surf2); }
|
||||
|
||||
.node { display: block; width: 100%; text-align: left; cursor: pointer; color: inherit;
|
||||
background: transparent; border: 0; border-left: 2px solid transparent;
|
||||
padding: 8px 12px 8px 22px; transition: background .12s; }
|
||||
.node:hover { background: rgba(255,255,255,.03); }
|
||||
.node.active { background: color-mix(in srgb, var(--gem, var(--cy)) 10%, transparent);
|
||||
border-left-color: var(--gem, var(--cy)); }
|
||||
.node .nlabel { display: block; font: 600 12.5px/1.3 var(--sans); }
|
||||
.node .nsub { display: block; font: 10.5px/1.4 var(--mono); color: var(--mut); margin-top: 1px; }
|
||||
.node .nedit { display: block; font: 9.5px/1.4 var(--mono); color: var(--dim); margin-top: 2px;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
/* kind glyphs */
|
||||
.node.k-instance .nlabel::before { content: "◇ "; color: var(--grn); }
|
||||
.node.k-profile .nlabel::before { content: "● "; color: var(--vi); }
|
||||
.node.k-nested .nlabel::before { content: "◉ "; color: var(--gold); }
|
||||
|
||||
/* ── Stage: the ONE live map inside its GEM FRAME (the recurring hero theme) ── */
|
||||
#stage { display: flex; flex-direction: column; min-width: 0; padding: 12px 16px 16px; }
|
||||
.stage-head { display: flex; align-items: center; gap: 10px; padding: 0 2px 9px; }
|
||||
.stage-head .sctag { font: 800 13px/1 var(--mono); color: var(--gem, var(--cy)); }
|
||||
.stage-head .sckind { font: 8.5px/1 var(--mono); color: var(--dim); border: 1px solid var(--line);
|
||||
border-radius: 999px; padding: 3px 8px; text-transform: uppercase; letter-spacing: .08em; }
|
||||
.stage-head .popout { margin-left: auto; font: 600 11px/1 var(--mono); color: var(--mut);
|
||||
text-decoration: none; border: 1px solid var(--line); border-radius: 999px; padding: 7px 12px; }
|
||||
.stage-head .popout:hover { color: var(--ink); border-color: #3a4a63; }
|
||||
/* the gem frame: colored jewel border + inner ring + soft glow in the node's gem color */
|
||||
.map-slot { flex: 1 1 0; min-height: 0; position: relative; border-radius: 12px;
|
||||
border: 1px solid var(--gem, var(--cy));
|
||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--gem, var(--cy)) 45%, transparent) inset,
|
||||
0 0 26px rgba(var(--gem-rgb, 70,212,255), .16),
|
||||
0 8px 30px rgba(0,0,0,.35);
|
||||
background: #0d1622; overflow: hidden; padding: 8px; }
|
||||
.map-slot iframe { position: absolute; inset: 8px; width: calc(100% - 16px);
|
||||
height: calc(100% - 16px); border: 0; border-radius: 8px; }
|
||||
|
||||
/* ── Mobile: single column — tree on top, the map takes the FULL SCREEN below ──
|
||||
(2026-07-11: mobile map = pretty much full screen; scroll must reach it.
|
||||
100dvh = dynamic viewport height, tracks the mobile URL bar; 100vh = fallback.) */
|
||||
@media (max-width: 899px) {
|
||||
html, body { height: auto; } /* let the page actually scroll */
|
||||
#shell { display: block; height: auto; }
|
||||
#treeNav { border-right: 0; border-bottom: 1px solid var(--surf2); max-height: 44vh; }
|
||||
#stage { height: 100vh; height: 100dvh; padding: 8px; } /* full-screen map pane */
|
||||
.stage-head { padding-bottom: 6px; }
|
||||
.map-slot { padding: 4px; border-radius: 10px; } /* slimmer gem frame = max map */
|
||||
.map-slot iframe { inset: 4px; width: calc(100% - 8px); height: calc(100% - 8px); }
|
||||
.node .nedit { display: none; } /* edit paths are a desktop affordance */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="top">
|
||||
<h1>doxa-maps<span class="ac"> / prayer-map</span></h1>
|
||||
<div class="sub">prayer-map — one bundle, its profiles as paste-anywhere maps</div>
|
||||
</header>
|
||||
<div class="strip" id="frameInfo">loading…</div>
|
||||
<div id="tokenBar" class="hidden"></div>
|
||||
<div id="shell">
|
||||
<nav id="treeNav" aria-label="semantic tree"></nav>
|
||||
<main id="stage"></main>
|
||||
</div>
|
||||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"prayer-map","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"prayer-map","scriptUrl":"./prayer-map/prayer-map.js","output":"doxa-maps/prayer-map/prayer-map.js","elements":["prayer-map"],"profiles":[{"profile":"prayer-map","file":"prayer-map.vue","description":"The flagship prayer map: one clean, search-free embed for partner sites.","element":"prayer-map","iframeUrl":"./prayer-map/prayer-map/index.html","scriptUrl":"./prayer-map/prayer-map.js","embed":{"iframe":"\u003ciframe src=\"./prayer-map/prayer-map/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./prayer-map/prayer-map.js\">\u003c/script>\n\u003cprayer-map profile-config='{\"profile\":\"prayer-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/prayer-map>"},"bundle":"prayer-map"}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
* design brief (2026-07-11, the merger of all prior staging files):
|
||||
* - ONE page. No folder-hopping: the semantic tree IS the navigation.
|
||||
* - Desktop: hierarchical tree SIDEBAR on the left (bundle → profile → instance,
|
||||
* kind-badged, "where do I edit" hints) · selected map fills the stage on the right.
|
||||
* - Mobile: tree on top, map below — scroll down to the map. Native responsive,
|
||||
* no simulator toggle.
|
||||
* - Token UX: resolve ?tk= → localStorage → none. If none, show a token bar
|
||||
* (paste once, stored locally in the browser, never in files/links unless you
|
||||
* use ?tk= on purpose). Maps must Just Work after one paste.
|
||||
* - Auto-generated: whatever app-profiles exist, the tree detects and shows them.
|
||||
*
|
||||
* file:// contract: classic script, manifest inlined at build (window.FORGE.manifest),
|
||||
* fetch only as dev fallback. No import/export. The DEV-HMR block at the bottom is
|
||||
* stripped by generate-tree.mjs for built pages.
|
||||
*/
|
||||
|
||||
/* global window, document */
|
||||
const FORGE = window.FORGE || {}
|
||||
const SCOPE = FORGE.scope || 'root'
|
||||
const MODE = FORGE.mode || 'built'
|
||||
const MANIFEST_URL = FORGE.manifestUrl || './manifest.json'
|
||||
const HREF_BASE = FORGE.hrefBase || './'
|
||||
|
||||
const GEMS = ['#46d4ff', '#b9a6f5', '#ffc24b', '#5ee0a0', '#f97fb5', '#8ef0e4']
|
||||
const $ = (sel) => document.querySelector(sel)
|
||||
|
||||
let tree = null
|
||||
let flat = [] // flat list of selectable map nodes (profiles + instances)
|
||||
let activeKey = null // selected node key
|
||||
|
||||
// ── Token resolution (maintainer ladder: ?tk= → localStorage → ask once) ─────────
|
||||
const TK_STORE = 'MAPS_STAGING_TK'
|
||||
function resolveToken() {
|
||||
const qp = new URLSearchParams(location.search).get('tk')
|
||||
if (qp) { try { localStorage.setItem(TK_STORE, qp) } catch (e) {} return qp }
|
||||
try { return localStorage.getItem(TK_STORE) || '' } catch (e) { return '' }
|
||||
}
|
||||
let TK = resolveToken()
|
||||
|
||||
function withTk(href) {
|
||||
if (!TK) return href
|
||||
return href + (href.includes('?') ? '&' : '?') + 'tk=' + encodeURIComponent(TK)
|
||||
}
|
||||
// Carry the page's ?provider= into the embedded map iframe so the engine choice propagates.
|
||||
function withProvider(href) {
|
||||
const p = typeof location !== 'undefined' ? new URLSearchParams(location.search).get('provider') : null
|
||||
return p ? href + (href.includes('?') ? '&' : '?') + 'provider=' + encodeURIComponent(p) : href
|
||||
}
|
||||
|
||||
// ── Load the semantic tree (inline-first; fetch = dev fallback) ──────────────
|
||||
async function loadTree() {
|
||||
if (FORGE.manifest) return FORGE.manifest
|
||||
const res = await fetch(MANIFEST_URL, { cache: 'no-cache' })
|
||||
if (!res.ok) throw new Error('manifest ' + res.status + ' from ' + MANIFEST_URL)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
const treeHref = (rel) => HREF_BASE + rel
|
||||
const hexRgb = (hex) => {
|
||||
const h = hex.replace('#', '')
|
||||
return parseInt(h.slice(0,2),16) + ',' + parseInt(h.slice(2,4),16) + ',' + parseInt(h.slice(4,6),16)
|
||||
}
|
||||
|
||||
function profileKind(p) {
|
||||
if (Array.isArray(p.instances) && p.instances.length) return 'parameterized'
|
||||
if (p.nested || /nested|tabs|web components/i.test(p.description || '')) return 'nested'
|
||||
return 'individual'
|
||||
}
|
||||
|
||||
// ── Build the flat selectable list + the sidebar tree ────────────────────────
|
||||
// Selectable = anything that renders as ONE map page: individual/nested profiles
|
||||
// and each instance of a parameterized profile.
|
||||
function buildFlat() {
|
||||
flat = []
|
||||
;(tree.bundles || []).forEach((b, bi) => { b.__gem = GEMS[bi % GEMS.length] })
|
||||
for (const b of tree.bundles || []) {
|
||||
for (const p of b.profiles || []) {
|
||||
const kind = profileKind(p)
|
||||
if (kind === 'parameterized') {
|
||||
for (const inst of p.instances) {
|
||||
flat.push({
|
||||
key: b.bundle + '/' + p.profile + '/' + inst.id,
|
||||
kind: 'instance', label: inst.title || inst.id,
|
||||
sub: p.profile + ' · preset "' + inst.id + '"',
|
||||
edit: b.bundle + '/' + p.profile + '.instances.json',
|
||||
href: treeHref(b.bundle + '/' + p.profile + '/' + inst.id + '/index.html'),
|
||||
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
flat.push({
|
||||
key: b.bundle + '/' + p.profile,
|
||||
kind: kind === 'nested' ? 'nested' : 'profile',
|
||||
label: p.profile,
|
||||
sub: p.description || (kind === 'nested' ? 'embeds child web components' : 'one web component = one map'),
|
||||
edit: 'app-profiles/' + b.bundle + '/' + (p.file || p.profile + '.vue'),
|
||||
href: treeHref(b.bundle + '/' + p.profile + '/index.html'),
|
||||
bundle: b.bundle, element: p.element || (b.elements || [])[0], gem: b.__gem,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) =>
|
||||
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]))
|
||||
}
|
||||
|
||||
// Sidebar: bundles are <details> groups (open by default), each map node a row.
|
||||
function renderSidebar() {
|
||||
const el = $('#treeNav')
|
||||
if (!el) return
|
||||
const parts = []
|
||||
const bundles = tree.bundles || []
|
||||
bundles.forEach((b, bi) => {
|
||||
const gem = b.__gem || GEMS[bi % GEMS.length]
|
||||
const rows = flat.filter((n) => n.bundle === b.bundle).map((n) => `
|
||||
<button class="node k-${n.kind}${n.key === activeKey ? ' active' : ''}" data-key="${esc(n.key)}" style="--gem:${gem}">
|
||||
<span class="nlabel">${esc(n.label)}</span>
|
||||
<span class="nsub">${esc(n.sub)}</span>
|
||||
<code class="nedit">${esc(n.edit)}</code>
|
||||
</button>`).join('')
|
||||
parts.push(`
|
||||
<details class="bgroup" open style="--gem:${gem}">
|
||||
<summary><span class="bdot"></span>${esc(b.bundle)}
|
||||
<span class="bmeta">${(b.profiles || []).length} profile${(b.profiles || []).length === 1 ? '' : 's'} · one .js</span>
|
||||
</summary>
|
||||
${rows}
|
||||
</details>`)
|
||||
})
|
||||
el.innerHTML = parts.join('') || '<p class="empty">no bundles built yet</p>'
|
||||
el.querySelectorAll('.node').forEach((btn) =>
|
||||
btn.addEventListener('click', () => select(btn.dataset.key)))
|
||||
}
|
||||
|
||||
// ── Stage: the ONE live map (iframe of the node's self-contained page) ───────
|
||||
function renderStage() {
|
||||
const st = $('#stage')
|
||||
if (!st) return
|
||||
const nd = flat.find((n) => n.key === activeKey) || flat[0]
|
||||
if (!nd) { st.innerHTML = '<p class="empty">no maps in this tree</p>'; return }
|
||||
activeKey = nd.key
|
||||
const gem = nd.gem || '#46d4ff'
|
||||
st.style.setProperty('--gem', gem)
|
||||
st.style.setProperty('--gem-rgb', hexRgb(gem))
|
||||
st.innerHTML = `
|
||||
<div class="stage-head">
|
||||
<span class="sctag"><${esc(nd.element || nd.label)}></span>
|
||||
<span class="sckind">${esc(nd.kind)}</span>
|
||||
<a class="popout" href="${esc(withProvider(withTk(nd.href)))}" target="_blank" rel="noopener">open ↗</a>
|
||||
</div>
|
||||
<div class="map-slot"><iframe allowfullscreen src="${esc(withProvider(withTk(nd.href)))}"></iframe></div>`
|
||||
// keep the sidebar highlight in sync
|
||||
document.querySelectorAll('#treeNav .node').forEach((b) =>
|
||||
b.classList.toggle('active', b.dataset.key === activeKey))
|
||||
}
|
||||
|
||||
function select(key) {
|
||||
activeKey = key
|
||||
renderStage()
|
||||
// Mobile: after picking from the tree, bring the map into view (scroll down to it).
|
||||
if (window.matchMedia('(max-width: 899px)').matches) {
|
||||
$('#stage')?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
}
|
||||
|
||||
// ── Token bar: shown only while no token resolves; paste once, stored locally ─
|
||||
function renderTokenBar() {
|
||||
const bar = $('#tokenBar')
|
||||
if (!bar) return
|
||||
if (TK) { bar.classList.add('hidden'); return }
|
||||
bar.classList.remove('hidden')
|
||||
bar.innerHTML = `
|
||||
<span>maps need a Mapbox token —</span>
|
||||
<input id="tkInput" type="password" placeholder="paste pk.… (stays in this browser only)" autocomplete="off" />
|
||||
<button id="tkSave">use it</button>`
|
||||
$('#tkSave').addEventListener('click', () => {
|
||||
const v = $('#tkInput').value.trim()
|
||||
if (!v) return
|
||||
TK = v
|
||||
try { localStorage.setItem(TK_STORE, v) } catch (e) {}
|
||||
bar.classList.add('hidden')
|
||||
renderStage()
|
||||
})
|
||||
}
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────────────────────
|
||||
async function boot() {
|
||||
const info = $('#frameInfo')
|
||||
try {
|
||||
tree = await loadTree()
|
||||
buildFlat()
|
||||
if (info) {
|
||||
info.innerHTML = '<span class="pat">semantic tree</span> — '
|
||||
+ (tree.bundles || []).length + ' bundle(s) · ' + flat.length + ' map(s) · '
|
||||
+ '<strong>' + esc(MODE) + '</strong>'
|
||||
}
|
||||
renderSidebar()
|
||||
renderTokenBar()
|
||||
if (flat.length) { activeKey = flat[0].key; renderStage() }
|
||||
} catch (e) {
|
||||
if (info) info.innerHTML = '<span class="err">failed to load semantic tree: ' + esc(e.message) + '</span>'
|
||||
}
|
||||
}
|
||||
|
||||
boot()
|
||||
|
||||
/* dev-hmr stripped for built pages */
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
181
public/doxa-maps/prayer-map/prayer-map.js
generated
181
public/doxa-maps/prayer-map/prayer-map.js
generated
File diff suppressed because one or more lines are too long
121
public/doxa-maps/prayer-map/prayer-map/index.html
generated
121
public/doxa-maps/prayer-map/prayer-map/index.html
generated
|
|
@ -1,121 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>prayer-map · prayer-map</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- Mapbox peer loaded ONCE here; the map custom element mounts in this page and
|
||||
uses window.mapboxgl directly (same realm → no cross-realm container errors). -->
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css" rel="stylesheet" />
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.css" rel="stylesheet" />
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js"></script>
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.min.js"></script>
|
||||
<style>
|
||||
/* BARE full-page map — no chrome. The custom element IS the page, so the
|
||||
browser URL shares the map itself (design note: "Share shares THE MAP"). */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, prayer-map { position: fixed; inset: 0; width: 100%; height: 100%; 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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="slot"></div>
|
||||
<div id="status">loading token…</div>
|
||||
|
||||
<!-- Bundle IIFE — registers the <prayer-map> custom element. -->
|
||||
<script src="../prayer-map.js"></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":"prayer-map","dataSource":"pray-tools","instanceId":"prayer-map-prayer-map","assetsBase":"../../../assets"};
|
||||
var TOKEN_SRC = "/api/maps/token.json";
|
||||
var 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);
|
||||
});
|
||||
}
|
||||
function done() { if (status && status.parentNode) { status.parentNode.removeChild(status); } }
|
||||
|
||||
function mount(token) {
|
||||
if (!token) { return askToken(); }
|
||||
try {
|
||||
var cfg = Object.assign({}, BASE_CONFIG, { tk: token });
|
||||
var el = document.createElement("prayer-map");
|
||||
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 || BAKED_TOKEN;
|
||||
if (directToken) { return mount(directToken); }
|
||||
|
||||
// Rungs 4-6: resolve a token endpoint and fetch it.
|
||||
var tokenUrl =
|
||||
(window.MAP_TOKEN_URL) ||
|
||||
(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '') ||
|
||||
DEFAULT_TOKEN_URL;
|
||||
|
||||
fetch(tokenUrl)
|
||||
.then(function (r) {
|
||||
var ct = r.headers.get('content-type') || '';
|
||||
return ct.indexOf('json') !== -1 ? r.json() : r.text();
|
||||
})
|
||||
.then(function (data) { mount(extractToken(data)); })
|
||||
.catch(function (e) {
|
||||
fail('Token fetch failed from ' + tokenUrl + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + (e && e.message ? e.message : e) + ')');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2
public/doxa-maps/template-bundle/index.html
generated
2
public/doxa-maps/template-bundle/index.html
generated
|
|
@ -129,7 +129,7 @@ header.top .sub { margin-top: 3px; font: 12px/1.4 var(--sans); color: var(--mut)
|
|||
|
||||
<script>window.FORGE = {"mode":"built","scope":"bundle","manifestUrl":"../manifest.json","bundle":"template-bundle","hrefBase":"../"};
|
||||
/* file:// contract: manifest inlined at build — no fetch needed to render. */
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":21,"bundles":[{"bundle":"template-bundle","scriptUrl":"./template-bundle/template-bundle.js","output":"doxa-maps/template-bundle/template-bundle.js","elements":["template-bundle"],"profiles":[{"profile":"template-map-a","file":"template-map-a.vue","description":"Minimal example profile A — a colored placeholder, no Mapbox or data.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-a/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"iframe":"\u003ciframe src=\"./template-bundle/template-map-a/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./template-bundle/template-bundle.js\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-a\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"},{"profile":"template-map-b","file":"template-map-b.vue","description":"Minimal example profile B — a second profile in the same bundle.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-b/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"iframe":"\u003ciframe src=\"./template-bundle/template-map-b/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./template-bundle/template-bundle.js\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-b\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":9,"profileCount":20,"bundles":[{"bundle":"template-bundle","scriptUrl":"./template-bundle/template-bundle.js","output":"doxa-maps/template-bundle/template-bundle.js","elements":["template-bundle"],"profiles":[{"profile":"template-map-a","file":"template-map-a.vue","description":"Minimal example profile A — a colored placeholder, no Mapbox or data.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-a/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"iframe":"\u003ciframe src=\"./template-bundle/template-map-a/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./template-bundle/template-bundle.js\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-a\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"},{"profile":"template-map-b","file":"template-map-b.vue","description":"Minimal example profile B — a second profile in the same bundle.","element":"template-bundle","iframeUrl":"./template-bundle/template-map-b/index.html","scriptUrl":"./template-bundle/template-bundle.js","embed":{"iframe":"\u003ciframe src=\"./template-bundle/template-map-b/index.html\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./template-bundle/template-bundle.js\">\u003c/script>\n\u003ctemplate-bundle profile-config='{\"profile\":\"template-map-b\",\"tk\":\"YOUR_MAPBOX_TOKEN\"}'>\u003c/template-bundle>"},"bundle":"template-bundle"}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -900,14 +900,6 @@ const PROFILES = {
|
|||
views: ['map'],
|
||||
config: { profile:'upg-neighbors', dataSource:'pray-tools', instanceId:'wrapper-upg-neighbor-lab' },
|
||||
},
|
||||
// Flagship prayer map — MapLibre by default (token-free; no single point of failure for
|
||||
// partner embeds), no search bar. Experimental until blessed to production.
|
||||
'prayer-map-maplibre': {
|
||||
tag: 'prayer-map',
|
||||
src: './doxa-maps/prayer-map/prayer-map.js',
|
||||
views: ['map'],
|
||||
config: { profile:'prayer-map', dataSource:'pray-tools', instanceId:'wrapper-prayer-map', provider:'maplibre' },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -98,9 +98,6 @@ export const NAV = [
|
|||
open: false,
|
||||
children: [
|
||||
{ kind: 'item', id: 'nationmapper', label: 'Nationmapper', icon: 'share', target: 'both' },
|
||||
// Flagship prayer-map (MapLibre-default, token-free, no search) — in Experimental
|
||||
// until blessed to production, per the experimental-profile law.
|
||||
{ kind: 'item', id: 'prayer-map-maplibre', label: 'Prayer Map (MapLibre)', icon: 'prayer', target: 'both' },
|
||||
// Country Posters MOVED here from Prayer Mobilization to Experimental.
|
||||
{ kind: 'item', id: 'countries-map', label: 'Country Posters', icon: 'poster', target: 'both' },
|
||||
// Outreach Planner = geo-steward, renamed + moved out of the Dashboard folder.
|
||||
|
|
|
|||
Loading…
Reference in a new issue