Public build folder is three entries: index.html, assets/, doxa-maps/
- index.html is generated by internal/generate-spa.mjs from doxa-maps/manifest.json: one self-contained page that lists every bundle, profile and instance, mounts the chosen one, and offers Open page / Copy embed; build ends with `bun run spa` (the material shell is no longer installed; nav.js, icons.js, languages.js, pane-wrapper/, external/ and _headers leave public/) - The website drop-in follows the same contract: dropin.include in the build config, sync-dropin --from-config --prune, public-hygiene --strict fails on any other root entry - Production bundles no longer carry CSS comments: a build-only Vite plugin strips them from every shadow-style block (about 140 per map bundle, 5% smaller); remaining comments reworded - Share pages and the pin renderer drop their note-style comments
This commit is contained in:
parent
7f6c2286f9
commit
12d1fee133
61 changed files with 1167 additions and 4654 deletions
|
|
@ -10,7 +10,7 @@ served web-root by the build (`internal/generate-tree.mjs`), exactly like the pe
|
|||
| `vendor/` | `/assets/vendor/` | Third-party runtime assets (e.g. table libraries) |
|
||||
| `posters/` | `/assets/posters/` | Rendered poster webp set (heavy pipeline output; poster walls fetch these) |
|
||||
| `tables/` | `/assets/tables/` | The MERGED served table set (its index.json is the merge; per-profile `app-profiles/*/data/tables` are build-time sources) |
|
||||
| `external-bundles/` | `/external/` | Prebuilt map IIFEs vendored from sibling projects (not built here) |
|
||||
| `external-bundles/` | not served | Prebuilt map IIFEs vendored from sibling projects (not built here); only the optional `layouts/` shell mounted them, so the build no longer copies them into `public/` |
|
||||
|
||||
Don't edit anything under `public/` by hand — change it here (or in `app-profiles/*/data/`,
|
||||
`layouts/*/assets/`) and rebuild.
|
||||
|
|
|
|||
|
|
@ -153,9 +153,9 @@ each bundle registers a **distinct** tag.
|
|||
| `bun run dev` | Vite dev server + HMR (default `http://localhost:5173`, or the next free port — read the terminal). `/` is the **auto-generated SPA** (playground) of every element, built from `internal/playground/{discover,render,dev-plugin}.mjs`; every bundle is also served at its own html path. Dev-only routes: `/__playground/registry.json` (the registry), `/assets/*` (served from `public/assets/`, else `app-profiles/*/data/`). **Writes nothing to `public/`** — no HMR across that boundary. |
|
||||
| `bun run smoke:playground` | Opens every element of the auto-generated SPA by deep link in Firefox and proves it boots: element defined, shadow DOM populated, no error card, a WebGL canvas for map profiles (checked through nested shadow roots), no HTML-for-JSON data responses. `--json <file>` writes the evidence; exit 1 on any failure. The pre-handoff gate. |
|
||||
| `bun run smoke:share` | The share round-trip gate (Firefox): opens a host page (`--playground` = every canvas element of the dev SPA; `--url <page> --tag <element>` = the website or any CDN page), clicks Share, reads the URL, fetches it, reopens it in a fresh page and asserts the SAME custom element mounts with a WebGL canvas. `--json <file>`; exit 1 on any failure — proves REQ-1 ("links work wherever the tree is hosted"). |
|
||||
| `bun run check:public-hygiene [<dir>] [--strict]` | Proves a served folder is fit to publish: scans for local paths, internal folder names, dev-server URLs, JWT-shaped tokens, `.env`/token files, source maps, documentation and staging/preview pages, and prints an inventory (bundles, leaf pages, assets by folder). Exit 1 on a finding (`--strict` also fails on warnings). Run it on the website's drop-in before any public push. |
|
||||
| `bun run sync:dropin <dest>` | Mirrors the built web-root `public/` into a host checkout (e.g. the website's `public/js/doxa-maps-build/`): adds, updates and deletes to match; **never copies credentials** (`api/maps/token.json`, `token.local.js`) **nor documentation** (`*.md`, `*.txt` — the README and docs stay private with the bundler; a stray doc in the destination is removed); `--protect a,b` keeps host-owned files; `--dry-run` first. The only sanctioned way a map changes on the site: change the bundler → `bun run build` → sync → commit in the host. |
|
||||
| `bun run build` | `clean` → one `vite build` per `app-profiles/*/` folder → `tree` → `check:drop` → `check:parse`. |
|
||||
| `bun run check:public-hygiene [<dir>] [--strict]` | Proves a served folder is fit to publish: scans for local paths, internal folder names, dev-server URLs, JWT-shaped tokens, `.env`/token files, source maps, documentation and staging/preview pages, and prints an inventory (bundles, leaf pages, assets by folder). Under `--strict` any root entry other than `index.html`, `assets/` and `doxa-maps/` is a finding ("outside the drop-in contract"). Exit 1 on a finding. Run it on the website's drop-in before any public push. |
|
||||
| `bun run sync:dropin <dest> [--from-config \| --only a,b] [--prune]` / `bun run deploy:dev` | Syncs the built `public/` into a host checkout (e.g. the website's `public/js/doxa-maps-build/`). **The drop-in contract** lives in ONE place — `vite.doxa-maps-build-config.json` → `dropin.include`, exactly three root entries: `index.html` (the root page generated by `internal/generate-spa.mjs`, listing every bundle/profile/instance and mounting the chosen one), `assets/` (data) and `doxa-maps/` (the bundle folders + `manifest.json`). `--from-config` copies only those, `--prune` removes everything else in the destination, so a host carries exactly the served web-root and nothing else at its root. Without an allowlist it is a plain full mirror. Never copies credentials, docs or staging pages; `--protect a,b` keeps host-owned files; `--dry-run` first; prints a copied/unchanged/deleted/pruned summary and the resulting top-level tree. `deploy:dev` = `build` → sync `--from-config --prune` into the website (`DOXA_DROPIN_DEST` overrides the path) — the only sanctioned way a map changes on the site; then commit in both repos. |
|
||||
| `bun run build` | `clean` → one `vite build` per `app-profiles/*/` folder → `tree` → `check:drop` → `check:parse` → `verify` → `spa` (writes `public/index.html` from the manifest). |
|
||||
| `bun run build:bundle` | Builds ONE bundle (needs `BUNDLE=<name>`). |
|
||||
| `bun run build:watch` | Rebuilds into `public/` on every change (integration loop). |
|
||||
| `bun run clean` | Wipes + recreates the `public/<name>/` output tree. |
|
||||
|
|
@ -175,6 +175,11 @@ CSS is injected by JS (`cssCodeSplit: false`), so there is no separate `.css`. T
|
|||
name comes from `vite.doxa-maps-build-config.json` `name` (default `doxa-maps`).
|
||||
`emptyOutDir: false` keeps sibling bundles intact when you rebuild one.
|
||||
|
||||
**Shadow-style comments are stripped from production bundles.** Component CSS is injected
|
||||
from `useShadowStyles(\`…\`)` template literals that ship verbatim, so `internal/vite-plugin-strip-shadow-css-comments.mjs`
|
||||
(build only, `enforce: 'pre'`) removes the `/* … */` blocks inside those literals and collapses the blank lines
|
||||
they leave; a literal with `${…}` is left untouched with a warning, and dev mode keeps every comment.
|
||||
|
||||
**What a built bundle folder contains.** Beyond the IIFE, `bun run tree` generates an
|
||||
`index.html` **per bundle** (staging/showcase), **per profile**, and **per instance** —
|
||||
each profile/instance page is a standalone, directly linkable/iframe-able map — plus
|
||||
|
|
|
|||
327
internal/generate-spa.mjs
Normal file
327
internal/generate-spa.mjs
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* generate-spa.mjs — write public/index.html, the root page of the served web-root.
|
||||
*
|
||||
* public/ holds exactly three things: assets/ (data), <tree>/ (the bundle folders + manifest.json)
|
||||
* and this one page, which lists every bundle → profile → instance the manifest declares and
|
||||
* mounts the chosen one. It is GENERATED — never hand-edited — as a single static file: inline
|
||||
* CSS, inline vanilla JS, every path relative, so the same file works from any static host,
|
||||
* under a subpath (the website serves it at /js/doxa-maps-build/index.html) and from file://.
|
||||
*
|
||||
* At runtime the page fetches <tree>/manifest.json so it stays correct when bundles change; a
|
||||
* compact copy of the manifest is baked in as the fallback (file://, or the fetch fails). Embed
|
||||
* snippets are re-derived from the manifest fields with the template generate-tree uses; this
|
||||
* script checks the derivation against every manifest snippet and bakes any that differ, so the
|
||||
* fallback is exact without carrying the snippets twice.
|
||||
*
|
||||
* The chosen map mounts exactly like its leaf page: same peer engine (Mapbox, or MapLibre for a
|
||||
* provider:'maplibre' instance), same profile-config (assetsBase, dataSource, instanceId or the
|
||||
* instance props), window.MAP_APP_API_URL, and the same token ladder — profile-config tk →
|
||||
* ?tk= on this page's URL → the paste-once localStorage token → baked token → window.MAP_TOKEN_URL
|
||||
* → the configured tokenUrl → same-origin /api/maps/token → ask once.
|
||||
*
|
||||
* Deep link: #/<bundle>/<profile>[/<instance>]. Actions: Open page (the leaf page), Copy embed
|
||||
* (the manifest's embed.iframe / embed.script).
|
||||
*
|
||||
* Usage: node internal/generate-spa.mjs (last step of `bun run build`)
|
||||
*/
|
||||
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
||||
import { join, resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
// Same fields the tree generator bakes into every leaf page (vite.doxa-maps-build-config.json).
|
||||
function readBuildConfig() {
|
||||
let cfg = {}
|
||||
try { cfg = JSON.parse(readFileSync(join(ROOT, 'vite.doxa-maps-build-config.json'), 'utf8')) } catch { /* defaults */ }
|
||||
const str = (k) => (typeof cfg[k] === 'string' ? cfg[k].trim() : '')
|
||||
let tokenUrl = str('tokenUrl')
|
||||
if (!tokenUrl) {
|
||||
try {
|
||||
const src = JSON.parse(readFileSync(join(ROOT, 'library/api/sources.json'), 'utf8'))
|
||||
const v = src?._meta?.mapboxTokenUrl || src?.mapboxTokenUrl
|
||||
if (typeof v === 'string' && v.trim()) tokenUrl = v.trim()
|
||||
} catch { /* fall through */ }
|
||||
}
|
||||
return {
|
||||
name: str('name') || 'doxa-maps',
|
||||
title: str('displayTitle') || 'doxa-maps',
|
||||
tokenUrl: tokenUrl || 'window.MAP_TOKEN_URL',
|
||||
token: str('token'),
|
||||
apiBaseUrl: str('apiBaseUrl').replace(/\/+$/, ''),
|
||||
assetsBaseUrl: str('assetsBaseUrl').replace(/\/+$/, ''),
|
||||
qaWidget: cfg.qaWidget !== false,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Shared with the page (injected by source, so Node and the browser run the same code) ──
|
||||
// The embed snippets exactly as generate-tree writes them into the manifest: profile → iframe +
|
||||
// script, instance → iframe only. b/p/x are the compact records below.
|
||||
function snip(b, p, x) {
|
||||
var u = (x || p).u, tag = (x && x.e) || p.e || b.e[0] || b.b
|
||||
var r = { iframe: '<iframe src="' + u + '?utm_source=YOUR_SOURCE" width="100%" height="600" style="border:none;border-radius:12px;" allowfullscreen></iframe>' }
|
||||
if (!x) r.script = '<script src="' + b.s + '"' + (b.i ? ' integrity="' + b.i + '" crossorigin="anonymous"' : '') + '><\/script>\n<' + tag + " profile-config='" + JSON.stringify({ profile: p.p, tk: 'YOUR_MAPBOX_TOKEN', utmSource: 'YOUR_SOURCE' }) + "'></" + tag + '>'
|
||||
return r
|
||||
}
|
||||
// Manifest → compact registry: [{b,s,i,e,p:[{p,e,u,d,em?,in?:[{id,t,a,pr,e,u,em?}]}]}].
|
||||
// lean = drop every embed snippet the page can re-derive with snip() (the baked fallback);
|
||||
// the live manifest keeps its snippets verbatim.
|
||||
function compact(m, lean) {
|
||||
return (m.bundles || []).map(function (src) {
|
||||
var b = { b: src.bundle, s: src.scriptUrl || '', i: src.integrity || '', e: src.elements || [], p: [] }
|
||||
b.p = (src.profiles || []).map(function (sp) {
|
||||
var p = { p: sp.profile, e: sp.element || '', u: sp.iframeUrl || '', d: sp.description || '' }
|
||||
var em = sp.embed || {}, d = snip(b, p, null)
|
||||
if (!lean || em.iframe !== d.iframe || em.script !== d.script) p.em = em
|
||||
var ins = (sp.instances || []).map(function (sx) {
|
||||
var x = { id: sx.id, t: sx.title || sx.id, a: sx.apiBaseUrl || '', pr: sx.props || {}, e: sx.element || '', u: sx.iframeUrl || '' }
|
||||
var xe = sx.embed || {}, xd = snip(b, p, x)
|
||||
if (!lean || xe.iframe !== xd.iframe) x.em = xe
|
||||
return x
|
||||
})
|
||||
if (ins.length) p.in = ins
|
||||
return p
|
||||
})
|
||||
return b
|
||||
})
|
||||
}
|
||||
|
||||
const CFG = readBuildConfig()
|
||||
const TREE = CFG.name
|
||||
const manifestPath = join(ROOT, 'public', TREE, 'manifest.json')
|
||||
if (!existsSync(manifestPath)) { console.error(`[generate-spa] missing ${manifestPath} — run \`bun run tree\` first`); process.exit(1) }
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
||||
const baked = compact(manifest, true)
|
||||
const nProfiles = baked.reduce((n, b) => n + b.p.length, 0)
|
||||
const nInstances = baked.reduce((n, b) => n + b.p.reduce((k, p) => k + (p.in ? p.in.length : 0), 0), 0)
|
||||
const nOverrides = baked.reduce((n, b) => n + b.p.reduce((k, p) => k + (p.em ? 1 : 0) + (p.in || []).filter(x => x.em).length, 0), 0)
|
||||
const json = (v) => JSON.stringify(v).replace(/</g, '\\u003c')
|
||||
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<')
|
||||
|
||||
const html = String.raw`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>${esc(CFG.title)}</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<style>
|
||||
:root{--bg:#fff;--bg2:#f6f7f9;--bg3:#eceef2;--bd:#e2e5ea;--fg:#15181d;--mut:#667085;--err:#d13c3c}
|
||||
@media(prefers-color-scheme:dark){:root{--bg:#111318;--bg2:#181b21;--bg3:#232730;--bd:#2b3038;--fg:#eef0f3;--mut:#9aa3b2;--err:#ff7b7b}}
|
||||
*{box-sizing:border-box}html,body{margin:0;height:100%}
|
||||
body{background:var(--bg);color:var(--fg);font:14px/1.4 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;display:flex;flex-direction:column;height:100vh}
|
||||
header{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:8px 14px;border-bottom:1px solid var(--bd)}
|
||||
header b{font-size:15px}.n{color:var(--mut);font-size:12px}
|
||||
#menu{display:none;margin-left:auto}
|
||||
button,.btn{font:inherit;font-size:12px;padding:5px 10px;border:1px solid var(--bd);border-radius:8px;background:var(--bg2);color:var(--fg);cursor:pointer;text-decoration:none;white-space:nowrap}
|
||||
button:hover,.btn:hover{background:var(--bg3)}
|
||||
.body{flex:1 1 auto;display:flex;min-height:0}
|
||||
nav{flex:0 0 270px;overflow:auto;border-right:1px solid var(--bd);padding:6px 0 20px}
|
||||
.gh{padding:12px 14px 4px;font-weight:600;font-size:13px;display:flex;justify-content:space-between;gap:8px}.gh span{color:var(--mut);font-weight:400}
|
||||
.row{display:block;width:100%;text-align:left;border:0;border-radius:0;background:none;padding:6px 14px;font-size:13px;line-height:1.3;white-space:normal}
|
||||
.row.in{padding-left:28px}.row code{display:block;font-size:11px;color:var(--mut)}
|
||||
.row:hover{background:var(--bg2)}.row[aria-current=true]{background:var(--bg3);font-weight:600}
|
||||
main{flex:1 1 auto;display:flex;flex-direction:column;min-width:0;padding:10px 12px 12px;gap:8px}
|
||||
.bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
||||
.bar .t{flex:1 1 auto;min-width:0}.bar b{font-size:15px}.s{display:block;font-size:12px;color:var(--mut)}
|
||||
code{font-family:ui-monospace,Menlo,monospace}
|
||||
#embed{display:flex;flex-direction:column;gap:6px;padding:8px;border:1px solid var(--bd);border-radius:8px;background:var(--bg2)}
|
||||
#embed label{font-size:12px;color:var(--mut);display:flex;justify-content:space-between;align-items:center}
|
||||
#embed textarea{width:100%;height:58px;font:11px/1.35 ui-monospace,Menlo,monospace;border:1px solid var(--bd);border-radius:6px;background:var(--bg);color:var(--fg);padding:6px;resize:vertical}
|
||||
#slot{flex:1 1 auto;position:relative;min-height:240px;border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg2)}
|
||||
#slot>*{position:absolute;inset:0;width:100%;height:100%;display:block;border:0}
|
||||
.hint{display:grid;place-items:center;padding:20px;text-align:center;color:var(--mut);font-size:13px}
|
||||
.hint.err{color:var(--err)}
|
||||
.hint input{font:12px ui-monospace,Menlo,monospace;padding:6px 8px;border-radius:6px;border:1px solid var(--bd);background:var(--bg);color:var(--fg);width:min(320px,70vw)}
|
||||
[hidden]{display:none!important}
|
||||
@media(max-width:700px){.body{flex-direction:column}nav{flex:0 0 auto;max-height:45vh;border-right:0;border-bottom:1px solid var(--bd)}#menu{display:inline-block}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header><b>${esc(CFG.title)}</b><span class="n" id="count"></span><button id="menu" type="button" aria-controls="nav" aria-expanded="true">Maps</button></header>
|
||||
<div class="body">
|
||||
<nav id="nav" aria-label="Maps"></nav>
|
||||
<main>
|
||||
<div class="bar"><div class="t"><b id="ttl">Choose a map</b><span class="s" id="sub"></span></div><a class="btn" id="open" target="_blank" rel="noopener" hidden>Open page</a><button id="emb" type="button" hidden aria-expanded="false">Copy embed</button></div>
|
||||
<div id="embed" hidden><label>iframe embed <button type="button" data-copy="ifr">Copy</button></label><textarea id="ifr" readonly></textarea><div id="scrw"><label>script embed <button type="button" data-copy="scr">Copy</button></label><textarea id="scr" readonly></textarea></div></div>
|
||||
<div id="slot"><div class="hint">Choose a map from the list.</div></div>
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var TREE = ${json(TREE)}, TOKEN_SRC = ${json(CFG.tokenUrl)}, BAKED_TOKEN = ${json(CFG.token)}, API_BASE = ${json(CFG.apiBaseUrl)}, ASSETS_BASE = ${json(CFG.assetsBaseUrl)}, QA = ${json(CFG.qaWidget)};
|
||||
var DEFAULT_TOKEN_URL = '/api/maps/token';
|
||||
var BAKED = ${json(baked)};
|
||||
var snip = ${snip.toString()};
|
||||
var compact = ${compact.toString()};
|
||||
// Peer engines, the same tags every leaf page carries; loaded once, before the bundle.
|
||||
var PEERS = {
|
||||
mapbox: { css: ['https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css', 'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.css'],
|
||||
js: ['https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js', 'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.min.js'] },
|
||||
maplibre: { css: ['https://cdn.jsdelivr.net/npm/maplibre-gl@5/dist/maplibre-gl.css'], js: ['https://cdn.jsdelivr.net/npm/maplibre-gl@5/dist/maplibre-gl.js'] }
|
||||
};
|
||||
var $ = function (id) { return document.getElementById(id) };
|
||||
var nav = $('nav'), slot = $('slot'), ttl = $('ttl'), sub = $('sub'), open = $('open'), embBtn = $('emb'), embed = $('embed'), ifr = $('ifr'), scr = $('scr'), scrw = $('scrw'), menu = $('menu'), count = $('count');
|
||||
var ASSETS = ASSETS_BASE || new URL('assets', location.href).href.replace(/\/+$/, '');
|
||||
var REG = BAKED, active = null, seq = 0, loaded = {}, fetchedToken = '';
|
||||
var narrow = function () { return window.matchMedia && window.matchMedia('(max-width:700px)').matches };
|
||||
|
||||
// Manifest refs are relative to the tree root; this page sits one level above it.
|
||||
function rel(u) { return /^([a-z]+:|\/)/i.test(u) ? u : TREE + '/' + u.replace(/^\.\//, '') }
|
||||
function qp(name) { try { return new URLSearchParams(location.search).get(name) || '' } catch (e) { return '' } }
|
||||
function hint(msg, err) { var d = document.createElement('div'); d.className = 'hint' + (err ? ' err' : ''); d.textContent = msg; slot.innerHTML = ''; slot.appendChild(d); return d }
|
||||
function assign(t) { for (var i = 1; i < arguments.length; i++) { var s = arguments[i]; for (var k in s) if (Object.prototype.hasOwnProperty.call(s, k)) t[k] = s[k] } return t }
|
||||
|
||||
function once(url, kind, attrs) {
|
||||
if (loaded[url]) return loaded[url];
|
||||
loaded[url] = new Promise(function (ok, fail) {
|
||||
var el = document.createElement(kind === 'css' ? 'link' : 'script');
|
||||
if (kind === 'css') { el.rel = 'stylesheet'; el.href = url } else { el.src = url; assign(el, attrs || {}) }
|
||||
el.onload = function () { ok() }; el.onerror = function () { delete loaded[url]; fail(new Error('failed to load ' + url)) };
|
||||
document.head.appendChild(el);
|
||||
});
|
||||
return loaded[url];
|
||||
}
|
||||
function peer(provider) {
|
||||
var p = PEERS[provider] || PEERS.mapbox, chain = Promise.resolve();
|
||||
p.css.forEach(function (u) { once(u, 'css') });
|
||||
p.js.forEach(function (u) { chain = chain.then(function () { return once(u, 'js') }) });
|
||||
return chain;
|
||||
}
|
||||
function bundle(b) { return once(rel(b.s), 'js', b.i ? { integrity: b.i, crossOrigin: 'anonymous' } : {}) }
|
||||
|
||||
function row(key, kind, label, tag, extra) {
|
||||
var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'row' + (extra || ''); btn.setAttribute('data-k', key); btn.setAttribute('data-kind', kind);
|
||||
var l = document.createElement('span'); l.textContent = label; btn.appendChild(l);
|
||||
var c = document.createElement('code'); c.textContent = '<' + tag + '>'; btn.appendChild(c);
|
||||
btn.onclick = function () { location.hash = '#/' + key.split('/').map(encodeURIComponent).join('/') };
|
||||
return btn;
|
||||
}
|
||||
function draw() {
|
||||
nav.innerHTML = ''; var np = 0, ni = 0;
|
||||
REG.forEach(function (b) {
|
||||
var g = document.createElement('div'); g.className = 'grp';
|
||||
var h = document.createElement('div'); h.className = 'gh'; var hn = document.createElement('span'); hn.textContent = b.b; h.appendChild(hn);
|
||||
var hc = document.createElement('span'); hc.textContent = b.p.length; h.appendChild(hc); g.appendChild(h);
|
||||
b.p.forEach(function (p) {
|
||||
np++; g.appendChild(row(b.b + '/' + p.p, 'profile', p.p, p.e || b.e[0] || b.b));
|
||||
(p.in || []).forEach(function (x) { ni++; g.appendChild(row(b.b + '/' + p.p + '/' + x.id, 'instance', x.t, x.e || p.e || b.e[0] || b.b, ' in')) });
|
||||
});
|
||||
nav.appendChild(g);
|
||||
});
|
||||
count.textContent = REG.length + ' bundles · ' + np + ' profiles · ' + ni + ' instances';
|
||||
}
|
||||
|
||||
function find(parts) {
|
||||
for (var i = 0; i < REG.length; i++) if (REG[i].b === parts[0]) {
|
||||
var b = REG[i];
|
||||
for (var j = 0; j < b.p.length; j++) if (b.p[j].p === parts[1]) {
|
||||
var p = b.p[j];
|
||||
if (parts.length < 3) return { b: b, p: p, x: null };
|
||||
for (var k = 0; k < (p.in || []).length; k++) if (p.in[k].id === parts[2]) return { b: b, p: p, x: p.in[k] };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function select(key) {
|
||||
var parts = key.split('/').map(function (s) { try { return decodeURIComponent(s) } catch (e) { return s } });
|
||||
var hit = find(parts);
|
||||
var rows = nav.querySelectorAll('.row');
|
||||
for (var i = 0; i < rows.length; i++) rows[i].setAttribute('aria-current', rows[i].getAttribute('data-k') === parts.join('/') ? 'true' : 'false');
|
||||
if (!hit) { ttl.textContent = 'No such map'; sub.textContent = key; open.hidden = embBtn.hidden = embed.hidden = true; hint('No such map: ' + key, true); return }
|
||||
var b = hit.b, p = hit.p, x = hit.x, tag = (x && x.e) || p.e || b.e[0] || b.b;
|
||||
ttl.textContent = x ? x.t : p.p; sub.textContent = b.b + ' · <' + tag + '>' + (p.d ? ' · ' + p.d : '');
|
||||
open.href = rel((x || p).u); open.hidden = false; embBtn.hidden = false; embed.hidden = true; embBtn.setAttribute('aria-expanded', 'false');
|
||||
var em = x ? (x.em || snip(b, p, x)) : (p.em || snip(b, p, null));
|
||||
ifr.value = em.iframe || ''; scr.value = em.script || ''; scrw.hidden = !em.script;
|
||||
if (narrow()) { nav.hidden = true; menu.setAttribute('aria-expanded', 'false') }
|
||||
mount(b, p, x, tag);
|
||||
}
|
||||
|
||||
// Same profile-config the leaf page bakes (profile page: profile + dataSource + instanceId;
|
||||
// instance page: its props), plus the host-supplied share URL of that leaf page.
|
||||
function mount(b, p, x, tag) {
|
||||
var cfg = x ? assign({ assetsBase: ASSETS }, x.pr) : { profile: p.p, dataSource: 'pray-tools', instanceId: b.b + '-' + p.p, assetsBase: ASSETS };
|
||||
if (QA) cfg.qa = true;
|
||||
cfg.shareUrl = new URL(rel((x || p).u), location.href).href;
|
||||
window.MAP_APP_API_URL = (x && x.a) || API_BASE;
|
||||
var provider = cfg.provider === 'maplibre' ? 'maplibre' : 'mapbox', my = ++seq;
|
||||
hint(provider === 'maplibre' ? 'loading map…' : 'loading token…');
|
||||
peer(provider).then(function () { return bundle(b) }).then(function () {
|
||||
if (my !== seq) return;
|
||||
resolveToken(cfg, provider === 'maplibre', function (token) {
|
||||
if (my !== seq) return;
|
||||
try {
|
||||
var c = token ? assign({}, cfg, { tk: token }) : cfg;
|
||||
var el = document.createElement(tag); el.setAttribute('profile-config', JSON.stringify(c));
|
||||
slot.innerHTML = ''; slot.appendChild(el);
|
||||
} catch (e) { hint(e && e.message ? e.message : String(e), true) }
|
||||
});
|
||||
}).catch(function (e) { if (my === seq) hint('Failed to load ' + b.b + ': ' + (e && e.message ? e.message : e), true) });
|
||||
}
|
||||
|
||||
// Mapbox token resolution — the leaf pages' ladder, in precedence order:
|
||||
// 1. prop (config.tk) 2. ?tk= on this page's URL (stored once for every page of the tree)
|
||||
// 3. baked token — then, none found, a token ENDPOINT is fetched, first answer wins:
|
||||
// 4. window.MAP_TOKEN_URL 5. configured TOKEN_SRC 6. same-origin /api/maps/token — else ask once.
|
||||
function resolveToken(cfg, tokenFree, done) {
|
||||
if (tokenFree) return done('');
|
||||
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 = cfg.tk || urlTk || stored || BAKED_TOKEN || fetchedToken;
|
||||
if (directToken) return done(directToken);
|
||||
function askToken(prefix) {
|
||||
var d = hint('', true);
|
||||
d.innerHTML = (prefix ? '<div></div>' : '') + '<div>map needs a Mapbox token — <input id="tkIn" type="password" placeholder="paste pk.… (stays in this browser)"> <button id="tkGo" type="button">use it</button></div>';
|
||||
if (prefix) d.firstChild.textContent = prefix;
|
||||
$('tkGo').onclick = function () { var v = ($('tkIn').value || '').trim(); if (!v) return; try { localStorage.setItem('MAPS_STAGING_TK', v) } catch (e) {} done(v) };
|
||||
}
|
||||
var candidates = [];
|
||||
function addCandidate(u) { if (u && candidates.indexOf(u) === -1) candidates.push(u) }
|
||||
addCandidate(window.MAP_TOKEN_URL);
|
||||
addCandidate(TOKEN_SRC && TOKEN_SRC !== 'window.MAP_TOKEN_URL' ? TOKEN_SRC : '');
|
||||
addCandidate(DEFAULT_TOKEN_URL);
|
||||
function extractToken(data) { if (typeof data === 'string') return data.trim(); if (data && typeof data === 'object') return data.token || data.tk || data.accessToken || ''; return '' }
|
||||
function fetchToken(i, errors) {
|
||||
if (i >= candidates.length) return askToken('Token fetch failed from ' + candidates.join(', ') + ' — set window.MAP_TOKEN_URL or pass ?tk=. (' + errors.join(' | ') + ')');
|
||||
var url = candidates[i];
|
||||
return fetch(url).then(function (r) {
|
||||
if (!r.ok) throw new Error(url + ' → HTTP ' + r.status);
|
||||
return (r.headers.get('content-type') || '').indexOf('json') !== -1 ? r.json() : r.text();
|
||||
}).then(function (data) {
|
||||
var tk = extractToken(data); if (!tk) throw new Error(url + ' → no token in response');
|
||||
fetchedToken = tk; done(tk);
|
||||
}).catch(function (e) { errors.push(e && e.message ? e.message : String(e)); return fetchToken(i + 1, errors) });
|
||||
}
|
||||
fetchToken(0, []);
|
||||
}
|
||||
|
||||
function copy(text, btn) {
|
||||
var ok = function () { var t = btn.textContent; btn.textContent = 'Copied'; setTimeout(function () { btn.textContent = t }, 1200) };
|
||||
var fallback = function () { var ta = btn.parentNode.nextSibling; ta.focus(); ta.select(); try { document.execCommand('copy'); ok() } catch (e) {} };
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(ok, fallback); else fallback();
|
||||
}
|
||||
embed.addEventListener('click', function (e) { var b = e.target.closest ? e.target.closest('[data-copy]') : null; if (b) copy($(b.getAttribute('data-copy')).value, b) });
|
||||
embBtn.onclick = function () { embed.hidden = !embed.hidden; embBtn.setAttribute('aria-expanded', String(!embed.hidden)) };
|
||||
menu.onclick = function () { nav.hidden = !nav.hidden; menu.setAttribute('aria-expanded', String(!nav.hidden)) };
|
||||
try { window.matchMedia('(max-width:700px)').addEventListener('change', function (e) { if (!e.matches) { nav.hidden = false; menu.setAttribute('aria-expanded', 'true') } }) } catch (e) {}
|
||||
|
||||
function route() { var h = (location.hash || '').replace(/^#\/?/, ''); if (h) select(h) }
|
||||
function init(m) {
|
||||
if (m) { try { REG = compact(m, false) } catch (e) { REG = BAKED } }
|
||||
draw(); window.addEventListener('hashchange', route); route();
|
||||
}
|
||||
// Live manifest first (the page stays right when bundles change); the baked copy is the fallback.
|
||||
if (typeof fetch === 'function' && location.protocol !== 'file:') {
|
||||
fetch(TREE + '/manifest.json', { cache: 'no-cache' }).then(function (r) { return r.ok ? r.json() : null }).then(init, function () { init(null) });
|
||||
} else init(null);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
const out = join(ROOT, 'public', 'index.html')
|
||||
writeFileSync(out, html)
|
||||
console.log(`[generate-spa] public/index.html — ${(html.length / 1024).toFixed(1)} KB · ${baked.length} bundle(s) · ${nProfiles} profile(s) · ${nInstances} instance(s) · ${nOverrides} embed snippet(s) baked verbatim (the rest re-derive)`)
|
||||
|
|
@ -409,7 +409,7 @@ function mapPageHtml(opts) {
|
|||
${peerBlock}
|
||||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, ${esc(tag)} { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -825,16 +825,19 @@ function main() {
|
|||
// DATA LIVES IN data/, THE WEB-ROOT IS GENERATED (the D1 reorganization):
|
||||
// source-of-truth folders that used to sit ONLY inside public/ now live in
|
||||
// data/ and are copied into the served tree here, exactly like boundaries
|
||||
// above. Served paths are unchanged (public/assets/people-groups, /vendor,
|
||||
// /external), so no runtime reference moves. public/ stays fully
|
||||
// regenerable: rm -rf it and `bun run build` rebuilds everything.
|
||||
// above. Served paths are unchanged (public/assets/people-groups, /vendor),
|
||||
// so no runtime reference moves. public/ stays fully regenerable: rm -rf it
|
||||
// and `bun run build` rebuilds everything. The web-root holds only assets/,
|
||||
// <name>/ and the generated root page (internal/generate-spa.mjs): the
|
||||
// vendored external bundles (data/external-bundles) were consumed by the
|
||||
// optional layouts/ shell alone, so they are no longer copied; the frame
|
||||
// policy is the serving host's (docs/architecture/embeddability.md).
|
||||
const repoData = resolve(__dirname, '..', 'data')
|
||||
for (const [srcName, destRel] of [
|
||||
['people-groups', join(pubAssets, 'people-groups')],
|
||||
['vendor', join(pubAssets, 'vendor')],
|
||||
['posters', join(pubAssets, 'posters')],
|
||||
['tables', join(pubAssets, 'tables')],
|
||||
['external-bundles', resolve(__dirname, '..', 'public', 'external')],
|
||||
]) {
|
||||
const src = join(repoData, srcName)
|
||||
if (!existsSync(src)) continue
|
||||
|
|
@ -842,29 +845,7 @@ function main() {
|
|||
mkdirSync(destRel, { recursive: true })
|
||||
cpSync(src, destRel, { recursive: true })
|
||||
}
|
||||
console.log('[generate-tree] copied data/ sources → web-root (people-groups, vendor, external)')
|
||||
|
||||
// FRAME-POLICY CONTRACT (docs/architecture/embeddability.md): emit the split
|
||||
// as a real serving artifact. Netlify/Cloudflare Pages consume /_headers
|
||||
// automatically when this tree is dropped on them; other hosts ignore the
|
||||
// file (Traefik reference: serving/traefik-embed-headers.yaml). Direct-embed
|
||||
// pages are framable anywhere (the product); staging pages are preview-only.
|
||||
writeFileSync(resolve(__dirname, '..', 'public', '_headers'), `# doxa-maps frame policy — generated by internal/generate-tree.mjs
|
||||
# (see docs/architecture/embeddability.md — the server owns frame-ancestors)
|
||||
|
||||
# Staging surfaces: internal preview only
|
||||
/${TREE_NAME}/index.html
|
||||
Content-Security-Policy: frame-ancestors 'self'
|
||||
/${TREE_NAME}/:bundle/index.html
|
||||
Content-Security-Policy: frame-ancestors 'self'
|
||||
|
||||
# Direct-embed pages (the product): framable on any partner site
|
||||
/${TREE_NAME}/:bundle/:profile/index.html
|
||||
Content-Security-Policy: frame-ancestors *
|
||||
/${TREE_NAME}/:bundle/:profile/:instance/index.html
|
||||
Content-Security-Policy: frame-ancestors *
|
||||
`)
|
||||
console.log('[generate-tree] wrote public/_headers (frame-policy contract)')
|
||||
console.log('[generate-tree] copied data/ sources → web-root/assets (people-groups, vendor, posters, tables)')
|
||||
}
|
||||
|
||||
for (const dest of dests) {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,13 @@
|
|||
* staging — preview surfaces: <tree>/index.html and <tree>/<bundle>/index.html (the internal
|
||||
* staging pages; leaf pages live one level deeper) — reported as WARN unless
|
||||
* --strict, because the shell root may be product for a host site
|
||||
* contract — the drop-in contract: a served root holds ONLY index.html (the generated root
|
||||
* page), assets/ (data) and <tree>/ (the bundle folders + manifest.json). Every other
|
||||
* root entry — a shell's nav.js, external/, _headers … — is a FINDING under --strict
|
||||
* ("outside the drop-in contract"); without --strict it is a warning
|
||||
* Also prints an inventory (bundles, leaf pages, staging pages, assets by folder, total size).
|
||||
*
|
||||
* Usage: node internal/public-hygiene-check.mjs [<dir>] [--tree doxa-maps] [--strict] [--json out.json]
|
||||
* Usage: node internal/public-hygiene-check.mjs [<dir> | --root <dir>] [--tree doxa-maps] [--strict] [--json out.json]
|
||||
* <dir> defaults to this repo's public/; for the website pass its drop-in folder.
|
||||
* Exit: 0 clean · 1 findings (or warnings with --strict) · 2 bad arguments
|
||||
*/
|
||||
|
|
@ -31,7 +35,8 @@ import { fileURLToPath } from 'node:url'
|
|||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const args = process.argv.slice(2)
|
||||
const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
|
||||
const DIR = resolve(args.find(a => !a.startsWith('--') && a !== argOf('--tree') && a !== argOf('--json')) || join(ROOT, 'public'))
|
||||
const VALUE_FLAGS = new Set(['--tree', '--json', '--root'])
|
||||
const DIR = resolve(argOf('--root', '') || args.find((a, i) => !a.startsWith('--') && !VALUE_FLAGS.has(args[i - 1])) || join(ROOT, 'public'))
|
||||
const TREE = argOf('--tree', 'doxa-maps')
|
||||
const STRICT = args.includes('--strict')
|
||||
const JSON_OUT = argOf('--json', '')
|
||||
|
|
@ -67,7 +72,16 @@ for (const f of files) {
|
|||
// staging surfaces: <tree>/index.html and <tree>/<bundle>/index.html (leaf pages are deeper)
|
||||
const staging = files.filter(f => f.rel === `${TREE}/index.html` || /^[^/]+\/[^/]+\/index\.html$/.test(f.rel) && f.rel.startsWith(`${TREE}/`)).map(f => f.rel)
|
||||
for (const s of staging) hit(STRICT ? findings : warnings, 'staging', s, 'preview/staging page (internal surface) — exclude from a public mirror or accept knowingly')
|
||||
if (files.some(f => f.rel === 'index.html')) hit(warnings, 'staging', 'index.html', 'shell root page at the drop-in root — product if the host site uses it, internal otherwise')
|
||||
// the drop-in contract: only index.html, assets/ and <tree>/ may sit at the root (the same three
|
||||
// entries vite.doxa-maps-build-config.json dropin.include declares). Strict = every other root
|
||||
// entry is a finding; otherwise a warning.
|
||||
const CONTRACT = new Set(['index.html', 'assets', TREE])
|
||||
const rootEntries = [...new Set(files.map(f => f.rel.split('/')[0]))].sort()
|
||||
for (const e of rootEntries) {
|
||||
if (CONTRACT.has(e)) continue
|
||||
const label = files.some(f => f.rel.startsWith(`${e}/`)) ? `${e}/` : e
|
||||
hit(STRICT ? findings : warnings, 'contract', label, `outside the drop-in contract (index.html, assets/, ${TREE}/ only)`)
|
||||
}
|
||||
|
||||
// inventory
|
||||
const inv = {
|
||||
|
|
|
|||
|
|
@ -16,29 +16,47 @@
|
|||
* removed like any other stale file);
|
||||
* · staging/preview pages are NEVER copied — <tree>/index.html and <tree>/<bundle>/index.html
|
||||
* are internal surfaces; partners embed the LEAF pages one level deeper (--keep-staging to
|
||||
* opt back in; the shell root index.html at the drop-in root is product and stays);
|
||||
* opt back in);
|
||||
* · host-owned files can be PROTECTED with --protect (none by default) — never overwritten,
|
||||
* never deleted.
|
||||
*
|
||||
* THE DROP-IN CONTRACT (allowlist): a host does not need the whole web-root. The website takes
|
||||
* ONLY the bundles it embeds, declared ONCE in vite.doxa-maps-build-config.json under
|
||||
* "dropin.include" — path prefixes relative to public/ (a folder, or a single file such as
|
||||
* doxa-maps/manifest.json) — and nothing at the drop-in root.
|
||||
* --from-config take the allowlist from that build-config (fails if it declares none);
|
||||
* --only a,b pass an allowlist explicitly (wins over --from-config);
|
||||
* --prune also delete destination files OUTSIDE the allowlist (an older full mirror:
|
||||
* the shell, assets/, headers …) so the drop-in ends up exactly the contract.
|
||||
* --protect is still honoured and nothing outside <dest> is ever touched.
|
||||
* Without an allowlist the tool is the plain full mirror it always was; --prune then has no
|
||||
* effect. Files in <dest> outside the allowlist are left alone unless --prune is given.
|
||||
* Refuses to CREATE <dest>: the drop-in folder is the host's decision, not this script's.
|
||||
* Gate: `node internal/public-hygiene-check.mjs <dest> --strict` must pass before a public push.
|
||||
*
|
||||
* Usage: node internal/sync-dropin.mjs <dest> [--from public] [--tree doxa-maps] [--keep-staging]
|
||||
* [--protect a.txt,other] [--dry-run]
|
||||
* e.g. node internal/sync-dropin.mjs /home/qwerty/Workspace/doxa-life/public/js/doxa-maps-build
|
||||
* Exit: 0 synced (or dry-run printed) · 1 bad arguments / dest missing
|
||||
* [--only a,b | --from-config] [--prune] [--protect a.txt,other] [--dry-run]
|
||||
* e.g. node internal/sync-dropin.mjs ../doxa-life/public/js/doxa-maps-build --from-config --prune
|
||||
* Prints: copied · unchanged · deleted (stale inside the allowlist) · pruned (outside it) ·
|
||||
* excluded (credentials/docs/staging), then the destination's top-level tree after the sync.
|
||||
* Exit: 0 synced (or dry-run printed) · 1 bad arguments / dest missing / no allowlist in config
|
||||
* Review the result with `git -C <host> status` — the host repo is the restore point.
|
||||
*/
|
||||
import { readdirSync, statSync, mkdirSync, copyFileSync, rmSync, existsSync, readFileSync } from 'node:fs'
|
||||
import { readdirSync, statSync, lstatSync, mkdirSync, copyFileSync, rmSync, existsSync, readFileSync, realpathSync } from 'node:fs'
|
||||
import { join, relative, resolve, dirname, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const CONFIG = join(ROOT, 'vite.doxa-maps-build-config.json')
|
||||
const norm = p => p.split(sep).join('/')
|
||||
const args = process.argv.slice(2)
|
||||
const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d }
|
||||
const DEST = args.find(a => !a.startsWith('--') && a !== argOf('--from') && a !== argOf('--protect'))
|
||||
const VALUE_FLAGS = new Set(['--from', '--tree', '--protect', '--only'])
|
||||
const argOf = (k, d) => { const i = args.indexOf(k); const v = args[i + 1]; return i >= 0 && v !== undefined && !v.startsWith('--') ? v : d }
|
||||
const DEST = args.find((a, i) => !a.startsWith('--') && !VALUE_FLAGS.has(args[i - 1]))
|
||||
const FROM = resolve(ROOT, argOf('--from', 'public'))
|
||||
const DRY = args.includes('--dry-run')
|
||||
const PRUNE = args.includes('--prune')
|
||||
const PROTECT = new Set(String(argOf('--protect', '')).split(',').map(s => s.trim()).filter(Boolean))
|
||||
// Never ship a credential into a host repo — these are gitignored here for the same reason.
|
||||
const EXCLUDE = new Set(['api/maps/token.json', 'token.local.js'])
|
||||
|
|
@ -52,12 +70,30 @@ const KEEP_STAGING = args.includes('--keep-staging')
|
|||
const isStaging = (f) => !KEEP_STAGING && (f === `${TREE}/index.html` || new RegExp(`^${TREE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/[^/]+/index\\.html$`).test(f))
|
||||
const excluded = (f) => EXCLUDE.has(f) || isDoc(f) || isStaging(f)
|
||||
|
||||
if (!DEST) { console.error('[sync-dropin] usage: node internal/sync-dropin.mjs <dest> [--from public] [--protect a,b] [--dry-run]'); process.exit(1) }
|
||||
const usage = 'usage: node internal/sync-dropin.mjs <dest> [--from public] [--only a,b | --from-config] [--prune] [--protect a,b] [--dry-run]'
|
||||
if (!DEST) { console.error(`[sync-dropin] ${usage}`); process.exit(1) }
|
||||
const dest = resolve(DEST)
|
||||
if (!existsSync(dest) || !statSync(dest).isDirectory()) { console.error(`[sync-dropin] dest does not exist or is not a directory: ${dest} — create the drop-in folder in the host first`); process.exit(1) }
|
||||
if (!existsSync(FROM)) { console.error(`[sync-dropin] source missing: ${FROM} — run \`bun run build\` first`); process.exit(1) }
|
||||
|
||||
const norm = p => p.split(sep).join('/')
|
||||
// The allowlist: path prefixes relative to <from>. A prefix names a folder (everything under it)
|
||||
// or one file. --only wins; --from-config reads "dropin.include" from the build-config.
|
||||
const normPrefix = p => String(p).trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+|\/+$/g, '')
|
||||
let ALLOW = null, ALLOW_FROM = ''
|
||||
if (args.includes('--only')) {
|
||||
ALLOW = String(argOf('--only', '')).split(',').map(normPrefix).filter(Boolean)
|
||||
ALLOW_FROM = '--only'
|
||||
} else if (args.includes('--from-config')) {
|
||||
let cfg
|
||||
try { cfg = JSON.parse(readFileSync(CONFIG, 'utf8')) } catch (e) { console.error(`[sync-dropin] cannot read ${CONFIG}: ${e.message}`); process.exit(1) }
|
||||
const inc = cfg?.dropin?.include
|
||||
if (!Array.isArray(inc) || !inc.length || !inc.every(s => typeof s === 'string')) { console.error(`[sync-dropin] --from-config: ${norm(relative(ROOT, CONFIG))} declares no "dropin": { "include": [ …path prefixes relative to public/… ] }`); process.exit(1) }
|
||||
ALLOW = inc.map(normPrefix).filter(Boolean)
|
||||
ALLOW_FROM = `${norm(relative(ROOT, CONFIG))} · dropin.include`
|
||||
}
|
||||
if (ALLOW && (!ALLOW.length || ALLOW.some(p => p.split('/').includes('..')))) { console.error(`[sync-dropin] allowlist is empty or climbs out of ${norm(relative(ROOT, FROM))}/: ${JSON.stringify(ALLOW)}`); process.exit(1) }
|
||||
const allowed = f => !ALLOW || ALLOW.some(p => f === p || f.startsWith(p + '/'))
|
||||
|
||||
function walk(dir, base = dir, out = []) {
|
||||
for (const name of readdirSync(dir)) {
|
||||
const p = join(dir, name), st = statSync(p)
|
||||
|
|
@ -66,31 +102,63 @@ function walk(dir, base = dir, out = []) {
|
|||
return out
|
||||
}
|
||||
const sha = p => createHash('sha1').update(readFileSync(p)).digest('hex')
|
||||
// A deletion only ever happens to a path that really lives under <dest> (a symlink that points
|
||||
// elsewhere is refused, not followed).
|
||||
const destReal = realpathSync(dest)
|
||||
const insideDest = f => { try { return realpathSync(join(dest, f)).startsWith(destReal + sep) } catch { return false } }
|
||||
|
||||
const allSrc = walk(FROM)
|
||||
const src = allSrc.filter(f => !excluded(f))
|
||||
const excludedCount = allSrc.filter(excluded).length
|
||||
const src = allSrc.filter(f => !excluded(f) && allowed(f))
|
||||
const outsideSrc = allSrc.length - excludedCount - src.length // built, but not part of the contract
|
||||
const have = new Set(walk(dest))
|
||||
let copied = 0, unchanged = 0, deleted = 0, protectedSkips = 0
|
||||
const excludedCount = allSrc.length - src.length
|
||||
const result = new Set() // what <dest> holds after the sync
|
||||
let copied = 0, unchanged = 0, deleted = 0, pruned = 0, kept = 0, protectedSkips = 0, refused = 0
|
||||
for (const f of src) {
|
||||
if (PROTECT.has(f)) { protectedSkips++; continue }
|
||||
if (PROTECT.has(f)) { protectedSkips++; if (have.has(f)) result.add(f); continue }
|
||||
const s = join(FROM, f), d = join(dest, f)
|
||||
result.add(f)
|
||||
if (have.has(f) && statSync(s).size === statSync(d).size && sha(s) === sha(d)) { unchanged++; continue }
|
||||
if (!DRY) { mkdirSync(dirname(d), { recursive: true }); copyFileSync(s, d) }
|
||||
copied++
|
||||
}
|
||||
const srcSet = new Set(src)
|
||||
for (const f of have) {
|
||||
if (srcSet.has(f)) continue
|
||||
// stale = not in the source, not protected. Credentials and docs found in <dest> are stale too:
|
||||
// they must not sit in a served folder (a stray README, a token file).
|
||||
if (srcSet.has(f) || PROTECT.has(f)) continue
|
||||
if (PROTECT.has(f)) { result.add(f); continue }
|
||||
if (!allowed(f) && !PRUNE) { kept++; result.add(f); continue }
|
||||
if (!insideDest(f)) { refused++; result.add(f); continue }
|
||||
if (!DRY) rmSync(join(dest, f))
|
||||
deleted++
|
||||
if (allowed(f)) deleted++; else pruned++
|
||||
}
|
||||
// prune directories emptied by deletions (deepest first)
|
||||
// prune directories emptied by deletions (deepest first; symlinked folders are never entered)
|
||||
if (!DRY) {
|
||||
const dirs = []; (function collect(d) { for (const n of readdirSync(d)) { const p = join(d, n); if (statSync(p).isDirectory()) { collect(p); dirs.push(p) } } })(dest)
|
||||
const dirs = []; (function collect(d) { for (const n of readdirSync(d)) { const p = join(d, n); if (lstatSync(p).isDirectory()) { collect(p); dirs.push(p) } } })(dest)
|
||||
for (const d of dirs) if (readdirSync(d).length === 0) rmSync(d, { recursive: true })
|
||||
}
|
||||
|
||||
// Top-level tree of the destination (two levels; deeper folders show a file count).
|
||||
function renderTree(paths, depth = 2) {
|
||||
const root = {}
|
||||
for (const p of paths) { let n = root; for (const s of p.split('/')) n = (n[s] ??= {}) }
|
||||
const isDir = n => Object.keys(n).length > 0
|
||||
const count = n => Object.values(n).reduce((a, c) => a + (isDir(c) ? count(c) : 1), 0)
|
||||
const show = (n, indent, level) => {
|
||||
const names = Object.keys(n).sort((a, b) => (isDir(n[b]) ? 1 : 0) - (isDir(n[a]) ? 1 : 0) || a.localeCompare(b))
|
||||
for (const name of names) {
|
||||
if (!isDir(n[name])) { console.log(`${indent}${name}`); continue }
|
||||
console.log(`${indent}${name}/${level >= depth ? ` (${count(n[name])} files)` : ''}`)
|
||||
if (level < depth) show(n[name], indent + ' ', level + 1)
|
||||
}
|
||||
}
|
||||
if (!paths.length) console.log(' (empty)'); else show(root, ' ', 1)
|
||||
}
|
||||
|
||||
console.log(`[sync-dropin] ${DRY ? 'DRY RUN — ' : ''}${norm(relative(ROOT, FROM))}/ → ${dest}`)
|
||||
console.log(`[sync-dropin] copied ${copied} · unchanged ${unchanged} · deleted ${deleted} · protected ${protectedSkips}${PROTECT.size ? ` (${[...PROTECT].join(', ')})` : ''} · credentials/docs excluded ${excludedCount}`)
|
||||
if (ALLOW) console.log(`[sync-dropin] allowlist (${ALLOW_FROM}): ${ALLOW.join(', ')} — ${PRUNE ? '--prune: everything else in the destination is removed' : 'no --prune: destination files outside it are left alone'}`)
|
||||
else if (PRUNE) console.log('[sync-dropin] --prune has no effect without an allowlist (--only or --from-config) — full mirror as usual')
|
||||
console.log(`[sync-dropin] copied ${copied} · unchanged ${unchanged} · deleted ${deleted} · pruned ${pruned} · protected ${protectedSkips}${PROTECT.size ? ` (${[...PROTECT].join(', ')})` : ''} · excluded ${excludedCount} (credentials/docs/staging)${ALLOW ? ` · outside allowlist: ${outsideSrc} built file(s) not shipped${kept ? `, ${kept} destination file(s) left` : ''}` : ''}${refused ? ` · refused ${refused} (resolve outside the destination)` : ''}`)
|
||||
console.log(`[sync-dropin] destination${DRY ? ' (as it would be)' : ''}: ${result.size} file(s)`)
|
||||
renderTree([...result])
|
||||
|
|
|
|||
155
internal/vite-plugin-strip-shadow-css-comments.mjs
Normal file
155
internal/vite-plugin-strip-shadow-css-comments.mjs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
// vite-plugin-strip-shadow-css-comments — production bundles carry only CSS rules.
|
||||
//
|
||||
// Every component injects its shadow-DOM styles from a template literal passed to
|
||||
// useShadowStyles(`…`). Those literals ship verbatim inside the built IIFE, so any
|
||||
// CSS block comment written in them would reach the public drop-in as dead weight.
|
||||
// This build-only plugin rewrites the source of every .vue and .js file under
|
||||
// library/ and app-profiles/ before the Vue compiler sees it (enforce: 'pre'):
|
||||
// inside each such literal it removes the block comments and collapses the blank
|
||||
// lines they leave behind. Nothing outside the literal changes, and dev mode is
|
||||
// untouched so the comments stay readable while working.
|
||||
//
|
||||
// A literal is left exactly as written (with a build warning) when it cannot be
|
||||
// rewritten safely: it contains a `${…}` substitution, or a comment, string or
|
||||
// url() inside it never closes. Comment markers that sit inside a CSS string
|
||||
// (`content:"*/"`) or an unquoted url() are string content and are preserved.
|
||||
import { resolve, dirname, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const SOURCE_ROOTS = ['library', 'app-profiles'].map(d => resolve(ROOT, d) + sep)
|
||||
const TAG = '[strip-shadow-css-comments]'
|
||||
|
||||
// The call site: `useShadowStyles(` then optional whitespace then the opening backtick.
|
||||
const CALL = /\buseShadowStyles\(\s*`/g
|
||||
|
||||
/**
|
||||
* Find the end of a template literal whose opening backtick is just before `from`.
|
||||
* Returns { end } = index of the closing backtick, or { reason } when the literal
|
||||
* contains a substitution or never closes.
|
||||
*/
|
||||
function findTemplateEnd(code, from) {
|
||||
for (let i = from; i < code.length; i++) {
|
||||
const c = code[i]
|
||||
if (c === '\\') { i++; continue }
|
||||
if (c === '`') return { end: i }
|
||||
if (c === '$' && code[i + 1] === '{') return { reason: 'contains a ${…} substitution', at: i }
|
||||
}
|
||||
return { reason: 'template literal never closes', at: code.length }
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove CSS block comments from one literal's text. Strings ('…' / "…") and
|
||||
* unquoted url(…) are copied through untouched so a comment marker inside them
|
||||
* is never mistaken for a comment. Returns { css, comments } or { reason }.
|
||||
*/
|
||||
export function stripCssComments(css) {
|
||||
const n = css.length
|
||||
let out = ''
|
||||
let comments = 0
|
||||
let i = 0
|
||||
while (i < n) {
|
||||
const c = css[i]
|
||||
if (c === '"' || c === "'") {
|
||||
let j = i + 1
|
||||
while (j < n && css[j] !== c && css[j] !== '\n') { if (css[j] === '\\') j++; j++ }
|
||||
if (j >= n) return { reason: 'a CSS string never closes' }
|
||||
if (css[j] === '\n') { out += css.slice(i, j); i = j; continue } // stray quote: only its own line is treated as a string
|
||||
out += css.slice(i, j + 1); i = j + 1; continue
|
||||
}
|
||||
if (c === '/' && css[i + 1] === '*') {
|
||||
const j = css.indexOf('*/', i + 2)
|
||||
if (j < 0) return { reason: 'a CSS comment never closes' }
|
||||
comments++; i = j + 2; continue
|
||||
}
|
||||
if ((c === 'u' || c === 'U') && /^url\(/i.test(css.slice(i, i + 4)) && (i === 0 || !/[\w-]/.test(css[i - 1]))) {
|
||||
let j = i + 4
|
||||
while (j < n && (css[j] === ' ' || css[j] === '\t')) j++
|
||||
if (css[j] === '"' || css[j] === "'") { out += css.slice(i, j); i = j; continue } // quoted url: the string branch owns it
|
||||
const k = css.indexOf(')', j)
|
||||
if (k < 0) return { reason: 'an unquoted url() never closes' }
|
||||
out += css.slice(i, k + 1); i = k + 1; continue
|
||||
}
|
||||
out += c; i++
|
||||
}
|
||||
if (comments > 0) {
|
||||
out = out.replace(/[ \t]+(?=\r?\n|$)/g, '') // whitespace left behind at the end of a line
|
||||
out = out.replace(/\r?\n(?:[ \t]*\r?\n)+/g, '\n') // runs of blank lines
|
||||
}
|
||||
return { css: out, comments }
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite every useShadowStyles(`…`) literal in a module's source.
|
||||
* Returns { code, changed, literals, comments, bytes, skipped: [{ line, reason }] }.
|
||||
*/
|
||||
export function stripShadowStyleComments(code) {
|
||||
const result = { code, changed: false, literals: 0, comments: 0, bytes: 0, skipped: [] }
|
||||
let out = ''
|
||||
let last = 0
|
||||
CALL.lastIndex = 0
|
||||
let m
|
||||
while ((m = CALL.exec(code))) {
|
||||
const open = m.index + m[0].length
|
||||
const lit = findTemplateEnd(code, open)
|
||||
if (lit.reason) {
|
||||
result.skipped.push({ line: lineOf(code, m.index), reason: lit.reason })
|
||||
CALL.lastIndex = lit.at
|
||||
continue
|
||||
}
|
||||
result.literals++
|
||||
const original = code.slice(open, lit.end)
|
||||
const stripped = stripCssComments(original)
|
||||
if (stripped.reason) {
|
||||
result.skipped.push({ line: lineOf(code, m.index), reason: stripped.reason })
|
||||
} else if (stripped.css !== original) {
|
||||
out += code.slice(last, open) + stripped.css
|
||||
last = lit.end
|
||||
result.comments += stripped.comments
|
||||
result.bytes += original.length - stripped.css.length
|
||||
result.changed = true
|
||||
}
|
||||
CALL.lastIndex = lit.end + 1
|
||||
}
|
||||
if (result.changed) result.code = out + code.slice(last)
|
||||
return result
|
||||
}
|
||||
|
||||
function lineOf(code, index) {
|
||||
let line = 1
|
||||
for (let i = 0; i < index; i++) if (code[i] === '\n') line++
|
||||
return line
|
||||
}
|
||||
|
||||
function isSourceModule(id) {
|
||||
const q = id.indexOf('?')
|
||||
if (q >= 0) return false // Vue sub-requests (?vue&type=…) read the already-rewritten descriptor
|
||||
if (!(id.endsWith('.vue') || id.endsWith('.js'))) return false
|
||||
return SOURCE_ROOTS.some(root => id.startsWith(root))
|
||||
}
|
||||
|
||||
export function stripShadowCssCommentsPlugin() {
|
||||
let logger = console
|
||||
const totals = { files: 0, literals: 0, comments: 0, bytes: 0, skipped: 0 }
|
||||
return {
|
||||
name: 'doxa:strip-shadow-css-comments',
|
||||
apply: 'build',
|
||||
enforce: 'pre',
|
||||
configResolved(config) { logger = config.logger },
|
||||
transform(code, id) {
|
||||
if (!isSourceModule(id)) return null
|
||||
const r = stripShadowStyleComments(code)
|
||||
for (const s of r.skipped) {
|
||||
totals.skipped++
|
||||
logger.warn(`${TAG} left untouched ${id.slice(ROOT.length + 1)}:${s.line} — ${s.reason}`)
|
||||
}
|
||||
if (!r.changed) return null
|
||||
totals.files++; totals.literals += r.literals; totals.comments += r.comments; totals.bytes += r.bytes
|
||||
return { code: r.code, map: null }
|
||||
},
|
||||
buildEnd() {
|
||||
logger.info(`${TAG} removed ${totals.comments} CSS comments (${totals.bytes} bytes) from ${totals.literals} shadow-style literals in ${totals.files} files` +
|
||||
(totals.skipped ? `; ${totals.skipped} literal(s) left untouched` : ''))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -348,7 +348,7 @@ useShadowStyles(`
|
|||
search bar at top:10, left:10, right:10. The geocoder pill renders ~60px
|
||||
tall on mobile, so its bottom edge is ~70px. MapToolbar sits at top:80
|
||||
(10px gap below search). We mirror MapToolbar exactly so the popover's
|
||||
top edge aligns with the top of the `+` button.
|
||||
top edge aligns with the top of the '+' button.
|
||||
right:52 = MapToolbar right(10) + button-width(36) + 6px breathing room. */
|
||||
@media(max-width:767px){
|
||||
.share-pop{
|
||||
|
|
|
|||
|
|
@ -22,10 +22,8 @@ function createGoMarkerElement(color = GO_ORANGE) {
|
|||
|
||||
el.innerHTML = `
|
||||
<style>
|
||||
/* Angled, brush-style pin (qa: 2026-05-06 user feedback — wanted a more
|
||||
hand-drawn / painterly feel like the reference image they shared).
|
||||
Slight tilt + diagonal gradient + a stronger drop-shadow gives the pin
|
||||
a "set down on the map" look without rendering raster art. */
|
||||
/* Angled, brush-style pin: a slight tilt, a diagonal gradient and a stronger
|
||||
drop-shadow give it a hand-drawn, "set down on the map" look without raster art. */
|
||||
.doxa-go-pin-wrapper {
|
||||
position: relative;
|
||||
width: 32px;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun x vite",
|
||||
"build": "bun run clean && bun run token && for d in app-profiles/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:drop && bun run check:parse && bun run verify && bun run shell",
|
||||
"build": "bun run clean && bun run token && for d in app-profiles/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:drop && bun run check:parse && bun run verify && bun run spa",
|
||||
"build:watch": "bun x vite build --watch",
|
||||
"preview": "bun x vite preview",
|
||||
"clean": "node internal/clean.mjs",
|
||||
"build:bundle": "bun x vite build",
|
||||
"tree": "node internal/generate-tree.mjs",
|
||||
"spa": "node internal/generate-spa.mjs # writes public/index.html, the one root page, from public/doxa-maps/manifest.json",
|
||||
"check:drop": "node internal/drop-portability-check.mjs",
|
||||
"check:parse": "node internal/inline-parse-check.mjs",
|
||||
"verify": "node library/scripts/research/verify-all.mjs",
|
||||
|
|
@ -24,7 +25,7 @@
|
|||
"apk:debug": "bun run build && bun run cap:sync && bun run cap:verify && cd capacitor/android && ./gradlew assembleDebug",
|
||||
"apk:install": "adb install -r capacitor/android/app/build/outputs/apk/debug/app-debug.apk",
|
||||
"phone": "bun run apk:debug && bun run apk:install",
|
||||
"shell": "node internal/install-shell.mjs --no-token # re-seed public/ from a layouts/ wrapper; --no-token so the local credential is never copied into the served root",
|
||||
"shell": "node internal/install-shell.mjs --no-token # OPTIONAL, not part of build: overlays a layouts/ wrapper onto public/ for local use (the served root is the generated index.html); --no-token so the local credential is never copied into the served root",
|
||||
"posters": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs",
|
||||
"posters:one": "cd library/scripts/posters && SPARSE=1 OFFLINE=1 VITE_API_BASE_URL=https://pray.doxa.life FORMAT=3x3 node generate.mjs # ONLY=<zoneKey> to build one",
|
||||
"posters:shoot": "cd library/scripts/posters && DSF=2 node shoot.mjs",
|
||||
|
|
@ -38,7 +39,7 @@
|
|||
"smoke:share": "node internal/share-roundtrip-smoke.mjs",
|
||||
"probe:legend-columns": "node internal/legend-columns-probe.mjs",
|
||||
"sync:dropin": "node internal/sync-dropin.mjs",
|
||||
"deploy:dev": "bun run build && node internal/sync-dropin.mjs \"${DOXA_DROPIN_DEST:-../doxa-life/public/js/doxa-maps-build}\" # every bundler slice ends here: the website worktree gets the fresh drop-in; then commit + push BOTH repos",
|
||||
"deploy:dev": "bun run build && node internal/sync-dropin.mjs \"${DOXA_DROPIN_DEST:-../doxa-life/public/js/doxa-maps-build}\" --from-config --prune # every bundler slice ends here: the website worktree gets exactly the drop-in contract (dropin.include in the build-config); then commit + push BOTH repos",
|
||||
"check:public-hygiene": "node internal/public-hygiene-check.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
# doxa-maps frame policy — generated by internal/generate-tree.mjs
|
||||
# (see docs/architecture/embeddability.md — the server owns frame-ancestors)
|
||||
|
||||
# Staging surfaces: internal preview only
|
||||
/doxa-maps/index.html
|
||||
Content-Security-Policy: frame-ancestors 'self'
|
||||
/doxa-maps/:bundle/index.html
|
||||
Content-Security-Policy: frame-ancestors 'self'
|
||||
|
||||
# Direct-embed pages (the product): framable on any partner site
|
||||
/doxa-maps/:bundle/:profile/index.html
|
||||
Content-Security-Policy: frame-ancestors *
|
||||
/doxa-maps/:bundle/:profile/:instance/index.html
|
||||
Content-Security-Policy: frame-ancestors *
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, all-people-groups { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
|
|||
83
public/doxa-maps/cities-internet/cities-internet.js
generated
83
public/doxa-maps/cities-internet/cities-internet.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, cities-internet-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, cities-internet-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, cities-internet-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
|
||||
<script src="../cities-internet.js" integrity="sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/cities-internet/index.html
generated
2
public/doxa-maps/cities-internet/index.html
generated
|
|
@ -130,7 +130,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":19,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","integrity":"sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"cities-internet","scriptUrl":"./cities-internet/cities-internet.js","output":"doxa-maps/cities-internet/cities-internet.js","integrity":"sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/cities-internet-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, country-posters-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, country-posters-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
545
public/doxa-maps/country-posters/country-posters.js
generated
545
public/doxa-maps/country-posters/country-posters.js
generated
File diff suppressed because one or more lines are too long
2
public/doxa-maps/country-posters/index.html
generated
2
public/doxa-maps/country-posters/index.html
generated
|
|
@ -130,7 +130,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":19,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","integrity":"sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"country-posters","scriptUrl":"./country-posters/country-posters.js","output":"doxa-maps/country-posters/country-posters.js","integrity":"sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./country-posters/country-posters.js\" integrity=\"sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI\" crossorigin=\"anonymous\">\u003c/script>\n\u003ccountry-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/country-posters-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
96
public/doxa-maps/demo-map/demo-map.js
generated
96
public/doxa-maps/demo-map/demo-map.js
generated
File diff suppressed because one or more lines are too long
4
public/doxa-maps/demo-map/demo-map/index.html
generated
4
public/doxa-maps/demo-map/demo-map/index.html
generated
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, demo-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <demo-map> custom element. -->
|
||||
<script src="../demo-map.js" integrity="sha384-LHGkKIEgv00d9oLnEZtLJJZ24oCTXrgoMItI12/wIOgUxlsefkyIb1bIPQKPkZiV" crossorigin="anonymous"></script>
|
||||
<script src="../demo-map.js" integrity="sha384-iwx1BbMrXkW8BmQMSfzl4MX4nyH6CDoOFivDM4DsIVr7832ELldLnRqrpaLV3BOY" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5/dist/maplibre-gl.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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, demo-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <demo-map> custom element. -->
|
||||
<script src="../../demo-map.js" integrity="sha384-LHGkKIEgv00d9oLnEZtLJJZ24oCTXrgoMItI12/wIOgUxlsefkyIb1bIPQKPkZiV" crossorigin="anonymous"></script>
|
||||
<script src="../../demo-map.js" integrity="sha384-iwx1BbMrXkW8BmQMSfzl4MX4nyH6CDoOFivDM4DsIVr7832ELldLnRqrpaLV3BOY" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/demo-map/index.html
generated
2
public/doxa-maps/demo-map/index.html
generated
|
|
@ -130,7 +130,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":19,"bundles":[{"bundle":"demo-map","scriptUrl":"./demo-map/demo-map.js","output":"doxa-maps/demo-map/demo-map.js","integrity":"sha384-LHGkKIEgv00d9oLnEZtLJJZ24oCTXrgoMItI12/wIOgUxlsefkyIb1bIPQKPkZiV","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./demo-map/demo-map.js\" integrity=\"sha384-LHGkKIEgv00d9oLnEZtLJJZ24oCTXrgoMItI12/wIOgUxlsefkyIb1bIPQKPkZiV\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdemo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/demo-map>"},"instances":[{"id":"maplibre","title":"Demo map — MapLibre (token-free)","apiBaseUrl":"","props":{"profile":"demo-map","provider":"maplibre","instanceId":"demo-maplibre"},"element":"demo-map","iframeUrl":"./demo-map/demo-map/maplibre/index.html","embed":{"iframe":"\u003ciframe src=\"./demo-map/demo-map/maplibre/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}],"bundle":"demo-map"}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"demo-map","scriptUrl":"./demo-map/demo-map.js","output":"doxa-maps/demo-map/demo-map.js","integrity":"sha384-iwx1BbMrXkW8BmQMSfzl4MX4nyH6CDoOFivDM4DsIVr7832ELldLnRqrpaLV3BOY","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./demo-map/demo-map.js\" integrity=\"sha384-iwx1BbMrXkW8BmQMSfzl4MX4nyH6CDoOFivDM4DsIVr7832ELldLnRqrpaLV3BOY\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdemo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/demo-map>"},"instances":[{"id":"maplibre","title":"Demo map — MapLibre (token-free)","apiBaseUrl":"","props":{"profile":"demo-map","provider":"maplibre","instanceId":"demo-maplibre"},"element":"demo-map","iframeUrl":"./demo-map/demo-map/maplibre/index.html","embed":{"iframe":"\u003ciframe src=\"./demo-map/demo-map/maplibre/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>"}}],"bundle":"demo-map"}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
583
public/doxa-maps/doxa-research-map/doxa-research-map.js
generated
583
public/doxa-maps/doxa-research-map/doxa-research-map.js
generated
File diff suppressed because one or more lines are too long
2
public/doxa-maps/doxa-research-map/index.html
generated
2
public/doxa-maps/doxa-research-map/index.html
generated
|
|
@ -130,7 +130,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":19,"bundles":[{"bundle":"doxa-research-map","scriptUrl":"./doxa-research-map/doxa-research-map.js","output":"doxa-maps/doxa-research-map/doxa-research-map.js","integrity":"sha384-v4CfNCIcWcojYz+iZPzigtU7Imb8CPk5YKe+tjv2z++m1IHtXCmo60EkWdFAed9d","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-v4CfNCIcWcojYz+iZPzigtU7Imb8CPk5YKe+tjv2z++m1IHtXCmo60EkWdFAed9d\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/doxa-research-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"doxa-research-map","scriptUrl":"./doxa-research-map/doxa-research-map.js","output":"doxa-maps/doxa-research-map/doxa-research-map.js","integrity":"sha384-K9GWnVJ6jQ4XpL1uat6IkrZ2ODyWEhDZWJ1yUkT0A70ZiLeJ7cD9dBpsG/5juF2b","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-K9GWnVJ6jQ4XpL1uat6IkrZ2ODyWEhDZWJ1yUkT0A70ZiLeJ7cD9dBpsG/5juF2b\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/doxa-research-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, doxa-research-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
|
||||
<script src="../doxa-research-map.js" integrity="sha384-v4CfNCIcWcojYz+iZPzigtU7Imb8CPk5YKe+tjv2z++m1IHtXCmo60EkWdFAed9d" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-research-map.js" integrity="sha384-K9GWnVJ6jQ4XpL1uat6IkrZ2ODyWEhDZWJ1yUkT0A70ZiLeJ7cD9dBpsG/5juF2b" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
498
public/doxa-maps/doxa-simple-map/doxa-simple-map.js
generated
498
public/doxa-maps/doxa-simple-map/doxa-simple-map.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, doxa-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, doxa-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, doxa-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, doxa-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/doxa-simple-map/index.html
generated
2
public/doxa-maps/doxa-simple-map/index.html
generated
|
|
@ -130,7 +130,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":19,"bundles":[{"bundle":"doxa-simple-map","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","output":"doxa-maps/doxa-simple-map/doxa-simple-map.js","integrity":"sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" 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?utm_source=YOUR_SOURCE\" 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?utm_source=YOUR_SOURCE\" 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":10,"profileCount":19,"bundles":[{"bundle":"doxa-simple-map","scriptUrl":"./doxa-simple-map/doxa-simple-map.js","output":"doxa-maps/doxa-simple-map/doxa-simple-map.js","integrity":"sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T\" crossorigin=\"anonymous\">\u003c/script>\n\u003cdoxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" 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?utm_source=YOUR_SOURCE\" 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?utm_source=YOUR_SOURCE\" 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).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, gospel-resources-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, gospel-resources-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
2
public/doxa-maps/gospel-resources/index.html
generated
2
public/doxa-maps/gospel-resources/index.html
generated
|
|
@ -130,7 +130,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":19,"bundles":[{"bundle":"gospel-resources","scriptUrl":"./gospel-resources/gospel-resources.js","output":"doxa-maps/gospel-resources/gospel-resources.js","integrity":"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"gospel-resources","scriptUrl":"./gospel-resources/gospel-resources.js","output":"doxa-maps/gospel-resources/gospel-resources.js","integrity":"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs","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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\">\u003c/script>\n\u003cgospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/gospel-resources-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, gospel-resources-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, gospel-resources-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
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
48
public/doxa-maps/manifest.json
generated
48
public/doxa-maps/manifest.json
generated
|
|
@ -33,7 +33,7 @@
|
|||
"bundle": "cities-internet",
|
||||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"output": "doxa-maps/cities-internet/cities-internet.js",
|
||||
"integrity": "sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U",
|
||||
"integrity": "sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1",
|
||||
"elements": [
|
||||
"cities-internet-map"
|
||||
],
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></cities-internet-map>"
|
||||
"script": "<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></cities-internet-map>"
|
||||
"script": "<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-C2lYpjkm+jDkGY+t5UkXn3qQfGKtxi7JTBjo6qZsw1TTXOkGiTsZaz9fOQSf/V9U\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></cities-internet-map>"
|
||||
"script": "<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-1u8zoyJyvUMjdin6Fowou+Lur8Tasmp2jJXC6dzrlhWpUYDvztHtq9FmPGZCi1K1\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></cities-internet-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
"bundle": "country-posters",
|
||||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"output": "doxa-maps/country-posters/country-posters.js",
|
||||
"integrity": "sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23",
|
||||
"integrity": "sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI",
|
||||
"elements": [
|
||||
"country-posters-map"
|
||||
],
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></country-posters-map>"
|
||||
"script": "<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-dBU1y4TEAqdXbhET85eUwE1ahRWktH59Cxk5mgUD2Rixo7MP2HDjGrejidLsSM23\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></country-posters-map>"
|
||||
"script": "<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-MJMmqcsSWrc61XNGUeI/zT2Ig7vl5yeWEpyLwjkePCj9coEulJhGljDuSP8SLSGI\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></country-posters-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
"bundle": "demo-map",
|
||||
"scriptUrl": "./demo-map/demo-map.js",
|
||||
"output": "doxa-maps/demo-map/demo-map.js",
|
||||
"integrity": "sha384-LHGkKIEgv00d9oLnEZtLJJZ24oCTXrgoMItI12/wIOgUxlsefkyIb1bIPQKPkZiV",
|
||||
"integrity": "sha384-iwx1BbMrXkW8BmQMSfzl4MX4nyH6CDoOFivDM4DsIVr7832ELldLnRqrpaLV3BOY",
|
||||
"elements": [
|
||||
"demo-map"
|
||||
],
|
||||
|
|
@ -129,7 +129,7 @@
|
|||
"scriptUrl": "./demo-map/demo-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./demo-map/demo-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./demo-map/demo-map.js\" integrity=\"sha384-LHGkKIEgv00d9oLnEZtLJJZ24oCTXrgoMItI12/wIOgUxlsefkyIb1bIPQKPkZiV\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></demo-map>"
|
||||
"script": "<script src=\"./demo-map/demo-map.js\" integrity=\"sha384-iwx1BbMrXkW8BmQMSfzl4MX4nyH6CDoOFivDM4DsIVr7832ELldLnRqrpaLV3BOY\" crossorigin=\"anonymous\"></script>\n<demo-map profile-config='{\"profile\":\"demo-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></demo-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -156,7 +156,7 @@
|
|||
"bundle": "doxa-research-map",
|
||||
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
|
||||
"output": "doxa-maps/doxa-research-map/doxa-research-map.js",
|
||||
"integrity": "sha384-v4CfNCIcWcojYz+iZPzigtU7Imb8CPk5YKe+tjv2z++m1IHtXCmo60EkWdFAed9d",
|
||||
"integrity": "sha384-K9GWnVJ6jQ4XpL1uat6IkrZ2ODyWEhDZWJ1yUkT0A70ZiLeJ7cD9dBpsG/5juF2b",
|
||||
"elements": [
|
||||
"doxa-research-map"
|
||||
],
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-v4CfNCIcWcojYz+iZPzigtU7Imb8CPk5YKe+tjv2z++m1IHtXCmo60EkWdFAed9d\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></doxa-research-map>"
|
||||
"script": "<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-K9GWnVJ6jQ4XpL1uat6IkrZ2ODyWEhDZWJ1yUkT0A70ZiLeJ7cD9dBpsG/5juF2b\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></doxa-research-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -179,7 +179,7 @@
|
|||
"bundle": "doxa-simple-map",
|
||||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"output": "doxa-maps/doxa-simple-map/doxa-simple-map.js",
|
||||
"integrity": "sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9",
|
||||
"integrity": "sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T",
|
||||
"elements": [
|
||||
"doxa-map",
|
||||
"doxa-simple-map"
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-tG0UdPJ6tjXJV0Hg3OFXY5I4yvu0ZwDvphYurVzw8cx4k4ne8RR7Rr2UP3MkeRy9\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></doxa-map>"
|
||||
"script": "<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-T815eVSU0j7QECeAlyNEVzQrDGUm7abImmsOukMW8/vTbamGODe3CMYILmlgSH9T\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></doxa-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -274,7 +274,7 @@
|
|||
"bundle": "gospel-resources",
|
||||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"output": "doxa-maps/gospel-resources/gospel-resources.js",
|
||||
"integrity": "sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL",
|
||||
"integrity": "sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs",
|
||||
"elements": [
|
||||
"gospel-resources-map"
|
||||
],
|
||||
|
|
@ -288,7 +288,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -300,7 +300,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -312,7 +312,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -324,7 +324,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-GGNkQjzHDoMF7Wz6mXQqTNGqzSEDKq4YTOjCwVsGpW+9IoUBcdN6JLbxwGJ05PZL\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
"script": "<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-KDyF/7avg/RzCZufMlCj5Jv5t7ox3nCd4YB21L8UsWQaLTM35/Avt1JxNiuf+tQs\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></gospel-resources-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -333,7 +333,7 @@
|
|||
"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",
|
||||
"integrity": "sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs",
|
||||
"integrity": "sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -347,7 +347,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></my-upg-100-list-map>"
|
||||
"script": "<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -359,7 +359,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></my-upg-100-list-map>"
|
||||
"script": "<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -371,7 +371,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></my-upg-100-list-map>"
|
||||
"script": "<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -380,7 +380,7 @@
|
|||
"bundle": "research-dashboard",
|
||||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"output": "doxa-maps/research-dashboard/research-dashboard.js",
|
||||
"integrity": "sha384-zt/p5jPpNqDg6lcSBIEt0HccyWTMZE6JtY5tH7QdX7johN+8nf7xAHpoXPq1Ls0O",
|
||||
"integrity": "sha384-KM1+8afDjk+fAFTzbxqv66v2xJtrgihGGkjQd7AFoWr87sctxGvAPpEG0aTsgKiY",
|
||||
"elements": [
|
||||
"research-dashboard-map"
|
||||
],
|
||||
|
|
@ -394,7 +394,7 @@
|
|||
"scriptUrl": "./research-dashboard/research-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-zt/p5jPpNqDg6lcSBIEt0HccyWTMZE6JtY5tH7QdX7johN+8nf7xAHpoXPq1Ls0O\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></research-dashboard-map>"
|
||||
"script": "<script src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-KM1+8afDjk+fAFTzbxqv66v2xJtrgihGGkjQd7AFoWr87sctxGvAPpEG0aTsgKiY\" crossorigin=\"anonymous\"></script>\n<research-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'></research-dashboard-map>"
|
||||
},
|
||||
"bundle": "research-dashboard"
|
||||
}
|
||||
|
|
|
|||
2
public/doxa-maps/my-upg-100-list/index.html
generated
2
public/doxa-maps/my-upg-100-list/index.html
generated
|
|
@ -130,7 +130,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":19,"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","integrity":"sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs","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?utm_source=YOUR_SOURCE\" 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\" integrity=\"sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" 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\" integrity=\"sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}},{"profile":"upg-100-mfe","file":"upg-100-mfe.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/upg-100-mfe/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"iframe":"\u003ciframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"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","integrity":"sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px","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?utm_source=YOUR_SOURCE\" 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\" integrity=\"sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\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?utm_source=YOUR_SOURCE\" 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\" integrity=\"sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}},{"profile":"upg-100-mfe","file":"upg-100-mfe.vue","description":"","element":"my-upg-100-list-map","iframeUrl":"./my-upg-100-list/upg-100-mfe/index.html","scriptUrl":"./my-upg-100-list/my-upg-100-list.js","embed":{"iframe":"\u003ciframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px\" crossorigin=\"anonymous\">\u003c/script>\n\u003cmy-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/my-upg-100-list-map>"}}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
346
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
346
public/doxa-maps/my-upg-100-list/my-upg-100-list.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, my-upg-100-list-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, my-upg-100-list-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, my-upg-100-list-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-Z1RXSgx5DrgvI618cta+6XbWC0p4P1PSunjEh37wc2w5HZrdzcFdOYsZlUOAr2rs" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-mbEjNg2czbPNcbsd8yqtppwFymur3/+1roDf54PmZCO11f6ekERchOH+F7J10+Px" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/research-dashboard/index.html
generated
2
public/doxa-maps/research-dashboard/index.html
generated
|
|
@ -130,7 +130,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":"research-dashboard","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":19,"bundles":[{"bundle":"research-dashboard","scriptUrl":"./research-dashboard/research-dashboard.js","output":"doxa-maps/research-dashboard/research-dashboard.js","integrity":"sha384-zt/p5jPpNqDg6lcSBIEt0HccyWTMZE6JtY5tH7QdX7johN+8nf7xAHpoXPq1Ls0O","elements":["research-dashboard-map"],"profiles":[{"profile":"research-dashboard","file":"research-dashboard.vue","description":"Coordinated-Multiple-Views research dashboard for 10/40-window UPGs (live pray.doxa.life): docked facet rail + map-as-hero + linked table/charts, brushing-and-linking over one in-memory store.","element":"research-dashboard-map","iframeUrl":"./research-dashboard/research-dashboard/index.html","scriptUrl":"./research-dashboard/research-dashboard.js","embed":{"iframe":"\u003ciframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-zt/p5jPpNqDg6lcSBIEt0HccyWTMZE6JtY5tH7QdX7johN+8nf7xAHpoXPq1Ls0O\" crossorigin=\"anonymous\">\u003c/script>\n\u003cresearch-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/research-dashboard-map>"},"bundle":"research-dashboard"}]}]};</script>
|
||||
window.FORGE.manifest = {"schema":"doxa-maps-manifest@1","generatedFrom":"app-profiles/*/*.vue","embedBase":".","bundleCount":10,"profileCount":19,"bundles":[{"bundle":"research-dashboard","scriptUrl":"./research-dashboard/research-dashboard.js","output":"doxa-maps/research-dashboard/research-dashboard.js","integrity":"sha384-KM1+8afDjk+fAFTzbxqv66v2xJtrgihGGkjQd7AFoWr87sctxGvAPpEG0aTsgKiY","elements":["research-dashboard-map"],"profiles":[{"profile":"research-dashboard","file":"research-dashboard.vue","description":"Coordinated-Multiple-Views research dashboard for 10/40-window UPGs (live pray.doxa.life): docked facet rail + map-as-hero + linked table/charts, brushing-and-linking over one in-memory store.","element":"research-dashboard-map","iframeUrl":"./research-dashboard/research-dashboard/index.html","scriptUrl":"./research-dashboard/research-dashboard.js","embed":{"iframe":"\u003ciframe src=\"./research-dashboard/research-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen>\u003c/iframe>","script":"\u003cscript src=\"./research-dashboard/research-dashboard.js\" integrity=\"sha384-KM1+8afDjk+fAFTzbxqv66v2xJtrgihGGkjQd7AFoWr87sctxGvAPpEG0aTsgKiY\" crossorigin=\"anonymous\">\u003c/script>\n\u003cresearch-dashboard-map profile-config='{\"profile\":\"research-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\"}'>\u003c/research-dashboard-map>"},"bundle":"research-dashboard"}]}]};</script>
|
||||
<script>/**
|
||||
* staging-forge.js — the ONE-PAGE semantic staging environment (root + bundle pages).
|
||||
*
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, research-dashboard-map { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <research-dashboard-map> custom element. -->
|
||||
<script src="../research-dashboard.js" integrity="sha384-zt/p5jPpNqDg6lcSBIEt0HccyWTMZE6JtY5tH7QdX7johN+8nf7xAHpoXPq1Ls0O" crossorigin="anonymous"></script>
|
||||
<script src="../research-dashboard.js" integrity="sha384-KM1+8afDjk+fAFTzbxqv66v2xJtrgihGGkjQd7AFoWr87sctxGvAPpEG0aTsgKiY" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, template-bundle { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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"). */
|
||||
browser URL shares the map itself. */
|
||||
html, body { height: 100%; margin: 0; background: #0a0e15; }
|
||||
#slot, template-bundle { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
|
||||
#status { position: fixed; inset: 0; z-index: 2; display: grid; place-items: center;
|
||||
|
|
|
|||
337
public/external/research-dashboard-map.js
vendored
337
public/external/research-dashboard-map.js
vendored
File diff suppressed because one or more lines are too long
548
public/external/upg-neighbor.js
vendored
548
public/external/upg-neighbor.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* icons.js — SELF-HOSTED icon set. No CDN, no icon font, no Google/YouTube request.
|
||||
*
|
||||
* These are hand-written 24x24 stroke paths in the Lucide idiom (ISC-licensed style:
|
||||
* 2px stroke, round cap/join, 24x24 box). Shipping them as inline SVG means:
|
||||
* - zero network requests (works offline, works in Capacitor from local assets)
|
||||
* - F-Droid safe (no proprietary blobs, no tracking, reproducible from source)
|
||||
* - no FOUT/icon-font flash
|
||||
*
|
||||
* To swap in a real icon package later, replace these path strings — the API
|
||||
* (`icon(name)` returning an <svg> string) stays the same.
|
||||
*/
|
||||
const P = {
|
||||
menu: '<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>',
|
||||
map: '<polygon points="3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21"/><line x1="9" y1="3" x2="9" y2="18"/><line x1="15" y1="6" x2="15" y2="21"/>',
|
||||
globe: '<circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a15 15 0 0 1 0 18a15 15 0 0 1 0-18"/>',
|
||||
heart: '<path d="M19 14c1.5-1.5 3-3.5 3-5.5A4.5 4.5 0 0 0 12 6a4.5 4.5 0 0 0-10 2.5C2 10.5 3.5 12.5 5 14l7 7Z"/>',
|
||||
// `prayer` = Prayer Mobilization — two hands pressed together (praying): pointed
|
||||
// fingertips at top, knuckle bulges on the outer edges, wrists curling in at the base.
|
||||
prayer: '<path d="M12 4.2c-.9 1.4-1.9 2.6-2.9 3.8-1 1.2-1.6 2.6-1.6 4.2v3.3c0 1.3.7 2.4 1.8 3 .5.3 1.1-.1 1.1-.7"/><path d="M12 4.2c.9 1.4 1.9 2.6 2.9 3.8 1 1.2 1.6 2.6 1.6 4.2v3.3c0 1.3-.7 2.4-1.8 3-.5.3-1.1-.1-1.1-.7"/><path d="M12 4.2V17"/>',
|
||||
list: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><circle cx="4" cy="6" r="1"/><circle cx="4" cy="12" r="1"/><circle cx="4" cy="18" r="1"/>',
|
||||
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9"/><path d="M16 3.1a4 4 0 0 1 0 7.8"/>',
|
||||
flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V4s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/>',
|
||||
target: '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1"/>',
|
||||
pin: '<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z"/><circle cx="12" cy="10" r="3"/>',
|
||||
monitor: '<rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>',
|
||||
chart: '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
|
||||
layers: '<polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/>',
|
||||
flask: '<path d="M9 3h6v5l5 11a2 2 0 0 1-2 3H6a2 2 0 0 1-2-3l5-11z"/><line x1="9" y1="3" x2="15" y2="3"/><line x1="6" y1="15" x2="18" y2="15"/>',
|
||||
share: '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.6" y1="13.5" x2="15.4" y2="17.5"/><line x1="15.4" y1="6.5" x2="8.6" y2="10.5"/>',
|
||||
beaker: '<path d="M6 3h12"/><path d="M8 3v7l-3 8a2 2 0 0 0 2 3h10a2 2 0 0 0 2-3l-3-8V3"/>',
|
||||
chevron: '<polyline points="9 18 15 12 9 6"/>',
|
||||
grid: '<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>',
|
||||
tree: '<rect x="9" y="3" width="6" height="5" rx="1"/><rect x="2" y="16" width="6" height="5" rx="1"/><rect x="16" y="16" width="6" height="5" rx="1"/><path d="M12 8v4M5 16v-2h14v2"/>',
|
||||
dots: '<circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/>',
|
||||
// `embed` = the <> script/code-embed mark. Signals "this map is the embeddable
|
||||
// one you paste into a partner site" (2026-07-29, for Prayer Progress).
|
||||
embed: '<polyline points="9 7 4 12 9 17"/><polyline points="15 7 20 12 15 17"/>',
|
||||
// `send` = Laborer Mobilization (sending labourers) — a paper-plane/send mark,
|
||||
// deliberately distinct from `heart` (Prayer Mobilization) and `users` (People Groups).
|
||||
send: '<path d="M21 3 L3 10.5 L9.5 13.5 L12.5 20 Z"/><line x1="21" y1="3" x2="9.5" y2="13.5"/>',
|
||||
// `poster` = a printable poster asset (Country Posters / Key City Posters) —
|
||||
// a framed sheet with a pinned marker, distinct from `map` and `pin`.
|
||||
poster: '<rect x="4" y="3" width="16" height="18" rx="2"/><circle cx="12" cy="10" r="2.5"/><path d="M8 17h8"/>',
|
||||
// `search` = Material "search" magnifying glass, hand-drawn to Material's geometry
|
||||
// (circle r=7.5 centred at 10.5,10.5 with the handle running to ~20,20 at 45°).
|
||||
// Written out locally — NEVER fetched from a Google/Material CDN. That is a standing
|
||||
// law, not a style choice: a CDN icon request leaks the end user's IP to Google,
|
||||
// which in a persecution context is a safety risk. Self-hosted also = works offline
|
||||
// and inside Capacitor/F-Droid from local files.
|
||||
search: '<circle cx="10.5" cy="10.5" r="7.5"/><line x1="15.8" y1="15.8" x2="20.5" y2="20.5"/>',
|
||||
// `book` = Gospel Access (an open book / the Word reaching a place).
|
||||
book: '<path d="M12 6.5C12 6.5 9.5 4.5 4 4.5v13C9.5 17.5 12 19.5 12 19.5"/><path d="M12 6.5C12 6.5 14.5 4.5 20 4.5v13C14.5 17.5 12 19.5 12 19.5"/>',
|
||||
// `wifi` = Cities with Internet (connectivity arcs + node). Local, no CDN.
|
||||
wifi: '<path d="M3.5 9a13 13 0 0 1 17 0"/><path d="M6.8 12.4a8.5 8.5 0 0 1 10.4 0"/><path d="M10 15.7a4 4 0 0 1 4 0"/><circle cx="12" cy="19" r="1"/>',
|
||||
}
|
||||
|
||||
export function icon(name, cls = '') {
|
||||
const d = P[name] || P.map
|
||||
return `<svg class="ic ${cls}" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`
|
||||
}
|
||||
1382
public/index.html
1382
public/index.html
File diff suppressed because one or more lines are too long
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* languages.js — the language list for the app wrapper's locale dropdown.
|
||||
*
|
||||
* PORTED from dx-life's single source of truth: `config/languages.ts`
|
||||
* (LANGUAGES / ENABLED_LANGUAGES / hideFromSwitcher). Kept DELIBERATELY MINIMAL:
|
||||
* dx-life's version also carries DeepL codes, Bible translation ids, glossary ids
|
||||
* and font flags — all of which belong to the CMS/translation pipeline, not to a
|
||||
* map wrapper. Only the four fields the switcher actually renders are kept, plus
|
||||
* `dir` (RTL) because the wrapper has to flip layout direction.
|
||||
*
|
||||
* WHY NOT A LIBRARY: the switcher is ~30 lines of state over this array. Adding a
|
||||
* dependency to render a <select> would be more complexity, not less — and every
|
||||
* candidate assumes a router (dx-life's uses `switchLocalePath()` because it
|
||||
* routes /es, /fr …). The wrapper is a STATIC app with no locale routing: it just
|
||||
* needs to emit a code. So: same DATA, simpler MECHANISM.
|
||||
*
|
||||
* THE CONTRACT (already built into the bundles — do not reinvent):
|
||||
* ProfileLoader reads `profile-config.locale` (falls back to `.lang`), lowercases
|
||||
* it, strips any region suffix ('pt-BR' -> 'pt'), drives the bundle's i18n global
|
||||
* locale from it, and provides `lang` to descendants. So switching language =
|
||||
* re-setting profile-config.locale on the mounted element. Nothing else.
|
||||
*/
|
||||
|
||||
export const LANGUAGES = [
|
||||
{ code: 'en', name: 'English', nativeName: 'English', flag: '🇺🇸' },
|
||||
{ code: 'es', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸' },
|
||||
{ code: 'fr', name: 'French', nativeName: 'Français', flag: '🇫🇷' },
|
||||
{ code: 'pt', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹' },
|
||||
{ code: 'ar', name: 'Arabic', nativeName: 'العربية', flag: '🇸🇦', dir: 'rtl' },
|
||||
{ code: 'ru', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺' },
|
||||
// Present in dx-life but hidden from its public switcher (hideFromSwitcher: true).
|
||||
// Kept here, flagged, so the list stays a faithful mirror of the source of truth.
|
||||
{ code: 'de', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪', hideFromSwitcher: true },
|
||||
{ code: 'hi', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳', hideFromSwitcher: true },
|
||||
// enabled:false in dx-life (routable/translatable but not offered): it, zh, ro.
|
||||
]
|
||||
|
||||
/** Languages offered in the dropdown. */
|
||||
export const SWITCHER_LANGUAGES = LANGUAGES.filter(l => !l.hideFromSwitcher)
|
||||
|
||||
export function getLanguage(code) {
|
||||
return LANGUAGES.find(l => l.code === code)
|
||||
}
|
||||
|
||||
/** Normalise any incoming tag to a supported code ('pt-BR' -> 'pt'; unknown -> 'en'). */
|
||||
export function normaliseLocale(tag) {
|
||||
const base = String(tag || '').split('-')[0].toLowerCase()
|
||||
return LANGUAGES.some(l => l.code === base) ? base : 'en'
|
||||
}
|
||||
|
||||
/** Persisted choice (survives reload), defaulting to the browser language. */
|
||||
const KEY = 'doxa-maps:locale'
|
||||
export function loadLocale() {
|
||||
try {
|
||||
const saved = localStorage.getItem(KEY)
|
||||
if (saved) return normaliseLocale(saved)
|
||||
} catch (_) { /* storage may be unavailable */ }
|
||||
return normaliseLocale(navigator?.language)
|
||||
}
|
||||
export function saveLocale(code) {
|
||||
try { localStorage.setItem(KEY, code) } catch (_) { /* non-fatal */ }
|
||||
}
|
||||
128
public/nav.js
128
public/nav.js
|
|
@ -1,128 +0,0 @@
|
|||
/**
|
||||
* nav.js — the sidebar data model for the app-wrapper-material layout.
|
||||
*
|
||||
* Extends a conventional `SidebarNavItem` shape ({ to, label, icon, children })
|
||||
* with the two things doxa-maps needs:
|
||||
* - `kind` : 'item' | 'section' | 'folder'
|
||||
* section = always-open, labelled group (YouTube "Subscriptions" style)
|
||||
* folder = collapsible group (click to open/close)
|
||||
* - `target` : 'both' | 'desktop' | 'mobile' <-- per-target packaging flag.
|
||||
* 'desktop' items are hidden on mobile AND excluded from the
|
||||
* Capacitor build's webDir (the mobile build copies fewer bundles).
|
||||
*
|
||||
* Order matters: this array renders top-to-bottom, exactly as authored.
|
||||
* Icons are LOCAL names resolved from ./icons/<name>.svg — no CDN, no icon font.
|
||||
* (F-Droid requires self-hosted, non-proprietary assets.)
|
||||
*/
|
||||
export const NAV = [
|
||||
// ── PRAYER MOBILIZATION FIRST: the prayer map is the landing view, so this
|
||||
// section sits at the TOP and its first child is the DEFAULT profile.
|
||||
// (2026-07-29: "so that the first map they see is the prayer map".)
|
||||
{
|
||||
kind: 'section', id: 'prayer-mobilization', label: 'Prayer Mobilization', icon: 'heart', target: 'both',
|
||||
children: [
|
||||
// Prayer Progress = the research map opened on its PRAYER tab (colorStrategy
|
||||
// 'prayerProgress'). That tab is tabs[0] in the bundle, so it is already the
|
||||
// default view — no separate bundle needed. The `embed` icon (<>) marks it as
|
||||
// the embeddable/share-first map.
|
||||
{ kind: 'item', id: 'prayer-progress', label: 'Prayer Progress', icon: 'embed', target: 'both' },
|
||||
{ kind: 'item', id: 'upg-100-list', label: 'My UPG 100 List', icon: 'list', target: 'both' },
|
||||
// REMOVED 'Prayer List' (2026-07-29): not a real app.
|
||||
],
|
||||
},
|
||||
|
||||
// ── LABORER MOBILIZATION — mobilising the labourers/disciple-makers themselves
|
||||
// (as distinct from mobilising prayer). UPG Neighbor MOVED here from Prayer
|
||||
// Mobilization: it is a labourer/sending tool, not a prayer tool.
|
||||
// (2026-07-29).
|
||||
{
|
||||
kind: 'section', id: 'laborer-mobilization', label: 'Laborer Mobilization', icon: 'send', target: 'both',
|
||||
children: [
|
||||
{ kind: 'item', id: 'upg-neighbor', label: 'UPG Neighbor', icon: 'users', target: 'both' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── RESEARCH — a TOP-LEVEL section, PEER of Prayer/Laborer Mobilization
|
||||
// (2026-07-29: "a section to the same degree as Laborer", i.e. a
|
||||
// sibling, NOT nested under it). Holds the research/general maps.
|
||||
{
|
||||
kind: 'section', id: 'research', label: 'Research', icon: 'search', target: 'both',
|
||||
children: [
|
||||
{ kind: 'item', id: 'research-map', label: 'Research Map', icon: 'map', target: 'both' },
|
||||
// 'All People Groups' = the FULL global people-group set (~15,000), as distinct
|
||||
// from the ~2,100-UPG DX cluster the prayer map plots. Different datasets —
|
||||
// keep them straight. (The "15k" suffix was dropped from the label at the
|
||||
// maintainer's request 2026-07-29; the distinction is recorded here instead.)
|
||||
// ⚠ NO BUNDLE YET — renders the placeholder card.
|
||||
{ kind: 'item', id: 'all-people-groups', label: 'All People Groups', icon: 'globe', target: 'both' },
|
||||
// REMOVED 'Reached People Groups' (2026-07-29) — added and withdrawn the
|
||||
// same round; do not re-add without an explicit ask.
|
||||
// REMOVED 'Simple Map' (2026-07-29). REASON WORTH KEEPING: the
|
||||
// doxa-simple-map BUNDLE is not one map — it carries THREE (prayer, adoption,
|
||||
// engagement). doxa-maps only wants to preview PRAYER, and that is already
|
||||
// surfaced as 'Prayer Progress' in Prayer Mobilization. Exposing "Simple Map"
|
||||
// would advertise adoption+engagement views we are not previewing here.
|
||||
// => If a simple prayer-only view is ever wanted as its own entry, add a
|
||||
// profile-config that pins doxa-simple-map to its prayer profile only.
|
||||
],
|
||||
},
|
||||
|
||||
// ── GOSPEL ACCESS — where the gospel can actually REACH: the infrastructure
|
||||
// (internet-connected cities) and the assets (gospel resources) that make
|
||||
// access possible. Populated by the maintainer 2026-07-29.
|
||||
// Both items are wired to real bundles (cities-internet + gospel-resources).
|
||||
{
|
||||
kind: 'section', id: 'gospel-access', label: 'Gospel Access', icon: 'book', target: 'both',
|
||||
children: [
|
||||
{ kind: 'item', id: 'cities-with-internet', label: 'Cities with Internet', icon: 'wifi', target: 'both' },
|
||||
{ kind: 'item', id: 'gospel-resources', label: 'Gospel Resources', icon: 'book', target: 'both' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── 'People Groups' section REMOVED (2026-07-29): it is now empty —
|
||||
// Countries -> Prayer Mobilization as Country Posters; Impact Map -> Experimental
|
||||
// maps; People Pins -> deleted outright.
|
||||
|
||||
// ── DASHBOARDS — a SECTION (always visible, no folder to open), DESKTOP ONLY
|
||||
// (excluded from the mobile/Capacitor build). "Big Picture" carries a Desktop Only badge. ──
|
||||
{
|
||||
kind: 'section', id: 'dashboards', label: 'Dashboards', icon: 'monitor', target: 'desktop',
|
||||
children: [
|
||||
{ kind: 'item', id: 'research-dashboard', label: 'Big Picture', icon: 'chart', target: 'desktop' },
|
||||
],
|
||||
},
|
||||
|
||||
// ── a FOLDER at the BOTTOM (design note: "experimental maps") ──
|
||||
{
|
||||
kind: 'folder', id: 'experimental', label: 'Experimental maps', icon: 'flask', target: 'both',
|
||||
open: false,
|
||||
children: [
|
||||
{ kind: 'item', id: 'nationmapper', label: 'Nationmapper', badge: 'soon', icon: 'share', 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.
|
||||
{ kind: 'item', id: 'geo-steward', label: 'Outreach Planner', badge: 'soon', icon: 'layers', target: 'both' },
|
||||
// Key City Posters MOVED here from Laborer Mobilization.
|
||||
{ kind: 'item', id: 'key-city-posters', label: 'Key City Posters', icon: 'poster', target: 'both' },
|
||||
// Full UPG-Neighbor hub (all lenses: cockpit / flower / lang-cluster / send).
|
||||
// The production 'UPG Neighbor' in Laborer Mobilization is the focused lang-cluster map.
|
||||
{ kind: 'item', id: 'upg-neighbor-lab', label: 'UPG Neighbor Lab', icon: 'flask', target: 'both' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Filter the tree for a build/runtime target. Used by BOTH the UI and the packager.
|
||||
* RECURSIVE — the tree is no longer only two levels deep (a section may hold a
|
||||
* 'subgroup', e.g. Laborer Mobilization > Research > Research Map), so a flat
|
||||
* one-level filter would silently drop or mis-filter nested entries.
|
||||
*/
|
||||
export function navForTarget(nav, target /* 'desktop' | 'mobile' */) {
|
||||
const keep = (n) => n.target === 'both' || n.target === target
|
||||
const walk = (nodes) => nodes
|
||||
.filter(keep)
|
||||
.map((n) => (n.children ? { ...n, children: walk(n.children) } : n))
|
||||
// prune groups whose children were all filtered out (an empty group is noise)
|
||||
.filter((n) => !n.children || n.children.length > 0)
|
||||
return walk(nav)
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
/**
|
||||
* pane-wrapper — the MFE pane GRID, a layout nested inside app-wrapper-material.
|
||||
*
|
||||
* WHAT IT IS
|
||||
* `app-wrapper-material` owns the chrome (header, side menu, the View fieldset).
|
||||
* This layout owns the STAGE INSIDE it: a parameterized grid of panes, each pane
|
||||
* holding a different app-profile MFE that is another VIEW of the same subject.
|
||||
* `my-upg-100-list` is a family — a map view and a poster-wall view — not one app
|
||||
* with tabs. Together the two layouts form the app harness that displays the
|
||||
* custom elements shipped in the vite bundles.
|
||||
*
|
||||
* THE MOTION CONTRACT (the design decision everything else follows from)
|
||||
* • VERTICAL neighbours are ONE SCROLLABLE PAGE. Both panes are live; you scroll
|
||||
* between them. The edge strips (useEdgeScrollAffordance) are what let you
|
||||
* escape a scroll-capturing child like a Mapbox canvas.
|
||||
* • HORIZONTAL movement is a FLY-TO. The target column is NOT loaded until you
|
||||
* go there, and the origin column is released on arrival.
|
||||
* So AT MOST TWO PANES ARE EVER ALIVE. That is not an arbitrary cap — it is what
|
||||
* preserves the ~16-WebGL-context budget the wrapper's teardown has always
|
||||
* protected. A naive "keep all four panes mounted" grid would break on a phone.
|
||||
*
|
||||
* WHY A COLUMN IS ONE SCROLLING PAGE RATHER THAN TWO SWAPPED VIEWS
|
||||
* A pane you scroll to keeps its sibling on screen during the transition, so the
|
||||
* relationship between the views stays legible ("the list is UNDER the map"). A
|
||||
* swap teaches nothing.
|
||||
*
|
||||
* 2×2 IS A DEFAULT, NOT A CEILING. Panes carry [row, col] coordinates rather than
|
||||
* a fixed enum, so the grid expands (2×3, 3×3 …) and contracts (a single column,
|
||||
* or one pane) from the same code. A profile with no `panes` key behaves exactly
|
||||
* as before this layout existed.
|
||||
*
|
||||
* AR is the reason the grid exists at all: today you see one pane (or one
|
||||
* scrolling column), but the coordinates mean a future AR client can zoom out and
|
||||
* see every pane laid out like posters on a wall.
|
||||
*/
|
||||
|
||||
export const PANE_GRID_STYLES = `
|
||||
/* The column that scrolls. Vertical neighbours stack here and the user scrolls
|
||||
between them; scroll-snap makes each pane settle cleanly instead of stopping
|
||||
half-way between two views. */
|
||||
.pane-col{
|
||||
position:absolute; inset:0; overflow-y:auto; overflow-x:hidden;
|
||||
scroll-snap-type:y mandatory; scroll-behavior:smooth;
|
||||
-webkit-overflow-scrolling:touch;
|
||||
/* DOUBLE SCROLLBAR FIX. A pane's own content (e.g. the poster wall) scrolls
|
||||
inside the pane; this column only scrolls BETWEEN panes. With both visible
|
||||
the desktop showed two bars side by side. The column's bar is hidden — pane
|
||||
navigation is driven by the View switcher and scroll-snap, not by dragging a
|
||||
bar — while the inner content keeps its own, which is the one a user wants.
|
||||
Scrolling still works here; only the indicator is suppressed. */
|
||||
scrollbar-width:none; /* Firefox */
|
||||
-ms-overflow-style:none; /* legacy Edge */
|
||||
}
|
||||
.pane-col::-webkit-scrollbar{ width:0; height:0; } /* WebKit/Blink */
|
||||
/* Each pane fills the stage exactly, so "scroll down" moves you one full view. */
|
||||
.pane{
|
||||
position:relative; width:100%; height:100%;
|
||||
scroll-snap-align:start; scroll-snap-stop:always;
|
||||
}
|
||||
/* The live custom element fills its pane. */
|
||||
.pane > .pane-mount{ position:absolute; inset:0; }
|
||||
/* Placeholder copy for a pane whose profile is not built yet — same card styling
|
||||
as the wrapper's own placeholder so an unbuilt pane never looks broken. */
|
||||
.pane-placeholder{
|
||||
position:absolute; inset:0; display:grid; place-items:center; padding:24px;
|
||||
text-align:center;
|
||||
}
|
||||
.pane-placeholder .pane-card{
|
||||
max-width:520px; background:var(--card,#fff); border-radius:14px; padding:22px 26px;
|
||||
box-shadow:0 1px 3px rgba(0,0,0,.12),0 6px 20px rgba(0,0,0,.08);
|
||||
}
|
||||
.pane-placeholder h2{ margin:0 0 8px; font-size:18px; font-weight:600 }
|
||||
.pane-placeholder p{ margin:0; font-size:13px; line-height:1.55; opacity:.78 }
|
||||
/* A small persistent hint that there IS something below — the affordance the
|
||||
edge strips reinforce once you start interacting. Hidden on the last pane. */
|
||||
.pane-more{
|
||||
position:absolute; left:50%; bottom:10px; transform:translateX(-50%);
|
||||
display:flex; align-items:center; gap:6px; padding:5px 12px; border-radius:999px;
|
||||
background:rgba(0,0,0,.55); color:#fff; font-size:11px; letter-spacing:.02em;
|
||||
pointer-events:none; z-index:6; opacity:.9;
|
||||
}
|
||||
`
|
||||
|
||||
/**
|
||||
* Build the pane column for a profile.
|
||||
*
|
||||
* @param {Object} spec the PROFILES registry entry
|
||||
* @param {Object} opts
|
||||
* @param {HTMLElement} opts.host container to render into (the wrapper's #slot)
|
||||
* @param {(pane:Object, mountEl:HTMLElement) => Promise<void>} opts.mountPane
|
||||
* called for each pane that should be LIVE; the caller owns element creation
|
||||
* so this layout never needs to know about bundles, tags or tokens.
|
||||
* @returns {{ panes:Object[], scrollTo:(paneId:string)=>void, destroy:()=>void }}
|
||||
*/
|
||||
export function buildPaneColumn(spec, { host, mountPane }) {
|
||||
const panes = Array.isArray(spec?.panes) ? spec.panes : []
|
||||
// No pane declaration → not a pane profile. The caller falls back to its
|
||||
// original single-mount path, so nothing changes for existing profiles.
|
||||
if (panes.length < 2) return null
|
||||
|
||||
// v1 renders ONE COLUMN (col 0). Other columns are reachable by fly-to, which is
|
||||
// a later slice — but the coordinates are already carried on every pane, so that
|
||||
// work does not require reshaping this data.
|
||||
const column = panes
|
||||
.filter(p => (p.at?.[1] ?? 0) === 0)
|
||||
.sort((a, b) => (a.at?.[0] ?? 0) - (b.at?.[0] ?? 0))
|
||||
|
||||
if (column.length < 2) return null
|
||||
|
||||
const col = document.createElement('div')
|
||||
col.className = 'pane-col'
|
||||
|
||||
const mounts = new Map()
|
||||
column.forEach((pane, i) => {
|
||||
const el = document.createElement('div')
|
||||
el.className = 'pane'
|
||||
el.dataset.paneId = pane.id
|
||||
const mount = document.createElement('div')
|
||||
mount.className = 'pane-mount'
|
||||
el.appendChild(mount)
|
||||
mounts.set(pane.id, mount)
|
||||
|
||||
// A pane with no bundle yet still renders — as a labelled placeholder, so the
|
||||
// column is scrollable and testable before every view profile exists.
|
||||
if (pane.placeholder) {
|
||||
const ph = document.createElement('div')
|
||||
ph.className = 'pane-placeholder'
|
||||
ph.innerHTML = `<div class="pane-card"><h2>${pane.label}</h2><p>${pane.placeholder}</p></div>`
|
||||
el.appendChild(ph)
|
||||
}
|
||||
|
||||
// "scroll for more" hint on every pane except the last.
|
||||
// OPT-OUT per profile via `paneHint: false` in the PROFILES registry entry.
|
||||
// The header already carries a Map | Posters view switcher, so on a profile
|
||||
// where that is visible this pill is a second, redundant control that just
|
||||
// sits on top of the map. Left ON by default so existing profiles are
|
||||
// unchanged. (design note 2026-08-01: "component that says posters below should
|
||||
// not exist.")
|
||||
if (i < column.length - 1 && spec?.paneHint !== false) {
|
||||
const more = document.createElement('div')
|
||||
more.className = 'pane-more'
|
||||
more.innerHTML = `<span>${column[i + 1].label} below</span><span aria-hidden="true">↓</span>`
|
||||
el.appendChild(more)
|
||||
}
|
||||
|
||||
col.appendChild(el)
|
||||
})
|
||||
|
||||
host.appendChild(col)
|
||||
|
||||
return {
|
||||
panes: column,
|
||||
/** Animate to a pane by id — this is what the View fieldset tabs call. */
|
||||
scrollTo(paneId) {
|
||||
const target = col.querySelector(`.pane[data-pane-id="${paneId}"]`)
|
||||
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
},
|
||||
/** Which pane is currently filling the viewport (for URL sync). */
|
||||
current() {
|
||||
const tops = column.map(p => {
|
||||
const el = col.querySelector(`.pane[data-pane-id="${p.id}"]`)
|
||||
return { id: p.id, d: Math.abs((el?.offsetTop ?? 0) - col.scrollTop) }
|
||||
})
|
||||
tops.sort((a, b) => a.d - b.d)
|
||||
return tops[0]?.id || column[0].id
|
||||
},
|
||||
onScroll(cb) {
|
||||
let t = null
|
||||
const h = () => { if (t) clearTimeout(t); t = setTimeout(cb, 120) }
|
||||
col.addEventListener('scroll', h, { passive: true })
|
||||
return () => { if (t) clearTimeout(t); col.removeEventListener('scroll', h) }
|
||||
},
|
||||
mountEl: (paneId) => mounts.get(paneId) || null,
|
||||
destroy() { col.remove(); mounts.clear() },
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import { defineConfig, loadEnv } from 'vite'
|
|||
import vue from '@vitejs/plugin-vue'
|
||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||
import { autoSpaDevPlugin } from './internal/playground/dev-plugin.mjs'
|
||||
import { stripShadowCssCommentsPlugin } from './internal/vite-plugin-strip-shadow-css-comments.mjs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { readdirSync, statSync, existsSync, readFileSync } from 'node:fs'
|
||||
|
|
@ -87,7 +88,10 @@ export default defineConfig(({ command, mode }) => {
|
|||
// artifacts. Default FALSE strips it from every bundle (dead-branch +
|
||||
// treeshake); an explicitly-flagged platform build sets DOXA_QA=1.
|
||||
define: { __DOXA_QA__: JSON.stringify(process.env.DOXA_QA === '1') },
|
||||
plugins: [vue(), cssInjectedByJsPlugin(), autoSpaDevPlugin(env.VITE_MAPBOX_TOKEN || process.env.VITE_MAPBOX_TOKEN || '')],
|
||||
// Build only: shadow-DOM CSS is injected from useShadowStyles(`…`) literals that
|
||||
// ship verbatim, so their block comments are stripped before the Vue compiler
|
||||
// runs (enforce: 'pre'). Dev keeps the comments.
|
||||
plugins: [stripShadowCssCommentsPlugin(), vue(), cssInjectedByJsPlugin(), autoSpaDevPlugin(env.VITE_MAPBOX_TOKEN || process.env.VITE_MAPBOX_TOKEN || '')],
|
||||
resolve: {
|
||||
alias: { '@map': resolve(__dirname, 'library') },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
{
|
||||
"//": "THE single build-config for this STANDALONE doxa-maps bundler. Edit this ONE file, then run `bun run build`. FIELDS: 'name' = the output folder name the tree is written as. 'paths' = EXTRA folders to copy the whole tree into. EMPTY and normally stays empty: the build writes into public/<name>/ directly, because public/ is the committed served web-root rather than something assembled at build time. STANDALONE NOTE (2026-07-30): the old value ['../../../public/js'] pointed into the dx-life Nuxt app's public dir \u2014 meaningless in this repo, and the whole point of standing this bundler up on its own is that dx-life consumes maps from the CDN instead of hosting a copy. So 'paths' is now EMPTY: the tree is written to ./app/<name>/ only, and that ONE tree is what all three targets consume (Railway/CDN static host, Capacitor webDir, local staging). If you ever want to ALSO publish into a local dx-life checkout, add its path back here. 'embedBaseUrl' = public URL where the <name>/ tree is served, used to build ABSOLUTE paste-anywhere embed snippets for partner sites ('' = RELATIVE ./bundle/profile/... URLs so the dropped folder is position-independent \u2014 keeps the drop-portability gate green and is what Capacitor needs, since the app loads from https://localhost). 'tokenUrl' = the endpoint every generated map page fetches its Mapbox token from \u2014 a URL, NOT a secret. Set it ABSOLUTE (https://doxa.life/api/maps/token) so both a CDN-hosted folder and the mobile app work; the token stays remote and only the URL ships. window.MAP_TOKEN_URL overrides at runtime. Your endpoint must send CORS headers permitting https://localhost (the Capacitor origin) and any partner domain. 'token' = OPT-IN literal token baked into every page \u2014 LEAVE EMPTY; prefer tokenUrl. 'assetsBaseUrl' = where the SHARED asset tree (previews, posters, boundaries) is served from. Vite no longer copies public/ into each bundle (publicDir:false in vite.config.js), so pages are TOLD where the one shared copy lives. EMPTY = a relative path derived per page depth, which keeps a dropped folder self-contained under one web-root. Set it ABSOLUTE (https://maps.doxa.life/assets) for partner embeds and the mobile app, where there is no shared web-root to climb to. Same modular pattern as tokenUrl and window.MAP_APP_API_URL.",
|
||||
"//": "THE single build-config for this STANDALONE doxa-maps bundler. Edit this ONE file, then run `bun run build`. FIELDS: 'name' = the output folder name the tree is written as. 'paths' = EXTRA folders to copy the whole tree into. EMPTY and normally stays empty: the build writes into public/<name>/ directly, because public/ is the committed served web-root rather than something assembled at build time. STANDALONE NOTE (2026-07-30): the old value ['../../../public/js'] pointed into the dx-life Nuxt app's public dir \u2014 meaningless in this repo, and the whole point of standing this bundler up on its own is that dx-life consumes maps from the CDN instead of hosting a copy. So 'paths' is now EMPTY: the tree is written to ./app/<name>/ only, and that ONE tree is what all three targets consume (Railway/CDN static host, Capacitor webDir, local staging). If you ever want to ALSO publish into a local dx-life checkout, add its path back here. 'embedBaseUrl' = public URL where the <name>/ tree is served, used to build ABSOLUTE paste-anywhere embed snippets for partner sites ('' = RELATIVE ./bundle/profile/... URLs so the dropped folder is position-independent \u2014 keeps the drop-portability gate green and is what Capacitor needs, since the app loads from https://localhost). 'tokenUrl' = the endpoint every generated map page fetches its Mapbox token from \u2014 a URL, NOT a secret. Set it ABSOLUTE (https://doxa.life/api/maps/token) so both a CDN-hosted folder and the mobile app work; the token stays remote and only the URL ships. window.MAP_TOKEN_URL overrides at runtime. Your endpoint must send CORS headers permitting https://localhost (the Capacitor origin) and any partner domain. 'token' = OPT-IN literal token baked into every page \u2014 LEAVE EMPTY; prefer tokenUrl. 'assetsBaseUrl' = where the SHARED asset tree (previews, posters, boundaries) is served from. Vite no longer copies public/ into each bundle (publicDir:false in vite.config.js), so pages are TOLD where the one shared copy lives. EMPTY = a relative path derived per page depth, which keeps a dropped folder self-contained under one web-root. Set it ABSOLUTE (https://maps.doxa.life/assets) for partner embeds and the mobile app, where there is no shared web-root to climb to. Same modular pattern as tokenUrl and window.MAP_APP_API_URL. 'dropin.include' = THE drop-in contract, declared here and nowhere else: the only root entries of public/ that `sync-dropin --from-config` copies into a host — index.html (the generated root page, internal/generate-spa.mjs), assets/ (data) and <name>/ (the bundle folders + manifest.json); with --prune everything else in the host's drop-in folder is removed, and `public-hygiene-check --strict` fails on any other root entry.",
|
||||
"name": "doxa-maps",
|
||||
"paths": [],
|
||||
"embedBaseUrl": "",
|
||||
"assetsBaseUrl": "",
|
||||
"tokenUrl": "https://doxa.life/api/maps/token",
|
||||
"token": "",
|
||||
"dropin": {
|
||||
"include": [
|
||||
"index.html",
|
||||
"assets",
|
||||
"doxa-maps"
|
||||
]
|
||||
},
|
||||
"apiBaseUrl": "https://pray.doxa.life",
|
||||
"gatedProfiles": [
|
||||
"key-cities"
|
||||
|
|
|
|||
Loading…
Reference in a new issue