Update from source ccc2b1bb
This commit is contained in:
parent
8757b31228
commit
92abbb468b
50 changed files with 6281 additions and 395 deletions
1
data/boundaries/gb-IND-ADM1-full.geojson
Normal file
1
data/boundaries/gb-IND-ADM1-full.geojson
Normal file
File diff suppressed because one or more lines are too long
14
data/boundaries/gb-IND-ADM1-full.meta.json
Normal file
14
data/boundaries/gb-IND-ADM1-full.meta.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"file": "gb-IND-ADM1-full.geojson",
|
||||
"units": 36,
|
||||
"source": "geoBoundaries gbOpen (www.geoboundaries.org)",
|
||||
"boundaryID": "IND-ADM1-1811400",
|
||||
"boundarySource": "DataMeet India community, Election Commission of India",
|
||||
"licence": "Creative Commons Attribution 2.5 India (CC BY 2.5 IN)",
|
||||
"licenceSource": "github.com/datameet/maps",
|
||||
"yearRepresented": "2011",
|
||||
"sourceUpdated": "Thu Jan 19 07:31:04 2023",
|
||||
"fetchedFrom": "https://github.com/wmgeolab/geoBoundaries/raw/9469f09/releaseData/gbOpen/IND/ADM1/geoBoundaries-IND-ADM1_simplified.geojson",
|
||||
"processing": "coordinates rounded to 3 decimals, consecutive duplicate points dropped (library/scripts/boundaries/source-full-adm1.mjs)",
|
||||
"note": "Complete ADM1. The curated gb-IND-ADM1.geojson (the 100-list partition, listId-tagged) is a different file and is not replaced."
|
||||
}
|
||||
23
data/internet/README.md
Normal file
23
data/internet/README.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# data/internet/ — internet use below the country level
|
||||
|
||||
Source files for `data/tables/internet-by-adm1.json`, which
|
||||
`plugin-designs/cities-internet/scripts/build-adm1-internet.mjs` writes. Edit the sources here and
|
||||
re-run the script; never edit the table by hand.
|
||||
|
||||
## nfhs5-internet-by-state.csv
|
||||
- **What:** India, National Family Health Survey 5 (NFHS-5, 2019–21, IIPS / Ministry of Health and
|
||||
Family Welfare), indicators 18 and 19: *Women / Men (age 15–49) who have ever used the internet (%)*,
|
||||
by state/UT, urban / rural / total (plus the NFHS-4 total).
|
||||
- **From:** the two internet rows of `NFHS-5-States.csv` in https://github.com/pratapvardhan/NFHS-5 at
|
||||
commit `93c67fed2403c8e533d178d293c5ba0411892b1e`, a CSV transcription of the official state fact sheets
|
||||
(http://rchiips.org/nfhs/). The transcriber notes that quality checks were minimal; the figures
|
||||
here were spot-checked against the national fact sheet (India: women 33.3%, men 57.1%).
|
||||
- **Licence:** the CSV is **CC BY 4.0**. Attribution: "NFHS-5 (IIPS/MoHFW) state fact sheets, via
|
||||
pratapvardhan/NFHS-5 (CC BY 4.0)".
|
||||
- **Measure:** a SURVEY SHARE OF PEOPLE (0–100%): has this person ever used the internet. It is
|
||||
not a subscription count (TRAI's "subscribers per 100" is a different measure and can exceed 100).
|
||||
|
||||
## adm1-crosswalk-IND.json
|
||||
Source state names → the `shapeName` of `data/boundaries/gb-IND-ADM1-full.geojson` (all 36 states,
|
||||
served as `assets/boundaries/gb-IND-ADM1-full.geojson`; source and licence in its `.meta.json`). Every source name must appear here, with either
|
||||
the boundary name or `null` plus a reason. The build fails on anything else, never guesses.
|
||||
118
data/internet/adm1-crosswalk-IND.json
Normal file
118
data/internet/adm1-crosswalk-IND.json
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
{
|
||||
"_about": "NFHS-5 state name -> gb-IND-ADM1-full.geojson shapeName. null = no geometry to draw it on, with the reason. Checked by build-adm1-internet.mjs.",
|
||||
"map": {
|
||||
"Andaman & Nicobar Islands": {
|
||||
"adm1": "Andaman and Nicobar Islands"
|
||||
},
|
||||
"Andhra Pradesh": {
|
||||
"adm1": "Andhra Pradesh"
|
||||
},
|
||||
"Arunachal Pradesh": {
|
||||
"adm1": "Arunāchal Pradesh"
|
||||
},
|
||||
"Assam": {
|
||||
"adm1": "Assam"
|
||||
},
|
||||
"Bihar": {
|
||||
"adm1": "Bihār"
|
||||
},
|
||||
"Chandigarh": {
|
||||
"adm1": "Chandīgarh"
|
||||
},
|
||||
"Chhattisgarh": {
|
||||
"adm1": "Chhattīsgarh"
|
||||
},
|
||||
"Dadra & Nagar Haveli and Daman & Diu": {
|
||||
"adm1": "Dādra and Nagar Haveli and Damān and Diu"
|
||||
},
|
||||
"Goa": {
|
||||
"adm1": "Goa"
|
||||
},
|
||||
"Gujarat": {
|
||||
"adm1": "Gujarāt"
|
||||
},
|
||||
"Haryana": {
|
||||
"adm1": "Haryāna"
|
||||
},
|
||||
"Himachal Pradesh": {
|
||||
"adm1": "Himāchal Pradesh"
|
||||
},
|
||||
"India": {
|
||||
"adm1": null,
|
||||
"national": true,
|
||||
"reason": "the national row, kept as the national reference"
|
||||
},
|
||||
"Jammu & Kashmir": {
|
||||
"adm1": "Jammu and Kashmīr"
|
||||
},
|
||||
"Jharkhand": {
|
||||
"adm1": "Jhārkhand"
|
||||
},
|
||||
"Karnataka": {
|
||||
"adm1": "Karnātaka"
|
||||
},
|
||||
"Kerala": {
|
||||
"adm1": "Kerala"
|
||||
},
|
||||
"Ladakh": {
|
||||
"adm1": "Ladākh"
|
||||
},
|
||||
"Lakshadweep": {
|
||||
"adm1": "Lakshadweep"
|
||||
},
|
||||
"Madhya Pradesh": {
|
||||
"adm1": "Madhya Pradesh"
|
||||
},
|
||||
"Maharashtra": {
|
||||
"adm1": "Mahārāshtra"
|
||||
},
|
||||
"Manipur": {
|
||||
"adm1": "Manipur"
|
||||
},
|
||||
"Meghalaya": {
|
||||
"adm1": "Meghālaya"
|
||||
},
|
||||
"Mizoram": {
|
||||
"adm1": "Mizoram"
|
||||
},
|
||||
"NCT Delhi": {
|
||||
"adm1": "Delhi"
|
||||
},
|
||||
"Nagaland": {
|
||||
"adm1": "Nāgāland"
|
||||
},
|
||||
"Odisha": {
|
||||
"adm1": "Odisha"
|
||||
},
|
||||
"Puducherry": {
|
||||
"adm1": "Puducherry"
|
||||
},
|
||||
"Punjab": {
|
||||
"adm1": "Punjab"
|
||||
},
|
||||
"Rajasthan": {
|
||||
"adm1": "Rājasthān"
|
||||
},
|
||||
"Sikkim": {
|
||||
"adm1": "Sikkim"
|
||||
},
|
||||
"Tamil Nadu": {
|
||||
"adm1": "Tamil Nādu"
|
||||
},
|
||||
"Telangana": {
|
||||
"adm1": "Telangāna"
|
||||
},
|
||||
"Tripura": {
|
||||
"adm1": "Tripura"
|
||||
},
|
||||
"Uttar Pradesh": {
|
||||
"adm1": "Uttar Pradesh"
|
||||
},
|
||||
"Uttarakhand": {
|
||||
"adm1": "Uttarākhand"
|
||||
},
|
||||
"West Bengal": {
|
||||
"adm1": "West Bengal"
|
||||
}
|
||||
}
|
||||
}
|
||||
75
data/internet/nfhs5-internet-by-state.csv
Normal file
75
data/internet/nfhs5-internet-by-state.csv
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
state,state_code,indicator,nfhs5_urban,nfhs5_rural,nfhs5_total,nfhs4_total
|
||||
India,,18. Women who have ever used the internet (%),51.8,24.6,33.3,
|
||||
India,,19. Men who have ever used the internet (%),72.5,48.7,57.1,
|
||||
Andaman & Nicobar Islands,AN,18. Women who have ever used the internet (%),44.1,27.9,34.8,
|
||||
Andaman & Nicobar Islands,AN,19. Men who have ever used the internet (%),54.6,41.1,46.5,
|
||||
Andhra Pradesh,AP,18. Women who have ever used the internet (%),33.9,15.4,21.0,
|
||||
Andhra Pradesh,AP,19. Men who have ever used the internet (%),65.1,41.5,48.8,
|
||||
Arunachal Pradesh,AR,18. Women who have ever used the internet (%),70.0,49.6,52.9,
|
||||
Arunachal Pradesh,AR,19. Men who have ever used the internet (%),86.9,68.5,71.6,
|
||||
Assam,AS,18. Women who have ever used the internet (%),49.0,24.4,28.2,
|
||||
Assam,AS,19. Men who have ever used the internet (%),67.4,37.8,42.3,
|
||||
Bihar,BR,18. Women who have ever used the internet (%),38.4,17.0,20.6,
|
||||
Bihar,BR,19. Men who have ever used the internet (%),58.4,39.4,43.6,
|
||||
Chandigarh,CH,18. Women who have ever used the internet (%),75.2,,75.2,
|
||||
Chandigarh,CH,19. Men who have ever used the internet (%),91.9,,91.9,
|
||||
Chhattisgarh,CT,18. Women who have ever used the internet (%),44.5,20.8,26.7,
|
||||
Chhattisgarh,CT,19. Men who have ever used the internet (%),75.4,50.4,56.3,
|
||||
Dadra & Nagar Haveli and Daman & Diu,DD,18. Women who have ever used the internet (%),49.4,23.8,36.7,
|
||||
Dadra & Nagar Haveli and Daman & Diu,DD,19. Men who have ever used the internet (%),76.2,61.3,68.3,
|
||||
NCT Delhi,DL,18. Women who have ever used the internet (%),63.7,69.2,63.8,
|
||||
NCT Delhi,DL,19. Men who have ever used the internet (%),85.1,87.4,85.2,
|
||||
Goa,GA,18. Women who have ever used the internet (%),78.1,68.3,73.7,
|
||||
Goa,GA,19. Men who have ever used the internet (%),86.1,76.6,82.9,
|
||||
Gujarat,GJ,18. Women who have ever used the internet (%),48.9,17.5,30.8,
|
||||
Gujarat,GJ,19. Men who have ever used the internet (%),72.9,48.0,58.9,
|
||||
Himachal Pradesh,HP,18. Women who have ever used the internet (%),78.9,45.2,49.7,
|
||||
Himachal Pradesh,HP,19. Men who have ever used the internet (%),83.7,65.1,67.9,
|
||||
Haryana,HR,18. Women who have ever used the internet (%),60.2,42.8,48.4,
|
||||
Haryana,HR,19. Men who have ever used the internet (%),79.7,68.8,72.4,
|
||||
Jharkhand,JH,18. Women who have ever used the internet (%),57.8,22.7,31.4,
|
||||
Jharkhand,JH,19. Men who have ever used the internet (%),70.8,53.2,58.0,
|
||||
Jammu & Kashmir,JK,18. Women who have ever used the internet (%),55.0,38.9,43.3,
|
||||
Jammu & Kashmir,JK,19. Men who have ever used the internet (%),79.4,68.8,72.0,
|
||||
Karnataka,KA,18. Women who have ever used the internet (%),50.1,24.8,35.0,
|
||||
Karnataka,KA,19. Men who have ever used the internet (%),71.5,55.6,62.4,
|
||||
Kerala,KL,18. Women who have ever used the internet (%),64.9,57.5,61.1,
|
||||
Kerala,KL,19. Men who have ever used the internet (%),78.3,74.2,76.1,
|
||||
Lakshadweep,LD,18. Women who have ever used the internet (%),61.8,36.0,56.4,
|
||||
Lakshadweep,LD,19. Men who have ever used the internet (%),81.5,77.0,80.3,
|
||||
Ladakh,LH,18. Women who have ever used the internet (%),66.5,54.0,56.4,
|
||||
Ladakh,LH,19. Men who have ever used the internet (%),82.9,64.3,67.8,
|
||||
Maharashtra,MH,18. Women who have ever used the internet (%),54.3,23.7,38.0,
|
||||
Maharashtra,MH,19. Men who have ever used the internet (%),76.8,47.2,61.5,
|
||||
Meghalaya,ML,18. Women who have ever used the internet (%),57.8,28.0,34.7,
|
||||
Meghalaya,ML,19. Men who have ever used the internet (%),59.2,38.5,42.1,
|
||||
Manipur,MN,18. Women who have ever used the internet (%),50.8,40.4,44.8,
|
||||
Manipur,MN,19. Men who have ever used the internet (%),81.5,68.2,73.9,
|
||||
Madhya Pradesh,MP,18. Women who have ever used the internet (%),46.5,20.1,26.9,
|
||||
Madhya Pradesh,MP,19. Men who have ever used the internet (%),72.7,49.3,55.7,
|
||||
Mizoram,MZ,18. Women who have ever used the internet (%),83.8,48.0,67.6,
|
||||
Mizoram,MZ,19. Men who have ever used the internet (%),92.7,63.9,79.7,
|
||||
Nagaland,NL,18. Women who have ever used the internet (%),66.5,40.3,49.9,
|
||||
Nagaland,NL,19. Men who have ever used the internet (%),81.0,55.2,64.6,
|
||||
Odisha,OR,18. Women who have ever used the internet (%),39.7,21.3,24.9,
|
||||
Odisha,OR,19. Men who have ever used the internet (%),64.2,47.2,50.7,
|
||||
Punjab,PB,18. Women who have ever used the internet (%),64.1,48.8,54.8,
|
||||
Punjab,PB,19. Men who have ever used the internet (%),85.6,73.0,78.2,
|
||||
Puducherry,PY,18. Women who have ever used the internet (%),66.9,50.4,61.9,
|
||||
Puducherry,PY,19. Men who have ever used the internet (%),85.1,69.4,80.7,
|
||||
Rajasthan,RJ,18. Women who have ever used the internet (%),56.1,30.8,36.9,
|
||||
Rajasthan,RJ,19. Men who have ever used the internet (%),81.7,59.4,65.2,
|
||||
Sikkim,SK,18. Women who have ever used the internet (%),90.0,68.1,76.7,
|
||||
Sikkim,SK,19. Men who have ever used the internet (%),94.2,69.5,78.2,
|
||||
Telangana,TG,18. Women who have ever used the internet (%),43.9,15.8,26.5,
|
||||
Telangana,TG,19. Men who have ever used the internet (%),72.3,46.7,57.4,
|
||||
Tamil Nadu,TN,18. Women who have ever used the internet (%),55.8,39.2,46.9,
|
||||
Tamil Nadu,TN,19. Men who have ever used the internet (%),76.1,64.9,70.2,
|
||||
Tripura,TR,18. Women who have ever used the internet (%),36.6,17.7,22.9,
|
||||
Tripura,TR,19. Men who have ever used the internet (%),47.0,45.2,45.7,
|
||||
Uttar Pradesh,UP,18. Women who have ever used the internet (%),50.2,24.5,30.6,
|
||||
Uttar Pradesh,UP,19. Men who have ever used the internet (%),72.4,54.2,59.1,
|
||||
Uttarakhand,UT,18. Women who have ever used the internet (%),58.4,39.4,45.1,
|
||||
Uttarakhand,UT,19. Men who have ever used the internet (%),82.1,71.2,74.6,
|
||||
West Bengal,WB,18. Women who have ever used the internet (%),48.1,14.0,25.5,
|
||||
West Bengal,WB,19. Men who have ever used the internet (%),64.6,38.3,46.7,
|
||||
|
2446
data/tables/internet-by-adm1.json
Normal file
2446
data/tables/internet-by-adm1.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -32,7 +32,23 @@ import { resolve, dirname } from 'node:path'
|
|||
import { pathToFileURL, fileURLToPath } from 'node:url'
|
||||
import { requirePathInThisCheckout } from './_serves-this-tree.mjs'
|
||||
|
||||
const target = process.argv[2] || process.env.PACKET_HTML
|
||||
// ★ --expect-funnel TURNS AN OPTIONAL PAGE INTO A DECLARED REQUIREMENT, and it exists because of a
|
||||
// real near-miss (regions-plugin, 2026-09-26, reported unprompted). A merge resolved with
|
||||
// `git checkout --ours` silently deleted the printPacket call site that supplies the funnel data —
|
||||
// 60 lines — AND EVERY GATE STAYED GREEN. It had to: the funnel page is optional by design
|
||||
// (buildPacket omits it when the arg is null), so a packet with no page 2 is not malformed, just
|
||||
// smaller. My own design choice is what made the deletion invisible.
|
||||
//
|
||||
// ⚠ THE FIX IS NOT "ALWAYS REQUIRE IT". That would fail the 11 pre-funnel laos packets and any host
|
||||
// that legitimately has no funnel data — a false red, which costs as much as a false green. Instead
|
||||
// the CALLER, who knows whether this packet was built with funnel data, says so. An absence is then
|
||||
// either DECLARED (no flag, fine) or a FAILURE (flag passed, page missing) — never a silent nothing.
|
||||
// ⚠ Flags are filtered out of the positional read: a bare `--expect-funnel` was otherwise taken as
|
||||
// the packet path, which is the flag-eaten-as-positional trap this repo has hit three times.
|
||||
const EXPECT_FUNNEL = process.argv.includes('--expect-funnel')
|
||||
|| process.env.PACKET_EXPECT_FUNNEL === '1'
|
||||
const positional = process.argv.slice(2).filter((a) => !a.startsWith('--'))
|
||||
const target = positional[0] || process.env.PACKET_HTML
|
||||
if (!target) {
|
||||
console.log('PACKET-RENDER: SKIP — no packet given (pass a path or set PACKET_HTML).')
|
||||
console.log(' The packet is generated on demand, so this gate is opt-in rather than')
|
||||
|
|
@ -220,6 +236,29 @@ const r = await page.evaluate(() => {
|
|||
// exact about list pages whether or not this page is there.
|
||||
funnel: document.querySelectorAll('.funnel-title').length,
|
||||
funnelSteps: document.querySelectorAll('.funnel-mine').length,
|
||||
// ★ MEASURED SO THE GATE CAN REPORT WHAT IT ASSERTED. rega: "the gate prints no funnel line, even
|
||||
// with --expect-funnel ... a reader cannot see WHAT it asserted about page 2." A silent pass is
|
||||
// the mirror of a printed-but-unasserted number: the check is real and the evidence is invisible.
|
||||
// ⚠ ZERO POPULATION IS COUNTED ON PURPOSE. The reader's row printed "0 people" in every packet
|
||||
// (Number(null) is 0, so the finite-number guard admitted a deliberate no-data marker). Fixed in
|
||||
// the renderer — and counted here so the fix cannot silently regress.
|
||||
funnelRows: (() => {
|
||||
const sheet = document.querySelector('.funnel-title')?.closest('.sheet')
|
||||
return sheet ? sheet.querySelectorAll('div[style*="line-height:1"]').length : 0
|
||||
})(),
|
||||
funnelBars: (() => {
|
||||
const sheet = document.querySelector('.funnel-title')?.closest('.sheet')
|
||||
return sheet ? sheet.querySelectorAll('div[style*="height:16px"]').length : 0
|
||||
})(),
|
||||
funnelZeroPop: (() => {
|
||||
const sheet = document.querySelector('.funnel-title')?.closest('.sheet')
|
||||
if (!sheet) return 0
|
||||
return [...sheet.querySelectorAll('div')]
|
||||
.filter((d) => !d.children.length && /(^|\D)0(\D|$)/.test(d.textContent.trim())
|
||||
&& d.textContent.trim().length < 24 && /\d/.test(d.textContent))
|
||||
.filter((d) => /^0(\.0+)?\s+\D/.test(d.textContent.trim())).length
|
||||
})(),
|
||||
funnelShare: (document.querySelector('.funnel-share')?.textContent || '').trim().slice(0, 80),
|
||||
// ★ A CARD MUST NAME ITS PEOPLE GROUP. Counting .card proves the element exists; the spec says
|
||||
// pages 2..n carry "the people groups, 20 per page, with photos" — a card with a photo and a
|
||||
// BLANK NAME satisfies every count here: the total holds, the photo/typographic split holds,
|
||||
|
|
@ -591,6 +630,29 @@ if (r.funnel && !r.funnelSteps) {
|
|||
fails.push('the funnel page rendered its title but no share figure (.funnel-mine) — '
|
||||
+ 'page 2 is asserting a narrowing it never states')
|
||||
}
|
||||
// ★ AND IF THE CALLER DECLARED IT, ITS ABSENCE IS A FAILURE. See the --expect-funnel note at the top:
|
||||
// a lost call site produces a packet that is CORRECT but missing page 2, which is why nothing caught
|
||||
// a merge deleting one.
|
||||
// ★ EVERY FUNNEL ROW MUST HAVE A BAR. rega measured the first version: the reader's step had no bar,
|
||||
// so the funnel never visibly narrowed to "yours" — a funnel whose final term is not drawn as part of
|
||||
// the funnel fails at the one thing it is for. Rows and bars are counted separately so the next
|
||||
// regression is a mismatch rather than an invisible omission.
|
||||
if (r.funnel && r.funnelRows !== r.funnelBars) {
|
||||
fails.push(`the funnel page has ${r.funnelRows} step row(s) but ${r.funnelBars} bar(s) — `
|
||||
+ 'every step must be drawn as part of the funnel, including the reader\'s own')
|
||||
}
|
||||
// ★ AND NO STEP MAY PRINT A ZERO POPULATION. "0 people" appeared on the reader's row of every packet
|
||||
// because Number(null) is 0 and the renderer's guard tested finiteness rather than PRESENCE. A people
|
||||
// group with no people is not a fact; it is missing data wearing a number's clothes.
|
||||
if (r.funnel && r.funnelZeroPop) {
|
||||
fails.push(`${r.funnelZeroPop} funnel row(s) print a ZERO population — absent data must be hidden, `
|
||||
+ 'not rendered as the number 0 (Number(null) === 0, so test presence, not finiteness)')
|
||||
}
|
||||
if (EXPECT_FUNNEL && !r.funnel) {
|
||||
fails.push('--expect-funnel was passed but this packet has NO funnel page (.funnel-title) — '
|
||||
+ 'the caller that supplies funnel data to buildPacket is missing or passing null, '
|
||||
+ 'and a packet without page 2 is otherwise indistinguishable from a correct one')
|
||||
}
|
||||
|
||||
// ── ...AND THAT ALONE IS NOT ENOUGH: IT ONLY PROVES SELF-CONSISTENCY ────────
|
||||
// ⚠ MUTATION-TESTED, AND THE FIRST VERSION OF THIS GATE FAILED THE TEST. Deleting a
|
||||
|
|
@ -646,6 +708,9 @@ console.log(` sheets/PDF pages : ${r.sheets} / ${pages}`)
|
|||
console.log(` printed artefact : ${(pdfBytes / 1024 / 1024).toFixed(1)}MB, ${pdfFonts} embedded font(s), `
|
||||
+ `${pdfImages} image(s) for ${r.distinctImgSrc} distinct source(s)`)
|
||||
console.log(` people cards : ${r.cards} (${r.imgs} photos, ${r.nophoto} typographic)`)
|
||||
console.log(` funnel (page 2) : ${r.funnel
|
||||
? `${r.funnelRows} row(s), ${r.funnelBars} bar(s)${r.funnelShare ? ` — "${r.funnelShare}"` : ' — no share line'}`
|
||||
: 'absent' + (EXPECT_FUNNEL ? ' (REQUIRED — see FAIL above)' : ' (not required)')}`)
|
||||
console.log(` prayer points : ${r.points} / ${POINTS_EXPECT}`)
|
||||
console.log(` wheel segments : ${r.wheelSegs} (${r.wheelLabels}/${WHEEL_LABELS} labelled)`)
|
||||
console.log(` Mark 5 in full : ${r.mark5Len} chars`
|
||||
|
|
|
|||
|
|
@ -43,9 +43,32 @@ if (!files.length) {
|
|||
}
|
||||
|
||||
const gate = join(HERE, 'check-packet-render.mjs')
|
||||
|
||||
// ★ THE FUNNEL PAGE IS REQUIRED HERE, and this line is the half of the fix that was missing.
|
||||
// check-packet-render gained --expect-funnel after a merge deleted the call site supplying the funnel
|
||||
// data and every gate stayed green (regions-plugin, 2026-09-26). But a DECLARABLE expectation that
|
||||
// nothing declares is not a closed hole — it is an invisible defect traded for a visible obligation.
|
||||
// I said as much to regions and then owed the second half.
|
||||
//
|
||||
// ⚠ WHY IT CAN BE UNCONDITIONAL HERE. Every packet in out/ is produced by
|
||||
// library/scripts/posters/packet.mjs, which always passes FUNNEL to buildPacket. So a packet in this
|
||||
// directory WITHOUT page 2 was either generated before the funnel existed or generated by a caller
|
||||
// that dropped the argument — and both are things this sweep should say out loud. Verified
|
||||
// 2026-09-26 across all 16 packets in out/ (4 fixtures, the ar tibet, and all 11 laos locales
|
||||
// regenerated): every one has page 2, so this requires a property the pipeline actually holds rather
|
||||
// than one it aspires to.
|
||||
// ⚠ ESCAPE HATCH, deliberately awkward to reach: PACKETS_ALL_NO_FUNNEL=1. If a legitimate host ever
|
||||
// generates funnel-less packets, that is a real state and this flag records it — but it must be a
|
||||
// decision someone takes, not a default that quietly tolerates a missing page.
|
||||
const EXPECT_FUNNEL = process.env.PACKETS_ALL_NO_FUNNEL !== '1'
|
||||
if (!EXPECT_FUNNEL) {
|
||||
console.log('PACKETS-ALL: ⚠ PACKETS_ALL_NO_FUNNEL=1 — page 2 is NOT required this run.')
|
||||
}
|
||||
|
||||
const failed = []
|
||||
for (const f of files) {
|
||||
const res = spawnSync(process.execPath, [gate, join(DIR, f)], { encoding: 'utf8' })
|
||||
const args = EXPECT_FUNNEL ? [gate, join(DIR, f), '--expect-funnel'] : [gate, join(DIR, f)]
|
||||
const res = spawnSync(process.execPath, args, { encoding: 'utf8' })
|
||||
const ok = res.status === 0
|
||||
console.log(` ${ok ? '✓' : '✗'} ${f}`)
|
||||
if (!ok) {
|
||||
|
|
|
|||
|
|
@ -1515,6 +1515,10 @@ function main() {
|
|||
[join(dataDir, 'adm0', 'world-adm0.geojson'), 'world-adm0.geojson'],
|
||||
[join(dataDir, 'gb-IND-ADM1.geojson'), 'gb-IND-ADM1.geojson'],
|
||||
[join(dataDir, 'gb-CHN-ADM1.geojson'), 'gb-CHN-ADM1.geojson'],
|
||||
// COMPLETE India ADM1 (36 units, geoBoundaries gbOpen, CC BY 2.5 IN; source + licence in its meta),
|
||||
// for maps that colour every state. The curated file above is UPG-100's 34-unit partition.
|
||||
[resolve(__dirname, '..', 'data', 'boundaries', 'gb-IND-ADM1-full.geojson'), 'gb-IND-ADM1-full.geojson'],
|
||||
[resolve(__dirname, '..', 'data', 'boundaries', 'gb-IND-ADM1-full.meta.json'), 'gb-IND-ADM1-full.meta.json'],
|
||||
]) if (existsSync(from)) cpSync(from, join(bDir, to))
|
||||
console.log('[generate-tree] gathered boundary geojson')
|
||||
|
||||
|
|
@ -1573,6 +1577,8 @@ function main() {
|
|||
'boundaries/world-adm0.geojson',
|
||||
'boundaries/gb-IND-ADM1.geojson',
|
||||
'boundaries/gb-CHN-ADM1.geojson',
|
||||
'boundaries/gb-IND-ADM1-full.geojson', // all 36 states (Cities with Internet's NFHS layer)
|
||||
'boundaries/gb-IND-ADM1-full.meta.json', // its source and licence, served beside it
|
||||
// people-groups — all-people-groups.vue (the slim JSON only; NOT the 16 MB CSV,
|
||||
// which no element fetches and which dropin.exclude already withholds).
|
||||
'people-groups/people-groups-slim.json',
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "تطبيقات المراسلة"
|
||||
},
|
||||
"loading": "جارٍ تحميل {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "الهند حسب الولاية: سبق لهم استخدام الإنترنت",
|
||||
"women": "النساء",
|
||||
"men": "الرجال",
|
||||
"total": "الإجمالي",
|
||||
"urban": "الحضر",
|
||||
"rural": "الريف",
|
||||
"source": "المسح NFHS-5 (2019–2021)، الأشخاص من 15 إلى 49 عامًا: نسبة من سبق لهم استخدام الإنترنت. مسح للأشخاص، لا عدد للاشتراكات.",
|
||||
"missing": "لا توجد بعد حدود ولاية لـ {names}: أرقامها موجودة في البيانات، لا على الخريطة."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Messenger"
|
||||
},
|
||||
"loading": "{name} wird geladen…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "Indien nach Bundesstaat: haben das Internet schon genutzt",
|
||||
"women": "Frauen",
|
||||
"men": "Männer",
|
||||
"total": "Gesamt",
|
||||
"urban": "Stadt",
|
||||
"rural": "Land",
|
||||
"source": "NFHS-5 (2019–21), Personen im Alter von 15–49 Jahren: Anteil derer, die das Internet schon einmal genutzt haben. Eine Befragung von Menschen, keine Zählung von Anschlüssen.",
|
||||
"missing": "Noch keine Staatsgrenze für {names}: Ihre Werte stehen in den Daten, nicht auf der Karte."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Messaging Apps"
|
||||
},
|
||||
"loading": "Loading {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "India by state: ever used the internet",
|
||||
"women": "Women",
|
||||
"men": "Men",
|
||||
"total": "Total",
|
||||
"urban": "Urban",
|
||||
"rural": "Rural",
|
||||
"source": "NFHS-5 (2019–21), people aged 15–49: the share who have ever used the internet. A survey of people, not a count of subscriptions.",
|
||||
"missing": "No state boundary yet for {names}: their figures are in the data, not on the map."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Mensajería"
|
||||
},
|
||||
"loading": "Cargando {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "India por estado: han usado internet alguna vez",
|
||||
"women": "Mujeres",
|
||||
"men": "Hombres",
|
||||
"total": "Total",
|
||||
"urban": "Urbano",
|
||||
"rural": "Rural",
|
||||
"source": "NFHS-5 (2019–21), personas de 15 a 49 años: proporción que ha usado internet alguna vez. Una encuesta a personas, no un recuento de suscripciones.",
|
||||
"missing": "Aún no hay límite estatal para {names}: sus cifras están en los datos, no en el mapa."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Messageries"
|
||||
},
|
||||
"loading": "Chargement de {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "Inde par État : ont déjà utilisé Internet",
|
||||
"women": "Femmes",
|
||||
"men": "Hommes",
|
||||
"total": "Total",
|
||||
"urban": "Urbain",
|
||||
"rural": "Rural",
|
||||
"source": "NFHS-5 (2019–21), personnes de 15 à 49 ans : part de celles qui ont déjà utilisé Internet. Une enquête auprès des personnes, pas un décompte d’abonnements.",
|
||||
"missing": "Pas encore de limite d’État pour {names} : leurs chiffres figurent dans les données, pas sur la carte."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "मैसेजिंग ऐप"
|
||||
},
|
||||
"loading": "{name} लोड हो रहा है…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "भारत राज्य अनुसार: कभी इंटरनेट का उपयोग किया",
|
||||
"women": "महिलाएँ",
|
||||
"men": "पुरुष",
|
||||
"total": "कुल",
|
||||
"urban": "शहरी",
|
||||
"rural": "ग्रामीण",
|
||||
"source": "NFHS-5 (2019–21), 15–49 वर्ष के लोग: जिन्होंने कभी इंटरनेट का उपयोग किया है उनका प्रतिशत। यह लोगों का सर्वेक्षण है, सब्सक्रिप्शन की गिनती नहीं।",
|
||||
"missing": "{names} की राज्य सीमा अभी उपलब्ध नहीं है: उनके आँकड़े डेटा में हैं, नक्शे पर नहीं।"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Messaggistica"
|
||||
},
|
||||
"loading": "Caricamento di {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "India per Stato: hanno mai usato internet",
|
||||
"women": "Donne",
|
||||
"men": "Uomini",
|
||||
"total": "Totale",
|
||||
"urban": "Urbano",
|
||||
"rural": "Rurale",
|
||||
"source": "NFHS-5 (2019–21), persone di 15–49 anni: quota di chi ha mai usato internet. Un’indagine sulle persone, non un conteggio di abbonamenti.",
|
||||
"missing": "Ancora nessun confine statale per {names}: i loro dati sono nel dataset, non sulla mappa."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Mensagens"
|
||||
},
|
||||
"loading": "A carregar {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "Índia por estado: já usaram a internet",
|
||||
"women": "Mulheres",
|
||||
"men": "Homens",
|
||||
"total": "Total",
|
||||
"urban": "Urbano",
|
||||
"rural": "Rural",
|
||||
"source": "NFHS-5 (2019–21), pessoas de 15 a 49 anos: proporção que já usou a internet. Uma pesquisa com pessoas, não uma contagem de assinaturas.",
|
||||
"missing": "Ainda não há limite estadual para {names}: os números estão nos dados, não no mapa."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Aplicații de mesagerie"
|
||||
},
|
||||
"loading": "Se încarcă {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "India pe state: au folosit vreodată internetul",
|
||||
"women": "Femei",
|
||||
"men": "Bărbați",
|
||||
"total": "Total",
|
||||
"urban": "Urban",
|
||||
"rural": "Rural",
|
||||
"source": "NFHS-5 (2019–21), persoane de 15–49 de ani: ponderea celor care au folosit vreodată internetul. Un sondaj despre oameni, nu o numărătoare de abonamente.",
|
||||
"missing": "Încă nu există limită de stat pentru {names}: cifrele lor sunt în date, nu pe hartă."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "Мессенджеры"
|
||||
},
|
||||
"loading": "Загрузка: {name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "Индия по штатам: когда-либо пользовались интернетом",
|
||||
"women": "Женщины",
|
||||
"men": "Мужчины",
|
||||
"total": "Всего",
|
||||
"urban": "Город",
|
||||
"rural": "Село",
|
||||
"source": "NFHS-5 (2019–21), люди 15–49 лет: доля тех, кто когда-либо пользовался интернетом. Опрос людей, а не подсчёт подписок.",
|
||||
"missing": "Для {names} ещё нет границы штата: их данные есть в таблице, но не на карте."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,5 +54,15 @@
|
|||
"messaging": "即时通讯"
|
||||
},
|
||||
"loading": "正在加载{name}…"
|
||||
},
|
||||
"adm1": {
|
||||
"title": "印度各邦:曾经使用过互联网",
|
||||
"women": "女性",
|
||||
"men": "男性",
|
||||
"total": "合计",
|
||||
"urban": "城市",
|
||||
"rural": "农村",
|
||||
"source": "NFHS-5(2019–21),15–49 岁人群:曾经使用过互联网的比例。这是对人的调查,而不是订阅数统计。",
|
||||
"missing": "{names} 尚无邦界:其数据在数据集中,但未显示在地图上。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,17 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null })
|
|||
// never claims that band; it states the share the data can defend. Shepherd approved exactly this
|
||||
// (ENDTOKEN-DXS-UPG-KDECIDE), K=27 including the two residual buckets until the agent rules.
|
||||
//
|
||||
// ⚠ "NUMERALS UNTOUCHED" IS ABOUT DIGITS, NOT GROUPING, and those are two properties. I verified the
|
||||
// digits and assumed the rest. rega measured the rendered hi sheet and found Indian grouping —
|
||||
// "8,28,85,63,969 लोग" (lakh/crore, 2-2-3) rather than Western 3-3-3. That is CORRECT hi-IN
|
||||
// formatting, so changing it would be worse than leaving it, and the real risk was never the
|
||||
// grouping itself but INCONSISTENCY: the same number printed two ways in one document.
|
||||
// Measured 2026-09-26, and it is consistent: num() is the ONLY formatter in this renderer, so the
|
||||
// funnel's populations and the people-group cards' populations group identically. Confirmed against
|
||||
// the rendered hi packet — card populations 1,70,000 / 3,37,000 / 8,10,000 are Indian-grouped like
|
||||
// the funnel's. (Only values at or above 100,000 can distinguish the two systems at all; my first
|
||||
// check classified "1,400" and read a mismatch that did not exist.)
|
||||
//
|
||||
// ⚠ NUMERALS STAY WESTERN. num() is toLocaleString(lang), and for the bare `ar`/`hi` tags CLDR's
|
||||
// default numbering system is `latn` — verified: 12,379 and 2,107 in both. Arabic-Indic digits
|
||||
// need an explicit -u-nu-arab, which nothing here asks for. The brief requires numerals untouched,
|
||||
|
|
@ -250,25 +261,23 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null })
|
|||
const BAR_W = W - 2 * M - 300;
|
||||
const width = (n) => Math.max(26, Math.round(BAR_W * (Number(n) / Number(top || 1))));
|
||||
|
||||
const rows = steps.map((s, i) => {
|
||||
const label = t(`funnel_${s.id}`) !== undefined ? t(`funnel_${s.id}`) : s.id;
|
||||
const pop = Number.isFinite(Number(s.population))
|
||||
? `<div style="font:400 15px Georgia,serif;color:#a89f8c;margin-top:1px">${num(s.population)} ${esc(t('funnel_people'))}</div>`
|
||||
: '';
|
||||
// The last IMB step is the network's own list — tint it so the eye lands where the reader is.
|
||||
const fillCol = i === steps.length - 1 ? '#7a3b2e' : '#cdbf9e';
|
||||
return `<div style="margin-bottom:22px">
|
||||
<div style="font:700 34px Georgia,serif;color:#1b1a17;line-height:1">${num(s.groups)}</div>
|
||||
<div style="font:400 18px Georgia,serif;color:#3b3226;margin-top:2px">${esc(label)}</div>
|
||||
${pop}
|
||||
<div style="height:12px;width:${width(s.groups)}px;background:${fillCol};margin-top:6px;border-radius:2px"></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// THIS list, the step the reader is holding. `groups.length` is the packet's own row count, so
|
||||
// the page cannot disagree with the pages that follow it.
|
||||
const mine = groups.length;
|
||||
const total = Number(funnel.listTotal ?? (steps[steps.length - 1] || {}).groups) || 0;
|
||||
// ⚠ THE DENOMINATOR MUST BE THE LIST THE PACKET IS ACTUALLY DRAWN FROM, and it is an EXPLICIT
|
||||
// input for that reason. An earlier version fell back to the last funnel step
|
||||
// (`funnel.listTotal ?? steps[last].groups`), which is 2,107 — DOXA's list. That is right today
|
||||
// because every map shows DOXA, and it becomes WRONG the moment the universal `list` prop lands
|
||||
// (exp-shepherd, library/lists/peopleGroupList.js): under ?list=frontier a Tier A map shows the
|
||||
// DOXA groups that are ALSO frontier, which SPEC-PEOPLE-GROUP-LIST-PROP.md measures at 1,753. The
|
||||
// page would have printed "N of the 2,107" with total confidence while the map showed a different
|
||||
// set — a faithful proxy that silently changes meaning when a new path appears, which is the same
|
||||
// family as inferring "did it work" from a field that only answers "did it isolate".
|
||||
//
|
||||
// So a caller that knows the active list says so. A caller that does not gets NO SHARE LINE rather
|
||||
// than a plausible wrong one: `mine` is always trustworthy (it is this packet's own row count) but
|
||||
// a percentage needs both terms, and half a fraction is not a fact.
|
||||
const total = Number(funnel.listTotal) || 0;
|
||||
const pct = total ? (100 * mine / total) : 0;
|
||||
// ⚠ A SHARE MUST NEVER ROUND TO ZERO — zero reads as "this packet covers none of them", which is
|
||||
// false for every list. Measured against the real catalogue (counts 1..118 of 2,107): one decimal
|
||||
|
|
@ -279,30 +288,130 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null })
|
|||
? String(Math.round(pct))
|
||||
: pct.toFixed(pct >= 1 ? 1 : 2).replace(/\.?0+$/, '');
|
||||
|
||||
const share = funnel.listCount
|
||||
? `<div class="funnel-share" style="font:700 22px Georgia,serif;color:#7a3b2e;margin-top:4px">
|
||||
${esc(fill(t('funnel_share'), { k: num(funnel.listCount), pct: pctStr, total: num(total) }))}</div>`
|
||||
: '';
|
||||
const foot = (funnel.sources && funnel.sources.imb)
|
||||
? esc(fill(t('funnel_foot'), {
|
||||
imb_pub: funnel.sources.imb.publisher || '',
|
||||
imb_date: funnel.sources.imb.captured || '',
|
||||
unassigned: num(funnel.unassigned ?? 0),
|
||||
total: num(total),
|
||||
}))
|
||||
: '';
|
||||
// ⚠ THE SHARE HAS TWO CLAUSES AND THEY ARE PROVABLE SEPARATELY. "1 of K lists" needs the zone
|
||||
// catalogue, which only describes the DEFAULT list; "{pct}% of the {total}" needs only this
|
||||
// packet's own row count and the active list's total, both of which are always known. A first
|
||||
// version suppressed the whole line whenever listCount was absent, which threw away a true
|
||||
// statement to avoid an unprovable one — the same over-correction as defaulting the unassigned
|
||||
// count to 0, in the opposite direction.
|
||||
const share = !total ? '' : `<div class="funnel-share" style="font:700 26px Georgia,serif;color:#7a3b2e;margin-top:10px">
|
||||
${esc(funnel.listCount
|
||||
? fill(t('funnel_share'), { k: num(funnel.listCount), pct: pctStr, total: num(total) })
|
||||
: fill(t('funnel_share_pct'), { pct: pctStr, total: num(total) }))}</div>`;
|
||||
// ⚠ THE FOOTNOTE IS TWO INDEPENDENT FACTS AND ONLY ONE IS ALWAYS KNOWABLE. Provenance ("counts
|
||||
// from X, captured Y") holds for every list; coverage ("N of the M are not yet in any list") is a
|
||||
// fact about the UPG-100 zone catalogue, which partitions DOXA and nothing else. The first version
|
||||
// filled it with `funnel.unassigned ?? 0`, so a non-default list would have printed
|
||||
// "0 of the 1,745 are not yet in any list" — a confident falsehood, and the zero makes it read as
|
||||
// a completeness claim rather than a missing measurement.
|
||||
// So an unknown coverage drops to the provenance-only string rather than defaulting the number.
|
||||
// ⚠ NOT BY SPLITTING THE TRANSLATED SENTENCE on punctuation: hi ends with "।" and zh with "。",
|
||||
// and parsing translated prose for structure is the same trap as reading a localized page header
|
||||
// positionally (see check-packet-render's "PARSE THE NUMBERS, NEVER THE WORD ORDER").
|
||||
const imbSrc = funnel.sources && funnel.sources.imb;
|
||||
const foot = !imbSrc ? '' : esc(
|
||||
Number.isFinite(Number(funnel.unassigned))
|
||||
? fill(t('funnel_foot'), {
|
||||
imb_pub: imbSrc.publisher || '',
|
||||
imb_date: imbSrc.captured || '',
|
||||
unassigned: num(funnel.unassigned),
|
||||
total: num(total),
|
||||
})
|
||||
: fill(t('funnel_foot_source'), {
|
||||
imb_pub: imbSrc.publisher || '',
|
||||
imb_date: imbSrc.captured || '',
|
||||
}),
|
||||
);
|
||||
|
||||
// ★ THE READER'S OWN STEP IS THE LAST ROW OF THE FUNNEL, NOT A FOOTER. rega measured the first
|
||||
// version in print (2026-09-26): the four global steps ended at y≈330 of 1056 while "43 · are in
|
||||
// THIS packet" sat alone at y≈965, leaving the middle ~60% of the sheet empty and making the
|
||||
// reader's own step read as a detached footnote. Worse, it had NO BAR, so the funnel never
|
||||
// visibly narrowed to "yours" — a funnel whose final term is not drawn as part of the funnel
|
||||
// fails at the one thing it is for.
|
||||
// So the five steps are ONE list, the reader's included, and the row rhythm is derived from the
|
||||
// space actually available rather than a fixed 22px gap: measured, four rows filled 451 of the
|
||||
// 1605px between the intro and the footer, i.e. 28% of the page.
|
||||
const ROWS_TOP = M + 110;
|
||||
const FOOT_RESERVE = 210; // share line + source footnote
|
||||
// ★ A NON-DEFAULT LIST GETS ITS OWN STEP, so the narrowing does not jump from one list's total to
|
||||
// another list's packet. The four IMB steps stay true under every list — they are global counts,
|
||||
// unaffected by which list the reader holds — but the LAST one is DOXA's 2,107, and going straight
|
||||
// from it to "71 in THIS packet" implies DOXA is the parent set of the reader's rows. It is not:
|
||||
// under ?list=frontier-500k the parent is that list's own DOXA-side total. packetFunnel supplies
|
||||
// it as listTotal + listLabel when the active list is not the default; when it IS the default,
|
||||
// listTotal equals the DOXA step and inserting it would repeat the row.
|
||||
// ⚠ AND IT IS OMITTED WHEN IT WOULD REPEAT A NUMBER ALREADY ON THE PAGE — either the DOXA step
|
||||
// above it (the default list, where listTotal IS that step) or the reader's row below it. Measured:
|
||||
// under ?list=frontier-500k the whole list is 71 DOXA groups and a Laos packet drawn from it holds
|
||||
// all 71, so the page rendered "71 -> 71", two identical bars reading as a funnel that narrows to
|
||||
// nothing. A step that repeats its neighbour is not a step.
|
||||
const lastImb = (steps[steps.length - 1] || {}).groups;
|
||||
const ownStep = (funnel.listLabel && total && total !== lastImb && total !== mine)
|
||||
? [{ groups: total, population: null, label: funnel.listLabel, tint: true }]
|
||||
: [];
|
||||
const allRows = [
|
||||
...steps.map((s, i) => ({
|
||||
groups: s.groups,
|
||||
population: s.population,
|
||||
label: t(`funnel_${s.id}`) !== undefined ? t(`funnel_${s.id}`) : s.id,
|
||||
// The network's own list is the hinge between "the world" and "yours" — tint it, and tint
|
||||
// the reader's row below it the same, so the last two read as one thought. With a
|
||||
// non-default list the hinge moves down to that list's own row instead.
|
||||
tint: i === steps.length - 1 && !ownStep.length,
|
||||
})),
|
||||
...ownStep,
|
||||
{ groups: mine, population: null, label: t('funnel_this'), tint: true, mine: true },
|
||||
];
|
||||
// ⚠ THE GAP IS DERIVED FROM THE MEASURED ROW HEIGHTS, NOT FROM A GUESS AT THEM. A first version
|
||||
// subtracted a magic 118 as "about one row" and left 418px of dead space at the foot — the same
|
||||
// emptiness rega reported, just redistributed. Summing the real heights closes it.
|
||||
//
|
||||
// ⚠ AND IT DELIBERATELY KEEPS SLACK, because two runtime facts are unknowable here: whether a
|
||||
// step carries a population line, and how many LINES its translated label wraps to. Arabic and
|
||||
// Hindi labels are longer than the English ("مجموعات حدودية — 0.1% أو أقل، ولا يكاد أحد يذهب
|
||||
// إليها" against "are frontier"), so a label that wraps on two rows must not push the footnote
|
||||
// off the sheet. LINE_SLACK is that room: one extra wrapped line per row. Tight-packing this
|
||||
// page would trade rega's "60% empty" for an overflow in exactly the locales I cannot eyeball.
|
||||
const LINE_SLACK = 30;
|
||||
const rowHeight = (r) => (r.mine ? 46 : 40) + 3 + (r.mine ? 22 : 20)
|
||||
+ (Number.isFinite(Number(r.population)) ? 19 : 0) + 8 + 16 + LINE_SLACK;
|
||||
const rowsHeight = allRows.reduce((sum, r) => sum + rowHeight(r), 0);
|
||||
const GAP = Math.max(18, Math.floor((H - ROWS_TOP - FOOT_RESERVE - rowsHeight) / (allRows.length - 1)));
|
||||
|
||||
const rows = allRows.map((r) => {
|
||||
// ⚠ `Number.isFinite(Number(x))` IS THE WRONG TEST and it printed "0 people" on the reader's row
|
||||
// of every packet (rega, rendered layer, 2026-09-26). Number(null) is 0 — only Number(undefined)
|
||||
// is NaN — so the deliberate `population: null` marker on the reader's row passed the guard and
|
||||
// rendered as the number zero. I asked "is this a finite number?" when the question was "did
|
||||
// anyone give me a population?". Presence and value are different properties; this is the same
|
||||
// confusion as inferring "did it work" from a field that answers "did it isolate".
|
||||
// AND ZERO IS ALSO SUPPRESSED: a people group with no people is not a fact, so a genuine 0 is
|
||||
// missing data wearing a number's clothes.
|
||||
const popN = (r.population === null || r.population === undefined || r.population === '')
|
||||
? null : Number(r.population);
|
||||
const pop = (popN !== null && Number.isFinite(popN) && popN > 0)
|
||||
? `<div style="font:400 17px Georgia,serif;color:#a89f8c;margin-top:2px">${num(popN)} ${esc(t('funnel_people'))}</div>`
|
||||
: '';
|
||||
// ⚠ THE LAST ROW DOES NOT KEEP THE FULL GAP. Measured in print (rega): the share line sat a full
|
||||
// 218px below the reader's row but only 42px above the footnote, so it read as part of the
|
||||
// FOOTNOTE rather than as the conclusion of the "43" row. The gap belongs BETWEEN rows; the share
|
||||
// is that last row's conclusion and should sit close under it. Proximity is the only thing
|
||||
// grouping them, so an even gap everywhere groups the share with the wrong element.
|
||||
return `<div style="margin-bottom:${r.mine ? 14 : GAP}px"${r.mine ? ' class="funnel-mine-row"' : ''}>
|
||||
<div${r.mine ? ' class="funnel-mine"' : ''} style="font:700 ${r.mine ? 46 : 40}px Georgia,serif;color:${r.tint ? '#7a3b2e' : '#1b1a17'};line-height:1">${num(r.groups)}</div>
|
||||
<div style="font:400 ${r.mine ? 22 : 20}px Georgia,serif;color:#3b3226;margin-top:3px">${esc(r.label)}</div>
|
||||
${pop}
|
||||
<div style="height:16px;width:${width(r.groups)}px;background:${r.tint ? '#7a3b2e' : '#cdbf9e'};margin-top:8px;border-radius:2px"></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
return sheet(`
|
||||
<div style="position:absolute;left:${M}px;top:44px;right:${M}px">
|
||||
<div class="funnel-title" style="font:700 40px Georgia,serif;color:#1b1a17">${esc(t('funnel_title'))}</div>
|
||||
<div style="font:italic 18px Georgia,serif;color:#6d766e;margin-top:6px">${esc(t('funnel_intro'))}</div>
|
||||
</div>
|
||||
<div style="position:absolute;left:${M}px;top:${M + 96}px;right:${M}px">${rows}</div>
|
||||
<div style="position:absolute;left:${M}px;right:${M}px;bottom:74px">
|
||||
<div class="funnel-mine" style="font:700 34px Georgia,serif;color:#7a3b2e;line-height:1">${num(mine)}</div>
|
||||
<div style="font:400 18px Georgia,serif;color:#3b3226;margin-top:2px">${esc(t('funnel_this'))}</div>
|
||||
${share}
|
||||
</div>
|
||||
<div style="position:absolute;left:${M}px;top:${ROWS_TOP}px;right:${M}px">${rows}${share}</div>
|
||||
<div class="fine">${foot}</div>`);
|
||||
}
|
||||
|
||||
|
|
|
|||
61
library/scripts/boundaries/source-full-adm1.mjs
Normal file
61
library/scripts/boundaries/source-full-adm1.mjs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* source-full-adm1.mjs — a COMPLETE ADM1 layer for one country, beside (never replacing) the curated file.
|
||||
*
|
||||
* WHY: the shipped boundaries/gb-IND-ADM1.geojson is UPG-100's 100-list PARTITION (34 of India's 36 units;
|
||||
* Mizoram and Nagaland excluded, and listId-tagged, see plugin-designs/my-upg-100-list/data/
|
||||
* source-boundaries.mjs). A map that colours EVERY state (Cities with Internet's NFHS-5 layer) had data for
|
||||
* 36 states and shapes for 34. This writes data/boundaries/gb-<ISO3>-ADM1-full.geojson, all units, from
|
||||
* geoBoundaries gbOpen, with the source and licence in a .meta.json beside it; generate-tree stages both.
|
||||
*
|
||||
* Schema { iso3, shapeGroup, shapeName, shapeISO } (the curated file's, minus the curation's listId).
|
||||
* Coordinates rounded to 3 decimals (~110 m, far finer than a state choropleth needs) and consecutive
|
||||
* duplicate points dropped, to keep the served file small.
|
||||
*
|
||||
* USAGE node library/scripts/boundaries/source-full-adm1.mjs [ISO3=IND] (network, build time only)
|
||||
*/
|
||||
import { writeFileSync, mkdirSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
|
||||
const ISO3 = (process.argv[2] || 'IND').toUpperCase()
|
||||
const API = `https://www.geoboundaries.org/api/current/gbOpen/${ISO3}/ADM1/`
|
||||
|
||||
const meta = await (await fetch(API)).json()
|
||||
const url = meta.simplifiedGeometryGeoJSON || meta.gjDownloadURL
|
||||
const src = await (await fetch(url)).json()
|
||||
|
||||
const r = (n) => Math.round(n * 1000) / 1000
|
||||
const ring = (pts) => {
|
||||
const out = []
|
||||
for (const [x, y] of pts) { const p = [r(x), r(y)]; const q = out[out.length - 1]; if (!q || q[0] !== p[0] || q[1] !== p[1]) out.push(p) }
|
||||
if (out.length && (out[0][0] !== out[out.length - 1][0] || out[0][1] !== out[out.length - 1][1])) out.push(out[0].slice())
|
||||
return out.length >= 4 ? out : null
|
||||
}
|
||||
const poly = (rings) => { const rs = rings.map(ring).filter(Boolean); return rs.length ? rs : null }
|
||||
const geom = (g) => {
|
||||
if (g.type === 'Polygon') { const p = poly(g.coordinates); return p && { type: 'Polygon', coordinates: p } }
|
||||
const ps = g.coordinates.map(poly).filter(Boolean)
|
||||
return ps.length ? { type: 'MultiPolygon', coordinates: ps } : null
|
||||
}
|
||||
const features = []
|
||||
for (const f of src.features) {
|
||||
const g = geom(f.geometry)
|
||||
if (!g) throw new Error(`${f.properties.shapeName}: geometry vanished when rounded`)
|
||||
features.push({ type: 'Feature', properties: { iso3: ISO3, shapeGroup: f.properties.shapeGroup || ISO3, shapeName: f.properties.shapeName, shapeISO: f.properties.shapeISO || '' }, geometry: g })
|
||||
}
|
||||
if (String(features.length) !== String(meta.admUnitCount)) throw new Error(`expected ${meta.admUnitCount} units, got ${features.length}`)
|
||||
|
||||
const dir = resolve(ROOT, 'data/boundaries'); mkdirSync(dir, { recursive: true })
|
||||
const out = resolve(dir, `gb-${ISO3}-ADM1-full.geojson`)
|
||||
writeFileSync(out, JSON.stringify({ type: 'FeatureCollection', features }))
|
||||
writeFileSync(resolve(dir, `gb-${ISO3}-ADM1-full.meta.json`), JSON.stringify({
|
||||
file: `gb-${ISO3}-ADM1-full.geojson`, units: features.length,
|
||||
source: 'geoBoundaries gbOpen (www.geoboundaries.org)', boundaryID: meta.boundaryID,
|
||||
boundarySource: meta.boundarySource, licence: meta.boundaryLicense, licenceSource: meta.licenseSource,
|
||||
yearRepresented: meta.boundaryYearRepresented, sourceUpdated: meta.sourceDataUpdateDate, fetchedFrom: url,
|
||||
processing: 'coordinates rounded to 3 decimals, consecutive duplicate points dropped (library/scripts/boundaries/source-full-adm1.mjs)',
|
||||
note: 'Complete ADM1. The curated gb-' + ISO3 + '-ADM1.geojson (the 100-list partition, listId-tagged) is a different file and is not replaced.',
|
||||
}, null, 2) + '\n')
|
||||
console.log(`${out}: ${features.length} units`)
|
||||
|
|
@ -175,7 +175,9 @@
|
|||
"funnel_this": "are in THIS packet",
|
||||
"funnel_share": "This packet is 1 of {k} lists · {pct}% of the {total}",
|
||||
"funnel_foot": "Counts from {imb_pub}, captured {imb_date}. {unassigned} of the {total} are not yet in any list.",
|
||||
"funnel_people": "people"
|
||||
"funnel_people": "people",
|
||||
"funnel_foot_source": "Counts from {imb_pub}, captured {imb_date}.",
|
||||
"funnel_share_pct": "This packet is {pct}% of the {total}"
|
||||
},
|
||||
"es": {
|
||||
"map_page_kicker": "GRUPOS ÉTNICOS NO EVANGELIZADOS",
|
||||
|
|
@ -343,7 +345,9 @@
|
|||
"funnel_this": "están en ESTE cuadernillo",
|
||||
"funnel_share": "Este cuadernillo es 1 de {k} listas · {pct}% de los {total}",
|
||||
"funnel_foot": "Cifras de {imb_pub}, tomadas el {imb_date}. {unassigned} de los {total} aún no están en ninguna lista.",
|
||||
"funnel_people": "personas"
|
||||
"funnel_people": "personas",
|
||||
"funnel_foot_source": "Cifras de {imb_pub}, tomadas el {imb_date}.",
|
||||
"funnel_share_pct": "Este cuadernillo es el {pct}% de los {total}"
|
||||
},
|
||||
"fr": {
|
||||
"map_page_kicker": "GROUPES ETHNIQUES NON ATTEINTS",
|
||||
|
|
@ -511,7 +515,9 @@
|
|||
"funnel_this": "sont dans CE livret",
|
||||
"funnel_share": "Ce livret est 1 des {k} listes · {pct}% des {total}",
|
||||
"funnel_foot": "Chiffres de {imb_pub}, relevés le {imb_date}. {unassigned} des {total} ne figurent encore sur aucune liste.",
|
||||
"funnel_people": "personnes"
|
||||
"funnel_people": "personnes",
|
||||
"funnel_foot_source": "Chiffres de {imb_pub}, relevés le {imb_date}.",
|
||||
"funnel_share_pct": "Ce livret représente {pct}% des {total}"
|
||||
},
|
||||
"pt": {
|
||||
"map_page_kicker": "GRUPOS ÉTNICOS NÃO ALCANÇADOS",
|
||||
|
|
@ -679,7 +685,9 @@
|
|||
"funnel_this": "estão NESTE caderno",
|
||||
"funnel_share": "Este caderno é 1 de {k} listas · {pct}% dos {total}",
|
||||
"funnel_foot": "Números de {imb_pub}, coletados em {imb_date}. {unassigned} dos {total} ainda não estão em nenhuma lista.",
|
||||
"funnel_people": "pessoas"
|
||||
"funnel_people": "pessoas",
|
||||
"funnel_foot_source": "Números de {imb_pub}, coletados em {imb_date}.",
|
||||
"funnel_share_pct": "Este caderno é {pct}% dos {total}"
|
||||
},
|
||||
"de": {
|
||||
"map_page_kicker": "UNERREICHTE VOLKSGRUPPEN",
|
||||
|
|
@ -847,7 +855,9 @@
|
|||
"funnel_this": "sind in DIESEM Heft",
|
||||
"funnel_share": "Dieses Heft ist 1 von {k} Listen · {pct}% der {total}",
|
||||
"funnel_foot": "Zahlen von {imb_pub}, erhoben am {imb_date}. {unassigned} der {total} sind noch auf keiner Liste.",
|
||||
"funnel_people": "Menschen"
|
||||
"funnel_people": "Menschen",
|
||||
"funnel_foot_source": "Zahlen von {imb_pub}, erhoben am {imb_date}.",
|
||||
"funnel_share_pct": "Dieses Heft ist {pct}% der {total}"
|
||||
},
|
||||
"it": {
|
||||
"map_page_kicker": "GRUPPI ETNICI NON RAGGIUNTI",
|
||||
|
|
@ -1015,7 +1025,9 @@
|
|||
"funnel_this": "sono in QUESTO libretto",
|
||||
"funnel_share": "Questo libretto è 1 di {k} liste · {pct}% dei {total}",
|
||||
"funnel_foot": "Dati da {imb_pub}, rilevati il {imb_date}. {unassigned} dei {total} non sono ancora in nessuna lista.",
|
||||
"funnel_people": "persone"
|
||||
"funnel_people": "persone",
|
||||
"funnel_foot_source": "Dati da {imb_pub}, rilevati il {imb_date}.",
|
||||
"funnel_share_pct": "Questo libretto è il {pct}% dei {total}"
|
||||
},
|
||||
"ro": {
|
||||
"map_page_kicker": "GRUPURI ETNICE NEATINSE",
|
||||
|
|
@ -1183,7 +1195,9 @@
|
|||
"funnel_this": "sunt în ACEASTĂ broșură",
|
||||
"funnel_share": "Această broșură este 1 din {k} liste · {pct}% din {total}",
|
||||
"funnel_foot": "Cifre de la {imb_pub}, colectate la {imb_date}. {unassigned} din {total} nu sunt încă pe nicio listă.",
|
||||
"funnel_people": "persoane"
|
||||
"funnel_people": "persoane",
|
||||
"funnel_foot_source": "Cifre de la {imb_pub}, colectate la {imb_date}.",
|
||||
"funnel_share_pct": "Această broșură reprezintă {pct}% din {total}"
|
||||
},
|
||||
"ru": {
|
||||
"map_page_kicker": "НЕОХВАЧЕННЫЕ НАРОДЫ",
|
||||
|
|
@ -1351,7 +1365,9 @@
|
|||
"funnel_this": "в ЭТОМ буклете",
|
||||
"funnel_share": "Этот буклет — 1 из {k} списков · {pct}% от {total}",
|
||||
"funnel_foot": "Данные {imb_pub}, собраны {imb_date}. {unassigned} из {total} пока не входят ни в один список.",
|
||||
"funnel_people": "человек"
|
||||
"funnel_people": "человек",
|
||||
"funnel_foot_source": "Данные {imb_pub}, собраны {imb_date}.",
|
||||
"funnel_share_pct": "Этот буклет — {pct}% от {total}"
|
||||
},
|
||||
"ar": {
|
||||
"map_page_kicker": "شعوب غير مبلَّغة",
|
||||
|
|
@ -1519,7 +1535,9 @@
|
|||
"funnel_this": "في هذا الكُتيّب",
|
||||
"funnel_share": "هذا الكُتيّب هو 1 من {k} قائمة · {pct}% من {total}",
|
||||
"funnel_foot": "الأرقام من {imb_pub}، جُمعت في {imb_date}. {unassigned} من {total} ليست بعد في أي قائمة.",
|
||||
"funnel_people": "نسمة"
|
||||
"funnel_people": "نسمة",
|
||||
"funnel_foot_source": "الأرقام من {imb_pub}، جُمعت في {imb_date}.",
|
||||
"funnel_share_pct": "هذا الكُتيّب يمثل {pct}% من {total}"
|
||||
},
|
||||
"hi": {
|
||||
"map_page_kicker": "अनछुए जनसमूह",
|
||||
|
|
@ -1687,7 +1705,9 @@
|
|||
"funnel_this": "इस पुस्तिका में हैं",
|
||||
"funnel_share": "यह पुस्तिका {k} सूचियों में से 1 है · {total} का {pct}%",
|
||||
"funnel_foot": "आंकड़े {imb_pub} से, {imb_date} को लिए गए। {total} में से {unassigned} अभी किसी सूची में नहीं हैं।",
|
||||
"funnel_people": "लोग"
|
||||
"funnel_people": "लोग",
|
||||
"funnel_foot_source": "आंकड़े {imb_pub} से, {imb_date} को लिए गए।",
|
||||
"funnel_share_pct": "यह पुस्तिका {total} का {pct}% है"
|
||||
},
|
||||
"zh": {
|
||||
"map_page_kicker": "未得之民",
|
||||
|
|
@ -1855,6 +1875,8 @@
|
|||
"funnel_this": "在这本祷告册里",
|
||||
"funnel_share": "这本祷告册是 {k} 份名单之一 · 占 {total} 的 {pct}%",
|
||||
"funnel_foot": "数据来自 {imb_pub},采集于 {imb_date}。{total} 中有 {unassigned} 还不在任何名单上。",
|
||||
"funnel_people": "人"
|
||||
"funnel_people": "人",
|
||||
"funnel_foot_source": "数据来自 {imb_pub},采集于 {imb_date}。",
|
||||
"funnel_share_pct": "这本祷告册占 {total} 的 {pct}%"
|
||||
}
|
||||
}
|
||||
|
|
@ -123,17 +123,24 @@ const clusters = JSON.parse(readFileSync(join(DATA, 'clusters.json'), 'utf8'));
|
|||
// them, at build time, so no figure on the page is a typed constant.
|
||||
// A missing file yields null, and buildPacket omits page 2 rather than printing numbers it could
|
||||
// not read.
|
||||
const FUNNEL = (() => {
|
||||
// ⚠ A FUNCTION, NOT A CONSTANT, because it depends on PACKET_LIST — which is resolved further down,
|
||||
// after the records load. It mirrors upg-100-list.vue's packetFunnel() exactly: the DEFAULT list is the
|
||||
// only one the zone catalogue describes, so it is the only one that may claim "1 of K lists" and an
|
||||
// unassigned count; a non-default list states its OWN total and goes quiet on the rest.
|
||||
function buildFunnel(membership) {
|
||||
try {
|
||||
const raw = JSON.parse(readFileSync(join(DATA, '../../../data/people-groups/vision-funnel.json'), 'utf8'));
|
||||
if (!raw || !Array.isArray(raw.steps) || !raw.steps.length) return null;
|
||||
if (membership) {
|
||||
return { ...raw, listTotal: membership.total, listId: LIST, listLabel: membership.label };
|
||||
}
|
||||
const lists = [...(clusters.lists || []), ...(clusters.asia_lists || []),
|
||||
...(clusters.uncovered_lists || [])];
|
||||
const listTotal = Number(raw.steps[raw.steps.length - 1].groups) || 0;
|
||||
const assigned = lists.reduce((s, l) => s + (Number(l.count) || 0), 0);
|
||||
return { ...raw, listCount: lists.length, listTotal, unassigned: Math.max(0, listTotal - assigned) };
|
||||
} catch (_) { return null; }
|
||||
})();
|
||||
}
|
||||
const { evaluate } = await import(join(DATA, 'selector-evaluator.js'));
|
||||
const { normalizeRecords, extractRecords } = await import(join(DATA, 'normalize.js'));
|
||||
|
||||
|
|
@ -229,7 +236,45 @@ async function buildPacket(zone, groups, mapSvg) {
|
|||
}
|
||||
|
||||
// ── MAIN ────────────────────────────────────────────────────────────────────
|
||||
const records = await loadRecords();
|
||||
const allRecords = await loadRecords();
|
||||
|
||||
// ★ PACKET_LIST MAKES THE NON-DEFAULT-LIST PATH REACHABLE FROM THIS GENERATOR, and it needed to be.
|
||||
// The BROWSER path honours the universal `list` prop — upg-100-list.vue calls applyPeopleGroupList()
|
||||
// at every fetch site — but this generator had no list support at all, so the SIX-ROW funnel (a list's
|
||||
// own total inserted before the reader's row) could not be rendered by the only pipeline rega can
|
||||
// drive. Its only evidence was a harness I wrote about it, which is the state I have been objecting to
|
||||
// elsewhere: a code path whose sole witness is its author's own test.
|
||||
//
|
||||
// ⚠ IT READS THE MEMBERSHIP FILE FROM DISK rather than through loadListMembership(), which fetches
|
||||
// from the asset pool over HTTP — there is no server here. Same file, same `slugs`, same counts.doxa.
|
||||
// ⚠ AND IT FAILS CLOSED. An unknown or unreadable list exits non-zero instead of quietly producing a
|
||||
// packet of every group: peopleGroupList.js makes the same choice for the same reason ("a map that
|
||||
// silently showed all 2,107 while an embed asked for Project 700 would be showing the wrong list with
|
||||
// no sign of it"). A packet is worse — it is printed and handed to someone.
|
||||
const LIST = process.env.PACKET_LIST || '';
|
||||
let LIST_MEMBERSHIP = null;
|
||||
if (LIST) {
|
||||
const mf = join(__dirname, `../../../data/people-groups/lists/${LIST}.json`);
|
||||
if (!existsSync(mf)) {
|
||||
console.error(`[packet] PACKET_LIST=${LIST}: no membership file at ${mf}`);
|
||||
console.error(' Run library/scripts/people-groups/build-lists.mjs, or use the default list.');
|
||||
process.exit(1);
|
||||
}
|
||||
const m = JSON.parse(readFileSync(mf, 'utf8'));
|
||||
LIST_MEMBERSHIP = { slugs: new Set(m.slugs || []), total: Number(m?.counts?.doxa) || (m.slugs || []).length, label: m.label || LIST };
|
||||
if (!LIST_MEMBERSHIP.slugs.size) {
|
||||
console.error(`[packet] PACKET_LIST=${LIST}: membership file lists no slugs. Failing closed.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
const records = LIST_MEMBERSHIP
|
||||
? allRecords.filter((r) => LIST_MEMBERSHIP.slugs.has(r.slug))
|
||||
: allRecords;
|
||||
if (LIST_MEMBERSHIP) {
|
||||
console.log(`[packet] list=${LIST} — ${records.length} of ${allRecords.length} record(s) `
|
||||
+ `belong to it (list total ${LIST_MEMBERSHIP.total})`);
|
||||
}
|
||||
const FUNNEL = buildFunnel(LIST_MEMBERSHIP);
|
||||
const bySlug = new Map(records.map((r) => [r.slug, r]));
|
||||
const zones = allZones().filter((z) => !ONLY || z.id === ONLY);
|
||||
if (!zones.length) { console.error(`[packet] no zone matches ONLY=${ONLY}`); process.exit(1); }
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@
|
|||
"check:packet-browser": "node internal/check-packet-browser.mjs",
|
||||
"check:packet-fixture": "node internal/generate-packet-fixture.mjs --check",
|
||||
"check:packet-render": "node internal/check-packet-render.mjs",
|
||||
"check:packet-render:expect-funnel": "node internal/check-packet-render.mjs --expect-funnel",
|
||||
"check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs",
|
||||
"check:packets-all": "node internal/check-packets-all.mjs",
|
||||
"check:parse": "node internal/inline-parse-check.mjs",
|
||||
|
|
@ -202,6 +203,7 @@
|
|||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
"lists": "node library/scripts/people-groups/build-lists.mjs",
|
||||
"packet-fixture": "node internal/generate-packet-fixture.mjs",
|
||||
|
|
@ -327,6 +329,7 @@
|
|||
"check:no-free-identifiers": "every name a .vue script uses is imported, declared, a parameter or a known global (scope-aware, across plugin-designs and library). A free identifier compiles and then throws at runtime in exactly the path nobody clicked.",
|
||||
"check:packet-browser": "the packet the READER gets — the legend's Export > Prayer packet path, proving the shared renderer runs in a BROWSER. Asserts the same 12 points / 12 wheel labels / page-1 geography / print layer as check:packet-render, which it did not until 2026-09-24. CONDITIONAL: SKIPs without a server over the repo root (python3 -m http.server 4898) and a reachable records API.",
|
||||
"check:packet-fixture": "the committed fixture must still match clusters.json and must never carry image_url (CI would fetch 43 photos from someone else's CDN).",
|
||||
"check:packet-render:expect-funnel": "check:packet-render with the page-2 FUNNEL declared REQUIRED. Exists because of a real near-miss: a merge resolved with `git checkout --ours` deleted the printPacket call site that supplies the funnel data (60 lines) and EVERY GATE STAYED GREEN — it had to, because the funnel page is OPTIONAL by design (buildPacket omits it when the arg is null), so a packet without page 2 is not malformed, just smaller. Always requiring it would be the wrong fix: the 11 pre-funnel laos packets and any host with no funnel data would go red, and a false red costs as much as a false green. So the CALLER, which knows whether this packet was built with funnel data, declares it — an absence is then either declared (no flag) or a failure (flag passed, page missing), never a silent nothing. Pass it for any packet from a profile that wires packetFunnel(). Browser gate.",
|
||||
"check:packet-strings-parity": "the PACKET catalog (packet-strings.json) must be whole in every language: all string keys, all 12 prayer points with every field, all 12 wheel rhythms. check:i18n-parity covers library/i18n/locales only — six gates READ this file and none checked it. Asserts PRESENCE, not translatedness, with point4_passage declared-absent on record.",
|
||||
"check:packets-all": "runs check:packet-render over EVERY generated packet, not one hand-named file. The single-file gate SKIPs with no argument, so it was in package.json, passing in all 11 locales, and in neither gates:pre nor gates:post — it only ran when someone typed a filename. SKIPs when no packets are generated.",
|
||||
"check:pin-dim-slider": "the side menu's pin-dim slider and the legend's dimming must COMPOSE, not compete. Both want the pin layer's circle-opacity, so there is ONE writer and the value is the PRODUCT: slider x (1 matched, 0 dimmed). Asserts the LIVE paint property, not the store: flat 1 with clustering off, flat 0.5 with clustering on, ['case', expr, 0.5, 0] once something is dimmed, and ['case', expr, 1, 0] when the slider returns to 1. Dims by RELIGION deliberately - a region selection sets a layer FILTER rather than an opacity expression, so testing that path would report a failure about code that never writes opacity.",
|
||||
|
|
@ -360,11 +363,12 @@
|
|||
"check:zone-census": "every zone a USER CAN SELECT must have a poster (asks the LEGEND, not clusters.json — two collapsed deaf lists are unreachable and not a gap). CONDITIONAL: SKIPs without out/posters/, which is gitignored — run `bun run posters` first. It cannot be fixtured like check:packets-all, because it asserts COVERAGE of real generated output and a fixture would only assert the fixture is complete.",
|
||||
"deploy:dev": "destination + allowlist come from vite.doxa-maps-build-config.json (dropin.dest / dropin.include); DOXA_DROPIN_DEST=<dir> overrides the destination",
|
||||
"gallery": "writes public/__gallery/index.html — the ARCHIVED bundle-index gallery, from public/doxa-maps/manifest.json. It is NO LONGER the root page: as of 2026-09-22 `/` mounts <plugin-studio> in BOTH trees (the built root page is written by `bun run tree`), and the gallery is the opt-in index at /__gallery. Archived, not deleted — the dev server imports renderGallery()/readBuildConfig() from this module.",
|
||||
"internet:adm1": "regenerate data/tables/internet-by-adm1.json (internet use by first-level area; India from NFHS-5 2019-21, CC BY 4.0) from data/internet/ through a strict name crosswalk to the shipped gb-IND-ADM1 boundaries. Fails on an unmatched name, never guesses. Generic rows (iso3, adm1, measure, sex, area, value, source, year) so other plugins and go-int can reuse it.",
|
||||
"library-index": "regenerate documentation/reference-library-index.md — the DERIVED menu of the 84 reusable library pieces an author can import instead of rebuilding. Was UNREACHABLE, and the index did not exist at all: its header promised 'can't drift from the code' while nothing ever ran it.",
|
||||
"lists": "regenerate data/people-groups/lists/<id>.json, the DOXA slugs in each people-group list of library/lists/lists.json, from the IMB CSV and the DOXA snapshot. Re-run after either changes.",
|
||||
"packet-fixture": "regenerate the committed records subset (smallest zone, image_url stripped) that lets CI build a packet at all — out/ is gitignored, so a clean checkout has neither the records cache nor any packet.",
|
||||
"packet:ci": "build EVERY committed fixture, no network, each renamed to its own output so they coexist. Was one fixture (tibet); packet.mjs names its output for the ZONE not the fixture, so sequential builds overwrote each other and check:packets-all saw a single SHAPE while reporting success. The fixtures are chosen at the PER_PAGE=20 page boundary — exactly 20, 21, and 1 group — because laos (95) and tibet (43) are both mid-range remainder cases and the boundary itself had never rendered.",
|
||||
"packets": "the prayer packets. READS out/posters/<zone>.html for page 1's map, which `bun run posters` writes — without it the packet still builds and page 1 is simply BLANK, so the script now warns per-zone AND in its summary. PACKET_LANG=<loc> for one language, ONLY=<zoneKey> for one zone, NOPHOTOS=1 to skip the photo fetch.",
|
||||
"packets": "the prayer packets. READS out/posters/<zone>.html for page 1's map, which `bun run posters` writes — without it the packet still builds and page 1 is simply BLANK, so the script now warns per-zone AND in its summary. PACKET_LANG=<loc> for one language, ONLY=<zoneKey> for one zone, NOPHOTOS=1 to skip the photo fetch. ENV: ONLY=<zone> one zone · PACKET_LANG=<locale> · PACKET_RECORDS=<file> a committed fixture instead of the live API · PACKET_LIST=<list-id> the universal list prop for this generator (doxa default; unreached/frontier/frontier-500k/unengaged read data/people-groups/lists/<id>.json from disk, since there is no server to fetch the asset pool from). PACKET_LIST FAILS CLOSED on an unknown or empty list rather than silently printing every group — a packet that showed the wrong list is printed and handed to someone. It exists because the browser path honours ?list= while this generator did not, so the six-row funnel (a list's own total inserted before the reader's row) was unreachable by the only pipeline that can render it.",
|
||||
"posters:one": "ONLY=<zoneKey> to build one",
|
||||
"serve": "serves public/ the way PRODUCTION does: a DIRECTORY request returns 404, no index fallback. This was python3 -m http.server, which DID resolve a directory to index.html while the live static layer does not — so gates passed on URLs that 404 live (measured 2026-09-23; it cost a false \"publish failed\" reading and hid a bad URL in verify-shell-scroll for a whole flight). Loopback only, same as before. Override port/host with PORT/HOST.",
|
||||
"serve:permissive": "LOOPBACK ONLY. This was --bind 0.0.0.0, which published the built tree on every interface of whatever machine ran a gate; measured on the build VPS it answered HTTP 200 on the box's PUBLIC IP while a browser gate was running. Every consumer (verify-browser-all, check:a11y-button-names, check:nav-reachable, smoke:share-all) already defaults to 127.0.0.1, so nothing needed the wider bind. Override with BASE_URL/BASE if you deliberately want to reach it from another host.",
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ const rows = ref([])
|
|||
const selected = ref(null)
|
||||
const aiNoticeOpen = ref(true) // dismissible AI-research disclaimer in the detail sheet
|
||||
const err = ref(null)
|
||||
// India by state (data/tables/internet-by-adm1.json, NFHS-5): the one country with sub-national data.
|
||||
const adm1 = shallowRef(null) // { meta, byArea: { [adm1]: { name, women: {total,urban,rural}, men: {…} } } }
|
||||
const adm1Sex = ref('women') // which half of the survey the fill shows; both are in the sheet
|
||||
const selectedAdm1 = ref(null)
|
||||
|
||||
// ── People-group dots (the agent: "red dots on the map to show me where the people groups are")
|
||||
// The DOXA list from pray-tools, the same list Prayer Progress draws. It carries the slug the
|
||||
|
|
@ -264,12 +268,78 @@ function paint(map, adm0) {
|
|||
// A tap on a people-group dot belongs to the dot, not the country under it.
|
||||
if (map.getLayer('ci-pg-dots') && map.queryRenderedFeatures(e.point, { layers: ['ci-pg-dots'] }).length) return
|
||||
const iso = e.features?.[0]?.properties?.iso3
|
||||
if (iso && byIso.value[iso]) { uiStore?.selectPeopleGroup(null); selected.value = byIso.value[iso] }
|
||||
if (iso && byIso.value[iso]) { uiStore?.selectPeopleGroup(null); selectedAdm1.value = null; selected.value = byIso.value[iso] }
|
||||
})
|
||||
map.on('mouseenter', 'ci-fill', () => { map.getCanvas().style.cursor = 'pointer' })
|
||||
map.on('mouseleave', 'ci-fill', () => { map.getCanvas().style.cursor = '' })
|
||||
}
|
||||
|
||||
// ── INDIA BY STATE: "ever used the internet", NFHS-5 2019–21, women and men asked separately ─────
|
||||
// A share of PEOPLE (0–100%), so it gets its own ramp and its own legend block, never the country
|
||||
// bands: the bands are World Bank national "internet users", a different measure and source.
|
||||
// Blues, so no state can be read as one of the band colours.
|
||||
const ADM1_STEPS = [
|
||||
{ from: 0, color: '#deebf7' },
|
||||
{ from: 25, color: '#9ecae1' },
|
||||
{ from: 40, color: '#6baed6' },
|
||||
{ from: 55, color: '#3182bd' },
|
||||
{ from: 70, color: '#08519c' },
|
||||
]
|
||||
const adm1Fill = (sex) => ['case', ['==', ['typeof', ['get', sex]], 'number'],
|
||||
['step', ['get', sex], ...ADM1_STEPS.flatMap((st, i) => (i === 0 ? [st.color] : [st.from, st.color]))],
|
||||
'rgba(0,0,0,0)']
|
||||
|
||||
async function loadAdm1() {
|
||||
const [table, geo] = await Promise.all([
|
||||
fetch(`${assetsBase.value}/tables/internet-by-adm1.json`).then((r) => { if (!r.ok) throw new Error('internet-by-adm1 ' + r.status); return r.json() }),
|
||||
// All 36 states (data/boundaries/gb-IND-ADM1-full.geojson). NOT gb-IND-ADM1.geojson: that one is
|
||||
// UPG-100's partition, curated to 34 units, and it left Mizoram and Nagaland with no geometry.
|
||||
fetch(`${assetsBase.value}/boundaries/gb-IND-ADM1-full.geojson`).then((r) => { if (!r.ok) throw new Error('gb-IND-ADM1-full ' + r.status); return r.json() }),
|
||||
])
|
||||
const byArea = {}
|
||||
for (const r of table.rows || []) {
|
||||
if (r.iso3 !== 'IND' || r.measure !== 'ever_used_internet' || !r.adm1) continue
|
||||
const a = (byArea[r.adm1] ??= { name: r.adm1_name || r.adm1, women: {}, men: {} })
|
||||
a[r.sex][r.area] = r.value
|
||||
}
|
||||
adm1.value = markRaw({ meta: table.meta || {}, byArea })
|
||||
return geo
|
||||
}
|
||||
|
||||
function paintAdm1(map, geo) {
|
||||
const byArea = adm1.value.byArea
|
||||
const fc = { type: 'FeatureCollection', features: (geo.features || []).map((f) => {
|
||||
const a = byArea[f.properties?.shapeName]
|
||||
return { type: 'Feature', geometry: f.geometry,
|
||||
properties: { adm1: f.properties?.shapeName, women: a?.women.total ?? null, men: a?.men.total ?? null } }
|
||||
}) }
|
||||
// Under the people-group dots, which stay tappable on top.
|
||||
const before = map.getLayer('ci-pg-dots') ? 'ci-pg-dots' : undefined
|
||||
map.addSource('ci-adm1-src', { type: 'geojson', data: fc })
|
||||
map.addLayer({ id: 'ci-adm1-fill', type: 'fill', source: 'ci-adm1-src',
|
||||
paint: { 'fill-color': adm1Fill(adm1Sex.value), 'fill-opacity': 0.85 } }, before)
|
||||
map.addLayer({ id: 'ci-adm1-line', type: 'line', source: 'ci-adm1-src',
|
||||
paint: { 'line-color': 'rgba(255,255,255,0.8)', 'line-width': 0.6 } }, before)
|
||||
// India's national band would sit under its states and show through any state without a
|
||||
// feature as if it were that state's figure. Take India out of the country layer.
|
||||
if (map.getLayer('ci-fill')) map.setFilter('ci-fill', ['!=', ['get', 'iso3'], 'IND'])
|
||||
map.on('click', 'ci-adm1-fill', (e) => {
|
||||
if (map.getLayer('ci-pg-dots') && map.queryRenderedFeatures(e.point, { layers: ['ci-pg-dots'] }).length) return
|
||||
const key = e.features?.[0]?.properties?.adm1
|
||||
const a = key && byArea[key]
|
||||
if (a) { uiStore?.selectPeopleGroup(null); selected.value = null; selectedAdm1.value = { key, ...a } }
|
||||
})
|
||||
map.on('mouseenter', 'ci-adm1-fill', () => { map.getCanvas().style.cursor = 'pointer' })
|
||||
map.on('mouseleave', 'ci-adm1-fill', () => { map.getCanvas().style.cursor = '' })
|
||||
}
|
||||
|
||||
function showAdm1Sex(sex) {
|
||||
adm1Sex.value = sex
|
||||
const map = mapRef.value
|
||||
if (map?.getLayer('ci-adm1-fill')) map.setPaintProperty('ci-adm1-fill', 'fill-color', adm1Fill(sex))
|
||||
}
|
||||
const adm1Missing = computed(() => (adm1.value?.meta?.boundaries?.IND?.missing || []).join(', '))
|
||||
|
||||
// ── HONOUR THE PROVIDER SWITCH: this profile builds its map RAW ─────────────
|
||||
// Every bundle that goes through useMapInstance gets the switch for free. This one calls
|
||||
// `new gl.Map` directly, so it was invisible to it — MEASURED by sweeping all 16 isMap leaves:
|
||||
|
|
@ -293,6 +363,8 @@ onMounted(async () => {
|
|||
try {
|
||||
// The dots are a second layer, not a precondition: if the list fails, the map still shades.
|
||||
const groupsLoaded = loadGroups().catch((e) => { console.warn('[connectivity-map] people groups:', e?.message || e) })
|
||||
// India by state is a third layer, and optional like the dots: without it India keeps its band.
|
||||
const adm1Loaded = loadAdm1().catch((e) => { console.warn('[connectivity-map] India by state:', e?.message || e); return null })
|
||||
const adm0 = await load()
|
||||
// The pane can unmount during the awaits above (a tab switch mid-load). Building a map
|
||||
// into a container that no longer exists throws; this is the raw-map twin of the
|
||||
|
|
@ -325,6 +397,8 @@ onMounted(async () => {
|
|||
selectedPin.initialize()
|
||||
await groupsLoaded
|
||||
paintGroups(map)
|
||||
const geo = await adm1Loaded
|
||||
if (geo && !unmounted) paintAdm1(map, geo)
|
||||
})
|
||||
} catch (e) { err.value = String(e.message || e) }
|
||||
})
|
||||
|
|
@ -367,6 +441,16 @@ useShadowStyles(
|
|||
'.gm-key{display:flex;align-items:center;gap:5px;font-size:11px;white-space:nowrap}' +
|
||||
'.gm-sw{width:12px;height:12px;border-radius:3px;flex:none}' +
|
||||
'.gm-caveat{margin-top:5px;font-size:10.5px;opacity:.72;line-height:1.4}' +
|
||||
'.gm-adm1{margin-top:8px;padding-top:7px;border-top:1px solid rgba(0,0,0,.12)}' +
|
||||
'.gm-adm1-h{font-weight:600;font-size:12px;margin-bottom:5px}' +
|
||||
'.gm-seg{display:inline-flex;border:1px solid rgba(0,0,0,.2);border-radius:6px;overflow:hidden;margin-bottom:6px}' +
|
||||
'.gm-seg button{font:inherit;font-size:11px;padding:3px 10px;border:0;background:transparent;color:inherit;cursor:pointer}' +
|
||||
'.gm-seg button.on{background:#08519c;color:#fff}' +
|
||||
'.gm-adm1-t{width:100%;border-collapse:collapse;font-size:12px;margin:4px 0 6px}' +
|
||||
'.gm-adm1-t th,.gm-adm1-t td{padding:3px 4px;text-align:end}' +
|
||||
'.gm-adm1-t th:first-child{text-align:start}' +
|
||||
'.is-dark .gm-adm1{border-top-color:rgba(255,255,255,.15)}' +
|
||||
'.is-dark .gm-seg{border-color:rgba(255,255,255,.3)}' +
|
||||
// DETAIL: a bottom sheet on a phone, a corner card on desktop.
|
||||
'.gm-sheet{position:absolute;left:0;right:0;bottom:0;z-index:6;max-height:62%;overflow-y:auto;' +
|
||||
'background:#fff;color:#0f1419;border-radius:14px 14px 0 0;padding:14px 16px 22px;' +
|
||||
|
|
@ -426,6 +510,21 @@ useShadowStyles(
|
|||
<div class="gm-caveat">
|
||||
{{ t('connectivity.caveat') }}
|
||||
</div>
|
||||
<!-- India by state: a different measure and source, so its own block and its own ramp. -->
|
||||
<div v-if="adm1" class="gm-adm1">
|
||||
<div class="gm-adm1-h">{{ t('connectivity.adm1.title') }}</div>
|
||||
<div class="gm-seg" role="group" :aria-label="t('connectivity.adm1.title')">
|
||||
<button type="button" :class="{ on: adm1Sex === 'women' }" :aria-pressed="adm1Sex === 'women'" @click="showAdm1Sex('women')">{{ t('connectivity.adm1.women') }}</button>
|
||||
<button type="button" :class="{ on: adm1Sex === 'men' }" :aria-pressed="adm1Sex === 'men'" @click="showAdm1Sex('men')">{{ t('connectivity.adm1.men') }}</button>
|
||||
</div>
|
||||
<div class="gm-keys">
|
||||
<span v-for="(st, i) in ADM1_STEPS" :key="st.from" class="gm-key">
|
||||
<i class="gm-sw" :style="{ background: st.color }"></i>{{ i + 1 < ADM1_STEPS.length ? st.from + '–' + ADM1_STEPS[i + 1].from + '%' : st.from + '%+' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="gm-caveat">{{ t('connectivity.adm1.source') }}</div>
|
||||
<div v-if="adm1Missing" class="gm-caveat">{{ t('connectivity.adm1.missing', { names: adm1Missing }) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="err" class="gm-err">{{ err }}</div>
|
||||
|
|
@ -438,6 +537,25 @@ useShadowStyles(
|
|||
<LegendMobile legend-type="prayer" popup-action="pray" :is-dark="isDark" />
|
||||
</div>
|
||||
|
||||
<!-- Tap an Indian state: the same sheet, with both halves of the survey. -->
|
||||
<div v-if="selectedAdm1" class="gm-sheet">
|
||||
<div class="gm-sh-head">
|
||||
<span class="gm-sh-name">{{ selectedAdm1.name }}</span>
|
||||
<button type="button" class="gm-x" @click="selectedAdm1 = null">{{ t('connectivity.sheet.close') }}</button>
|
||||
</div>
|
||||
<div class="gm-list-h">{{ t('connectivity.adm1.title') }}</div>
|
||||
<table class="gm-adm1-t">
|
||||
<thead><tr><th></th><th>{{ t('connectivity.adm1.total') }}</th><th>{{ t('connectivity.adm1.urban') }}</th><th>{{ t('connectivity.adm1.rural') }}</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="sex in ['women', 'men']" :key="sex">
|
||||
<th>{{ t('connectivity.adm1.' + sex) }}</th>
|
||||
<td v-for="area in ['total', 'urban', 'rural']" :key="area">{{ selectedAdm1[sex][area] != null ? selectedAdm1[sex][area] + '%' : '—' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="gm-note">{{ t('connectivity.adm1.source') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Tap a country: bottom sheet on mobile, corner card on desktop. -->
|
||||
<div v-if="selected" class="gm-sheet">
|
||||
<div class="gm-sh-head">
|
||||
|
|
|
|||
118
plugin-designs/cities-internet/scripts/build-adm1-internet.mjs
Normal file
118
plugin-designs/cities-internet/scripts/build-adm1-internet.mjs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* build-adm1-internet.mjs — internet use by first-level administrative area (ADM1), from data.
|
||||
*
|
||||
* node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs
|
||||
*
|
||||
* READS data/internet/nfhs5-internet-by-state.csv India, NFHS-5 2019–21 (see data/internet/README.md)
|
||||
* data/internet/adm1-crosswalk-IND.json source names -> boundary shapeName (strict)
|
||||
* data/boundaries/gb-IND-ADM1-full.geojson the boundary file the map draws (all 36)
|
||||
* WRITES data/tables/internet-by-adm1.json served as assets/tables/internet-by-adm1.json
|
||||
*
|
||||
* THE TABLE IS GENERIC ON PURPOSE (other plugins and go-int reuse it). One row per
|
||||
* (iso3, adm1, measure, sex, area): no India-specific shape, so a second country or a second
|
||||
* measure is more rows, not a new format. `meta.boundaries[iso3]` names the geometry file and the
|
||||
* property its `adm1` joins on; `meta.measures` and `meta.sources` say what each number is.
|
||||
*
|
||||
* STRICT. The run FAILS when a source name is missing from the crosswalk, when a crosswalk target
|
||||
* is not a shapeName in the boundary file, or when a value is not a number in 0–100. A name is
|
||||
* never guessed. Source rows that have no geometry (crosswalk `adm1: null`) are KEPT, keyed by
|
||||
* their source name, and listed in meta.boundaries[iso3].missing, so nothing is silently dropped.
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
|
||||
const CSV = join(ROOT, 'data/internet/nfhs5-internet-by-state.csv')
|
||||
const CROSSWALK = join(ROOT, 'data/internet/adm1-crosswalk-IND.json')
|
||||
const BOUNDARY = join(ROOT, 'data/boundaries/gb-IND-ADM1-full.geojson')
|
||||
const OUT = join(ROOT, 'data/tables/internet-by-adm1.json')
|
||||
|
||||
const fail = (msg) => { console.error('build-adm1-internet: FAIL — ' + msg); process.exit(1) }
|
||||
|
||||
// A quoted-field CSV reader (the NFHS file quotes names that contain commas).
|
||||
function parseCsv(text) {
|
||||
const rows = []; let row = []; let f = ''; let q = false
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i]
|
||||
if (q) { if (c === '"') { if (text[i + 1] === '"') { f += '"'; i++ } else q = false } else f += c }
|
||||
else if (c === '"') q = true
|
||||
else if (c === ',') { row.push(f); f = '' }
|
||||
else if (c === '\n') { row.push(f); rows.push(row); row = []; f = '' }
|
||||
else if (c !== '\r') f += c
|
||||
}
|
||||
if (f || row.length) { row.push(f); rows.push(row) }
|
||||
const head = rows.shift()
|
||||
return rows.filter((r) => r.length === head.length).map((r) => Object.fromEntries(head.map((h, i) => [h, r[i]])))
|
||||
}
|
||||
|
||||
const src = parseCsv(readFileSync(CSV, 'utf8'))
|
||||
const crosswalk = JSON.parse(readFileSync(CROSSWALK, 'utf8')).map
|
||||
const shapeNames = new Set(JSON.parse(readFileSync(BOUNDARY, 'utf8')).features.map((f) => f.properties.shapeName))
|
||||
|
||||
const SEX = { 'Women who have ever used the internet (%)': 'women', 'Men who have ever used the internet (%)': 'men' }
|
||||
const AREAS = { total: 'nfhs5_total', urban: 'nfhs5_urban', rural: 'nfhs5_rural' }
|
||||
|
||||
const rows = []
|
||||
const national = []
|
||||
const missing = new Set()
|
||||
for (const r of src) {
|
||||
const sex = SEX[r.indicator.replace(/^\d+\.\s*/, '')]
|
||||
if (!sex) fail(`unexpected indicator "${r.indicator}"`)
|
||||
const cw = crosswalk[r.state]
|
||||
if (!cw) fail(`"${r.state}" is not in data/internet/adm1-crosswalk-IND.json`)
|
||||
if (cw.adm1 && !shapeNames.has(cw.adm1)) fail(`crosswalk maps "${r.state}" to "${cw.adm1}", which is not a shapeName in gb-IND-ADM1-full.geojson`)
|
||||
if (!cw.adm1 && !cw.reason) fail(`crosswalk entry for "${r.state}" has no adm1 and no reason`)
|
||||
for (const [area, col] of Object.entries(AREAS)) {
|
||||
const raw = String(r[col] ?? '').trim()
|
||||
if (raw === '' || raw === '*' || raw === 'na') continue // not reported for this cell
|
||||
const value = Number(raw)
|
||||
if (!Number.isFinite(value) || value < 0 || value > 100) fail(`"${r.state}" ${sex} ${area}: "${raw}" is not a share in 0–100`)
|
||||
const row = { iso3: 'IND', measure: 'ever_used_internet', sex, area, value, source: 'nfhs5', year: '2019-21' }
|
||||
if (cw.national) { national.push(row); continue }
|
||||
if (!cw.adm1) missing.add(r.state)
|
||||
rows.push({ ...row, adm1: cw.adm1 || null, adm1_name: r.state })
|
||||
}
|
||||
}
|
||||
if (!rows.length) fail('no rows (vacuous run)')
|
||||
|
||||
const out = {
|
||||
meta: {
|
||||
title: 'Internet use by first-level administrative area',
|
||||
grain: 'one row per iso3 x adm1 x measure x sex x area',
|
||||
measures: {
|
||||
ever_used_internet: {
|
||||
label: 'Ever used the internet',
|
||||
unit: '%',
|
||||
population: 'people aged 15–49, women and men asked separately',
|
||||
note: 'A survey SHARE OF PEOPLE (0–100). Not a subscription count: never put it on one scale with subscriptions per 100.',
|
||||
},
|
||||
},
|
||||
sources: {
|
||||
nfhs5: {
|
||||
name: 'National Family Health Survey 5 (NFHS-5), IIPS / Ministry of Health and Family Welfare, India',
|
||||
year: '2019-21',
|
||||
url: 'http://rchiips.org/nfhs/',
|
||||
via: 'https://github.com/pratapvardhan/NFHS-5 (CSV transcription of the state fact sheets)',
|
||||
licence: 'CC BY 4.0',
|
||||
attribution: 'NFHS-5 (IIPS/MoHFW) state fact sheets, via pratapvardhan/NFHS-5 (CC BY 4.0)',
|
||||
},
|
||||
},
|
||||
boundaries: {
|
||||
IND: {
|
||||
file: 'boundaries/gb-IND-ADM1-full.geojson',
|
||||
key: 'shapeName',
|
||||
licence: 'geoBoundaries gbOpen IND-ADM1-1811400 (DataMeet / ECI): CC BY 2.5 IN',
|
||||
missing: [...missing].sort(),
|
||||
missing_reason: 'no feature for it in the boundary file',
|
||||
},
|
||||
},
|
||||
national,
|
||||
generated_by: 'plugin-designs/cities-internet/scripts/build-adm1-internet.mjs',
|
||||
},
|
||||
rows,
|
||||
}
|
||||
writeFileSync(OUT, JSON.stringify(out, null, 1) + '\n')
|
||||
const drawn = new Set(rows.filter((r) => r.adm1).map((r) => r.adm1)).size
|
||||
console.log(`build-adm1-internet: ${rows.length} rows · IND ${drawn} ADM1 areas drawn · ${missing.size} without geometry (${[...missing].join(', ') || 'none'}) · national ${national.length} rows`)
|
||||
|
|
@ -23,7 +23,11 @@
|
|||
*/
|
||||
import { inject, provide, ref, shallowRef, markRaw, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
||||
// The page's people-group LIST (library/lists/): every people-group fetch below keeps only its groups.
|
||||
import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js'
|
||||
// resolvePeopleGroupList/listInfo/LIST_DEFAULT are for the PACKET'S FUNNEL page, which must state the
|
||||
// total of the list the packet was actually drawn from — see packetFunnel().
|
||||
import {
|
||||
applyPeopleGroupList, resolvePeopleGroupList, listInfo, LIST_DEFAULT,
|
||||
} from '@map/lists/peopleGroupList.js'
|
||||
import { retryableAsyncComponent } from '@map/composables/retryableAsyncComponent.js'
|
||||
import SemanticTreeLegend from '@map/components/legends/SemanticTreeLegend.vue'
|
||||
// The shared mobile bottom-sheet. Fed this profile's own tree through the `nodes`
|
||||
|
|
@ -1326,19 +1330,58 @@ function allLists() {
|
|||
// said "~30 lists of 50-100" — only about half sit in that band, so no copy here claims it. The
|
||||
// 27 lists account for 2,086 of the 2,107, leaving 21 in no list; the footnote states that plainly
|
||||
// rather than letting a partner discover it by adding up rows (shepherd, ENDTOKEN-DXS-UPG-KDECIDE).
|
||||
// ⚠ IT IS LIST-AWARE, AND IT HAS TO BE — THIS WAS A LIVE WRONG NUMBER, not a future risk. This
|
||||
// profile already honours the universal `list` prop: applyPeopleGroupList() filters the rows at all
|
||||
// four fetch sites, including packetRecords (:1258), and the membership files are on disk. So under
|
||||
// ?list=frontier-500k the packet's rows are the 71 DOXA groups that are Project 700 — while the
|
||||
// funnel, which took its denominator from the last step of vision-funnel.json, printed
|
||||
// "of the 2,107". Off by a factor of 30, with nothing on the page looking broken.
|
||||
// Measured 2026-09-26 from data/people-groups/lists/*.json (counts.doxa, what a Tier A map shows):
|
||||
// frontier-500k 71 · frontier 1,745 · unengaged 1,929 · unreached 2,082 · doxa (default) 2,107
|
||||
//
|
||||
// ★ THREE NUMBERS WERE WRONG, NOT ONE, because all three were derived from DOXA's total:
|
||||
// listTotal — the share's denominator;
|
||||
// unassigned — the footnote's "N are in no list", computed as listTotal minus the assigned sum;
|
||||
// listCount — 27, which is a property of the UPG-100 ZONE CATALOGUE, not of the active list. The
|
||||
// zones partition DOXA; they do not partition frontier.
|
||||
// So under a non-default list this states only what it can prove: the real total from the membership
|
||||
// file, and NO listCount and NO unassigned — funnelPage omits the share line and the footnote rather
|
||||
// than print a plausible wrong one. Half a fraction is not a fact.
|
||||
async function packetFunnel() {
|
||||
try {
|
||||
const raw = (await import('../../data/people-groups/vision-funnel.json')).default;
|
||||
if (!raw || !Array.isArray(raw.steps) || !raw.steps.length) return null;
|
||||
const lists = allLists();
|
||||
const listTotal = Number(raw.steps[raw.steps.length - 1].groups) || 0;
|
||||
const assigned = lists.reduce((sum, l) => sum + (Number(l.count) || 0), 0);
|
||||
return {
|
||||
...raw,
|
||||
listCount: lists.length,
|
||||
listTotal,
|
||||
unassigned: Math.max(0, listTotal - assigned),
|
||||
};
|
||||
|
||||
const listId = resolvePeopleGroupList(profileConfig);
|
||||
const doxaTotal = Number(raw.steps[raw.steps.length - 1].groups) || 0;
|
||||
|
||||
// THE DEFAULT LIST is the one the zone catalogue actually describes, so it is the only one that
|
||||
// can honestly claim "1 of K lists" and an unassigned count.
|
||||
if (listId === LIST_DEFAULT) {
|
||||
const lists = allLists();
|
||||
const assigned = lists.reduce((sum, l) => sum + (Number(l.count) || 0), 0);
|
||||
return {
|
||||
...raw,
|
||||
listCount: lists.length,
|
||||
listTotal: doxaTotal,
|
||||
unassigned: Math.max(0, doxaTotal - assigned),
|
||||
};
|
||||
}
|
||||
|
||||
// A NON-DEFAULT LIST: read its own DOXA-side total from the membership file the build writes.
|
||||
// counts.doxa is precisely "how many of this list DOXA carries", i.e. what a Tier A map shows.
|
||||
// If it cannot be read, the share line is omitted — the steps above it are still true.
|
||||
let listTotal = 0;
|
||||
try {
|
||||
const base = resolveAssetsBase(profileConfig.value);
|
||||
const res = await fetch(`${base}/people-groups/lists/${listId}.json`);
|
||||
if (res.ok) {
|
||||
const m = await res.json();
|
||||
listTotal = Number(m?.counts?.doxa) || (Array.isArray(m?.slugs) ? m.slugs.length : 0);
|
||||
}
|
||||
} catch (_) { /* leave listTotal 0 -> no share line */ }
|
||||
|
||||
return { ...raw, listTotal, listId, listLabel: (listInfo(listId) || {}).label || listId };
|
||||
} catch (_) {
|
||||
// A missing funnel file omits page 2 rather than failing the packet — see buildPacket.
|
||||
return null;
|
||||
|
|
@ -1461,14 +1504,33 @@ function packetLang() {
|
|||
return (typeof l === 'string' && l) || 'en';
|
||||
}
|
||||
|
||||
// ★ IT REPORTS WHETHER IT ACTED. Returns true when the selection was applied, false when it bailed.
|
||||
//
|
||||
// WHY: a silent `return` on a precondition is INDISTINGUISHABLE AT THE RENDERED LAYER from the
|
||||
// request never arriving (regions-plugin + upg100, 2026-09-26). The "Show on map" interop bug had
|
||||
// three layers stacked under one symptom — an element event with no replay, an id resolved against
|
||||
// unloaded legendNodes, and THIS function no-op'ing on a missing layer — and each fix looked
|
||||
// sufficient because all three produced the same nothing. Two were fixed by retaining the request;
|
||||
// this one is the third, and it will grow a fourth layer for whoever comes next unless the applier
|
||||
// says when it did not apply.
|
||||
//
|
||||
// ⚠ THE EARLY RETURN STAYS, and it stays SILENT of side effects — it is a documented readiness gate
|
||||
// (see the comment at :630, "THE REAL READINESS SIGNAL"), and other code relies on it bailing rather
|
||||
// than throwing or queueing. Returning a boolean is the smallest change that makes the bail
|
||||
// OBSERVABLE without changing when it happens: a caller that cares can retain and retry, and the two
|
||||
// callers that do not (paintAll's re-apply, the un-isolate at :945) are unaffected by a value they
|
||||
// ignore.
|
||||
function onLegendSelect(node) {
|
||||
if (!map || !map.getLayer('tapestry-fill')) return
|
||||
if (!map || !map.getLayer('tapestry-fill')) return false
|
||||
// R1.2 D-6: in batch mode the legend feeds the selection union, NOT the isolate.
|
||||
// (node is non-null on a row tap; the legend emits null when a row is re-tapped
|
||||
// to deselect — in batch mode that null is a no-op, the union persists.)
|
||||
if (batchMode.value) {
|
||||
if (node) toggleZoneSelection(node)
|
||||
return
|
||||
// TRUE: batch mode HANDLED the request — it fed the union, which is the correct behaviour here
|
||||
// rather than a failure to isolate. A caller retrying because it read `false` would toggle the
|
||||
// zone back OUT of the union, so this distinction is load-bearing, not cosmetic.
|
||||
return true
|
||||
}
|
||||
if (!node) {
|
||||
// restore the full 26-zone tapestry
|
||||
|
|
@ -1478,7 +1540,7 @@ function onLegendSelect(node) {
|
|||
map.setFilter('tapestry-line', null)
|
||||
// restore FULL opacity to match the new visible-dots baseline (BUG-UPG100-DOTS 2026-09-19)
|
||||
map.setPaintProperty('upg-pins', 'circle-opacity', 1)
|
||||
return
|
||||
return true // the un-isolate IS the action; it succeeded
|
||||
}
|
||||
_isolatedNode = node
|
||||
const keys = [...descendantKeys(node)]
|
||||
|
|
@ -1493,6 +1555,10 @@ function onLegendSelect(node) {
|
|||
}
|
||||
const b = boundsFor((f) => keys.includes(f.properties.zoneKey))
|
||||
if (b) map.fitBounds(b, { padding: 40, duration: 700, maxZoom: 6 })
|
||||
// EXPLICIT, not an implicit undefined: the isolate was applied. An implicit fall-through returns
|
||||
// undefined, which is falsy, so a caller checking the result would read the SUCCESS path as a
|
||||
// failure and retry — the opposite of the bug this return value exists to expose.
|
||||
return true
|
||||
}
|
||||
|
||||
// ── ONE paint entrypoint (P0 4188af3d / LESSON-03 L7+L10) ────────────────────
|
||||
|
|
@ -1602,12 +1668,18 @@ let _selectZoneObserver = null; // watches the retained `select-zone` at
|
|||
// onLegendSelect and invisible: nothing throws, and "selected but not visibly selected" is
|
||||
// indistinguishable at the rendered layer from "never selected". Writing the outcome onto the HOST
|
||||
// makes it readable by rega's existing 50 ms poll, with no CDP and no new instrument.
|
||||
// ★ NAMESPACED, on upg100-plugin's point: the host element is SHARED SURFACE. A partner page
|
||||
// embedding this custom element could reasonably own an attribute called `data-select-zone-state`,
|
||||
// and a diagnostic has no business squatting a generic name on someone else's element. The
|
||||
// data-doxa- prefix makes the owner obvious and the collision risk nil.
|
||||
// Renamed AFTER rega's re-shoot rather than before, deliberately: their probe polls these names, and
|
||||
// renaming mid-flight would have burned a measurement instead of costing a few minutes.
|
||||
function _markZoneState(state, zoneId) {
|
||||
try {
|
||||
const h = mapEl.value?.getRootNode?.()?.host;
|
||||
if (!h) return;
|
||||
h.setAttribute('data-select-zone-state', state);
|
||||
if (zoneId) h.setAttribute('data-select-zone-applied', String(zoneId));
|
||||
h.setAttribute('data-doxa-select-zone-state', state);
|
||||
if (zoneId) h.setAttribute('data-doxa-select-zone-applied', String(zoneId));
|
||||
} catch (_) { /* never let a diagnostic break the apply */ }
|
||||
}
|
||||
|
||||
|
|
@ -1615,7 +1687,7 @@ function applySelectZone(zoneId) {
|
|||
if (!zoneId) { onLegendSelect(null); _pendingZoneId = undefined; _markZoneState('cleared', ''); return true; }
|
||||
const node = findLegendNode(legendNodes.value, zoneId);
|
||||
if (!node) { _pendingZoneId = zoneId; _markZoneState('unresolved', zoneId); return false; } // not ready -- REMEMBER it
|
||||
onLegendSelect(node);
|
||||
const applied = onLegendSelect(node);
|
||||
// selectedKey (:163, "currently isolated zoneKey (or null)") is this component's own record of the
|
||||
// isolation, so 'applied-but-no-selection' means the path was REACHED and its EFFECT lost -- a
|
||||
// different bug from delivery, and the one rega's timeline points at.
|
||||
|
|
@ -1629,7 +1701,22 @@ function applySelectZone(zoneId) {
|
|||
// map's tapestry layer is not up, which at t=4.9s it was not. So the readiness signal was never
|
||||
// legendNodes or mapRef -- it is THE LAYER, and the old code cleared _pendingZoneId here as though
|
||||
// the work were done. Keeping it means ensureLayers() can drain it the moment the layer exists.
|
||||
if (!selectedKey.value) { _pendingZoneId = zoneId; _markZoneState('applied-but-no-selection', zoneId); return false; }
|
||||
// ★ ASK THE APPLIER, DO NOT INFER FROM A SIDE EFFECT -- and the inference was WRONG IN BATCH MODE.
|
||||
// upg100-plugin caught this prospectively: onLegendSelect feeds the selection UNION in batch mode
|
||||
// and returns without ever setting selectedKey, because isolating is not what it should do there.
|
||||
// So `!selectedKey.value` read that correct behaviour as a failure, and retaining on it would have
|
||||
// re-applied the zone and toggled it back OUT of the union. Un-isolating (node == null) is the same
|
||||
// shape: the un-isolate IS the action, and selectedKey is null afterwards BY DESIGN.
|
||||
// They have made onLegendSelect return a boolean for exactly this (fb076c8b on packet/vertical),
|
||||
// where false means one thing only: the map was not ready.
|
||||
// FORWARD-COMPATIBLE ON PURPOSE. That change is not on main yet, and a function with no explicit
|
||||
// return yields undefined, which is FALSY -- so depending on it today would read every success as a
|
||||
// failure and retry it, which is the very trap they warned about. So the boolean is authoritative
|
||||
// WHEN IT IS A BOOLEAN, and until then the old inference stands with batch mode excluded.
|
||||
const ok = (typeof applied === 'boolean')
|
||||
? applied
|
||||
: (batchMode.value ? true : !!selectedKey.value);
|
||||
if (!ok) { _pendingZoneId = zoneId; _markZoneState('applied-but-no-selection', zoneId); return false; }
|
||||
_markZoneState('applied', zoneId);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
14
public/dx-maps/assets/boundaries/gb-IND-ADM1-full.meta.json
Normal file
14
public/dx-maps/assets/boundaries/gb-IND-ADM1-full.meta.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"file": "gb-IND-ADM1-full.geojson",
|
||||
"units": 36,
|
||||
"source": "geoBoundaries gbOpen (www.geoboundaries.org)",
|
||||
"boundaryID": "IND-ADM1-1811400",
|
||||
"boundarySource": "DataMeet India community, Election Commission of India",
|
||||
"licence": "Creative Commons Attribution 2.5 India (CC BY 2.5 IN)",
|
||||
"licenceSource": "github.com/datameet/maps",
|
||||
"yearRepresented": "2011",
|
||||
"sourceUpdated": "Thu Jan 19 07:31:04 2023",
|
||||
"fetchedFrom": "https://github.com/wmgeolab/geoBoundaries/raw/9469f09/releaseData/gbOpen/IND/ADM1/geoBoundaries-IND-ADM1_simplified.geojson",
|
||||
"processing": "coordinates rounded to 3 decimals, consecutive duplicate points dropped (library/scripts/boundaries/source-full-adm1.mjs)",
|
||||
"note": "Complete ADM1. The curated gb-IND-ADM1.geojson (the 100-list partition, listId-tagged) is a different file and is not replaced."
|
||||
}
|
||||
2446
public/dx-maps/assets/tables/internet-by-adm1.json
Normal file
2446
public/dx-maps/assets/tables/internet-by-adm1.json
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -32,7 +32,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-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,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-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,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-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb" crossorigin="anonymous"></script>
|
||||
<script src="../cities-internet.js" integrity="sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,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-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,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-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,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-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -104,7 +104,7 @@
|
|||
"bundle": "cities-internet",
|
||||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"output": "dx-maps/cities-internet/cities-internet.js",
|
||||
"integrity": "sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb",
|
||||
"integrity": "sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w",
|
||||
"elements": [
|
||||
"cities-internet-map"
|
||||
],
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -134,7 +134,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -148,7 +148,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
"bundle": "country-posters",
|
||||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"output": "dx-maps/country-posters/country-posters.js",
|
||||
"integrity": "sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/",
|
||||
"integrity": "sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw",
|
||||
"elements": [
|
||||
"country-posters-map"
|
||||
],
|
||||
|
|
@ -177,7 +177,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -205,7 +205,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -663,7 +663,7 @@
|
|||
"bundle": "my-upg-100-list",
|
||||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
|
||||
"integrity": "sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1",
|
||||
"integrity": "sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -683,7 +683,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -697,7 +697,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -711,7 +711,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -887,7 +887,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy",
|
||||
"integrity": "sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -907,7 +907,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -921,7 +921,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-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy\" 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-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -935,7 +935,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R\" 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>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -104,7 +104,7 @@
|
|||
"bundle": "cities-internet",
|
||||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"output": "dx-maps/cities-internet/cities-internet.js",
|
||||
"integrity": "sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb",
|
||||
"integrity": "sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w",
|
||||
"elements": [
|
||||
"cities-internet-map"
|
||||
],
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -134,7 +134,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -148,7 +148,7 @@
|
|||
"scriptUrl": "./cities-internet/cities-internet.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-dmLMT9Nj5CcCrJ+Fd2rJSTeFPIp6IdZMwlDWGuUZRaRZuekeTVJgFd4uXUuYcXxb\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-K1YI/L1c574EYK5Wrr8omwHK1BExIjd/348gi1L8H/WSs5whTjEH4pkESO/QHR+w\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
"bundle": "country-posters",
|
||||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"output": "dx-maps/country-posters/country-posters.js",
|
||||
"integrity": "sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/",
|
||||
"integrity": "sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw",
|
||||
"elements": [
|
||||
"country-posters-map"
|
||||
],
|
||||
|
|
@ -177,7 +177,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -205,7 +205,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-riJmSVRhsdkMQSbHHdt4vHW6xSJ5/O5sx67LIesHNfUjKTf5H2IFw0R7YCJEWPc/\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-/7gss6lpFoPS1ww0mbVdAPmmzAwIG2q0WFaiSD25s7i+8Of+qQ9RNDYgrnMF07Pw\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -663,7 +663,7 @@
|
|||
"bundle": "my-upg-100-list",
|
||||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
|
||||
"integrity": "sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1",
|
||||
"integrity": "sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -683,7 +683,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -697,7 +697,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -711,7 +711,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -887,7 +887,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy",
|
||||
"integrity": "sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -907,7 +907,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -921,7 +921,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-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy\" 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-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R\" 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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -935,7 +935,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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy\" 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": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R\" 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>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -32,7 +32,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-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,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-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,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-VhZri8Qh1TSJ2JVPDVrMqkcgZXtV8omcW9R6b8qk/aSkyGqtsZm5+hjLXha3W/o1" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-GuGN5aNC2EkSR4BDhl2MVTmSnuNW2Faob6rFzHHCwy7e71YVMkKxQKcINpt4EDiZ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,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-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -32,7 +32,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-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,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-lVTa9tfmmNWafuXn4kn4kekzcN/nm2TrZp1HJTgTxpMZ+kyvT0x6XT/KeW9u+jMy" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-geDFTb1+yi9+SR7Inll5n1WSorDYVa3k+FRsOhrTLytgTtztkGMKnSXWGtdZxO3R" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue