Merge main into bundler-2/work
# Conflicts: # public/doxa-maps/index.html # public/doxa-maps/manifest.json # public/doxa-maps/my-upg-100-list/my-upg-100-list.js # public/doxa-maps/my-upg-100-list/poster-wall/index.html # public/doxa-maps/my-upg-100-list/upg-100-list/index.html # public/doxa-maps/my-upg-100-list/upg-100-mfe/index.html # public/doxa-maps/regions-dashboard/region-dashboards/index.html # public/doxa-maps/regions-dashboard/regions-dashboard.js # public/doxa-maps/regions-dashboard/regions-list/index.html # public/doxa-maps/regions-dashboard/regions-mfe/index.html
This commit is contained in:
commit
874186660e
12 changed files with 229 additions and 13 deletions
86
internal/check-poster-raster.mjs
Normal file
86
internal/check-poster-raster.mjs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// check-poster-raster — a poster with NO pre-rendered image must still download.
|
||||
//
|
||||
// ★ THE POINT: 378 posters ship as .webp — 41 MB in every clone, delivered to every
|
||||
// reader whether or not they open one, and a publish was ALREADY rejected once at a
|
||||
// 74 MB asset tree. Driver: "it would be a big win to not have to have 80mb of
|
||||
// images in a public folder and let the posters get generated by the website."
|
||||
//
|
||||
// The fallback rasterises posters/<id>.html in the browser. This gate proves the
|
||||
// generated image is REAL, because the failure mode here is silent and total:
|
||||
//
|
||||
// ⚠ AN SVG LOADED THROUGH <img> IS AN ISOLATED DOCUMENT. It cannot see the host
|
||||
// page's CSS, so every var() falls back to its initial value — and `fill`'s initial
|
||||
// value is BLACK. A poster that lost its stylesheet downloads as a black rectangle
|
||||
// with no error, no exception and a perfectly normal file size. So this gate
|
||||
// MEASURES PIXELS: mean luminance and colour variety, not "did it produce bytes".
|
||||
//
|
||||
// Usage: node internal/check-poster-raster.mjs [zoneId]
|
||||
// PACKET_ORIGIN (default http://127.0.0.1:4898)
|
||||
import { chromium } from 'playwright'
|
||||
|
||||
const ORIGIN = process.env.PACKET_ORIGIN || 'http://127.0.0.1:4898'
|
||||
const ZONE = process.argv[2] || 'laos'
|
||||
const HTML = `${ORIGIN}/library/scripts/posters/out/posters/${ZONE}.html`
|
||||
|
||||
const b = await chromium.launch()
|
||||
const pg = await b.newPage({ viewport: { width: 1200, height: 900 } })
|
||||
const errs = []; pg.on('pageerror', e => errs.push(String(e).slice(0, 140)))
|
||||
await pg.goto(ORIGIN + '/')
|
||||
|
||||
const r = await pg.evaluate(async ({ ORIGIN, HTML }) => {
|
||||
const { rasterPoster, looksRendered, inlineSvg } =
|
||||
await import(`${ORIGIN}/library/packet/poster-raster.js`)
|
||||
const res = await fetch(HTML)
|
||||
if (!res.ok) return { error: `poster HTML ${res.status}` }
|
||||
const html = await res.text()
|
||||
|
||||
// The stylesheet must actually travel with the svg.
|
||||
const svg = inlineSvg(html)
|
||||
const carriesCss = /<style>/.test(svg)
|
||||
|
||||
const blob = await rasterPoster(html, { scale: 2 })
|
||||
const ok = await looksRendered(blob)
|
||||
|
||||
// Independent pixel read: sample a grid and count distinct coarse colours. A
|
||||
// black rectangle has ~1; a real poster has many.
|
||||
const bmp = await createImageBitmap(blob)
|
||||
const c = document.createElement('canvas')
|
||||
c.width = 200; c.height = 260
|
||||
const x = c.getContext('2d')
|
||||
x.drawImage(bmp, 0, 0, 200, 260)
|
||||
const d = x.getImageData(0, 0, 200, 260).data
|
||||
const seen = new Set()
|
||||
let sum = 0
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
sum += (d[i] + d[i + 1] + d[i + 2]) / 3
|
||||
seen.add(`${d[i] >> 5},${d[i + 1] >> 5},${d[i + 2] >> 5}`)
|
||||
}
|
||||
return { bytes: blob.size, w: bmp.width, h: bmp.height, ok, carriesCss,
|
||||
meanLuma: Math.round(sum / (d.length / 4)), colours: seen.size }
|
||||
}, { ORIGIN, HTML })
|
||||
await b.close()
|
||||
|
||||
if (r.error) { console.error(`POSTER-RASTER: FAIL — ${r.error}`); process.exit(1) }
|
||||
|
||||
const fails = []
|
||||
if (!r.carriesCss) fails.push('the inlined SVG carries no <style> — every fill will render BLACK')
|
||||
if (!r.bytes) fails.push('no image produced')
|
||||
if (r.w < 1000 || r.h < 1000) fails.push(`rasterised at ${r.w}x${r.h} — too small to print`)
|
||||
if (r.meanLuma < 60) fails.push(`mean luminance ${r.meanLuma} — the poster rendered dark/black`)
|
||||
if (r.colours < 12) fails.push(`only ${r.colours} distinct colours — this is not a painted poster`)
|
||||
if (!r.ok) fails.push('looksRendered() rejected the output')
|
||||
if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`)
|
||||
|
||||
console.log(` zone : ${ZONE}`)
|
||||
console.log(` rasterised : ${r.w} x ${r.h}`)
|
||||
console.log(` image bytes : ${(r.bytes / 1024).toFixed(0)} KB`)
|
||||
console.log(` stylesheet inline: ${r.carriesCss}`)
|
||||
console.log(` mean luminance : ${r.meanLuma} (near 0 = black = CSS lost)`)
|
||||
console.log(` distinct colours: ${r.colours}`)
|
||||
|
||||
if (fails.length) {
|
||||
console.error(`\nPOSTER-RASTER: FAIL — ${fails.length} problem(s)`)
|
||||
for (const f of fails) console.error(' - ' + f)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`\nPOSTER-RASTER: PASS — ${ZONE} generates a real ${r.w}x${r.h} poster with no pre-rendered image.`)
|
||||
|
|
@ -161,8 +161,31 @@ async function exportPoster(item) {
|
|||
exportState.value = { label: item.label, kind: 'Poster', phase: 'fetch',
|
||||
message: 'fetching poster', pct: 20 }
|
||||
try {
|
||||
const res = await fetch(`${base.value}/posters/${item.id}.webp`)
|
||||
if (!res.ok) throw new Error(`poster not found (${res.status})`)
|
||||
let res = await fetch(`${base.value}/posters/${item.id}.webp`)
|
||||
if (!res.ok) {
|
||||
// ★ NO PRE-RENDERED IMAGE? GENERATE IT. The 378 .webp posters were 41 MB in
|
||||
// every clone, shipped to every reader whether or not they opened one — and a
|
||||
// publish was already rejected once at a 74 MB asset tree. The poster HTML is
|
||||
// SMALLER than the image it produces (~115 KB gzipped vs ~213 KB) and it is
|
||||
// the source, so the reader picks the resolution instead of being handed one.
|
||||
// This is the same move the prayer packet already makes: generate on click,
|
||||
// ship nothing. A 404 used to end here as "poster not found".
|
||||
exportState.value = { ...exportState.value, phase: 'render',
|
||||
message: 'generating the poster', pct: 45 }
|
||||
const htmlRes = await fetch(`${base.value}/posters/${item.id}.html`)
|
||||
if (!htmlRes.ok) throw new Error(`poster not found (${htmlRes.status})`)
|
||||
const { rasterPoster, looksRendered } = await import('@map/packet/poster-raster.js')
|
||||
const blob = await rasterPoster(await htmlRes.text(), { scale: 2 })
|
||||
// Fail LOUDLY rather than hand over a black rectangle: an SVG in an <img> is
|
||||
// an isolated document, so a missing stylesheet makes every fill fall back to
|
||||
// black with no error at all.
|
||||
if (!(await looksRendered(blob))) throw new Error('the generated poster rendered blank')
|
||||
exportState.value = { ...exportState.value, phase: 'save', message: 'saving', pct: 80 }
|
||||
download(blob, `${props.downloadPrefix}-${item.id}-poster.webp`)
|
||||
exportState.value = { ...exportState.value, phase: 'done', message: 'saved', pct: 100 }
|
||||
setTimeout(() => { exportState.value = null }, 900)
|
||||
return
|
||||
}
|
||||
exportState.value = { ...exportState.value, phase: 'save', message: 'saving', pct: 80 }
|
||||
download(await res.blob(), `${props.downloadPrefix}-${item.id}-poster.webp`)
|
||||
exportState.value = { ...exportState.value, phase: 'done', message: 'saved', pct: 100 }
|
||||
|
|
|
|||
86
library/packet/poster-raster.js
Normal file
86
library/packet/poster-raster.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// poster-raster.js — turn a poster's HTML/SVG into a downloadable image, in the
|
||||
// browser, on click.
|
||||
//
|
||||
// ★ WHY: 378 posters were committed as .webp — 41 MB in every clone, shipped to
|
||||
// every reader whether or not they open one. Driver: "it would be a big win to not
|
||||
// have to have 80mb of images in a public folder and let the posters get generated
|
||||
// by the website." A publish was ALREADY rejected once at a 74 MB asset tree
|
||||
// (git-packed to 57 MiB), which is what not doing this costs.
|
||||
//
|
||||
// THE MOVE IS NOT A REWRITE. generate.mjs is 2,816 lines with 26 Node touchpoints;
|
||||
// porting it to the browser would be a large, risky job. But the poster's shipped
|
||||
// artefact does not have to be a RASTER — the browser can rasterise the SVG itself:
|
||||
//
|
||||
// posters/<id>.html ~115 KB gzipped (text, compresses well)
|
||||
// posters/<id>.webp ~213 KB (already rasterised, one fixed size)
|
||||
//
|
||||
// So the HTML is SMALLER than the image it produces, and it is the source: the
|
||||
// reader picks the resolution at download time instead of being handed one. The
|
||||
// packet already fetches this same HTML to draw its map page, so the file is
|
||||
// already a shipped, proven artefact.
|
||||
//
|
||||
// ⚠ AN SVG LOADED THROUGH <img> IS AN ISOLATED DOCUMENT. It cannot see the host
|
||||
// page's CSS, so every var() falls back to its initial value — and `fill`'s initial
|
||||
// value is BLACK. That is exactly how the packet's map first shipped as a solid
|
||||
// black rectangle with no error. The stylesheet must be INLINED into the SVG, and
|
||||
// the result must be checked by MEASURING the pixels, not by looking at the code.
|
||||
|
||||
/**
|
||||
* Lift the <svg> out of a poster document with its stylesheet inlined, so it
|
||||
* renders standalone.
|
||||
*/
|
||||
export function inlineSvg(posterHtml) {
|
||||
const svg = (posterHtml.match(/<svg[\s\S]*?<\/svg>/) || [])[0];
|
||||
if (!svg) throw new Error('poster HTML contains no <svg>');
|
||||
const css = (posterHtml.match(/<style>([\s\S]*?)<\/style>/) || [])[1] || '';
|
||||
// Strip any nested </style> so the injected block cannot terminate early.
|
||||
const safe = css.replace(/<\/style>/gi, '');
|
||||
return svg.replace(/<svg([^>]*)>/, `<svg$1><style>${safe}</style>`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rasterise a poster to a Blob in the browser.
|
||||
* @param {string} posterHtml the poster document (posters/<id>.html)
|
||||
* @param {object} opts scale (default 2), type ('image/webp'|'image/png'), quality
|
||||
*/
|
||||
export async function rasterPoster(posterHtml, { scale = 2, type = 'image/webp', quality = 0.85 } = {}) {
|
||||
const svg = inlineSvg(posterHtml);
|
||||
// encodeURIComponent, not btoa: the posters carry non-Latin-1 characters (people
|
||||
// group and place names), and btoa throws on those.
|
||||
const url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
await img.decode();
|
||||
|
||||
const w = Math.round(img.naturalWidth * scale);
|
||||
const h = Math.round(img.naturalHeight * scale);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
// White under the art: a transparent canvas prints as black on some viewers, and
|
||||
// the poster's own paper colour only covers its drawn area.
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
ctx.drawImage(img, 0, 0, w, h);
|
||||
|
||||
const blob = await new Promise((res) => canvas.toBlob(res, type, quality));
|
||||
if (!blob) throw new Error('canvas.toBlob returned nothing');
|
||||
return blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this canvas mostly black? A cheap guard against the isolated-document trap
|
||||
* above, so a broken render fails LOUDLY instead of downloading a black rectangle.
|
||||
*/
|
||||
export async function looksRendered(blob) {
|
||||
const bmp = await createImageBitmap(blob);
|
||||
const c = document.createElement('canvas');
|
||||
c.width = 40; c.height = 40;
|
||||
const x = c.getContext('2d');
|
||||
x.drawImage(bmp, bmp.width * 0.4, bmp.height * 0.45, 40, 40, 0, 0, 40, 40);
|
||||
const d = x.getImageData(0, 0, 40, 40).data;
|
||||
let sum = 0;
|
||||
for (let i = 0; i < d.length; i += 4) sum += (d[i] + d[i + 1] + d[i + 2]) / 3;
|
||||
return sum / (d.length / 4) > 40;
|
||||
}
|
||||
|
|
@ -56,6 +56,7 @@
|
|||
"check:heartbeat:self-test": "node internal/check-heartbeat-threshold.mjs --self-test # THREE regression cases, each required to fire ITS OWN assertion: [literal] resolver replaced by the old hard-coded array -> B; [shipped-default] dashboard.config.js heartbeatThreshold changed -> A2; [shipped-mode] heartbeatMode flipped to quantile -> A2. Poisons IN MEMORY, never the file. Testing that the FEATURE works is not testing that the REGRESSION is caught — this gate shipped with only the first and missed the second.",
|
||||
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
|
||||
"check:card-deck": "node internal/check-card-deck.mjs",
|
||||
"check:poster-raster": "node internal/check-poster-raster.mjs",
|
||||
"check:packet-browser": "node internal/check-packet-browser.mjs",
|
||||
"check:packet-render": "node internal/check-packet-render.mjs",
|
||||
"check:template-literal": "node internal/template-literal-check.mjs",
|
||||
|
|
|
|||
|
|
@ -653,7 +653,17 @@ useShadowStyles(
|
|||
// .container to the DETAIL routes too -- RegionDetail.vue:46 and CountryDetail.vue:35 are both
|
||||
// class="container page-content ..." -- so the dashboard half is capped by the same rule.
|
||||
// gap = their .stack--3xl (55.349px), the gap between every block on that page.
|
||||
'.rd-scroll{ flex:1 1 auto; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch;' +
|
||||
// ★ scrollbar-gutter: stable — ported from their base/_globals.scss html rule, and it is
|
||||
// LOAD-BEARING for layout, not cosmetic. MEASURED at 390px: their container is 353.5px
|
||||
// and ours was 367px, because their html reserves the scrollbar gutter always and our
|
||||
// scroller did not. 13.5px, invisible at 1440 where both cap at 1200 — which is exactly
|
||||
// why a desktop-only diff read 68/68 while every mobile width was wrong.
|
||||
// Worse, it CASCADED: at 768 our container came out 707.2px and theirs 693.7px, and
|
||||
// their .switcher flips at 700px of container — so their three goal cards stacked and
|
||||
// ours stayed three-across. One missing property, a different layout.
|
||||
// We cannot set this on the host document from inside a shadow root, and should not;
|
||||
// this element's own scroller is the box every child measures against.
|
||||
'.rd-scroll{ flex:1 1 auto; min-height:0; overflow-y:auto; scrollbar-gutter:stable; -webkit-overflow-scrolling:touch;' +
|
||||
' padding:55.349px 0 73.6142px; display:flex; flex-direction:column; gap:55.349px;' +
|
||||
' container-type:inline-size; }' +
|
||||
'.rd-scroll > *{ box-sizing:border-box; width:calc(90% + 16px); max-width:1200px;' +
|
||||
|
|
|
|||
|
|
@ -277,7 +277,17 @@ useShadowStyles(
|
|||
// in research-dashboard.vue:284. Safe here because nothing in this element is position:fixed --
|
||||
// inline-size containment is what made a fixed scrim pane-relative in check:scrim-viewport.
|
||||
// Side padding is deliberately 0: the .container formula below supplies the gutter, as theirs does.
|
||||
'.rl-scroll{ flex:1 1 auto; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch;' +
|
||||
// ★ scrollbar-gutter: stable — ported from their base/_globals.scss html rule, and it is
|
||||
// LOAD-BEARING for layout, not cosmetic. MEASURED at 390px: their container is 353.5px
|
||||
// and ours was 367px, because their html reserves the scrollbar gutter always and our
|
||||
// scroller did not. 13.5px, invisible at 1440 where both cap at 1200 — which is exactly
|
||||
// why a desktop-only diff read 68/68 while every mobile width was wrong.
|
||||
// Worse, it CASCADED: at 768 our container came out 707.2px and theirs 693.7px, and
|
||||
// their .switcher flips at 700px of container — so their three goal cards stacked and
|
||||
// ours stayed three-across. One missing property, a different layout.
|
||||
// We cannot set this on the host document from inside a shadow root, and should not;
|
||||
// this element's own scroller is the box every child measures against.
|
||||
'.rl-scroll{ flex:1 1 auto; min-height:0; overflow-y:auto; scrollbar-gutter:stable; -webkit-overflow-scrolling:touch;' +
|
||||
' padding:20px 0; display:flex; flex-direction:column; gap:23.5264px;' +
|
||||
' container-type:inline-size; }' +
|
||||
// ★ THE DESKTOP MAX-WIDTH, ported verbatim from DX-life's .container
|
||||
|
|
|
|||
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
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -838,7 +838,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-+z7qRIJAEJa+7I4OzeL88//qZ/9lXLuYHQWDFI0Y5AUtnOUqXCSx8YE6Y8Nx0myd",
|
||||
"integrity": "sha384-pUQBtbB29Jo9rUXZKWEPKnp+qU8zk70BS9rvrfxlb1J7cOJM4llb/jsqN9qZ07V7",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -858,7 +858,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+z7qRIJAEJa+7I4OzeL88//qZ/9lXLuYHQWDFI0Y5AUtnOUqXCSx8YE6Y8Nx0myd\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-pUQBtbB29Jo9rUXZKWEPKnp+qU8zk70BS9rvrfxlb1J7cOJM4llb/jsqN9qZ07V7\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -872,7 +872,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+z7qRIJAEJa+7I4OzeL88//qZ/9lXLuYHQWDFI0Y5AUtnOUqXCSx8YE6Y8Nx0myd\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-pUQBtbB29Jo9rUXZKWEPKnp+qU8zk70BS9rvrfxlb1J7cOJM4llb/jsqN9qZ07V7\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -886,7 +886,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-+z7qRIJAEJa+7I4OzeL88//qZ/9lXLuYHQWDFI0Y5AUtnOUqXCSx8YE6Y8Nx0myd\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-pUQBtbB29Jo9rUXZKWEPKnp+qU8zk70BS9rvrfxlb1J7cOJM4llb/jsqN9qZ07V7\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-+z7qRIJAEJa+7I4OzeL88//qZ/9lXLuYHQWDFI0Y5AUtnOUqXCSx8YE6Y8Nx0myd" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-pUQBtbB29Jo9rUXZKWEPKnp+qU8zk70BS9rvrfxlb1J7cOJM4llb/jsqN9qZ07V7" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-+z7qRIJAEJa+7I4OzeL88//qZ/9lXLuYHQWDFI0Y5AUtnOUqXCSx8YE6Y8Nx0myd" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-pUQBtbB29Jo9rUXZKWEPKnp+qU8zk70BS9rvrfxlb1J7cOJM4llb/jsqN9qZ07V7" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-+z7qRIJAEJa+7I4OzeL88//qZ/9lXLuYHQWDFI0Y5AUtnOUqXCSx8YE6Y8Nx0myd" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-pUQBtbB29Jo9rUXZKWEPKnp+qU8zk70BS9rvrfxlb1J7cOJM4llb/jsqN9qZ07V7" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue