Merge origin/development into studio/fake-token

This commit is contained in:
Doxa Maps 2026-09-30 22:29:31 +00:00
commit 3060619d93
46 changed files with 2029 additions and 691 deletions

View file

@ -0,0 +1,55 @@
# india-states.json
India's 28 states and 8 union territories, each with the UPG-100 people groups located inside it. The term is
**"states and union territories"**, never "provinces".
## Who reads it
- **UPG-100's batch selection:** a state selected on the map finds its people groups (Copy / Export CSV / the
prayer packet).
- **Country Posters' India section:** one poster per state / union territory.
Both fetch it at runtime through `library/data/indiaStates.js`, only when a state is needed. It is **served**
(`assets/people-groups/india-states.json`, via `internal/generate-tree.mjs`), not bundled, because this build inlines
every `import()`.
## Shape
```
{ "meta": { what, regenerate, inputs: { membership, geometry }, served_geometry, shape_property, counts },
"states": [ { "id": "india-gj", "iso": "IN-GJ", "kind": "state" | "union_territory",
"name": "Gujarat", "shapeName": "Gujarāt", "bbox": [w, s, e, n], "count": 90, "slugs": [ … ] }, … ×36 ] }
```
- `iso` and `kind` follow ISO 3166-2:IN.
- `name` is the English name; the display names in 11 languages come through each consumer's strings.
- `shapeName` is the exact `shapeName` feature property of `served_geometry`
(`boundaries/gb-IND-ADM1-full-web.geojson`), so a poster can highlight its shape.
- `bbox` is `[west, south, east, north]` in degrees, the poster frame.
- A division with no people groups (Nagaland, Mizoram today) is listed with `"slugs": []`. Each consumer
decides what an empty division shows.
## How it is made: REPRODUCIBLE
When the people-group data changes:
1. `node plugin-designs/my-upg-100-list/data/pip-stamp.mjs` re-stamps every record's coordinates against the
ADM1 shapes (India against all 36, `data/boundaries/gb-IND-ADM1-full.geojson`), writing `admin-membership.json`.
2. `node plugin-designs/my-upg-100-list/data/emit-india-states.mjs` derives this table from `admin-membership.json`
and the geometry, and writes its sha384 pin (`library/data/indiaStates.integrity.js`) **in the same run**.
3. `bun run tree` serves the new copy.
`meta.inputs` records the sha384 of both inputs.
## The gate
`check:india-states` (gates:pre) fails if any of these breaks:
- the table drifts from `admin-membership.json` (it re-derives the table and compares bytes, and it compares
the recorded input sha384s);
- a people group sits in zero or two divisions;
- a division is missing or duplicated (all 36, 28 + 8);
- a `shapeName` is not in the served geometry;
- the pin, the table and the served copy disagree.
The emitter is deterministic: no timestamps, sorted by ISO code, slugs sorted.

View file

@ -0,0 +1,60 @@
{
"meta":{
"what": "India's 28 states and 8 union territories, each with the UPG-100 people groups located inside it (slugs). The term is \"states and union territories\", never \"provinces\".",
"regenerate": "node plugin-designs/my-upg-100-list/data/emit-india-states.mjs (after pip-stamp.mjs re-stamps admin-membership.json)",
"inputs": {
"membership": {
"path": "plugin-designs/my-upg-100-list/data/admin-membership.json",
"sha384": "sha384-0D8paDuTT3HmH+YQkuuQP0ZIjp84ysz3lIXNaFlNbZKp+OevjnpZjj4FPyi7SJUl"
},
"geometry": {
"path": "data/boundaries/gb-IND-ADM1-full.geojson",
"sha384": "sha384-0tTsrv3FE4otJ2OGq7IXCs84Y9lHF7rz3cB+Vhxv76ARuw90JtJt7RLAG91L+/9j"
}
},
"served_geometry": "boundaries/gb-IND-ADM1-full-web.geojson",
"shape_property": "shapeName",
"counts": {
"states": 28,
"union_territories": 8,
"people_groups": 855
}
},
"states":[
{"id":"india-an","iso":"IN-AN","kind":"union_territory","name":"Andaman and Nicobar Islands","shapeName":"Andaman and Nicobar Islands","bbox":[92.204,6.754,93.943,13.673],"count":9,"slugs":["andamanese","bhatu-andamani","brahmin-andamani","coorgi-andamanese","great-andamanese","jarawas","onge","sentinelese","telengi"]},
{"id":"india-ap","iso":"IN-AP","kind":"state","name":"Andhra Pradesh","shapeName":"Andhra Pradesh","bbox":[76.761,12.624,84.765,19.168],"count":81,"slugs":["accukattavandlu","aguru","aiyarakulu","andhra-vaisnawa","ansari-momin-telugu","arakala","are-bondili","are-katika","atollu","bandollu","bania-agarwal-telegu","bayita-kammara","berad","bhondari","budakukkala","cakketakare","camadollu","cundollu","dammali","dasari","gandalla","gandla","ganta-sayebulu","garadi","gavara","godagali","godari","guddi-eluguwallu","hajjam-telugu","hatgar","jakkula","jandra","jethi","kaikali","karnam","karnibhakthulu","kommu-dasari","konda-kammara","kuliya","kurakula","madiga-dasu","majjula","malis","mandula","manna-dhora","medari","mondivogula","muli","musti-chenchu","nagavamsam","nakhasa","neyyala","ontaris","padigarajulu","pagativeshagallu","paky","pamidi","pamula","pancaratra-srivaisnavas-brahmans","reddi-dora","reddika","runjalavallu","sadaru","sadhu-chetty","sakunapaksollu","samayavallu","senapathi","silavantulu","swakulasali-koshti","tamboli-telugu","thogataveera-kshatriya","tirugatigantla","tolakari","udupula","uppara","vaikhanasa-brahmans","vipravinodulu","yata","yatagiri","yatla","yite"]},
{"id":"india-ar","iso":"IN-AR","kind":"state","name":"Arunachal Pradesh","shapeName":"Arunāchal Pradesh","bbox":[91.546,26.651,97.411,29.462],"count":7,"slugs":["khamba","khamyang","khowa","momba","na","serdukpen","zakhrin"]},
{"id":"india-as","iso":"IN-AS","kind":"state","name":"Assam","shapeName":"Assam","bbox":[89.699,24.136,96.018,27.972],"count":15,"slugs":["aiton","bilaspuri","chamar-assamese","hajjam-assamese","kumhar-assamese","man","maria-muslims","matak","mukhi","nai-assamese","namasudra-assamese","sudra-das","taiphake","teli-assamese","turun"]},
{"id":"india-br","iso":"IN-BR","kind":"state","name":"Bihar","shapeName":"Bihār","bbox":[83.32,24.286,88.3,27.522],"count":18,"slugs":["banpar","camma","dhamin","gadihar","gandharv","hindu-santarash","jains-maithili","jogi-angika","kadar","mukeri","nalband","rajdhop","sahisia","shershabadia","thakurai","tikulhara","tilli","turuk-pasi"]},
{"id":"india-ch","iso":"IN-CH","kind":"union_territory","name":"Chandigarh","shapeName":"Chandīgarh","bbox":[76.705,30.666,76.849,30.795],"count":1,"slugs":["khatik-punjabi"]},
{"id":"india-ct","iso":"IN-CT","kind":"state","name":"Chhattisgarh","shapeName":"Chhattīsgarh","bbox":[80.244,17.782,84.396,24.106],"count":24,"slugs":["badhai-chhattisgarhi","bairagi-chhattisgarhi","bargah","beldar-chhattisgarhi","bhangi-chhattisgarhi","bharewa","bharia-bhumia-chhattisgarhi","bhattra-bhatri","bhunjia","chaikwa","dhakad-halbi","dom-chhattisgarhi","ghadwa","gosain-chhattisgarhi","jogi-halbi","kalal-sadri","khairwar-chhattisgarhi","khatik-chhattisgarhi","kol-chhattisgarhi","munnewar","panara","pando","sayyid-chhattisgarhi","son-koli"]},
{"id":"india-dh","iso":"IN-DH","kind":"union_territory","name":"Dadra and Nagar Haveli and Daman and Diu","shapeName":"Dādra and Nagar Haveli and Damān and Diu","bbox":[70.876,20.047,73.218,20.743],"count":1,"slugs":["sagharias"]},
{"id":"india-dl","iso":"IN-DL","kind":"union_territory","name":"Delhi","shapeName":"Delhi","bbox":[76.839,28.405,77.347,28.884],"count":1,"slugs":["khatbune"]},
{"id":"india-ga","iso":"IN-GA","kind":"state","name":"Goa","shapeName":"Goa","bbox":[73.675,14.901,74.336,15.801],"count":5,"slugs":["bania-konkani","gabit","gomantak-maratha","mhalo","mith-gauda"]},
{"id":"india-gj","iso":"IN-GJ","kind":"state","name":"Gujarat","shapeName":"Gujarāt","bbox":[68.093,20.12,74.477,24.712],"count":90,"slugs":["agaria","agri","anjana-patel-gujarati","bafan","bairagi-gujarati","baloch","bamcha","banjania","barot","barthari","bhodala","borpi","caki","champta","cunara","dafer","dangasiya","dasnami-bawa","depala","dudhwala","gadhai","galiara","garmatan","garvi","gavli","ghantiya","golarana","gosain-gujarati","hajjam-gujarati","halaypotra","hingora","hingorza","jat-muslim","juneja","kahar-gujarati","kamalia","kambodia-bhagat","kapadi","kasar-gujarati","kasbati","kathi","ker","khalpa","khaskeli","khatik-gujarati","langa","machhiyara","majothi","makrani","mandali-muslims","mer","mir","miyana","moghol-gujarati","mujavar","muslim-khatri","muslim-lakhera","muslim-patel","mutwa","narsinghpura","nayak-muslims","node","padhar","panar","paradhi","patanwadia","patni-jamat","patni-vankars","pomla","rathodia","raysipotra","royma","sagar","sandhai-muslims","saranias","sayyid-gujarati","shravan","siddi","sompura-salat","tai","targala","theba","tirgar","turi-barot","vadi","vahivanca-barot","vayati","vyapari","wagheri","wandharo"]},
{"id":"india-hp","iso":"IN-HP","kind":"state","name":"Himachal Pradesh","shapeName":"Himāchal Pradesh","bbox":[75.594,30.378,79.009,33.257],"count":20,"slugs":["bhojki","daule","dhaki","dosali","gosain-kangri","hadi","hesi","khatik-chambeali","malaneese","masku","nar","nicar","pangwal-buddhists","pangwala","rehar","sanhai","sansoi","seok","swangla","thavi"]},
{"id":"india-hr","iso":"IN-HR","kind":"state","name":"Haryana","shapeName":"Haryāna","bbox":[74.474,27.653,77.604,30.929],"count":4,"slugs":["arain-punjabi","hajjam-haryanvi","kucband","lakhera-haryanvi"]},
{"id":"india-jh","iso":"IN-JH","kind":"state","name":"Jharkhand","shapeName":"Jhārkhand","bbox":[83.33,21.97,87.962,25.349],"count":9,"slugs":["dhekaro","jadupetia","jarkhandi-hajjam","jarkhandi-julaha","jharkhandi-nai","modaks","nagbansi","puran","sukiyar"]},
{"id":"india-jk","iso":"IN-JK","kind":"union_territory","name":"Jammu and Kashmir","shapeName":"Jammu and Kashmīr","bbox":[73.39,32.276,76.78,35.118],"count":33,"slugs":["arain-dogri","bania-dogri","bhand-kashmiri","bomba","budhan","cheintz","chopan","dhobi-dogri","dhyar","domal","galwan","ganz","gardi","gorkun","hajjam-kashmiri","kawoj","kraal","lohar-dogri","manhas","manjygur-muslims","moghol-dogri","nagbansi-dogri","najjar","namdagur","naqqas","pohul","ratal","sayyid-pahari","shaksaz","shina-india","sofi","sudhan","waza"]},
{"id":"india-ka","iso":"IN-KA","kind":"state","name":"Karnataka","shapeName":"Karnātaka","bbox":[74.086,11.595,78.588,18.459],"count":81,"slugs":["adiyan","agasa","ansari-momin-kannada","arasu","ayri","bagga","baira","balasantoshi","bandi","bania-kannada","batada","bayil-kammara","bayil-pattar","bellara","bestha","chaptegar","charodi","chenna-dasar","dombi-dasa","durga-murga","galada-kokani","ganti-chores","gatti","gauli","ghadi","godda","gond-kannada","gudikara","guraw","hajjam-kannada","halleer","harkantra","hasalaru","helawa","holeya-dasari","jatigar","jaweri","jogi-telugu","jogi-tulu","khakar","kolari","komara-panta","koosa","koraga-korra","kottari","koyava","kudiya","kudubi","kummara-kumbara","kurahina-setti","ladar","maila","malaikudi","maleru","manser","meda","mogaveera","mundala","nadors","nai-kannada","nairy","natua","nawaiyat","padarthis","padiyar","pambada","pattegar","pendar","pinjara-kannada","rachavar","raddi","rajapuri","raneyar","raval","raya-rawat","sapalyas","sayyid-kannada","soligaru","sudir","thammadi","vilyagaras"]},
{"id":"india-kl","iso":"IN-KL","kind":"state","name":"Kerala","shapeName":"Kerala","bbox":[74.868,8.292,77.412,12.796],"count":72,"slugs":["allar","ambalavasi","anthur-nair","arayavati","ayyanavar","bakuda","bania-malayalam","bowis-mogeyar","cakkala","cakkyar-nambiyar","chakkiyar","chamar-muchi-malayalam","chengazhi-nambiar","cingatanmar","dheevara","erawallan","ezhavathi","ilayatu","kakkalan","kalanadies","kalladi","kallattu-kurup","karimpalan","kavara","kavuthiyan","kidaran","kocu-wellan","kollan","kootan","kumar-kshatriya","kummara-kusuvan","kunduvadian","kurukkal","malai-pandaram","malayalar","maran-nairs","marasari","mavilan","moger-malayalam","moothan","mukkari","musari","muttatu","nalkadaya","nambutiris","narsanna","nasuvan","nayadi","otans","padannan","palakad-tandans","pandithar","pathinaickans","pathiyan","pisaroti","poduval","pulisari","puspakan-unni","reddi","samanthan","thachanad-moopan","tiyattunni","urindavan","vadugan","valinjiyar","vetan","vilkurup","visavan","wainadan-cetti","wainadan-kader","wainadan-pulayan","wayanad-pathiyan"]},
{"id":"india-la","iso":"IN-LA","kind":"union_territory","name":"Ladakh","shapeName":"Ladākh","bbox":[72.529,32.338,80.327,37.077],"count":6,"slugs":["bania-kashmiri","brukpa","campa","dokhpa","mon-india","purig-pa"]},
{"id":"india-ld","iso":"IN-LD","kind":"union_territory","name":"Lakshadweep","shapeName":"Lakshadweep","bbox":[72.165,8.264,73.702,11.704],"count":1,"slugs":["laccadive"]},
{"id":"india-mh","iso":"IN-MH","kind":"state","name":"Maharashtra","shapeName":"Mahārāshtra","bbox":[72.655,15.606,80.898,22.034],"count":54,"slugs":["attar-saibulu","baharupi","barda","bari-marathi","bharwad","bhoi-marathi","chari","chitari","citrakathi","dhawad","dom-marathi","garodi","garpagari","gondhali","gopal","hardas","hatkar","jhadi","kachchi-kushwaha","kaghzi","kapewar","kasikapadi","kathar","kharvi","khatik-marathi","kokaniz","kolhati","kudimahan","ladsi","lajjhar","lodha-marathi","lonar","mana","mitna","mondiwar","muslim-kachera","nahal","naikpod","nandiwala","neeli","nilkanth","pangul","phularis","pinjara-marathi","pochammalollu","prabhu-patane","sahi","sanghar","sarodi","sutar-mistry","takari","velip","waidu","yelmar"]},
{"id":"india-ml","iso":"IN-ML","kind":"state","name":"Meghalaya","shapeName":"Meghālaya","bbox":[89.814,25.029,92.803,26.12],"count":2,"slugs":["banai","dalu-india"]},
{"id":"india-mn","iso":"IN-MN","kind":"state","name":"Manipur","shapeName":"Manipur","bbox":[92.971,23.833,94.745,25.692],"count":5,"slugs":["koiren","lois","pangans","thanga","yatibi"]},
{"id":"india-mp","iso":"IN-MP","kind":"state","name":"Madhya Pradesh","shapeName":"Madhya Pradesh","bbox":[74.03,21.07,82.81,26.869],"count":33,"slugs":["anjana","banchada","baraua","bari-hindi","basudeva","berar","bhanumati","bhurtia","dhed","gachha","hajjam-hindi","jadam","jains-hindi","kachera-bagheli","kandera","kasar-hindi","ladia","lahangir","manbhav","mankar","muskhan","muslim-gond","naita","namdeo","naoda","nat-bagheli","nema","rujjhar","sais","sikligar","sisgars","soria","zamral"]},
{"id":"india-mz","iso":"IN-MZ","kind":"state","name":"Mizoram","shapeName":"Mizoram","bbox":[92.257,21.941,93.436,24.521],"count":0,"slugs":[]},
{"id":"india-nl","iso":"IN-NL","kind":"state","name":"Nagaland","shapeName":"Nāgāland","bbox":[93.327,25.199,95.242,27.036],"count":0,"slugs":[]},
{"id":"india-or","iso":"IN-OR","kind":"state","name":"Odisha","shapeName":"Odisha","bbox":[81.388,17.813,87.486,22.568],"count":64,"slugs":["baha-i","bairagi-odia","bangti","banka","bari-odia","bauti","bavuri","bhoi-odia","bondo","brahman-jyoti","candhai-maru","chatarkheya","cherua","citrakar","daitapati","damla","darji-odia","dhera","didayi","eneti-dora","ghantarghada","ghogia","ghusuria","godra","guna","irika","janughanta","jogi-odia","kachchi-alia","kamila","kapasiya","kasar-odia","khadura","khandwal","khati-odia","kobari","koraga","kuda","kuli","kurunga","kusta","liari","maghia","mahuria","malia","markande-kanjia","mundapotta","nai-odia","naksia","noliya","oriya","panka-odia","parenga","radhi","rana","sabakhia","samasi","sanei","sayyid-odia","sinduria","suda","thanapati","tharua","tula-vina"]},
{"id":"india-pb","iso":"IN-PB","kind":"state","name":"Punjab","shapeName":"Punjab","bbox":[73.88,29.542,76.939,32.513],"count":4,"slugs":["daoli","gagra","pinjara-punjabi","racband"]},
{"id":"india-py","iso":"IN-PY","kind":"union_territory","name":"Puducherry","shapeName":"Puducherry","bbox":[75.527,10.827,82.344,16.761],"count":5,"slugs":["indo-french","indo-vietnamese","sonavar","vettalaikarans","vettiyan"]},
{"id":"india-rj","iso":"IN-RJ","kind":"state","name":"Rajasthan","shapeName":"Rājasthān","bbox":[69.484,23.061,78.272,30.198],"count":48,"slugs":["badi-marwari","baret","bargi","bhopa","bhutta","cejara","chadwa","chheepi","chobdar","chungar","cudigar","dadupanthi","damami","dhola","ghihara","gosain-rajasthani","gurru","hiranbaj","khadim","kharol","kriyagar","laskari","mangnyar-muslims","moghol-marwari","mogia","murhid","nagda","nai-mewari","nyaria","pajabgar","pakhimara","rajkumhar","rajpurohit","rath","runwalas","sain","sanchia","santia","saraogi","shikari","singiwala","sorgars","sunga","tak","usta","van-baoriya","ved","vijabargiya"]},
{"id":"india-sk","iso":"IN-SK","kind":"state","name":"Sikkim","shapeName":"Sikkim","bbox":[88.014,27.079,88.92,28.129],"count":1,"slugs":["yakha"]},
{"id":"india-tg","iso":"IN-TG","kind":"state","name":"Telangana","shapeName":"Telangāna","bbox":[77.236,15.836,81.323,19.916],"count":26,"slugs":["are-kapu","attagara","bhatrajulu","bindla","bolla","cippollu","gangireddula","gosangi","gurika","jains-telugu","kachi","katipamula","khatik-telugu","lakkamari-kapu","mala-masti","matangi","mitha-ayyalavar","moghal-telugu","muchi-chamar","pambala","pariki-muggula","patkars","rajannalu","saradagallu","sindhollu","verra-musti"]},
{"id":"india-tn","iso":"IN-TN","kind":"state","name":"Tamil Nadu","shapeName":"Tamil Nādu","bbox":[76.233,8.077,80.347,13.565],"count":64,"slugs":["alavan","ansari-momin-tamil","anuppan","aranadan","baig","bondili","cekkala","chamar-muchi-tamil","chayakkaran","dasa-palanjika","dayare","dhobi-tamil","gangawar","gudugudupandi","gurukkal","jains-tamil","jati-pillai","kambalattan","kambar","kannada-naicker","kannada-saineegar","karaiyar","karlar","karna-sale","katesar","kattiakaran","kayalar","kepmari","koliyan","kongu-vaisnawa","kongu-vellala-gounder","kota","krishnavaka","kudumi","kuluvan","kummara-kummari","kunnuva-vellalar","malai-vellala","maleyan","maniyattikkaran","mudugar","nakkala","namdeo-maratha","nanjil-mudali","pannadi","panniandi","parkavakulam","parvatarajakulam","pattanavan","perumkollar","salivahanam","savalakkarar","sayyid-tamil","semman","senaithalaivar","siviar","tambiran","tholuva-naicker","thoraiyar","tondman","uppillian","urikkar-naicker","vellaiyan-kuppam-padaiyaci","vettaikkaran"]},
{"id":"india-tr","iso":"IN-TR","kind":"state","name":"Tripura","shapeName":"Tripura","bbox":[91.15,22.939,92.332,24.528],"count":11,"slugs":["bajandar","banik-bania","barua","haluadas","kanda","mahisyadas","malakar","sabar","saha","shabdakar","tripuri-namasudra"]},
{"id":"india-up","iso":"IN-UP","kind":"state","name":"Uttar Pradesh","shapeName":"Uttar Pradesh","bbox":[77.084,23.87,84.635,30.406],"count":23,"slugs":["ahmadiya","arain-kannauji","badhik","badi","balahar","bargala","bargujar","bhuiyar","bisati","charwaha","gadhia","habura","hijra","kankali","khangar-bundeli","mujavir","pankhiya","patari","putliwale","rai-bhat-muslims","rastogi","rave","sheikhzade"]},
{"id":"india-ut","iso":"IN-UT","kind":"state","name":"Uttarakhand","shapeName":"Uttarākhand","bbox":[77.573,28.723,81.045,31.456],"count":7,"slugs":["anwal","chaudangsi","jimdar","kuthliya-bora","raji-india","sayyid-garhwali","tagewale"]},
{"id":"india-wb","iso":"IN-WB","kind":"state","name":"West Bengal","shapeName":"West Bengal","bbox":[85.82,21.484,89.874,27.221],"count":30,"slugs":["angarok","ansari-momin-bengali","bagti","baida","baisya","bhakta","bhalla","bhujel","byadh","chunari","dhal-kstriya","dhimal","ghazi","hajjam-bengali","jains-bengali","kakmara","kapuria","kasar-bengali","khairwar-bengali","khalifa","khatik-bengali","kurol","mirshikar","paharia","pinjara-dhunia-bengali","siyalgar","sunuwar","sutradhar","tamboli-bengali","tripuri-kumhar"]}
]}

View file

@ -90,6 +90,8 @@ and a change here can break other maps.
`import … from '@map/components/maps/RegionCountryMap.vue'`
- **MapPrintProgress** — MapPrintProgress.vue — SHARED. The loading screen of a map print (mapPrintTemplate.js).
`import … from '@map/components/poster/MapPrintProgress.vue'`
- **PanelSheets** — PanelSheets.vue — SHARED. A preview of a large poster as the sheets it prints on: the sheets pulled apart, eac
`import … from '@map/components/poster/PanelSheets.vue'`
- **PosterDialog** — PosterDialog.vue — modal for previewing + rendering a poster export.
`import … from '@map/components/poster/PosterDialog.vue'`
- **PosterPreview** — PosterPreview.vue — scaled live preview of the poster composition.

View file

@ -123,9 +123,12 @@ const wired = {
'the Export CSV button': /@click="exportSelectionCsv">/.test(vue) && vue.includes("t('upgMap.exportCsv')"),
'the prefetch on batch mode': /if \(next\) selectionRecordsLoad\(\)/.test(vue),
'one detection for every action': (vue.match(/await selectionPeople\(\)/g) || []).length >= 2 && /selectionPeople\(\)\.then/.test(vue),
// India's states resolve through the served table (library/data/indiaStates.js), fetched only once a state is selected
'India states detected from the served table': /import \{ loadIndiaStates \} from '@map\/data\/indiaStates\.js'/.test(vue)
&& /india\.byShapeName\.get\(t\.name\)/.test(vue) && /t\.kind === 'adm1'\) indiaStates\(\)\.catch/.test(vue),
}
const missingWires = Object.entries(wired).filter(([, v]) => !v).map(([k]) => k)
ok(!missingWires.length, `the HUD is missing: ${missingWires.join(', ')}`, 'the HUD wires the swatches, hex input, Copy, Export CSV, and the prefetch')
ok(!missingWires.length, `the HUD is missing: ${missingWires.join(', ')}`, 'the HUD wires the swatches, hex input, Copy, Export CSV, the prefetch, and India\'s states from the served table')
// ── D. the strings, ×11 ──────────────────────────────────────────────────────────────────────────
const LANGS = ['en', 'es', 'fr', 'pt', 'de', 'it', 'ro', 'ru', 'ar', 'hi', 'zh']

View file

@ -0,0 +1,108 @@
#!/usr/bin/env node
// check-india-states — India's states-and-union-territories table (data/people-groups/india-states.json) stays
// TRUE to the stamping it is derived from, and the served copy is the pinned one.
//
// WHY: UPG-100's batch selection finds a state's people groups in it, and Country Posters builds one poster per
// state / union territory from it. When the people-group data changes, the stamping is redone
// (pip-stamp.mjs → admin-membership.json) and this table must follow; a table that silently kept the old
// membership would print confident, wrong posters. Headless, no network. Asserts:
// A. SHAPE: all 36 divisions of ISO 3166-2:IN, 28 'state' + 8 'union_territory', unique iso and id
// (india-<code>), each with a sane bbox inside India, count == slugs.length; Nagaland + Mizoram present
// even with no people groups (each consumer decides what an empty division shows).
// B. MEMBERSHIP: the union of every division's slugs == admin-membership.json's India slugs (855 today), and
// every slug sits in exactly ONE division.
// C. SHAPES MATCH: every shapeName is a feature of the served geometry (and of the full geometry it is cut from),
// so a poster can highlight its shape by the exact property.
// D. NO DRIFT: re-deriving the table from today's inputs gives the committed bytes, and the input sha384s
// recorded in meta are the files on disk — a changed stamping without a re-emit FAILS here.
// E. PIN: INDIA_STATES_SHA384 == sha384(the committed bytes) == sha384(the served copy in the tree); size ceiling.
import { readFileSync, existsSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const P = (...a) => join(REPO, ...a)
const fails = []
const notes = []
const ok = (cond, msg, note) => { if (!cond) fails.push(msg); else if (note) notes.push(` ok ${note}`) }
const sha = (buf) => 'sha384-' + createHash('sha384').update(buf).digest('base64')
const TABLE = P('data/people-groups/india-states.json')
const SERVED = P('public/dx-maps/assets/people-groups/india-states.json')
const SIZE_CEILING = 24 * 1024
let raw, table, membership, emitter, pin, full, web
try {
raw = readFileSync(TABLE)
table = JSON.parse(raw)
membership = JSON.parse(readFileSync(P('plugin-designs/my-upg-100-list/data/admin-membership.json'), 'utf8'))
emitter = await import(pathToFileURL(P('plugin-designs/my-upg-100-list/data/emit-india-states.mjs')).href)
;({ INDIA_STATES_SHA384: pin } = await import(pathToFileURL(P('library/data/indiaStates.integrity.js')).href))
full = JSON.parse(readFileSync(P('data/boundaries/gb-IND-ADM1-full.geojson'), 'utf8'))
web = JSON.parse(readFileSync(P('data', table.meta.served_geometry), 'utf8'))
} catch (e) {
console.error(`INDIA-STATES: NOT MEASURED (exit 75) — an input did not load: ${e.message}`)
process.exit(75)
}
const states = table.states || []
// ── A. shape ─────────────────────────────────────────────────────────────────────────────────────
const isos = states.map((s) => s.iso), ids = states.map((s) => s.id)
const kinds = { state: 0, union_territory: 0 }
for (const s of states) kinds[s.kind] = (kinds[s.kind] || 0) + 1
ok(states.length === 36 && new Set(isos).size === 36 && isos.every((i) => emitter.IN_DIVISIONS[i]),
`the table has ${states.length} divisions (${new Set(isos).size} distinct ISO codes); ISO 3166-2:IN has 36`)
ok(kinds.state === 28 && kinds.union_territory === 8 && Object.keys(kinds).length === 2,
`kinds are ${JSON.stringify(kinds)}, expected 28 state + 8 union_territory`, 'all 36 divisions: 28 states + 8 union territories, unique ISO codes')
ok(new Set(ids).size === 36 && states.every((s) => s.id === 'india-' + s.iso.slice(3).toLowerCase()),
'ids are not the unique india-<code> form', 'ids are india-<code>, unique (wall cards, packet file names)')
const badBox = states.filter((s) => !(Array.isArray(s.bbox) && s.bbox.length === 4 && s.bbox[0] < s.bbox[2] && s.bbox[1] < s.bbox[3]
&& s.bbox[0] >= 67 && s.bbox[2] <= 98 && s.bbox[1] >= 6 && s.bbox[3] <= 38)).map((s) => s.iso)
ok(!badBox.length, `bboxes out of shape or outside India: ${badBox.join(', ')}`)
const badCount = states.filter((s) => !Array.isArray(s.slugs) || s.count !== s.slugs.length).map((s) => s.iso)
ok(!badCount.length, `count != slugs.length for ${badCount.join(', ')}`, 'every bbox sits inside India; every count is its slugs\' length')
ok(['IN-NL', 'IN-MZ'].every((i) => isos.includes(i)), 'Nagaland or Mizoram is missing: an empty division must still be listed')
// ── B. membership ────────────────────────────────────────────────────────────────────────────────
const india = Object.entries(membership).filter(([, m]) => m && m.country === 'IND').map(([s]) => s).sort()
const seen = new Map()
for (const s of states) for (const slug of s.slugs || []) seen.set(slug, (seen.get(slug) || 0) + 1)
const twice = [...seen].filter(([, n]) => n > 1).map(([s]) => s)
const missing = india.filter((s) => !seen.has(s)), extra = [...seen.keys()].filter((s) => !india.includes(s))
ok(!twice.length, `slugs in more than one division: ${twice.slice(0, 6).join(', ')}`)
ok(!missing.length && !extra.length,
`membership differs from admin-membership.json: missing ${missing.slice(0, 5).join(', ') || '-'}; extra ${extra.slice(0, 5).join(', ') || '-'}`,
`the ${india.length} India people groups, each in exactly one division (= admin-membership.json)`)
// ── C. shapes match the geometry ─────────────────────────────────────────────────────────────────
const webNames = new Set(web.features.map((f) => f.properties[table.meta.shape_property]))
const fullNames = new Set(full.features.map((f) => f.properties.shapeName))
const noShape = states.filter((s) => !webNames.has(s.shapeName) || !fullNames.has(s.shapeName)).map((s) => s.shapeName)
ok(!noShape.length, `shapeNames not in ${table.meta.served_geometry}: ${noShape.join(', ')}`,
`every shapeName is a feature of ${table.meta.served_geometry}`)
// ── D. no drift ──────────────────────────────────────────────────────────────────────────────────
for (const [k, inp] of Object.entries(table.meta.inputs || {})) {
const now = existsSync(P(inp.path)) ? sha(readFileSync(P(inp.path))) : 'MISSING'
ok(now === inp.sha384, `meta.inputs.${k} (${inp.path}) changed since the table was emitted: re-run node plugin-designs/my-upg-100-list/data/emit-india-states.mjs`)
}
let rebuilt = null
try { rebuilt = emitter.buildIndiaStates().text } catch (e) { fails.push(`re-deriving the table threw: ${e.message}`) }
ok(rebuilt === raw.toString('utf8'), 'the committed table is not what the emitter derives from today\'s inputs: re-run the emitter',
'NO DRIFT: re-derived from today\'s inputs it is byte-identical, and meta\'s input sha384s are the files on disk')
// ── E. pin + served copy ─────────────────────────────────────────────────────────────────────────
ok(pin === sha(raw), `INDIA_STATES_SHA384 (${String(pin).slice(0, 20)}…) is not the table's sha384 (${sha(raw).slice(0, 20)}…): re-run the emitter`)
ok(existsSync(SERVED) && sha(readFileSync(SERVED)) === pin, existsSync(SERVED)
? 'the served copy (public/dx-maps/assets/people-groups/india-states.json) is not the pinned table: run bun run tree'
: 'the table is not in the tree (public/dx-maps/assets/people-groups/india-states.json): add it to generate-tree\'s allowlist and run bun run tree',
'pin == the table == the served copy')
ok(raw.length <= SIZE_CEILING, `the table is ${raw.length} B, over the ${SIZE_CEILING} B ceiling`, `${raw.length} B (ceiling ${SIZE_CEILING})`)
for (const n of notes) console.log(n)
if (fails.length) {
console.error('INDIA-STATES: FAIL')
for (const f of fails) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log('INDIA-STATES: PASS — 36 divisions, every India people group in exactly one, shapes match the served geometry, no drift from the stamping, and the served copy is the pinned one.')

View file

@ -0,0 +1,118 @@
#!/usr/bin/env node
/**
* check-panel-sheets — the shared panel geometry (library/components/poster/panelSheets.js). Headless.
*
* A poster too big for one sheet prints as several sheets, each trimmed edge laid over the next sheet's
* join band. This pins:
* A. the two multi-sheet sizes: 60x96 = 3 × 60×36 stacked, 6 in joins (the Bless 5 × 8 ft poster);
* 66x66 = 2 × 36×66 side by side, 6 in join (India); and a single sheet has no join;
* B. the sheets TILE the finished poster: what each sheet shows (itself minus the band the sheet before
* covers) covers every inch once, no gap, no double;
* C. sliceFor: with 'glue' the slices tile the poster; with 'bleed' neighbours repeat exactly joinIn;
* D. the print marks: a 'glue' join prints the red band; a 'bleed' join prints only a guide (a solid band
* would show through a misaligned trim); a screen preview always draws the red band; the trim line is on
* every sheet but the last; every sheet has its "s / N" tag;
* E. the Bless poster takes its sheets, bands and marks from this module (no private copy to drift): its
* 5x8ft content bands equal the panels' content boxes, and blessPoster.js carries no glue band of its own.
* Two-sided: each arm runs against a planted defect and must fail.
* EXIT 0 pass · 1 fail
*/
import { readFileSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const real = await import(pathToFileURL(join(ROOT, 'library/components/poster/panelSheets.js')).href)
const bless = await import(pathToFileURL(join(ROOT, 'plugin-designs/wall-of-people-groups/src/blessPoster.js')).href)
const blessSrc = readFileSync(join(ROOT, 'plugin-designs/wall-of-people-groups/src/blessPoster.js'), 'utf8')
const eq = (a, b) => Math.abs(a - b) < 1e-9
const SIZES = {
'60x96': { sheetW: 60, sheetH: 36, count: 3, direction: 'vertical', joinIn: 6, want: { w: 60, h: 96, at: [0, 30, 60] } },
'66x66': { sheetW: 36, sheetH: 66, count: 2, direction: 'horizontal', joinIn: 6, want: { w: 66, h: 66, at: [0, 30] } },
'36x36': { sheetW: 36, sheetH: 36, count: 1, direction: 'vertical', joinIn: 0, want: { w: 36, h: 36, at: [0] } },
}
function run(M) {
const errs = []
let checked = 0
for (const [key, z] of Object.entries(SIZES)) {
for (const joinFill of ['glue', 'bleed']) {
const P = M.planPanels({ ...z, borderIn: 2, joinFill })
const V = P.direction === 'vertical', along = V ? P.sheetH : P.sheetW, total = V ? P.finished.h : P.finished.w
// A. geometry
if (!eq(P.finished.w, z.want.w) || !eq(P.finished.h, z.want.h)) errs.push(`A ${key}: finished ${P.finished.w} × ${P.finished.h}, want ${z.want.w} × ${z.want.h}`)
if (P.sheets.length !== z.count) errs.push(`A ${key}: ${P.sheets.length} sheets, want ${z.count}`)
P.sheets.forEach((sh, s) => {
const off = V ? sh.at.y : sh.at.x
if (!eq(off, z.want.at[s])) errs.push(`A ${key}: sheet ${s + 1} starts at ${off} in, want ${z.want.at[s]}`)
if ((s > 0) !== !!sh.band) errs.push(`A ${key}: sheet ${s + 1} ${sh.band ? 'has' : 'lacks'} a join band`)
if (sh.band && !eq(V ? sh.band.h : sh.band.w, z.joinIn)) errs.push(`A ${key}: sheet ${s + 1}'s band is not ${z.joinIn} in deep`)
if (sh.trimmed !== (s < z.count - 1)) errs.push(`A ${key}: sheet ${s + 1} trimmed=${sh.trimmed}`)
const c = sh.content, lead = V ? c.y0 : c.x0, trail = along - (V ? c.y1 : c.x1)
if (!eq(lead, s === 0 ? 2 : z.joinIn) || !eq(trail, s === z.count - 1 ? 2 : 0)) errs.push(`A ${key}: sheet ${s + 1}'s content box leaves ${lead} / ${trail} in`)
})
// B. the visible parts tile the finished poster (1/8 in samples)
const hits = new Array(Math.round(total * 8)).fill(0)
P.sheets.forEach((sh, s) => {
const off = V ? sh.at.y : sh.at.x, from = off + (sh.band ? P.joinIn : 0), to = off + along
for (let k = 0; k < hits.length; k++) { const t = (k + 0.5) / 8; if (t >= from && t < to) hits[k]++ }
})
const bad = hits.findIndex((h) => h !== 1)
if (bad >= 0) errs.push(`B ${key}: at ${(bad + 0.5) / 8} in the poster is shown by ${hits[bad]} sheets, not 1`)
// C. slices
const sl = P.sheets.map((_, s) => M.sliceFor(P, s))
for (let s = 1; s < sl.length; s++) {
const endPrev = V ? sl[s - 1].y + sl[s - 1].h : sl[s - 1].x + sl[s - 1].w, start = V ? sl[s].y : sl[s].x
const want = joinFill === 'bleed' ? endPrev - z.joinIn : endPrev
if (!eq(start, want)) errs.push(`C ${key} ${joinFill}: slice ${s + 1} starts at ${start} in, want ${want}`)
}
const last = sl[sl.length - 1]
if (!eq(V ? last.y + last.h : last.x + last.w, total) || !eq(V ? sl[0].y : sl[0].x, 0)) errs.push(`C ${key} ${joinFill}: the slices do not span the poster`)
// D. marks
P.sheets.forEach((sh, s) => {
const pr = M.panelChromeHtml(P, s, {}), sc = M.panelChromeHtml(P, s, { screen: true })
const solid = /class="glue[ "]/.test(pr.before), guide = /class="glue-guide/.test(pr.before)
if (sh.band && joinFill === 'glue' && !solid) errs.push(`D ${key}: a glue join does not print its red band`)
if (sh.band && joinFill === 'bleed' && (solid || !guide)) errs.push(`D ${key}: a bleed join prints ${solid ? 'a solid band' : 'no guide'}`)
if (sh.band && !/class="glue[ "]/.test(sc.before)) errs.push(`D ${key}: the screen preview does not draw the red band`)
if (!sh.band && (pr.before || sc.before)) errs.push(`D ${key}: sheet 1 draws a join band`)
if (/class="trim/.test(pr.after) !== sh.trimmed) errs.push(`D ${key}: sheet ${s + 1}'s trim line is wrong`)
if (!pr.after.includes(`>${s + 1} / ${z.count}<`)) errs.push(`D ${key}: sheet ${s + 1} has no "${s + 1} / ${z.count}" tag`)
})
checked++
}
}
return { errs, checked }
}
const fails = []
const r = run(real)
fails.push(...r.errs)
if (r.checked < 6) fails.push(`only ${r.checked} of 6 size × fill cases ran`)
// E. the Bless consumer
const plan = bless.planPoster('5x8ft', 2107)
const P = real.planPanels({ sheetW: 60, sheetH: 36, count: 3, direction: 'vertical', joinIn: 6, borderIn: 6, joinFill: 'glue' })
plan.bands.forEach((bd, s) => {
const c = P.sheets[s].content
if (!eq(bd.y0 - (s === 0 ? plan.bannerH : 0), c.y0) || !eq(bd.y1 + (s === 2 ? plan.footerH + (plan.gapH || 0) : 0), c.y1)) fails.push(`E: Bless 5x8ft sheet ${s + 1}'s band is not the panels' content box`)
})
if (!eq(plan.posterW, P.finished.w) || !eq(plan.posterH, P.finished.h)) fails.push('E: the Bless poster\'s finished size is not the panels\'')
if (!/from '[./]+library\/components\/poster\/panelSheets\.js'/.test(blessSrc)) fails.push('E: blessPoster.js does not import panelSheets.js')
if (/class="glue"/.test(blessSrc) || /\.glue\{/.test(blessSrc)) fails.push('E: blessPoster.js carries its own glue band (a private copy to drift)')
if (!/class="glue"/.test(bless.buildBlessPosterHtml(Array.from({ length: 50 }, () => ({ name: 'x' })), '5x8ft', {}))) fails.push('E: the Bless 5x8ft print has no glue band')
// Two-sided: each arm against a planted defect.
const planted = {
'A/B (the joins not subtracted)': { ...real, planPanels: (o) => { const p = real.planPanels(o); const V = p.direction === 'vertical'; return { ...p, finished: V ? { w: p.sheetW, h: p.count * p.sheetH } : { w: p.count * p.sheetW, h: p.sheetH } } } },
'C (a glue slice keeps the band)': { ...real, sliceFor: (p, s) => real.sliceFor({ ...p, joinFill: 'bleed' }, s) },
'D (a bleed join prints a solid band)': { ...real, panelChromeHtml: (p, s, o = {}) => real.panelChromeHtml(p, s, { ...o, screen: true }) },
'D (no trim line)': { ...real, panelChromeHtml: (p, s, o) => { const h = real.panelChromeHtml(p, s, o); return { ...h, after: h.after.replace(/<div class="trim[^>]*><\/div>/, '') } } },
}
for (const [name, M] of Object.entries(planted)) {
const want = name[0]
if (!run(M).errs.some((e) => e.startsWith(want) || (want === 'A' && e.startsWith('B')))) fails.push(`SELF-TEST: the planted defect "${name}" was not caught`)
}
if (fails.length) { console.log('PANEL-SHEETS: FAIL'); for (const f of fails) console.log(' - ' + f); process.exitCode = 1 }
else console.log(`PANEL-SHEETS: PASS — ${r.checked} size × fill cases (60x96 stacked, 66x66 side by side, one sheet; glue and bleed): the sheets tile the finished poster, the slices tile or repeat exactly the join, the marks follow the fill, and the Bless 5x8ft takes its bands and marks from panelSheets.js; ${Object.keys(planted).length} planted defects caught.`)

View file

@ -0,0 +1,352 @@
#!/usr/bin/env node
/**
* check-poster-wall-host-seams.mjs: PosterWall's two host seams, headerAction and previewSrc, do what they
* say, and with neither set every wall behaves as before.
*
* WHY (headerAction). PosterWall's card header was hard-wired to "Show on map". A wall with no map beside it (the
* Poster Design gallery) needs the same card with a different action, so the header became a seam:
* headerAction null (the default) keeps the "Show on map" TOGGLE; { labelKey, ariaKey } makes it a
* plain button that emits 'header-action'. Every existing wall passes nothing, so the default path is
* the one that must not move, and the new path must not leak the old one's side effects (a window
* event a map would act on, aria-pressed, the is-last highlight).
*
* WHY (previewSrc). The card's thumbnail was always the stored previews/<id>.webp. A gallery of templates
* drawn from the library has no stored file and must show a library change, so the thumbnail can come
* from the host. A gallery renders many cards, so the seam is LAZY (only cards in view), ONE render at a
* time, cached per page, and bounded: no answer, a throw or a timeout falls back to the wall's
* "preview unavailable" placeholder, never a blank or an endless wait.
*
* HOW. No browser (fleet policy). PosterWall has no <style> block, so it is compiled here as a CLIENT
* component (compiler-sfc, inline template) and loaded through Vite with the repo's @map alias. It is
* mounted on a recording renderer: every element keeps its tag, props and children, so the gate reads
* the rendered header buttons and CALLS their real onClick. Two arms, one card each:
* A (default) one .tape-h, aria-pressed="false"; a click dispatches ONE window event named by
* eventName with listId = the item id, sets aria-pressed="true" and is-last, and
* emits nothing; a second click dispatches listId null (the toggle).
* B (headerAction) one .tape-h, NO aria-pressed, label = "7 UPG ·" + t(labelKey), aria-label = t(ariaKey);
* a click emits 'header-action' with the item, dispatches NO window event, sets no is-last.
* C (headerAction, an item with no count) the sticker reads t(labelKey) alone: no "UPG", no "·".
* D (no previewSrc) the card is an <img class="img"> on previews/<id>.webp; no pending placeholder, and the
* wall creates no IntersectionObserver.
* E (previewSrc) 3 cards, all pending, 0 renders before any is in view (lazy), observed with the wall's
* own scroller as the root (it clips its cards). Two come into view: exactly
* one render runs; when it answers a URL that card becomes an <img> with that src and the
* second render starts; an answer of null gives the "preview unavailable" placeholder. The
* card never in view is never rendered.
* F (previewSrc that never answers) after the timeout the card shows the placeholder.
* G (cache) a remount with the same instanceId shows the cached URL with no second render.
* H (no previewSrc, the stored webp fails) the first error retries the tree's own copy
* (<tree>/assets/previews/<id>.webp) and only the second shows the placeholder.
* I (previewSrc, the generated image fails) one error shows the placeholder: there is no second base.
* embedBase.js is served to the gate as if from a BUILT tree (<tree>/<bundle>/<bundle>.js), because
* PosterWall's second base is derived from that URL; from source it is empty and H could not fail.
* A vacuous run (no .tape-h found, or no items rendered) FAILS.
*/
import { readFileSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { TREE_NAME } from './_tree-name.mjs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const WALL = join(ROOT, 'library/components/poster/PosterWall.vue')
const failures = []
const fail = (m) => failures.push(m)
// ── the thinnest page PosterWall's setup touches ───────────────────────────────────────────────
const dispatched = []
const observers = []
{
const noop = () => {}
const el = () => ({ style: {}, dataset: {}, setAttribute: noop, getAttribute: () => null, removeAttribute: noop,
appendChild: (c) => c, removeChild: noop, addEventListener: noop, removeEventListener: noop,
querySelector: () => null, querySelectorAll: () => [], getRootNode() { return this },
classList: { add: noop, remove: noop, toggle: noop, contains: () => false } })
const html = el(); html.lang = 'en'
Object.assign(globalThis, {
window: globalThis, self: globalThis,
document: Object.assign(el(), { documentElement: html, head: el(), body: el(), createElement: el, createTextNode: el,
currentScript: null, readyState: 'complete', cookie: '', getElementById: () => null }),
location: new URL(`https://gate.invalid/${TREE_NAME}/x/x/index.html?lang=en`),
localStorage: { getItem: () => null, setItem: noop, removeItem: noop },
sessionStorage: { getItem: () => null, setItem: noop, removeItem: noop },
matchMedia: () => ({ matches: false, addEventListener: noop, removeEventListener: noop, addListener: noop, removeListener: noop }),
addEventListener: noop, removeEventListener: noop,
dispatchEvent: (e) => { dispatched.push(e); return true },
requestAnimationFrame: (f) => setTimeout(f, 0), cancelAnimationFrame: clearTimeout,
ResizeObserver: class { observe() {} unobserve() {} disconnect() {} },
// A RECORDING observer, so the gate can bring a card into view on demand.
IntersectionObserver: class { constructor(cb, opts) { this.cb = cb; this.opts = opts || {}; this.els = new Set(); observers.push(this) }
observe(e) { this.els.add(e) } unobserve(e) { this.els.delete(e) } disconnect() { this.els.clear() } },
MutationObserver: class { observe() {} disconnect() {} },
HTMLElement: class {}, ShadowRoot: globalThis.ShadowRoot || class ShadowRoot {},
CustomEvent: class { constructor(type, o) { this.type = type; this.detail = o?.detail } },
})
if (!globalThis.navigator?.language) globalThis.navigator = { language: 'en', languages: ['en'], userAgent: 'gate' }
}
// ── PosterWall as a CLIENT component, loaded through Vite for the alias ────────────────────────
const CLIENT_ID = '\0poster-wall-client.js'
// PosterWall's fallback base comes from the bundle's own script URL (embedBase.js bundleTreeRoot), which
// from source is not a built bundle URL, so the fallback would be empty and arm H vacuous.
const GATE_TREE = 'https://gate.invalid/tree'
const builtScriptUrl = {
name: 'gate-built-script-url',
transform(code, id) {
if (!id.endsWith('/library/utils/embedBase.js')) return null
if (!/const SCRIPT_URL = import\.meta\.url/.test(code)) throw new Error('embedBase.js no longer reads SCRIPT_URL from import.meta.url; update arm H')
return code.replace(/const SCRIPT_URL = import\.meta\.url/, `const SCRIPT_URL = ${JSON.stringify(GATE_TREE + '/poster-design/poster-design.js')}`)
},
}
const clientCompile = {
name: 'gate-poster-wall-client',
resolveId: (id) => (id === 'virtual:poster-wall-client' ? CLIENT_ID : null),
async load(id) {
if (id !== CLIENT_ID) return null
const sfc = await import('@vue/compiler-sfc')
const { descriptor, errors } = sfc.parse(readFileSync(WALL, 'utf8'), { filename: WALL })
if (errors.length) throw new Error(`PosterWall.vue did not parse: ${errors[0].message}`)
const out = sfc.compileScript(descriptor, { id: 'pw-gate', inlineTemplate: true })
// Relative imports are relative to the .vue, not to this virtual id.
return out.content.replace(/from\s+(['"])\.\.\/\.\.\//g, `from $1${join(ROOT, 'library')}/`)
},
}
const { createServer } = await import('vite')
const server = await createServer({
root: ROOT, configFile: false, logLevel: 'error', appType: 'custom',
server: { middlewareMode: true, hmr: false, ws: false, watch: null },
plugins: [clientCompile, builtScriptUrl],
resolve: { alias: { '@map': join(ROOT, 'library') } },
define: { __DOXA_TREE_NAME__: JSON.stringify(TREE_NAME) },
})
// ── a renderer that records what it draws ──────────────────────────────────────────────────────
// Recorded elements also answer the few DOM calls PosterWall's hooks make (v-show writes style.display,
// the row fitter queries the wall, useShadowStyles finds its root node).
const noopFn = () => {}
function node(tag) {
return { tag, props: {}, children: [], parent: null, text: '', style: { setProperty: noopFn, getPropertyValue: () => '' },
dataset: {}, classList: { add: noopFn, remove: noopFn, toggle: noopFn, contains: () => false },
querySelector: () => null, querySelectorAll: () => [], addEventListener: noopFn, removeEventListener: noopFn,
setAttribute: noopFn, getAttribute: () => null, removeAttribute: noopFn, appendChild: (c) => c,
getBoundingClientRect: () => ({ top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }),
getRootNode() { return { host: null, adoptedStyleSheets: [], appendChild: (c) => c, querySelector: () => null } } }
}
function insert(child, parent, anchor) {
if (child.parent) child.parent.children.splice(child.parent.children.indexOf(child), 1)
child.parent = parent
const i = anchor ? parent.children.indexOf(anchor) : -1
if (i >= 0) parent.children.splice(i, 0, child); else parent.children.push(child)
}
const walk = (n, f) => { f(n); for (const c of n.children) walk(c, f) }
const textOf = (n) => { let s = ''; walk(n, (x) => { if (x.tag === '#text') s += x.text }); return s }
const hasClass = (n, c) => String(n.props.class || '').split(/\s+/).includes(c)
const classStr = (v) => (typeof v === 'string' ? v : Array.isArray(v) ? v.map(classStr).join(' ')
: v && typeof v === 'object' ? Object.keys(v).filter((k) => v[k]).join(' ') : '')
let buttonsSeen = 0
try {
const rc = await import('@vue/runtime-core')
const { createApp } = rc.createRenderer({
insert, remove: (c) => { if (c.parent) c.parent.children.splice(c.parent.children.indexOf(c), 1); c.parent = null },
createElement: (tag) => node(tag), createText: (t) => Object.assign(node('#text'), { text: t }),
createComment: () => node('#comment'), setText: (n, t) => { n.text = t },
setElementText: (n, t) => { n.children = [Object.assign(node('#text'), { text: t, parent: n })] },
parentNode: (n) => n.parent, nextSibling: (n) => { const p = n.parent; return p ? p.children[p.children.indexOf(n) + 1] || null : null },
patchProp: (el, key, _prev, next) => { el.props[key] = key === 'class' ? classStr(next) : next },
})
const PosterWall = (await server.ssrLoadModule('virtual:poster-wall-client')).default
const item = { id: 'gate-item', label: 'Gate Item', count: 7 }
async function mountWall(extra, items = [item]) {
const emitted = []
const root = node('root')
const app = createApp(PosterWall, { items, eventName: 'gate:show', instanceId: 'gate', ...extra,
'onHeader-action': (i) => emitted.push(i) })
app.config.errorHandler = (e) => fail(`mount error: ${e?.message || e}`)
app.mount(root)
await rc.nextTick()
const tapes = []; walk(root, (n) => { if (n.tag === 'button' && hasClass(n, 'tape-h')) tapes.push(n) })
const cards = []; walk(root, (n) => { if (n.tag === 'div' && hasClass(n, 'card')) cards.push(n) })
buttonsSeen += tapes.length
return { root, tapes, cards, emitted, app }
}
// ── A: the default is the toggle it always was ─────────────────────────────────────────────
{
dispatched.length = 0
const w = await mountWall({})
if (w.cards.length !== 1) fail(`A: expected 1 card, rendered ${w.cards.length}`)
if (w.tapes.length !== 1) fail(`A: expected 1 header sticker, found ${w.tapes.length}`)
const b = w.tapes[0]
if (b) {
if (b.props['aria-pressed'] !== 'false') fail(`A: aria-pressed should start "false", got ${JSON.stringify(b.props['aria-pressed'])}`)
if (typeof b.props.onClick !== 'function') fail('A: the sticker has no click handler')
else {
b.props.onClick(); await rc.nextTick()
const ev = dispatched.filter((e) => e.type === 'gate:show')
if (ev.length !== 1 || ev[0].detail?.listId !== item.id) fail(`A: first click should dispatch gate:show with listId ${item.id}, got ${JSON.stringify(ev.map((e) => e.detail))}`)
if (b.props['aria-pressed'] !== 'true') fail('A: aria-pressed should be "true" after the first click')
if (!hasClass(w.cards[0], 'is-last')) fail('A: the card should carry is-last after the first click')
b.props.onClick(); await rc.nextTick()
const ev2 = dispatched.filter((e) => e.type === 'gate:show')
if (ev2.length !== 2 || ev2[1].detail?.listId !== null) fail('A: second click should dispatch listId null (the toggle)')
}
if (w.emitted.length) fail(`A: the default must emit nothing, emitted ${w.emitted.length}`)
}
w.app.unmount()
}
// ── B: headerAction swaps the action and drops the toggle's side effects ────────────────────
{
dispatched.length = 0
const w = await mountWall({ headerAction: { labelKey: 'gate.fullDetails', ariaKey: 'gate.fullDetailsAria' } })
if (w.tapes.length !== 1) fail(`B: expected 1 header sticker, found ${w.tapes.length}`)
const b = w.tapes[0]
if (b) {
if ('aria-pressed' in b.props && b.props['aria-pressed'] != null) fail(`B: a plain button must not carry aria-pressed, got ${JSON.stringify(b.props['aria-pressed'])}`)
const label = textOf(b)
// No i18n is installed, so t() falls back to humanizing the key: the label must come from
// labelKey, and must not be the map wording.
if (!/full\s*details/i.test(label)) fail(`B: label should come from labelKey, got ${JSON.stringify(label)}`)
if (/on\s*map/i.test(label)) fail(`B: label still says the map wording: ${JSON.stringify(label)}`)
if (!/7\s*UPG/.test(label)) fail(`B: an item WITH a count keeps its "7 UPG ·" prefix, got ${JSON.stringify(label)}`)
if (!/aria/i.test(String(b.props['aria-label']))) fail(`B: aria-label should come from ariaKey, got ${JSON.stringify(b.props['aria-label'])}`)
if (typeof b.props.onClick !== 'function') fail('B: the sticker has no click handler')
else {
b.props.onClick(); await rc.nextTick()
if (w.emitted.length !== 1 || w.emitted[0]?.id !== item.id) fail(`B: a click should emit header-action with the item once, emitted ${JSON.stringify(w.emitted)}`)
if (dispatched.some((e) => e.type === 'gate:show')) fail('B: a click must dispatch NO show-on-map window event')
if (hasClass(w.cards[0], 'is-last')) fail('B: the card must not take the is-last highlight')
}
}
w.app.unmount()
}
// ── C: a template card has no count, so its sticker is the label alone ─────────────────────
{
const w = await mountWall({ headerAction: { labelKey: 'gate.fullDetails' } }, [{ id: 'gate-template', label: 'Gate Template' }])
const label = w.tapes[0] ? textOf(w.tapes[0]) : ''
if (w.tapes.length !== 1) fail(`C: expected 1 header sticker, found ${w.tapes.length}`)
else if (/UPG|·|undefined/.test(label) || !/full\s*details/i.test(label)) fail(`C: a countless item should read the label alone, got ${JSON.stringify(label)}`)
w.app.unmount()
}
// ── the previewSrc seam ───────────────────────────────────────────────────────────────────────
const settle = async () => { for (let i = 0; i < 6; i++) { await new Promise((r) => setTimeout(r, 0)); await rc.nextTick() } }
const imgs = (root) => { const out = []; walk(root, (n) => { if (n.tag === 'img' && hasClass(n, 'img')) out.push(n) }); return out }
const pending = (root) => { const out = []; walk(root, (n) => { if (n.tag === 'div' && hasClass(n, 'ph') && hasClass(n, 'is-pending')) out.push(n) }); return out }
const phFor = (root, label) => { let hit = null; walk(root, (n) => { if (!hit && n.tag === 'div' && hasClass(n, 'ph') && textOf(n).includes(label)) hit = n }); return hit }
const inView = (el) => { for (const io of observers) if (io.els.has(el)) io.cb([{ target: el, isIntersecting: true }]) }
// D: no previewSrc, the stored file exactly as before
{
const before = observers.length
const w = await mountWall({})
const im = imgs(w.root)
if (im.length !== 1 || !String(im[0].props.src).endsWith('/previews/gate-item.webp')) fail(`D: expected one img on previews/gate-item.webp, got ${JSON.stringify(im.map((x) => x.props.src))}`)
if (pending(w.root).length) fail('D: a wall without previewSrc must show no pending placeholder')
if (observers.length !== before) fail('D: a wall without previewSrc must create no IntersectionObserver')
w.app.unmount()
}
// E + G: lazy, one at a time, fallback on null, cached
const three = [{ id: 'p1', label: 'Pone', count: 3 }, { id: 'p2', label: 'Ptwo', count: 2 }, { id: 'p3', label: 'Pthree', count: 1 }]
const calls = []; let inFlight = 0; let maxInFlight = 0; const answer = {}
const previewSrc = (it) => { calls.push(it.id); inFlight++; maxInFlight = Math.max(maxInFlight, inFlight)
return new Promise((res) => { answer[it.id] = (v) => { inFlight--; res(v) } }) }
{
const w = await mountWall({ previewSrc, instanceId: 'gate-e' }, three)
await settle()
if (pending(w.root).length !== 3) fail(`E: expected 3 pending cards before any is in view, got ${pending(w.root).length}`)
if (imgs(w.root).length) fail('E: no card may show an img before its preview arrives')
if (calls.length) fail(`E: LAZY broken, ${calls.length} render(s) before any card was in view`)
// A pane wall scrolls inside .wall, so the observer must watch THAT scroller or its margin never looks ahead.
const io = observers[observers.length - 1]
if (!io || !io.opts.root || io.opts.root.tag !== 'div' || !hasClass(io.opts.root, 'wall')) fail(`E: the observer's root should be the wall's own scroller (.wall), got ${io?.opts.root ? io.opts.root.tag + '.' + io.opts.root.props.class : String(io?.opts.root)}`)
inView(phFor(w.root, 'Pone')); inView(phFor(w.root, 'Ptwo')); await settle()
if (calls.length !== 1) fail(`E: ONE AT A TIME broken, ${calls.length} renders running with two cards in view`)
answer.p1?.('blob:gate-one'); await settle()
const im = imgs(w.root)
if (im.length !== 1 || im[0].props.src !== 'blob:gate-one') fail(`E: the answered card should be an img on blob:gate-one, got ${JSON.stringify(im.map((x) => x.props.src))}`)
if (calls.length !== 2 || calls[1] !== 'p2') fail(`E: the second render should start after the first answered, calls ${JSON.stringify(calls)}`)
answer.p2?.(null); await settle()
const ph2 = phFor(w.root, 'Ptwo')
if (!ph2 || hasClass(ph2, 'is-pending') || !/unavailable/i.test(textOf(ph2))) fail(`E: a null answer should show the "preview unavailable" placeholder, got ${JSON.stringify(ph2 && textOf(ph2))}`)
if (calls.includes('p3')) fail('E: a card never in view was rendered')
if (maxInFlight !== 1) fail(`E: ${maxInFlight} renders overlapped`)
w.app.unmount()
}
{
const n = calls.length
const w = await mountWall({ previewSrc, instanceId: 'gate-e' }, three)
await settle()
const p1 = phFor(w.root, 'Pone'); if (p1) inView(p1)
await settle()
if (calls.length !== n) fail(`G: CACHE broken, a remount rendered again (${calls.slice(n).join(',')})`)
const im = imgs(w.root)
if (!im.some((x) => x.props.src === 'blob:gate-one')) fail('G: the remount should show the cached blob:gate-one')
w.app.unmount()
}
// F: a render that never answers is bounded by the timeout
{
const realSetTimeout = globalThis.setTimeout
globalThis.setTimeout = (f, ms, ...a) => realSetTimeout(f, ms >= 10000 ? 1 : ms, ...a)
try {
const w = await mountWall({ previewSrc: () => new Promise(() => {}), instanceId: 'gate-f' }, [{ id: 'f1', label: 'Fone', count: 1 }])
await settle()
inView(phFor(w.root, 'Fone'))
await settle(); await new Promise((r) => realSetTimeout(r, 20)); await settle()
const ph = phFor(w.root, 'Fone')
if (!ph || hasClass(ph, 'is-pending') || !/unavailable/i.test(textOf(ph))) fail(`F: a render that never answers should end on the placeholder, got ${JSON.stringify(ph && textOf(ph))}`)
w.app.unmount()
} finally { globalThis.setTimeout = realSetTimeout }
}
// H: a default wall's broken stored webp tries the tree's copy before giving up
{
const w = await mountWall({})
const first = imgs(w.root)[0]
const firstSrc = first?.props.src
if (!first || typeof first.props.onError !== 'function') fail('H: the default card has no img error handler')
else {
first.props.onError(); await settle()
const second = imgs(w.root)[0]
const want = `${GATE_TREE}/assets/previews/gate-item.webp`
if (!second || second.props.src !== want) fail(`H: the first error should retry ${want}, got ${JSON.stringify(second?.props.src)} (was ${JSON.stringify(firstSrc)})`)
else {
second.props.onError(); await settle()
const ph = phFor(w.root, 'Gate Item')
if (imgs(w.root).length || !ph || !/unavailable/i.test(textOf(ph))) fail('H: the second error should show the "preview unavailable" placeholder')
}
}
w.app.unmount()
}
// I: a generated preview that fails to load has no second base
{
const w = await mountWall({ previewSrc: async () => 'blob:gate-broken', instanceId: 'gate-i' }, [{ id: 'i1', label: 'Ione', count: 1 }])
await settle(); inView(phFor(w.root, 'Ione')); await settle()
const im = imgs(w.root)[0]
if (!im || im.props.src !== 'blob:gate-broken') fail(`I: the generated URL should be on the img, got ${JSON.stringify(im?.props.src)}`)
else {
im.props.onError(); await settle()
const ph = phFor(w.root, 'Ione')
if (imgs(w.root).length || !ph || !/unavailable/i.test(textOf(ph))) fail('I: one error on a generated preview should show the placeholder, not retry another base')
}
w.app.unmount()
}
} catch (e) {
fail(`threw: ${e?.stack || e}`)
} finally {
await server.close()
}
if (!buttonsSeen) fail('vacuous: no header sticker rendered in either arm')
if (failures.length) {
console.error(`POSTER-WALL-HOST-SEAMS: FAIL (${failures.length})\n - ${failures.join('\n - ')}`)
process.exitCode = 1
} else {
console.log(`POSTER-WALL-HOST-SEAMS: PASS (9 arms, ${buttonsSeen} stickers rendered; defaults unchanged: the Show-on-map toggle and the stored webp; headerAction emits, previewSrc is lazy, one at a time, cached and bounded)`)
}

View file

@ -1954,6 +1954,10 @@ function main() {
// 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',
// …and India's states and union territories with their people groups (data/people-groups/india-states.json),
// fetched by library/data/indiaStates.js only when a state is needed (UPG-100's batch selection, Country
// Posters' India section). sha384-pinned in library/data/indiaStates.integrity.js.
'people-groups/india-states.json',
// …and its compact cut (build-points.mjs): what all-people-groups actually fetches, 0.74 MB.
'people-groups/people-groups-points.json',
// pplr-data-maps — fetched through dataUrl() (src/utils/pplrData.js). pg-names.json loads

View file

@ -0,0 +1,121 @@
<!--
PanelSheets.vue — SHARED. A preview of a large poster as the sheets it prints on: the sheets pulled apart, each
join's under-strip drawn as a red "glue here" band, the trimmed edges and the "s / N" tags, the way a reader
will assemble it on the wall. The geometry is panelSheets.js (the same plan the print uses).
- Give it `src` (the finished poster as one image or SVG URL) and each sheet shows its own slice of it, cut with
CSS (an overflow:hidden box, the image sized to the whole poster and offset by the sheet's place). Or use
the scoped slot #sheet="{ s, slice, band }" to draw a sheet yourself.
- It fills its container's width; its height follows the layout's aspect. The container decides the size.
- It handles no pointer events (pointer-events:none throughout), so a card can make the preview its control.
- It emits ONE `load` when `src` has loaded, or ONE `error` when it cannot, per src.
- The red band is always drawn here, even for a poster whose print BLEEDS under the join: a preview's job is
to show how the pieces go together.
Styles go in through useShadowStyles, because it renders inside a plugin's shadow root.
-->
<template>
<div class="pnl" :style="{ aspectRatio: aspect }" aria-hidden="true">
<div v-for="sh in view" :key="sh.s" class="pnl-sheet" :style="sh.box">
<img v-if="src && !failed" class="pnl-img" :src="src" alt="" draggable="false" :style="sh.img">
<slot name="sheet" :s="sh.s" :slice="sh.slice" :band="sh.band"></slot>
<svg class="pnl-marks" :viewBox="`0 0 ${plan.sheetW} ${plan.sheetH}`" preserveAspectRatio="none">
<g v-if="sh.band">
<rect :x="sh.band.x" :y="sh.band.y" :width="sh.band.w" :height="sh.band.h" fill="rgba(176,0,0,.14)" />
<line v-bind="sh.bandEdge" stroke="#b00" :stroke-width="stroke" :stroke-dasharray="`${stroke * 4} ${stroke * 3}`" />
</g>
<line v-if="sh.trimEdge" v-bind="sh.trimEdge" stroke="#999" :stroke-width="stroke" />
</svg>
<!-- Text in HTML, not in the stretched SVG, so it keeps its proportions; sized in the sheet's own units. -->
<div v-if="sh.band" class="pnl-glue" :class="{ h: horizontal }" :style="sh.bandBox">
<span :style="{ fontSize: sh.labelFs }">{{ glueHere }} <svg v-if="horizontal" class="pnl-arr" viewBox="0 0 10 10"><path d="M1 5 9 1v8z" fill="currentColor"/></svg><template v-else>▲</template> <bdi dir="ltr">{{ sh.s }} / {{ plan.count }}</bdi></span>
</div>
<div class="pnl-tag" dir="ltr" :style="{ fontSize: sh.tagFs }">{{ sh.s + 1 }} / {{ plan.count }}</div>
</div>
</div>
</template>
<script setup>
import { computed, ref, watch, onBeforeUnmount } from 'vue'
import { planPanels, sliceFor, screenLayout } from './panelSheets.js'
import { useShadowStyles } from '../../composables/useShadowStyles.js'
import { useOptionalT } from '../../composables/useOptionalT.js'
const props = defineProps({
/** One printed sheet, in inches. */
sheetW: { type: Number, required: true },
sheetH: { type: Number, required: true },
count: { type: Number, default: 1 },
direction: { type: String, default: 'vertical' }, // 'vertical' (stacked) | 'horizontal' (side by side)
/** How far each sheet slides under the one before it (inches). */
joinIn: { type: Number, default: 0 },
/** The gap drawn between sheets, in inches (the Bless wall uses 0.75). */
gapIn: { type: Number, default: 0.75 },
/** The finished poster as one image (webp/png/svg URL). Optional: the #sheet slot can draw instead. */
src: { type: String, default: '' },
/** Translated strings: { glueHere }. Without one, the catalogue's poster.glue_here in the reader's language. */
labels: { type: Object, default: () => ({}) },
})
const emit = defineEmits(['load', 'error'])
// The preview shows the finished poster continuing under each band ('bleed'), so a sliced image lines up.
const plan = computed(() => planPanels({ sheetW: props.sheetW, sheetH: props.sheetH, count: props.count,
direction: props.direction, joinIn: props.joinIn, joinFill: 'bleed' }))
const horizontal = computed(() => plan.value.direction === 'horizontal')
const layout = computed(() => screenLayout(plan.value, { gapIn: props.gapIn, pxPerIn: 1 }))
const aspect = computed(() => `${layout.value.worldW} / ${layout.value.worldH}`)
const t = useOptionalT('poster')
const glueHere = computed(() => props.labels.glueHere || t('poster.glue_here'))
// Marks scale with the sheet: a line ~1/300 of the sheet's short side, text from the band's depth.
const stroke = computed(() => Math.min(plan.value.sheetW, plan.value.sheetH) / 300)
const pct = (v, of) => `${(v / of) * 100}%`
const view = computed(() => {
const P = plan.value, L = layout.value, F = P.finished
return L.sheets.map((r) => {
const sh = P.sheets[r.s], slice = sliceFor(P, r.s), b = sh.band
const H = P.direction === 'horizontal'
// font sizes as a share of the SHEET's width, via container query units (the sheet is the container)
const cq = (inches) => `${(inches / P.sheetW) * 100}cqw`
return {
s: r.s, slice, band: b,
box: { left: pct(r.x, L.worldW), top: pct(r.y, L.worldH), width: pct(r.w, L.worldW), height: pct(r.h, L.worldH) },
// the whole finished poster behind this sheet, shifted so the sheet's slice shows
img: { width: pct(F.w, P.sheetW), height: pct(F.h, P.sheetH), left: pct(-sh.at.x, P.sheetW), top: pct(-sh.at.y, P.sheetH) },
bandBox: b ? { left: pct(b.x, P.sheetW), top: pct(b.y, P.sheetH), width: pct(b.w, P.sheetW), height: pct(b.h, P.sheetH) } : null,
bandEdge: b ? (H ? { x1: b.w, y1: 0, x2: b.w, y2: P.sheetH } : { x1: 0, y1: b.h, x2: P.sheetW, y2: b.h }) : null,
trimEdge: sh.trimmed ? (H ? { x1: P.sheetW, y1: 0, x2: P.sheetW, y2: P.sheetH } : { x1: 0, y1: P.sheetH, x2: P.sheetW, y2: P.sheetH }) : null,
labelFs: cq(Math.min(P.joinIn * 0.16, P.sheetW * 0.03)),
tagFs: cq(Math.max(0.35, Math.min(P.sheetW, P.sheetH) * 0.02)),
}
})
})
// ONE load or error per src, from one probe image (the per-sheet <img>s share its cached response). A src that
// fails shows blank sheets, never the browser's broken-image icon; the card says why.
const failed = ref(false)
let probe = null
watch(() => props.src, (src) => {
if (probe) { probe.onload = probe.onerror = null; probe = null }
failed.value = false
if (!src || typeof Image === 'undefined') return
const img = new Image()
probe = img
img.onload = () => { if (probe === img) { probe = null; emit('load') } }
img.onerror = () => { if (probe === img) { probe = null; failed.value = true; emit('error') } }
img.src = src
}, { immediate: true })
onBeforeUnmount(() => { if (probe) { probe.onload = probe.onerror = null; probe = null } })
useShadowStyles(
'.pnl{position:relative;width:100%;pointer-events:none;user-select:none}' +
'.pnl-sheet{position:absolute;overflow:hidden;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.28);container-type:inline-size}' +
'.pnl-img{position:absolute;max-width:none;display:block;object-fit:fill}' +
'.pnl-marks{position:absolute;inset:0;width:100%;height:100%;overflow:visible}' +
'.pnl-glue{position:absolute;display:flex;align-items:center;justify-content:center;overflow:hidden}' +
'.pnl-glue span{color:#b00;font:700 1em/1.1 system-ui,-apple-system,"Segoe UI",sans-serif;letter-spacing:.05em;text-transform:uppercase;white-space:nowrap;background:rgba(255,255,255,.82);padding:.1em .35em;border-radius:.2em}' +
'.pnl-glue.h span{writing-mode:vertical-rl}' +
'.pnl-arr{width:.8em;height:.8em;vertical-align:middle;display:inline-block}' +
'.pnl-tag{position:absolute;top:1.5%;right:2%;color:#999;font:600 1em/1 system-ui,-apple-system,"Segoe UI",sans-serif}',
'panel-sheets'
)
</script>

View file

@ -185,7 +185,23 @@ const props = defineProps({
// cards are thumbnails can set it lower so a phone keeps two per row; see the clamp below for why
// that is safe now.
minCardPx: { type: Number, default: 0 },
// ★ WHAT THE CARD'S HEADER STICKER DOES. null (the default) is today's "Show on map" toggle,
// unchanged for every wall that does not set it. A wall with no map beside it (a gallery of
// templates) passes { labelKey, ariaKey } instead: the sticker then reads t(labelKey), is a plain
// button rather than a toggle (no aria-pressed, no is-last highlight), and emits 'header-action'
// with the item, dispatching no window event. ariaKey gets { label: item.label }. An item with no
// count (a template, not a list of people) shows the label alone, without the "N UPG ·" prefix.
headerAction: { type: Object, default: null },
// ★ WHERE A CARD'S PREVIEW IMAGE COMES FROM. null (the default) is the stored
// previews/<id>.webp every wall uses today, untouched. A wall whose cards have no stored
// preview (a gallery of templates drawn from the library, so a library change shows up in it)
// passes async (item) => an image URL | null. It runs only for cards that come into view, one at
// a time, cached per page by instanceId + item id; until it answers the card shows its label, and
// null, a throw or no answer within PREVIEW_TIMEOUT_MS shows the "preview unavailable" placeholder.
// The page keeps each returned URL and never revokes it (see GENERATED PREVIEWS below).
previewSrc: { type: Function, default: null },
})
const emit = defineEmits(['header-action'])
// The live count, seeded from the prop. A ref rather than the prop directly, because the control
// changes it and a prop is not ours to write.
@ -314,6 +330,8 @@ const fallbackBase = computed(() => {
const attempt = ref({})
const previewUrl = (id) => `${attempt.value[id] ? fallbackBase.value : base.value}/previews/${id}.webp`
function onPreviewError(id) {
// A generated preview has no second base to try.
if (props.previewSrc) { missing.value = { ...missing.value, [id]: true }; return }
const fb = fallbackBase.value
if (!attempt.value[id] && fb && fb !== base.value) { attempt.value = { ...attempt.value, [id]: 1 }; return }
missing.value = { ...missing.value, [id]: true }
@ -419,6 +437,81 @@ onMounted(() => {
onUpdated(observeRows)
onBeforeUnmount(() => { if (rowRO) rowRO.disconnect(); rowRO = null })
// ─── GENERATED PREVIEWS (the previewSrc prop) ────────────────────────────────
// Nothing here runs unless the caller passes previewSrc. A pending card registers its placeholder
// through a function ref; the observer queues it once it is in view; the queue renders ONE preview
// at a time, so a gallery never starts a burst of renders on load. The answer is cached on the page
// (a module cache would be per bundle), so a remount or a second tab shows it without a re-render.
// Two limits, both deliberate: (1) the timeout stops the WAIT, not the render, so after one times out
// the next may start while the slow one still runs ("one at a time" is best-effort past a timeout);
// (2) the cache keeps whatever URL previewSrc returns for the life of the page and never revokes it,
// which suits a gallery that shows the same few previews again; a caller that makes many should say so.
const PREVIEW_TIMEOUT_MS = 20000
const previewCache = (globalThis.__posterWallPreviews_v1 ||= new Map())
const generated = ref({}) // id -> the URL previewSrc answered
const previewEls = new Map() // id -> its pending placeholder
const previewIds = new WeakMap() // placeholder -> id
const previewQueue = []
let previewBusy = false
let previewIO = null
function setPreviewEl(id, el) {
const old = previewEls.get(id)
if (old === el) return
if (old && previewIO) previewIO.unobserve(old)
if (!el) { previewEls.delete(id); return }
previewEls.set(id, el)
previewIds.set(el, id)
if (previewIO) previewIO.observe(el)
}
function queuePreview(id) {
if (id == null || generated.value[id] || missing.value[id] || previewQueue.includes(id)) return
previewQueue.push(id)
pumpPreviews()
}
function withTimeout(promise, ms) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('preview timed out')), ms)
promise.then((v) => { clearTimeout(timer); resolve(v) }, (e) => { clearTimeout(timer); reject(e) })
})
}
async function pumpPreviews() {
if (previewBusy || !previewQueue.length) return
const id = previewQueue.shift()
const item = (props.items || []).find((x) => x && x.id === id)
if (!item || !props.previewSrc) { pumpPreviews(); return }
previewBusy = true
const key = `${props.instanceId}\u0000${id}`
try {
if (!previewCache.has(key)) {
previewCache.set(key, withTimeout(Promise.resolve().then(() => props.previewSrc(item)), PREVIEW_TIMEOUT_MS))
}
let url = null
try { url = await previewCache.get(key) } catch (_) { url = null }
if (url) generated.value = { ...generated.value, [id]: url }
else {
previewCache.delete(key) // a later mount may try again
missing.value = { ...missing.value, [id]: true }
}
} finally {
previewBusy = false
pumpPreviews()
}
}
onMounted(() => {
if (!props.previewSrc) return
// No observer (an old engine): still one at a time, just not deferred to view.
if (typeof IntersectionObserver === 'undefined') { for (const id of previewEls.keys()) queuePreview(id); return }
// The ROOT is the wall's own scroller: a pane wall scrolls inside .wall, which clips its cards, so
// a viewport root would never see a card ahead of view and the margin would buy nothing. An embedded
// wall is in normal flow and the page scrolls it, so there the viewport is the right root.
previewIO = new IntersectionObserver((entries) => {
for (const e of entries) if (e.isIntersecting) queuePreview(previewIds.get(e.target))
}, { root: props.embedded ? null : wallEl.value, rootMargin: '200px' })
for (const el of previewEls.values()) previewIO.observe(el)
})
onBeforeUnmount(() => { if (previewIO) previewIO.disconnect(); previewIO = null; previewQueue.length = 0 })
function showOnMap(item) {
// TOGGLE, not a one-way set: a control the user can turn on but not off reads as
// broken even when the selection is correct.
@ -1255,12 +1348,21 @@ useShadowStyles(
<!-- A TOGGLE (showOnMap clears the selection on a second press), so it says whether it is on:
without aria-pressed a second press sounded identical to the first to a screen reader. -->
<button type="button" class="tape tape-h" :aria-pressed="lastShown === item.id ? 'true' : 'false'"
<button v-if="!headerAction" type="button" class="tape tape-h" :aria-pressed="lastShown === item.id ? 'true' : 'false'"
:aria-label="t('buttons.showOnMapAria', { label: item.label })" @click="showOnMap(item)">
<span class="hcount">{{ item.count }} {{ unit }}</span>
<span class="hsep">·</span>
<span class="hlabel">{{ t('buttons.showOnMap') }}</span>
</button>
<!-- headerAction set: a plain button that hands the item to the host (see the prop). -->
<button v-else type="button" class="tape tape-h"
:aria-label="t(headerAction.ariaKey || headerAction.labelKey, { label: item.label })" @click="emit('header-action', item)">
<template v-if="item.count != null">
<span class="hcount">{{ item.count }} {{ unit }}</span>
<span class="hsep">·</span>
</template>
<span class="hlabel">{{ t(headerAction.labelKey) }}</span>
</button>
<div class="main">
<!-- Unsupported comes FIRST: there is no poster to try to load. -->
@ -1271,8 +1373,14 @@ useShadowStyles(
</div>
<!-- previewAction='print': the poster preview IS the control. Keyboard-
reachable (role/tabindex/Enter/Space), not click-only. -->
<!-- previewSrc set and the preview not arrived yet: the card's label, never a spinner. -->
<div v-else-if="previewSrc && !generated[item.id] && !missing[item.id]" class="ph is-pending"
:ref="(el) => setPreviewEl(item.id, el)">
<div class="ph-label">{{ item.label }}</div>
<div v-if="item.count" class="ph-count">{{ countOf('wall.peopleGroupCount', item.count) }}</div>
</div>
<img v-else-if="!missing[item.id]" class="img"
:class="{ 'is-clickable': previewIsControl }" :src="previewUrl(item.id)"
:class="{ 'is-clickable': previewIsControl }" :src="previewSrc ? generated[item.id] : previewUrl(item.id)"
:alt="previewIsControl
? hud('wall.posterAltAction', '{label} — prayer poster (click to preview and print)', { label: item.label })
: hud('wall.posterAlt', '{label} — prayer poster', { label: item.label })"

View file

@ -0,0 +1,149 @@
/**
* panelSheets.js — SHARED. The geometry of a large poster printed as several sheets and joined on the wall,
* and the marks that tell the print shop how to join them. Pure: numbers and HTML/CSS strings, no Vue.
*
* A poster too big for one sheet is printed as `count` sheets of sheetW × sheetH inches, joined in a line
* (`direction` 'vertical': stacked top to bottom; 'horizontal': side by side). At each join the EARLIER sheet's
* edge is trimmed flat and laid over the first `joinIn` inches of the next one, so
* finished length = count × sheet − (count − 1) × joinIn (3 × 36 − 2 × 6 = 96; 2 × 36 − 6 = 66).
* The strip that ends up underneath (the JOIN BAND) is filled one of two ways, chosen by the renderer:
* · 'glue' — left blank and marked "glue here" in red; content is fitted around it, so nothing crosses a
* join (the Bless wall's cards);
* · 'bleed' — the poster simply continues under it, so a trim a few millimetres off shows poster, never a
* white seam (a map, whose title and land cross the join). Only a thin guide and a small tag
* are printed there: a solid band would show through a misaligned join.
* On SCREEN (PanelSheets.vue, and any preview) the band is always drawn red with the sheets pulled apart, so a
* reader sees how the pieces go together, whichever way the print fills it.
*
* The parameter names are the size table's `sheets` object (library/constants/posterSizes.js), so a consumer
* can call planPanels(SIZES[key].sheets); borderIn and joinFill belong to the renderer, not the size.
* Lengths are inches unless a name says px.
*/
/**
* @param {{ sheetW:number, sheetH:number, count?:number, direction?:'vertical'|'horizontal', joinIn?:number,
* borderIn?:number, joinFill?:'glue'|'bleed' }} o
* @returns the plan: finished size, and per sheet where it sits in the finished poster, its join band (sheet
* coordinates, null on the first sheet), whether its trailing edge is trimmed, and its CONTENT box (sheet
* coordinates): the outer border on the poster's outer edges, the join band on a joined leading edge, and
* nothing on a trimmed trailing edge (that edge lies over the next sheet's join band).
*/
export function planPanels(o) {
const sheetW = +o.sheetW, sheetH = +o.sheetH
const count = Math.max(1, Math.round(+o.count || 1))
const direction = o.direction === 'horizontal' ? 'horizontal' : 'vertical'
const joinIn = count > 1 ? Math.max(0, +o.joinIn || 0) : 0
const borderIn = Math.max(0, +o.borderIn || 0)
const joinFill = o.joinFill === 'bleed' ? 'bleed' : 'glue'
if (!(sheetW > 0 && sheetH > 0)) throw new Error('planPanels: sheetW and sheetH must be positive inches')
const along = direction === 'vertical' ? sheetH : sheetW
if (joinIn >= along) throw new Error('planPanels: joinIn must be smaller than the sheet along the join')
const H = direction === 'vertical'
const sheets = []
for (let s = 0; s < count; s++) {
const first = s === 0, last = s === count - 1
const off = s * (along - joinIn) // this sheet's leading edge in the finished poster
const at = H ? { x: 0, y: off } : { x: off, y: 0 }
const band = first ? null : H ? { x: 0, y: 0, w: sheetW, h: joinIn } : { x: 0, y: 0, w: joinIn, h: sheetH }
const lead = first ? borderIn : joinIn, trail = last ? borderIn : 0
const content = H
? { x0: borderIn, y0: lead, x1: sheetW - borderIn, y1: sheetH - trail }
: { x0: lead, y0: borderIn, x1: sheetW - trail, y1: sheetH - borderIn }
sheets.push({ s, first, last, at, band, trimmed: !last && count > 1, content })
}
const finished = H
? { w: sheetW, h: count * sheetH - (count - 1) * joinIn }
: { w: count * sheetW - (count - 1) * joinIn, h: sheetH }
return { sheetW, sheetH, count, direction, joinIn, borderIn, joinFill, finished, sheets }
}
/**
* The part of the FINISHED poster that sheet s (0-based: 0 … count − 1) prints, and where on the sheet it goes (inches). With 'bleed' the
* whole sheet is poster (its band repeats what the sheet before shows); with 'glue' the band is left out.
* A renderer draws the finished poster once and places this rect of it on page s (the per-sheet PDF page).
*/
export function sliceFor(plan, s) {
const sh = plan.sheets[s]
if (!sh) throw new Error(`sliceFor: no sheet ${s}; sheets are 0-based, 0..${plan.count - 1}`)
const skip = plan.joinFill === 'glue' && sh.band ? plan.joinIn : 0
const V = plan.direction === 'vertical'
return V
? { x: sh.at.x, y: sh.at.y + skip, w: plan.sheetW, h: plan.sheetH - skip, sheetX: 0, sheetY: skip }
: { x: sh.at.x + skip, y: sh.at.y, w: plan.sheetW - skip, h: plan.sheetH, sheetX: skip, sheetY: 0 }
}
/**
* The sheets laid out for a SCREEN preview, pulled apart by gapIn so the joins show: stacked (vertical) or side
* by side (horizontal), in px at pxPerIn. `step` is the distance between sheet origins in inches.
*/
export function screenLayout(plan, { gapIn = 0.75, pxPerIn = 96 } = {}) {
const V = plan.direction === 'vertical'
const step = (V ? plan.sheetH : plan.sheetW) + gapIn
const sheets = plan.sheets.map((sh) => V
? { s: sh.s, x: 0, y: sh.s * step * pxPerIn, w: plan.sheetW * pxPerIn, h: plan.sheetH * pxPerIn }
: { s: sh.s, x: sh.s * step * pxPerIn, y: 0, w: plan.sheetW * pxPerIn, h: plan.sheetH * pxPerIn })
const n = plan.count
return {
step, gapIn, pxPerIn, sheets,
worldW: (V ? plan.sheetW : n * plan.sheetW + (n - 1) * gapIn) * pxPerIn,
worldH: (V ? n * plan.sheetH + (n - 1) * gapIn : plan.sheetH) * pxPerIn,
}
}
// A left-pointing arrow as a shape: vertical text rotates arrow CHARACTERS, never an inline SVG.
export const LEFT_ARROW = '<svg viewBox="0 0 10 10" width="0.8em" height="0.8em" style="vertical-align:middle"><path d="M1 5 9 1v8z" fill="currentColor"/></svg>'
const esc = (v) => String(v ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]))
const inch = (v) => v.toFixed(3) + 'in'
/**
* The join marks for sheet s (0-based), as HTML to place inside the sheet's own positioned box (class "sheet"):
* before — the join band ("glue here ▲ s / N"), drawn first so content paints over nothing it needs;
* after — the trim line on a trimmed edge, and the "s / N" sheet tag in the corner.
* `screen: true` always draws the red band (a preview shows how the sheets join, even for a bleed print).
* glueHere: the translated "glue here" (default English; the Bless wall passes wall.glueHere).
*/
export function panelChromeHtml(plan, s, { glueHere = 'glue here', screen = false } = {}) {
const sh = plan.sheets[s], N = plan.count, V = plan.direction === 'vertical'
let before = ''
if (sh.band) {
// The arrow points at the sheet laid over this band: the one above, or (side by side) the one to its left.
// (In the side-by-side band the label runs top to bottom, which would turn a ◀ character on its side; the
// arrow is drawn instead, so it points left in every browser.)
const label = V ? `${esc(glueHere)} ▲ ${s} / ${N}`
: `${esc(glueHere)} ${LEFT_ARROW} <bdi dir="ltr">${s} / ${N}</bdi>`
if (plan.joinFill === 'glue' || screen) {
before = V
? `<div class="glue" style="top:0;height:${inch(plan.joinIn)}">${label}</div>`
: `<div class="glue glue-h" style="left:0;width:${inch(plan.joinIn)}">${label}</div>`
} else {
before = `<div class="glue-guide${V ? '' : ' glue-h'}"><span>${label}</span></div>`
}
}
const trim = sh.trimmed ? (V ? '<div class="trim"></div>' : '<div class="trim trim-h"></div>') : ''
return { before, after: `${trim}<div class="tag">${s + 1} / ${N}</div>` }
}
/**
* The CSS for panelChromeHtml's marks. The vertical rules are the Bless poster's own, byte for byte (its print
* is pinned by check:wall-wysiwyg and check:panel-sheets). The label and tag scale with the join band (the
* border on a single sheet).
*/
export function panelCss(plan) {
const j = plan.joinIn || plan.borderIn, b = plan.borderIn
let css = `.glue{position:absolute;left:0;width:100%;border-bottom:.02in dashed #b00;display:flex;align-items:center;justify-content:center;font:700 ${inch(Math.min(0.5, j * 0.08))} sans-serif;color:#b00;background:rgba(176,0,0,.035);letter-spacing:.05em;text-transform:uppercase}
.trim{position:absolute;left:0;bottom:0;width:100%;border-bottom:.02in solid #999}
.tag{position:absolute;right:${inch(Math.max(0.15, b * 0.1))};top:${inch(Math.max(0.1, b * 0.08))};font:600 ${inch(Math.max(0.12, b * 0.035))} sans-serif;color:#aaa}`
if (plan.direction === 'horizontal') {
css += `
.glue.glue-h{top:0;height:100%;width:auto;border-bottom:0;border-right:.02in dashed #b00;writing-mode:vertical-rl}
.trim.trim-h{left:auto;right:0;top:0;bottom:auto;width:0;height:100%;border-bottom:0;border-right:.02in solid #999}`
}
if (plan.joinFill === 'bleed') {
css += `
.glue-guide{position:absolute;left:0;top:0;width:100%;height:${inch(plan.joinIn)};border-bottom:.01in dashed #b00;pointer-events:none}
.glue-guide.glue-h{width:${inch(plan.joinIn)};height:100%;border-bottom:0;border-right:.01in dashed #b00}
.glue-guide span{position:absolute;left:${inch(0.15)};top:${inch(0.1)};font:600 ${inch(Math.max(0.12, j * 0.02))} sans-serif;color:#b00;background:rgba(255,255,255,.85);padding:0 .06in;letter-spacing:.04em;text-transform:uppercase}`
}
return css
}

View file

@ -0,0 +1,4 @@
// GENERATED by plugin-designs/my-upg-100-list/data/emit-india-states.mjs, in the same run that writes
// data/people-groups/india-states.json: the sha384 of its exact bytes. library/data/indiaStates.js refuses a served
// copy that does not match. Do not edit by hand; re-run the emitter.
export const INDIA_STATES_SHA384 = 'sha384-PC2ve+U+IHimP8b4GBPS7LpbXqQKFzxtS8fs6I4pXBP7OqXOAqBx7kiWG0FWjxL6'

View file

@ -0,0 +1,52 @@
// indiaStates.js — India's 28 states and 8 union territories, each with the people groups located inside it.
//
// SERVED, NOT BUNDLED: the table (data/people-groups/india-states.json, written by
// plugin-designs/my-upg-100-list/data/emit-india-states.mjs) ships as assets/people-groups/india-states.json and is
// fetched here only when a state is actually needed (a state in UPG-100's batch selection, Country Posters' India
// section). This build inlines every import(), so a bundled table would ride every page load.
//
// Same shape as library/packet/packetStrings.js: the copy must match the sha384 pinned in the SAME emitter run
// (indiaStates.integrity.js), one promise per URL, and a failure is dropped from the cache so a later call
// fetches again instead of inheriting one bad moment. It REJECTS on failure: there is no honest fallback for
// "which people groups live in this state".
//
// const t = await loadIndiaStates(assetsBase)
// t.states → [{ id:'india-gj', iso:'IN-GJ', kind:'state'|'union_territory', name, shapeName, bbox, count, slugs }]
// t.byShapeName → Map(shapeName → entry) (the map's ADM1 features carry shapeName)
// t.byIso → Map(iso → entry)
import { INDIA_STATES_SHA384 } from './indiaStates.integrity.js'
import { sha384 } from '../packet/sha384.js'
const PAGE_BASE = globalThis.document?.baseURI || undefined
const cache = new Map()
export function indiaStatesUrl(assetsBase) {
return new URL(`${assetsBase}/people-groups/india-states.json`, PAGE_BASE).href
}
export function loadIndiaStates(assetsBase) {
if (!assetsBase) return Promise.reject(new Error('no assets base to fetch india-states.json from'))
let url
try { url = indiaStatesUrl(assetsBase) } catch (e) { return Promise.reject(e) }
if (!cache.has(url)) {
const p = (async () => {
const res = await fetch(url, { cache: 'force-cache' })
if (!res.ok) throw new Error(`india-states.json not served (${res.status})`)
const text = await res.text()
if ((await sha384(text)) !== INDIA_STATES_SHA384) throw new Error('india-states.json failed its integrity check')
const data = JSON.parse(text)
const states = Array.isArray(data.states) ? data.states : []
return {
meta: data.meta || {},
states,
byShapeName: new Map(states.map((s) => [s.shapeName, s])),
byIso: new Map(states.map((s) => [s.iso, s])),
}
})()
p.catch(() => { if (cache.get(url) === p) cache.delete(url) })
cache.set(url, p)
}
return cache.get(url)
}
export default { loadIndiaStates, indiaStatesUrl }

View file

@ -39,5 +39,6 @@
"error_too_large": "هذا الملصق أكبر من ذاكرة هذا الجهاز. اختر حجمًا أصغر أو دقة أقل.",
"error_failed": "تعذّر إنشاء الملصق. حاول مرة أخرى أو اختر حجمًا أصغر.",
"optional": "(اختياري)",
"phoneLimit": "على الهاتف، أكبر المقاسات غير متاحة: لا يستطيع الهاتف رسم ملصق بهذا الحجم. استخدم حاسوبًا لها."
"phoneLimit": "على الهاتف، أكبر المقاسات غير متاحة: لا يستطيع الهاتف رسم ملصق بهذا الحجم. استخدم حاسوبًا لها.",
"glue_here": "الصق هنا"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "Dieses Poster ist zu groß für den Speicher dieses Geräts. Wählen Sie eine kleinere Größe oder eine niedrigere Auflösung.",
"error_failed": "Das Poster konnte nicht erstellt werden. Versuchen Sie es erneut oder wählen Sie eine kleinere Größe.",
"optional": "(optional)",
"phoneLimit": "Auf dem Handy sind die größten Formate ausgeschaltet: Ein Handy kann kein so großes Poster zeichnen. Nutzen Sie dafür einen Computer."
"phoneLimit": "Auf dem Handy sind die größten Formate ausgeschaltet: Ein Handy kann kein so großes Poster zeichnen. Nutzen Sie dafür einen Computer.",
"glue_here": "hier kleben"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "This poster is too large for this device's memory. Choose a smaller size or a lower resolution.",
"error_failed": "The poster could not be rendered. Try again, or choose a smaller size.",
"optional": "(optional)",
"phoneLimit": "On a phone, the largest sizes are off: a phone can't draw a poster that big. Use a computer for them."
"phoneLimit": "On a phone, the largest sizes are off: a phone can't draw a poster that big. Use a computer for them.",
"glue_here": "glue here"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "Este póster es demasiado grande para la memoria de este dispositivo. Elige un tamaño menor o una resolución más baja.",
"error_failed": "No se pudo generar el póster. Inténtalo de nuevo o elige un tamaño menor.",
"optional": "(opcional)",
"phoneLimit": "En un teléfono, los tamaños más grandes están desactivados: un teléfono no puede dibujar un póster tan grande. Usa un ordenador para ellos."
"phoneLimit": "En un teléfono, los tamaños más grandes están desactivados: un teléfono no puede dibujar un póster tan grande. Usa un ordenador para ellos.",
"glue_here": "pegar aquí"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "Cette affiche est trop grande pour la mémoire de cet appareil. Choisissez un format plus petit ou une résolution plus basse.",
"error_failed": "L’affiche n’a pas pu être générée. Réessayez ou choisissez un format plus petit.",
"optional": "(facultatif)",
"phoneLimit": "Sur un téléphone, les plus grands formats sont désactivés : un téléphone ne peut pas dessiner une affiche aussi grande. Utilisez un ordinateur pour ceux-là."
"phoneLimit": "Sur un téléphone, les plus grands formats sont désactivés : un téléphone ne peut pas dessiner une affiche aussi grande. Utilisez un ordinateur pour ceux-là.",
"glue_here": "coller ici"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "यह पोस्टर इस डिवाइस की मेमोरी के लिए बहुत बड़ा है। छोटा आकार या कम रिज़ॉल्यूशन चुनें।",
"error_failed": "पोस्टर नहीं बन सका। फिर से कोशिश करें या छोटा आकार चुनें।",
"optional": "(वैकल्पिक)",
"phoneLimit": "फ़ोन पर सबसे बड़े आकार बंद हैं: फ़ोन इतना बड़ा पोस्टर नहीं बना सकता। उनके लिए कंप्यूटर का उपयोग करें।"
"phoneLimit": "फ़ोन पर सबसे बड़े आकार बंद हैं: फ़ोन इतना बड़ा पोस्टर नहीं बना सकता। उनके लिए कंप्यूटर का उपयोग करें।",
"glue_here": "यहाँ चिपकाएँ"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "Questo poster è troppo grande per la memoria di questo dispositivo. Scegli un formato più piccolo o una risoluzione più bassa.",
"error_failed": "Impossibile generare il poster. Riprova o scegli un formato più piccolo.",
"optional": "(facoltativo)",
"phoneLimit": "Sul telefono i formati più grandi sono disattivati: un telefono non può disegnare un poster così grande. Per quelli usa un computer."
"phoneLimit": "Sul telefono i formati più grandi sono disattivati: un telefono non può disegnare un poster così grande. Per quelli usa un computer.",
"glue_here": "incollare qui"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "Este pôster é grande demais para a memória deste dispositivo. Escolha um tamanho menor ou uma resolução mais baixa.",
"error_failed": "Não foi possível gerar o pôster. Tente novamente ou escolha um tamanho menor.",
"optional": "(opcional)",
"phoneLimit": "No telefone, os tamanhos maiores ficam desativados: um telefone não consegue desenhar um cartaz tão grande. Use um computador para eles."
"phoneLimit": "No telefone, os tamanhos maiores ficam desativados: um telefone não consegue desenhar um cartaz tão grande. Use um computador para eles.",
"glue_here": "colar aqui"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "Acest poster este prea mare pentru memoria acestui dispozitiv. Alegeți o dimensiune mai mică sau o rezoluție mai mică.",
"error_failed": "Posterul nu a putut fi generat. Încercați din nou sau alegeți o dimensiune mai mică.",
"optional": "(opțional)",
"phoneLimit": "Pe telefon, cele mai mari dimensiuni sunt dezactivate: un telefon nu poate desena un poster atât de mare. Folosește un calculator pentru ele."
"phoneLimit": "Pe telefon, cele mai mari dimensiuni sunt dezactivate: un telefon nu poate desena un poster atât de mare. Folosește un calculator pentru ele.",
"glue_here": "lipiți aici"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "Этот плакат слишком велик для памяти устройства. Выберите меньший размер или более низкое разрешение.",
"error_failed": "Не удалось создать плакат. Попробуйте ещё раз или выберите меньший размер.",
"optional": "(необязательно)",
"phoneLimit": "На телефоне самые большие размеры отключены: телефон не может нарисовать такой большой плакат. Используйте для них компьютер."
"phoneLimit": "На телефоне самые большие размеры отключены: телефон не может нарисовать такой большой плакат. Используйте для них компьютер.",
"glue_here": "клеить здесь"
}

View file

@ -39,5 +39,6 @@
"error_too_large": "此海报超出了本设备的内存。请选择较小的尺寸或较低的分辨率。",
"error_failed": "无法生成海报。请重试或选择较小的尺寸。",
"optional": "(可选)",
"phoneLimit": "在手机上,最大的尺寸已关闭:手机无法绘制这么大的海报。请使用电脑制作这些尺寸。"
"phoneLimit": "在手机上,最大的尺寸已关闭:手机无法绘制这么大的海报。请使用电脑制作这些尺寸。",
"glue_here": "在此粘贴"
}

View file

@ -224,6 +224,7 @@
"check:label-key-coverage": "node internal/check-label-key-coverage.mjs",
"check:host-locale-fetch": "node internal/check-host-locale-fetch.mjs",
"check:packet-a1-structure": "node internal/check-packet-a1-structure.mjs",
"check:india-states": "node internal/check-india-states.mjs",
"check:batch-selection-export": "node internal/check-batch-selection-export.mjs",
"check:zone-title-tier-a": "node internal/check-zone-title-tier-a.mjs",
"check:evangelical-share": "node internal/check-evangelical-share.mjs",
@ -346,7 +347,7 @@
"fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.mjs",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check: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:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs",
@ -405,7 +406,9 @@
"check:snippets-draw": "node internal/check-snippets-draw.mjs",
"check:snippets-draw:mapbox": "node internal/check-snippets-draw.mjs --mapbox",
"pin:poster-render": "node library/scripts/posters/pin-poster-render.mjs",
"check:board-map-options": "node internal/check-board-map-options.mjs"
"check:board-map-options": "node internal/check-board-map-options.mjs",
"check:panel-sheets": "node internal/check-panel-sheets.mjs",
"check:poster-wall-host-seams": "node internal/check-poster-wall-host-seams.mjs"
},
"//scripts": {
"_": "What each script does. These notes used to sit inside the command strings as trailing \"# …\" comments, which made the shell discard any arguments passed after \"--\" (so a gate given --url silently measured its hardcoded default port — possibly another agent's server). Keep notes here, never in a command.",
@ -557,6 +560,7 @@
"check:label-key-coverage": "node internal/check-label-key-coverage.mjs",
"check:host-locale-fetch": "node internal/check-host-locale-fetch.mjs",
"check:packet-a1-structure": "the prayer packet is built to its specified structure, read page by page from each sheet's CONTENT: full detail = map, triangle, life calendar, % evangelical, profiles, bounded set, centered set, wheel, points; lightweight = map, triangle, profiles, wheel, points. Page 1 places a map-only vertical sheet on the packet's own margin and says it is one of the UPG 100 prayer lists only when a count is supplied; page 2 is one inverted triangle whose packets tier and \"- N\" steps appear only where true; the wheel carries the Metacamp Module 1 PDF's colours and opens at nine o'clock; all 28 legend zones resolve, Deaf included.",
"check:india-states": "India's states-and-union-territories table (data/people-groups/india-states.json) stays true to the stamping it is derived from: all 36 ISO 3166-2:IN divisions (28 state + 8 union_territory, Nagaland + Mizoram listed even when empty), every India people group of admin-membership.json in exactly ONE division, every shapeName a feature of the served geometry, NO DRIFT (re-derived from today's inputs it is byte-identical and meta's input sha384s are the files on disk), and pin == table == served copy under a size ceiling. Mutation-tested. Headless; in gates:pre. By the UPG-100 owner.",
"check:batch-selection-export": "UPG-100's batch selection EXPORTS its selection (restored 2026-09-30): Copy's first line parses in data/apply-grouping.mjs (names, UUPG total, colour) with the people-group CSV after it; the CSV header, quoting and formula-neutralising hold under a real CSV parse; the HUD wires the colour swatches, hex input, Copy, Export CSV and the records prefetch; the 9 new strings are in all 11 locales, translated. Mutation-tested (CSV first, no formula guard, Copy unwired: each FAILS). Headless; in gates:pre. By the UPG-100 owner.",
"check:tree-relative": "the committed tree names no host of its own. With embedBaseUrl empty in the build config, the manifest must say embedBase \".\" and every scriptUrl, iframeUrl and embed snippet src must be relative. check:drop cannot see this: it looks for root-relative \"/...\" references and skips https:// ones, so a tree built with EMBED_BASE_URL (a deploy build) would pass it and pin every snippet to one host, the way the retired CDN route broke. Data addresses (runtime.apiBaseUrl) are not checked. SKIPs when the config sets embedBaseUrl. Static; takes an optional manifest path for poison tests.",
"check:universal-props": "every plugin reads the props the Plugin Studio header sets for all of them through the library: map engine, locale, Mapbox token and token URL, and the people-group list. It reads each bundle's source AND its shipped bundle and fails on a bypass (a mapbox:// style literal, a private token global, a list fetched around the library); a by-design exception is ALLOWed by name with its reason. --report also prints the matrix. Headless.",
@ -662,7 +666,9 @@
"pin:poster-render": "rewrite library/packet/poster-render.integrity.js from library/packet/poster-render.js — run after any change to the generator; check:poster-render-pinned fails until it is.",
"check:board-map-options": "the GEOGRAPHIC map caller must state whiteboard/boundaries EXPLICITLY, never inherit them. library/maps/whiteboard-map.js defaults to whiteboard:true/boundaries:false (it was extracted for the whiteboard case), the inverse of what a real map needs, so a caller relying on defaults renders a blank basemap. check:gospel-whiteboard cannot see this — it asserts chart geometry and imports no component.",
"check:popup-pan-controls": "the popup auto-pan reserves the map's bottom control band, so a popup that clears the CLIP is also clear of the things that take the click. Measured at 390 on a 366x732 map: the pan fitted the popup inside the container, cut 0, and a real click on Full Profile still did nothing because the top element at the button's centre was div.maplibregl-ctrl-attrib-inner (and span.pin-hint-pill on the sibling pin) - a cut-height check cannot see this, because the cut really was zero. Attribution is a licence term of the basemap, so the popup may not be raised over it; the usable frame is shorter than the container and the fit test says so. Runs the SHIPPED panPlanFor, read out of the SFC with new Function (it is not exported), never a re-typed copy, and asserts where the popup ENDS UP rather than re-deriving the expected number from the same formula. Sweeps band heights 0-120px and every width the component renders at. Source-only, headless.",
"check:control-inset": "bottomControlInset MEASURES the map's real bottom control band, so the pan reserves the band the controls actually occupy. check:popup-pan-controls proves the ARITHMETIC; this proves the band is real. If the inset silently returned 0 -- a selector typo, an engine renaming its control classes, a control moving outside the container -- panPlanFor would get bottomInset 0, behave exactly as it did before the fix, and check:popup-pan-controls would still pass every case, because its arithmetic is still correct: the bug back with both gates green. 8 cases against a real DOM, running the SHIPPED bottomControlInset read out of the SFC (it is not exported), never a copy: the band equals what the DOM independently says (not merely non-zero); no controls reserves 0; a zero-sized control reserves nothing; the taller of two corners wins; a wrapped attribution reserves more than one line; a control outside the container is ignored; a null container returns 0; and the MAPBOX corners count too, since the component takes a provider prop and every other case renders MapLibre names. Needs a browser: every assertion rests on getBoundingClientRect. 75 = no slot, never a pass."
"check:control-inset": "bottomControlInset MEASURES the map's real bottom control band, so the pan reserves the band the controls actually occupy. check:popup-pan-controls proves the ARITHMETIC; this proves the band is real. If the inset silently returned 0 -- a selector typo, an engine renaming its control classes, a control moving outside the container -- panPlanFor would get bottomInset 0, behave exactly as it did before the fix, and check:popup-pan-controls would still pass every case, because its arithmetic is still correct: the bug back with both gates green. 8 cases against a real DOM, running the SHIPPED bottomControlInset read out of the SFC (it is not exported), never a copy: the band equals what the DOM independently says (not merely non-zero); no controls reserves 0; a zero-sized control reserves nothing; the taller of two corners wins; a wrapped attribution reserves more than one line; a control outside the container is ignored; a null container returns 0; and the MAPBOX corners count too, since the component takes a provider prop and every other case renders MapLibre names. Needs a browser: every assertion rests on getBoundingClientRect. 75 = no slot, never a pass.",
"check:panel-sheets": "the shared panel geometry (library/components/poster/panelSheets.js) for posters printed on several sheets: 60x96 = 3 × 60×36 stacked and 66x66 = 2 × 36×66 side by side, 6 in joins, and a single sheet; the sheets tile the finished poster, glue slices tile and bleed slices repeat exactly the join, a bleed join prints a guide not a solid band, a screen preview always shows the red band, and the Bless 5x8ft takes its bands and marks from the module. Two-sided (4 planted defects). Headless.",
"check:poster-wall-host-seams": "PosterWall's two host seams, and that with neither set every wall behaves as before. headerAction: default null is the Show-on-map TOGGLE (aria-pressed, one window event per click, is-last, no emit); { labelKey, ariaKey } is a plain button that emits header-action with the item, no window event, no aria-pressed, and reads the label alone for an item with no count. previewSrc: default null is the stored previews/<id>.webp and no IntersectionObserver; set, a card renders only once in view of the wall's own scroller, one render at a time, cached per page, and null / a throw / a timeout ends on the preview-unavailable placeholder. Image errors: a default wall retries the tree's own copy before the placeholder; a generated preview goes straight to it. Headless: PosterWall compiled as a client component on a recording renderer; real onClick and a recording IntersectionObserver. 15 mutants each FAIL."
},
"devDependencies": {
"@capacitor/android": "^7.6.9",

View file

@ -0,0 +1,103 @@
#!/usr/bin/env node
// emit-india-states.mjs — India's states and union territories, each with the people groups inside it.
//
// node plugin-designs/my-upg-100-list/data/emit-india-states.mjs
//
// ONE table, two consumers: UPG-100's batch selection (a state selection finds its people groups) and Country
// Posters' India section (one poster per state / union territory). It is SERVED, not bundled: the tree ships it
// as assets/people-groups/india-states.json and library/data/indiaStates.js fetches it only when a state is
// needed, checked against the sha384 pin this script writes in the SAME run (so a re-emit can never leave a
// stale pin behind).
//
// DERIVED, NOT RE-STAMPED. The membership comes from the committed admin-membership.json (pip-stamp.mjs: a
// point-in-polygon of every live record's coordinates against the ADM1 shapes, with a nearest-centroid fallback
// for border slivers). Re-stamping here would re-place all 2,106 records from today's data and move the counts
// baked from that file; deriving keeps the two in step, and check:india-states fails if they ever drift (it
// compares the input sha384s recorded in meta with the files on disk).
//
// The ids, codes, kinds and English names are fixed by ISO 3166-2:IN (28 states + 8 union territories). The
// shape names come from the geometry, so a poster can highlight its shape by the exact feature property.
import { readFileSync, writeFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { dirname, join, resolve, relative } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = resolve(HERE, '..', '..', '..')
const MEMBERSHIP = join(HERE, 'admin-membership.json')
const GEOMETRY = join(REPO, 'data', 'boundaries', 'gb-IND-ADM1-full.geojson')
const SERVED_GEOMETRY = 'boundaries/gb-IND-ADM1-full-web.geojson' // what a poster fetches; the same 36 shapeNames
const OUT = join(REPO, 'data', 'people-groups', 'india-states.json')
const PIN = join(REPO, 'library', 'data', 'indiaStates.integrity.js')
// ISO 3166-2:IN — code → [English name, kind]
export const IN_DIVISIONS = {
'IN-AN': ['Andaman and Nicobar Islands', 'union_territory'], 'IN-AP': ['Andhra Pradesh', 'state'],
'IN-AR': ['Arunachal Pradesh', 'state'], 'IN-AS': ['Assam', 'state'], 'IN-BR': ['Bihar', 'state'],
'IN-CH': ['Chandigarh', 'union_territory'], 'IN-CT': ['Chhattisgarh', 'state'],
'IN-DH': ['Dadra and Nagar Haveli and Daman and Diu', 'union_territory'], 'IN-DL': ['Delhi', 'union_territory'],
'IN-GA': ['Goa', 'state'], 'IN-GJ': ['Gujarat', 'state'], 'IN-HP': ['Himachal Pradesh', 'state'],
'IN-HR': ['Haryana', 'state'], 'IN-JH': ['Jharkhand', 'state'], 'IN-JK': ['Jammu and Kashmir', 'union_territory'],
'IN-KA': ['Karnataka', 'state'], 'IN-KL': ['Kerala', 'state'], 'IN-LA': ['Ladakh', 'union_territory'],
'IN-LD': ['Lakshadweep', 'union_territory'], 'IN-MH': ['Maharashtra', 'state'], 'IN-ML': ['Meghalaya', 'state'],
'IN-MN': ['Manipur', 'state'], 'IN-MP': ['Madhya Pradesh', 'state'], 'IN-MZ': ['Mizoram', 'state'],
'IN-NL': ['Nagaland', 'state'], 'IN-OR': ['Odisha', 'state'], 'IN-PB': ['Punjab', 'state'],
'IN-PY': ['Puducherry', 'union_territory'], 'IN-RJ': ['Rajasthan', 'state'], 'IN-SK': ['Sikkim', 'state'],
'IN-TG': ['Telangana', 'state'], 'IN-TN': ['Tamil Nadu', 'state'], 'IN-TR': ['Tripura', 'state'],
'IN-UP': ['Uttar Pradesh', 'state'], 'IN-UT': ['Uttarakhand', 'state'], 'IN-WB': ['West Bengal', 'state'],
}
const sha = (buf) => 'sha384-' + createHash('sha384').update(buf).digest('base64')
const rel = (p) => relative(REPO, p).split('\\').join('/')
export function buildIndiaStates() {
const membershipRaw = readFileSync(MEMBERSHIP)
const geometryRaw = readFileSync(GEOMETRY)
const membership = JSON.parse(membershipRaw)
const features = JSON.parse(geometryRaw).features
const bySlugState = new Map()
for (const [slug, m] of Object.entries(membership)) if (m && m.country === 'IND') bySlugState.set(slug, m.admin1)
const bbox = (geom) => {
let w = Infinity, s = Infinity, e = -Infinity, n = -Infinity
const walk = (c) => { if (typeof c[0] === 'number') { w = Math.min(w, c[0]); e = Math.max(e, c[0]); s = Math.min(s, c[1]); n = Math.max(n, c[1]) } else for (const x of c) walk(x) }
walk(geom.coordinates)
return [w, s, e, n].map((v) => Math.round(v * 1000) / 1000)
}
const states = features
.map((f) => ({ iso: f.properties.shapeISO, shapeName: f.properties.shapeName, geom: f.geometry }))
.sort((a, b) => a.iso.localeCompare(b.iso))
.map(({ iso, shapeName, geom }) => {
const def = IN_DIVISIONS[iso]
if (!def) throw new Error(`the geometry has ${iso} (${shapeName}), which is not an ISO 3166-2:IN division`)
const slugs = [...bySlugState].filter(([, st]) => st === shapeName).map(([s]) => s).sort()
return { id: 'india-' + iso.slice(3).toLowerCase(), iso, kind: def[1], name: def[0], shapeName, bbox: bbox(geom), count: slugs.length, slugs }
})
const meta = {
what: 'India\'s 28 states and 8 union territories, each with the UPG-100 people groups located inside it (slugs). The term is "states and union territories", never "provinces".',
regenerate: 'node plugin-designs/my-upg-100-list/data/emit-india-states.mjs (after pip-stamp.mjs re-stamps admin-membership.json)',
inputs: {
membership: { path: rel(MEMBERSHIP), sha384: sha(membershipRaw) },
geometry: { path: rel(GEOMETRY), sha384: sha(geometryRaw) },
},
served_geometry: SERVED_GEOMETRY,
shape_property: 'shapeName',
counts: { states: states.filter((s) => s.kind === 'state').length, union_territories: states.filter((s) => s.kind === 'union_territory').length,
people_groups: states.reduce((a, s) => a + s.count, 0) },
}
// One division per line: small enough to review as a diff, and still one fetch of ~17 KB.
const text = '{\n"meta":' + JSON.stringify(meta, null, 1) + ',\n"states":[\n'
+ states.map((s) => JSON.stringify(s)).join(',\n') + '\n]}\n'
return { text, meta, states }
}
if (import.meta.url === `file://${process.argv[1]}`) {
const { text, meta } = buildIndiaStates()
writeFileSync(OUT, text)
const pin = sha(Buffer.from(text, 'utf8'))
writeFileSync(PIN, '// GENERATED by plugin-designs/my-upg-100-list/data/emit-india-states.mjs, in the same run that writes\n'
+ '// data/people-groups/india-states.json: the sha384 of its exact bytes. library/data/indiaStates.js refuses a served\n'
+ '// copy that does not match. Do not edit by hand; re-run the emitter.\n'
+ `export const INDIA_STATES_SHA384 = '${pin}'\n`)
console.log(`emit-india-states: ${meta.counts.states} states + ${meta.counts.union_territories} union territories, ${meta.counts.people_groups} people groups`)
console.log(` wrote ${rel(OUT)} (${Buffer.byteLength(text)} B) + ${rel(PIN)} (${pin.slice(0, 20)}…)`)
}

View file

@ -89,7 +89,10 @@ function loadAdm1(file) {
}));
}
const IND_POLYS = loadAdm1("gb-IND-ADM1.geojson");
// India is stamped against ALL 36 states and union territories (data/boundaries/gb-IND-ADM1-full.geojson). The
// 34-shape map copy beside this file has no Nāgāland or Mizoram, so a group located there would have been
// stamped into a neighbour by the fallback (none is today: re-stamped 2026-09-30, 853/855 agree, 0 differ).
const IND_POLYS = loadAdm1("../../../data/boundaries/gb-IND-ADM1-full.geojson");
const CHN_POLYS = loadAdm1("gb-CHN-ADM1.geojson");
// Stamp one point against a polygon set. Returns { shapeName, fallback }
@ -186,3 +189,4 @@ console.log(` records: ${records.length}`);
console.log(` IND located: ${indCountedTotal} CHN located: ${chnCountedTotal}`);
console.log(` real-stamp: ${stampedReal} fallback: ${fallbackCount}`);
console.log(` wrote admin-membership.json + admin-counts.json`);
console.log(" NEXT: node plugin-designs/my-upg-100-list/data/emit-india-states.mjs (re-derives data/people-groups/india-states.json + its pin; check:india-states fails until you do)");

View file

@ -284,6 +284,7 @@ function toggleTerritory(t) {
const idx = list.findIndex((x) => x.key === t.key)
if (idx >= 0) selectedTerritories.value = list.filter((_, i) => i !== idx) // toggle OFF
else selectedTerritories.value = [...list, t] // toggle ON
if (idx < 0 && t.kind === 'adm1') indiaStates().catch(() => {}) // warm India's table for Copy / CSV
refreshSelectionPaint()
copyFlash.value = ''
}
@ -498,6 +499,7 @@ import { useShadowStyles } from '@map/composables/useShadowStyles.js'
// non-useMapInstance profiles like this one can use it).
import { useEdgeScrollAffordance } from '@map/composables/useEdgeScrollAffordance.js'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
import { loadIndiaStates } from '@map/data/indiaStates.js'
// Reads a GeoJSON source's features WITHOUT touching the engine-private _data, whose shape differs
// between MapLibre and Mapbox (see openPgDetail).
import { readSourceFeatures } from '@map/maps/pin-selection.js'
@ -1840,11 +1842,21 @@ function selectionRecordsLoad() {
// { records, from: Map(slug → the selected territories that contain it), missing: adm1 names } in the
// SELECTION'S order: a zone's groups, then a country's, each record once. Territory names are the HUD's own
// (selectedTerritories[].name), the same names the clipboard's first line carries.
// ★ INDIA'S STATES AND UNION TERRITORIES resolve through the served table (data/people-groups/india-states.json,
// library/data/indiaStates.js), fetched only once a state is selected. Any other ADM1 (China's provinces) has no
// table yet, so it stays `missing` and is NAMED to the reader, never silently dropped.
function indiaStates() { return loadIndiaStates(resolveAssetsBase(profileConfig.value)); }
async function selectionPeople() {
const lists = selectedLists();
const codes = selectedCountryCodes();
const adm1 = selectedAdm1Names();
if (!lists.length && !codes.length) {
let adm1 = selectedAdm1Names();
let india = null;
if (adm1.length) {
try { india = await indiaStates(); } catch (_) { india = null; } // unreachable table: every state stays missing
if (india) adm1 = adm1.filter((n) => !india.byShapeName.has(n));
}
const indiaCount = india ? selectedTerritories.value.filter((t) => t.kind === 'adm1' && india.byShapeName.has(t.name)).length : 0;
if (!lists.length && !codes.length && !indiaCount) {
// Name WHY when the only thing selected is a province — "nothing selected" would be a lie the
// reader can see is false.
if (adm1.length) throw keyedError('upgMap.selectionAdm1NoData', 'no people-group data for {names} — select the country instead', { names: formatList(adm1, { locale: packetLang() }) });
@ -1868,6 +1880,9 @@ async function selectionPeople() {
for (const s of slugs) add(s, t.name);
} else if (t.kind === 'adm0') {
for (const s of evaluate({ type: 'country_codes', values: [String(t.key).replace(/^adm0:/, '')] }, records)) add(s, t.name);
} else if (t.kind === 'adm1' && india) {
const st = india.byShapeName.get(t.name);
if (st) for (const s of st.slugs) add(s, t.name);
}
}
return { records: [...from.keys()].map((s) => bySlug.get(s)).filter(Boolean), from, missing: adm1 };

View file

@ -21,6 +21,8 @@
* SAME layout for the screen (screenPoster: what the wall shows is what prints). No Vue.
*/
import { blessingFor, POSTER, THEMES } from './blessings.js'
// The sheets, their glue bands and the print marks: the shared panel geometry (also India's large posters).
import { planPanels, screenLayout, panelChromeHtml, panelCss } from '../../../library/components/poster/panelSheets.js'
// Sheet sizes in inches (w × h of ONE printed page), how many stack vertically, and the border. The brief's
// three: 3 × 4 ft (the default, a living-room wall), 2 × 3 ft (small), 5 × 8 ft (three glued panels).
@ -33,6 +35,9 @@ export const DEFAULT_PRESET = '3x4ft'
export const MIN_READABLE_PT = 6 // verse size below which a verse under every card is not readable
export const STRIP_PT = 9 // the one-verse strip under each row, on the sizes that use it
// A preset as panels: its sheets stacked top to bottom, each join a blank GLUE band as deep as the border.
const panelsOf = (P) => planPanels({ sheetW: P.sheetW, sheetH: P.sheetH, count: P.sheets, direction: 'vertical', joinIn: P.border, borderIn: P.border, joinFill: 'glue' })
const CARD_ASPECT = 0.8 // card width / height: a portrait card (name, photo, verse)
const PT = 1 / 72 // one point in inches
@ -48,15 +53,14 @@ export function planPoster(presetId, n) {
// priestly blessing, the journey from Abraham to the church and the closing line. All scale with the width.
const headH = W * 0.075, tableH = W * 0.17
const bannerH = headH + tableH, footerH = footerHeight(W)
// Each sheet's content band, in the sheet's own coordinates: y0..y1.
const bands = []
for (let s = 0; s < P.sheets; s++) {
const last = s === P.sheets - 1
let y0 = b // sheet 1: outer border; the others: the glue band
let y1 = P.sheetH - (last ? b : 0) // flat bottom edge, except the last sheet's border
// Each sheet's content band, in the sheet's own coordinates: y0..y1. The panels give each sheet's content box:
// sheet 1 starts below its outer border, the others below their glue band; only the last has a bottom border
// (the others' bottom edge is trimmed flat and glued over the next sheet's band).
const panels = panelsOf(P)
const bands = panels.sheets.map(({ s, last, content: { y0, y1 } }) => {
const top = s === 0 ? bannerH : 0, bottom = last ? footerH : 0
bands.push({ s, y0: y0 + top, y1: y1 - bottom, bannerY: s === 0 ? y0 : null, footerY: last ? y1 - footerH : null })
}
return { s, y0: y0 + top, y1: y1 - bottom, bannerY: s === 0 ? y0 : null, footerY: last ? y1 - footerH : null }
})
// The largest card (fewest columns) whose grid holds n when rows are fitted to each band; `strip` is the
// height of the verse strip under each row (0 = the card layout, a verse on every card).
const fit = (strip) => {
@ -87,7 +91,7 @@ export function planPoster(presetId, n) {
preset: P, presetId: PRESETS[presetId] ? presetId : DEFAULT_PRESET, n, cols, rowsPer, cw, ch, bands, cards,
layout: stripLayout ? 'strip' : 'cards', strips,
bannerH, footerH, headH, tableH, contentW: W,
posterW: P.sheetW, posterH: P.sheetH * P.sheets - (P.sheets - 1) * b, // after gluing: 60 × 96 for 5x8ft
posterW: panels.finished.w, posterH: panels.finished.h, // after gluing: 60 × 96 for 5x8ft
text: { versePt: +versePt.toFixed(1), namePt: +(versePt * 1.25).toFixed(1), readable: !stripLayout, stripPt: STRIP_PT },
}
}
@ -173,12 +177,11 @@ const PX_IN = 96 // CSS px per inch: the screen world is the print, in CSS px
*/
export function screenPoster(groups, presetId = DEFAULT_PRESET, texts = {}) {
const { plan, css, pages } = posterParts(groups, presetId, texts, false)
const P = plan.preset, step = P.sheetH + SCREEN_GAP_IN
const sheets = pages.map((pg, s) => ({ x: 0, y: s * step * PX_IN, w: P.sheetW * PX_IN, h: P.sheetH * PX_IN }))
const L = screenLayout(panelsOf(plan.preset), { gapIn: SCREEN_GAP_IN, pxPerIn: PX_IN }), step = L.step
return {
plan, css, pxPerIn: PX_IN,
worldW: P.sheetW * PX_IN, worldH: (P.sheets * P.sheetH + (P.sheets - 1) * SCREEN_GAP_IN) * PX_IN,
sheets,
worldW: L.worldW, worldH: L.worldH,
sheets: L.sheets.map(({ x, y, w, h }) => ({ x, y, w, h })),
cards: plan.cards.map((c) => ({ i: c.i, x: c.x * PX_IN, y: (c.s * step + c.y) * PX_IN, w: c.w * PX_IN, h: c.h * PX_IN })),
chromeHtml: pages.map((pg, s) => `<section class="sheet" style="position:absolute;left:0;top:${s * step}in">${pg.chrome}</section>`).join(''),
}
@ -217,9 +220,10 @@ function posterParts(groups, presetId, texts, withCards = true) {
return `<div class="card" style="left:${f(c.x)};top:${f(c.y)};width:${f(c.w)};height:${f(c.h)}">`
+ `<div class="nm${nf.wrap ? ' w2' : ''}" dir="auto" style="font-size:${nf.pt}pt${nf.wrap ? ';-webkit-line-clamp:' + nf.lines : ''}">${esc(g.name || '')}</div>${img}${verse}</div>`
}
const panels = panelsOf(P)
const pages = plan.bands.map((bd) => {
const s = bd.s, last = s === plan.bands.length - 1
const glue = s > 0 ? `<div class="glue" style="top:0;height:${f(b)}">${esc(glueHere)} ▲ ${s} / ${P.sheets}</div>` : ''
const s = bd.s
const marks = panelChromeHtml(panels, s, { glueHere }) // the glue band (before), the trim line + s / N tag (after)
// THE STORY, top to bottom: in Jesus' name → three prayer themes (OT + NT) → every people group →
// the priestly blessing → the calling to bless, Abraham to the church with Christ at its centre →
// the closing line, at the foundation. Each station's text takes dir="auto": the grid runs right to left
@ -247,11 +251,10 @@ function posterParts(groups, presetId, texts, withCards = true) {
+ `<div class="jny">${POSTER.journey.map((v) => `<div class="st${v.centre ? ' ctr' : ''}"><p class="sn" dir="auto">${esc(lab('journey_' + v.id, JOURNEY_EN[v.id]))}</p><p class="sv" dir="auto">${esc(v.text)}</p><p class="sr" dir="auto">${esc(v.ref)}</p></div>`).join('')}</div>`
+ `<p class="close" dir="auto">${esc(lab('closing', CLOSING_EN))}</p>`
+ `</footer>` : ''
const trim = !last && P.sheets > 1 ? '<div class="trim"></div>' : ''
// STRIP layout: one blessing verse per row, on a strip under the row (the same rotation as the cards').
const strips = plan.strips.filter((t) => t.s === s).map((t) => { const v = blessingFor(t.row)
return `<div class="vstrip" dir="auto" style="left:${f(t.x)};top:${f(t.y)};width:${f(t.w)};height:${f(t.h)}">${esc(v.text)}<span class="vref">${esc(v.ref)}</span></div>` }).join('')
return { cards: withCards ? plan.cards.filter((c) => c.s === s).map(card).join('') : '', chrome: `${glue}${head}${strips}${foot}${trim}<div class="tag">${s + 1} / ${P.sheets}</div>` }
return { cards: withCards ? plan.cards.filter((c) => c.s === s).map(card).join('') : '', chrome: `${marks.before}${head}${strips}${foot}${marks.after}` }
})
const css = `@page{size:${P.sheetW}in ${P.sheetH}in;margin:0}
*{box-sizing:border-box}html,body{margin:0;padding:0}
@ -293,8 +296,6 @@ body,.poster{font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;-webkit-p
.card .vs{font-size:${vPt}pt;line-height:1.15;font-style:italic}
.card .vs,.card .rf{flex:none}
.card .rf{font-size:${rPt.toFixed(1)}pt;color:#777;margin-top:1pt}
.glue{position:absolute;left:0;width:100%;border-bottom:.02in dashed #b00;display:flex;align-items:center;justify-content:center;font:700 ${f(Math.min(0.5, b * 0.08))} sans-serif;color:#b00;background:rgba(176,0,0,.035);letter-spacing:.05em;text-transform:uppercase}
.trim{position:absolute;left:0;bottom:0;width:100%;border-bottom:.02in solid #999}
.tag{position:absolute;right:${f(Math.max(0.15, b * 0.1))};top:${f(Math.max(0.1, b * 0.08))};font:600 ${f(Math.max(0.12, b * 0.035))} sans-serif;color:#aaa}`
${panelCss(panels)}`
return { plan, css, pages, banner }
}

View file

@ -0,0 +1,60 @@
{
"meta":{
"what": "India's 28 states and 8 union territories, each with the UPG-100 people groups located inside it (slugs). The term is \"states and union territories\", never \"provinces\".",
"regenerate": "node plugin-designs/my-upg-100-list/data/emit-india-states.mjs (after pip-stamp.mjs re-stamps admin-membership.json)",
"inputs": {
"membership": {
"path": "plugin-designs/my-upg-100-list/data/admin-membership.json",
"sha384": "sha384-0D8paDuTT3HmH+YQkuuQP0ZIjp84ysz3lIXNaFlNbZKp+OevjnpZjj4FPyi7SJUl"
},
"geometry": {
"path": "data/boundaries/gb-IND-ADM1-full.geojson",
"sha384": "sha384-0tTsrv3FE4otJ2OGq7IXCs84Y9lHF7rz3cB+Vhxv76ARuw90JtJt7RLAG91L+/9j"
}
},
"served_geometry": "boundaries/gb-IND-ADM1-full-web.geojson",
"shape_property": "shapeName",
"counts": {
"states": 28,
"union_territories": 8,
"people_groups": 855
}
},
"states":[
{"id":"india-an","iso":"IN-AN","kind":"union_territory","name":"Andaman and Nicobar Islands","shapeName":"Andaman and Nicobar Islands","bbox":[92.204,6.754,93.943,13.673],"count":9,"slugs":["andamanese","bhatu-andamani","brahmin-andamani","coorgi-andamanese","great-andamanese","jarawas","onge","sentinelese","telengi"]},
{"id":"india-ap","iso":"IN-AP","kind":"state","name":"Andhra Pradesh","shapeName":"Andhra Pradesh","bbox":[76.761,12.624,84.765,19.168],"count":81,"slugs":["accukattavandlu","aguru","aiyarakulu","andhra-vaisnawa","ansari-momin-telugu","arakala","are-bondili","are-katika","atollu","bandollu","bania-agarwal-telegu","bayita-kammara","berad","bhondari","budakukkala","cakketakare","camadollu","cundollu","dammali","dasari","gandalla","gandla","ganta-sayebulu","garadi","gavara","godagali","godari","guddi-eluguwallu","hajjam-telugu","hatgar","jakkula","jandra","jethi","kaikali","karnam","karnibhakthulu","kommu-dasari","konda-kammara","kuliya","kurakula","madiga-dasu","majjula","malis","mandula","manna-dhora","medari","mondivogula","muli","musti-chenchu","nagavamsam","nakhasa","neyyala","ontaris","padigarajulu","pagativeshagallu","paky","pamidi","pamula","pancaratra-srivaisnavas-brahmans","reddi-dora","reddika","runjalavallu","sadaru","sadhu-chetty","sakunapaksollu","samayavallu","senapathi","silavantulu","swakulasali-koshti","tamboli-telugu","thogataveera-kshatriya","tirugatigantla","tolakari","udupula","uppara","vaikhanasa-brahmans","vipravinodulu","yata","yatagiri","yatla","yite"]},
{"id":"india-ar","iso":"IN-AR","kind":"state","name":"Arunachal Pradesh","shapeName":"Arunāchal Pradesh","bbox":[91.546,26.651,97.411,29.462],"count":7,"slugs":["khamba","khamyang","khowa","momba","na","serdukpen","zakhrin"]},
{"id":"india-as","iso":"IN-AS","kind":"state","name":"Assam","shapeName":"Assam","bbox":[89.699,24.136,96.018,27.972],"count":15,"slugs":["aiton","bilaspuri","chamar-assamese","hajjam-assamese","kumhar-assamese","man","maria-muslims","matak","mukhi","nai-assamese","namasudra-assamese","sudra-das","taiphake","teli-assamese","turun"]},
{"id":"india-br","iso":"IN-BR","kind":"state","name":"Bihar","shapeName":"Bihār","bbox":[83.32,24.286,88.3,27.522],"count":18,"slugs":["banpar","camma","dhamin","gadihar","gandharv","hindu-santarash","jains-maithili","jogi-angika","kadar","mukeri","nalband","rajdhop","sahisia","shershabadia","thakurai","tikulhara","tilli","turuk-pasi"]},
{"id":"india-ch","iso":"IN-CH","kind":"union_territory","name":"Chandigarh","shapeName":"Chandīgarh","bbox":[76.705,30.666,76.849,30.795],"count":1,"slugs":["khatik-punjabi"]},
{"id":"india-ct","iso":"IN-CT","kind":"state","name":"Chhattisgarh","shapeName":"Chhattīsgarh","bbox":[80.244,17.782,84.396,24.106],"count":24,"slugs":["badhai-chhattisgarhi","bairagi-chhattisgarhi","bargah","beldar-chhattisgarhi","bhangi-chhattisgarhi","bharewa","bharia-bhumia-chhattisgarhi","bhattra-bhatri","bhunjia","chaikwa","dhakad-halbi","dom-chhattisgarhi","ghadwa","gosain-chhattisgarhi","jogi-halbi","kalal-sadri","khairwar-chhattisgarhi","khatik-chhattisgarhi","kol-chhattisgarhi","munnewar","panara","pando","sayyid-chhattisgarhi","son-koli"]},
{"id":"india-dh","iso":"IN-DH","kind":"union_territory","name":"Dadra and Nagar Haveli and Daman and Diu","shapeName":"Dādra and Nagar Haveli and Damān and Diu","bbox":[70.876,20.047,73.218,20.743],"count":1,"slugs":["sagharias"]},
{"id":"india-dl","iso":"IN-DL","kind":"union_territory","name":"Delhi","shapeName":"Delhi","bbox":[76.839,28.405,77.347,28.884],"count":1,"slugs":["khatbune"]},
{"id":"india-ga","iso":"IN-GA","kind":"state","name":"Goa","shapeName":"Goa","bbox":[73.675,14.901,74.336,15.801],"count":5,"slugs":["bania-konkani","gabit","gomantak-maratha","mhalo","mith-gauda"]},
{"id":"india-gj","iso":"IN-GJ","kind":"state","name":"Gujarat","shapeName":"Gujarāt","bbox":[68.093,20.12,74.477,24.712],"count":90,"slugs":["agaria","agri","anjana-patel-gujarati","bafan","bairagi-gujarati","baloch","bamcha","banjania","barot","barthari","bhodala","borpi","caki","champta","cunara","dafer","dangasiya","dasnami-bawa","depala","dudhwala","gadhai","galiara","garmatan","garvi","gavli","ghantiya","golarana","gosain-gujarati","hajjam-gujarati","halaypotra","hingora","hingorza","jat-muslim","juneja","kahar-gujarati","kamalia","kambodia-bhagat","kapadi","kasar-gujarati","kasbati","kathi","ker","khalpa","khaskeli","khatik-gujarati","langa","machhiyara","majothi","makrani","mandali-muslims","mer","mir","miyana","moghol-gujarati","mujavar","muslim-khatri","muslim-lakhera","muslim-patel","mutwa","narsinghpura","nayak-muslims","node","padhar","panar","paradhi","patanwadia","patni-jamat","patni-vankars","pomla","rathodia","raysipotra","royma","sagar","sandhai-muslims","saranias","sayyid-gujarati","shravan","siddi","sompura-salat","tai","targala","theba","tirgar","turi-barot","vadi","vahivanca-barot","vayati","vyapari","wagheri","wandharo"]},
{"id":"india-hp","iso":"IN-HP","kind":"state","name":"Himachal Pradesh","shapeName":"Himāchal Pradesh","bbox":[75.594,30.378,79.009,33.257],"count":20,"slugs":["bhojki","daule","dhaki","dosali","gosain-kangri","hadi","hesi","khatik-chambeali","malaneese","masku","nar","nicar","pangwal-buddhists","pangwala","rehar","sanhai","sansoi","seok","swangla","thavi"]},
{"id":"india-hr","iso":"IN-HR","kind":"state","name":"Haryana","shapeName":"Haryāna","bbox":[74.474,27.653,77.604,30.929],"count":4,"slugs":["arain-punjabi","hajjam-haryanvi","kucband","lakhera-haryanvi"]},
{"id":"india-jh","iso":"IN-JH","kind":"state","name":"Jharkhand","shapeName":"Jhārkhand","bbox":[83.33,21.97,87.962,25.349],"count":9,"slugs":["dhekaro","jadupetia","jarkhandi-hajjam","jarkhandi-julaha","jharkhandi-nai","modaks","nagbansi","puran","sukiyar"]},
{"id":"india-jk","iso":"IN-JK","kind":"union_territory","name":"Jammu and Kashmir","shapeName":"Jammu and Kashmīr","bbox":[73.39,32.276,76.78,35.118],"count":33,"slugs":["arain-dogri","bania-dogri","bhand-kashmiri","bomba","budhan","cheintz","chopan","dhobi-dogri","dhyar","domal","galwan","ganz","gardi","gorkun","hajjam-kashmiri","kawoj","kraal","lohar-dogri","manhas","manjygur-muslims","moghol-dogri","nagbansi-dogri","najjar","namdagur","naqqas","pohul","ratal","sayyid-pahari","shaksaz","shina-india","sofi","sudhan","waza"]},
{"id":"india-ka","iso":"IN-KA","kind":"state","name":"Karnataka","shapeName":"Karnātaka","bbox":[74.086,11.595,78.588,18.459],"count":81,"slugs":["adiyan","agasa","ansari-momin-kannada","arasu","ayri","bagga","baira","balasantoshi","bandi","bania-kannada","batada","bayil-kammara","bayil-pattar","bellara","bestha","chaptegar","charodi","chenna-dasar","dombi-dasa","durga-murga","galada-kokani","ganti-chores","gatti","gauli","ghadi","godda","gond-kannada","gudikara","guraw","hajjam-kannada","halleer","harkantra","hasalaru","helawa","holeya-dasari","jatigar","jaweri","jogi-telugu","jogi-tulu","khakar","kolari","komara-panta","koosa","koraga-korra","kottari","koyava","kudiya","kudubi","kummara-kumbara","kurahina-setti","ladar","maila","malaikudi","maleru","manser","meda","mogaveera","mundala","nadors","nai-kannada","nairy","natua","nawaiyat","padarthis","padiyar","pambada","pattegar","pendar","pinjara-kannada","rachavar","raddi","rajapuri","raneyar","raval","raya-rawat","sapalyas","sayyid-kannada","soligaru","sudir","thammadi","vilyagaras"]},
{"id":"india-kl","iso":"IN-KL","kind":"state","name":"Kerala","shapeName":"Kerala","bbox":[74.868,8.292,77.412,12.796],"count":72,"slugs":["allar","ambalavasi","anthur-nair","arayavati","ayyanavar","bakuda","bania-malayalam","bowis-mogeyar","cakkala","cakkyar-nambiyar","chakkiyar","chamar-muchi-malayalam","chengazhi-nambiar","cingatanmar","dheevara","erawallan","ezhavathi","ilayatu","kakkalan","kalanadies","kalladi","kallattu-kurup","karimpalan","kavara","kavuthiyan","kidaran","kocu-wellan","kollan","kootan","kumar-kshatriya","kummara-kusuvan","kunduvadian","kurukkal","malai-pandaram","malayalar","maran-nairs","marasari","mavilan","moger-malayalam","moothan","mukkari","musari","muttatu","nalkadaya","nambutiris","narsanna","nasuvan","nayadi","otans","padannan","palakad-tandans","pandithar","pathinaickans","pathiyan","pisaroti","poduval","pulisari","puspakan-unni","reddi","samanthan","thachanad-moopan","tiyattunni","urindavan","vadugan","valinjiyar","vetan","vilkurup","visavan","wainadan-cetti","wainadan-kader","wainadan-pulayan","wayanad-pathiyan"]},
{"id":"india-la","iso":"IN-LA","kind":"union_territory","name":"Ladakh","shapeName":"Ladākh","bbox":[72.529,32.338,80.327,37.077],"count":6,"slugs":["bania-kashmiri","brukpa","campa","dokhpa","mon-india","purig-pa"]},
{"id":"india-ld","iso":"IN-LD","kind":"union_territory","name":"Lakshadweep","shapeName":"Lakshadweep","bbox":[72.165,8.264,73.702,11.704],"count":1,"slugs":["laccadive"]},
{"id":"india-mh","iso":"IN-MH","kind":"state","name":"Maharashtra","shapeName":"Mahārāshtra","bbox":[72.655,15.606,80.898,22.034],"count":54,"slugs":["attar-saibulu","baharupi","barda","bari-marathi","bharwad","bhoi-marathi","chari","chitari","citrakathi","dhawad","dom-marathi","garodi","garpagari","gondhali","gopal","hardas","hatkar","jhadi","kachchi-kushwaha","kaghzi","kapewar","kasikapadi","kathar","kharvi","khatik-marathi","kokaniz","kolhati","kudimahan","ladsi","lajjhar","lodha-marathi","lonar","mana","mitna","mondiwar","muslim-kachera","nahal","naikpod","nandiwala","neeli","nilkanth","pangul","phularis","pinjara-marathi","pochammalollu","prabhu-patane","sahi","sanghar","sarodi","sutar-mistry","takari","velip","waidu","yelmar"]},
{"id":"india-ml","iso":"IN-ML","kind":"state","name":"Meghalaya","shapeName":"Meghālaya","bbox":[89.814,25.029,92.803,26.12],"count":2,"slugs":["banai","dalu-india"]},
{"id":"india-mn","iso":"IN-MN","kind":"state","name":"Manipur","shapeName":"Manipur","bbox":[92.971,23.833,94.745,25.692],"count":5,"slugs":["koiren","lois","pangans","thanga","yatibi"]},
{"id":"india-mp","iso":"IN-MP","kind":"state","name":"Madhya Pradesh","shapeName":"Madhya Pradesh","bbox":[74.03,21.07,82.81,26.869],"count":33,"slugs":["anjana","banchada","baraua","bari-hindi","basudeva","berar","bhanumati","bhurtia","dhed","gachha","hajjam-hindi","jadam","jains-hindi","kachera-bagheli","kandera","kasar-hindi","ladia","lahangir","manbhav","mankar","muskhan","muslim-gond","naita","namdeo","naoda","nat-bagheli","nema","rujjhar","sais","sikligar","sisgars","soria","zamral"]},
{"id":"india-mz","iso":"IN-MZ","kind":"state","name":"Mizoram","shapeName":"Mizoram","bbox":[92.257,21.941,93.436,24.521],"count":0,"slugs":[]},
{"id":"india-nl","iso":"IN-NL","kind":"state","name":"Nagaland","shapeName":"Nāgāland","bbox":[93.327,25.199,95.242,27.036],"count":0,"slugs":[]},
{"id":"india-or","iso":"IN-OR","kind":"state","name":"Odisha","shapeName":"Odisha","bbox":[81.388,17.813,87.486,22.568],"count":64,"slugs":["baha-i","bairagi-odia","bangti","banka","bari-odia","bauti","bavuri","bhoi-odia","bondo","brahman-jyoti","candhai-maru","chatarkheya","cherua","citrakar","daitapati","damla","darji-odia","dhera","didayi","eneti-dora","ghantarghada","ghogia","ghusuria","godra","guna","irika","janughanta","jogi-odia","kachchi-alia","kamila","kapasiya","kasar-odia","khadura","khandwal","khati-odia","kobari","koraga","kuda","kuli","kurunga","kusta","liari","maghia","mahuria","malia","markande-kanjia","mundapotta","nai-odia","naksia","noliya","oriya","panka-odia","parenga","radhi","rana","sabakhia","samasi","sanei","sayyid-odia","sinduria","suda","thanapati","tharua","tula-vina"]},
{"id":"india-pb","iso":"IN-PB","kind":"state","name":"Punjab","shapeName":"Punjab","bbox":[73.88,29.542,76.939,32.513],"count":4,"slugs":["daoli","gagra","pinjara-punjabi","racband"]},
{"id":"india-py","iso":"IN-PY","kind":"union_territory","name":"Puducherry","shapeName":"Puducherry","bbox":[75.527,10.827,82.344,16.761],"count":5,"slugs":["indo-french","indo-vietnamese","sonavar","vettalaikarans","vettiyan"]},
{"id":"india-rj","iso":"IN-RJ","kind":"state","name":"Rajasthan","shapeName":"Rājasthān","bbox":[69.484,23.061,78.272,30.198],"count":48,"slugs":["badi-marwari","baret","bargi","bhopa","bhutta","cejara","chadwa","chheepi","chobdar","chungar","cudigar","dadupanthi","damami","dhola","ghihara","gosain-rajasthani","gurru","hiranbaj","khadim","kharol","kriyagar","laskari","mangnyar-muslims","moghol-marwari","mogia","murhid","nagda","nai-mewari","nyaria","pajabgar","pakhimara","rajkumhar","rajpurohit","rath","runwalas","sain","sanchia","santia","saraogi","shikari","singiwala","sorgars","sunga","tak","usta","van-baoriya","ved","vijabargiya"]},
{"id":"india-sk","iso":"IN-SK","kind":"state","name":"Sikkim","shapeName":"Sikkim","bbox":[88.014,27.079,88.92,28.129],"count":1,"slugs":["yakha"]},
{"id":"india-tg","iso":"IN-TG","kind":"state","name":"Telangana","shapeName":"Telangāna","bbox":[77.236,15.836,81.323,19.916],"count":26,"slugs":["are-kapu","attagara","bhatrajulu","bindla","bolla","cippollu","gangireddula","gosangi","gurika","jains-telugu","kachi","katipamula","khatik-telugu","lakkamari-kapu","mala-masti","matangi","mitha-ayyalavar","moghal-telugu","muchi-chamar","pambala","pariki-muggula","patkars","rajannalu","saradagallu","sindhollu","verra-musti"]},
{"id":"india-tn","iso":"IN-TN","kind":"state","name":"Tamil Nadu","shapeName":"Tamil Nādu","bbox":[76.233,8.077,80.347,13.565],"count":64,"slugs":["alavan","ansari-momin-tamil","anuppan","aranadan","baig","bondili","cekkala","chamar-muchi-tamil","chayakkaran","dasa-palanjika","dayare","dhobi-tamil","gangawar","gudugudupandi","gurukkal","jains-tamil","jati-pillai","kambalattan","kambar","kannada-naicker","kannada-saineegar","karaiyar","karlar","karna-sale","katesar","kattiakaran","kayalar","kepmari","koliyan","kongu-vaisnawa","kongu-vellala-gounder","kota","krishnavaka","kudumi","kuluvan","kummara-kummari","kunnuva-vellalar","malai-vellala","maleyan","maniyattikkaran","mudugar","nakkala","namdeo-maratha","nanjil-mudali","pannadi","panniandi","parkavakulam","parvatarajakulam","pattanavan","perumkollar","salivahanam","savalakkarar","sayyid-tamil","semman","senaithalaivar","siviar","tambiran","tholuva-naicker","thoraiyar","tondman","uppillian","urikkar-naicker","vellaiyan-kuppam-padaiyaci","vettaikkaran"]},
{"id":"india-tr","iso":"IN-TR","kind":"state","name":"Tripura","shapeName":"Tripura","bbox":[91.15,22.939,92.332,24.528],"count":11,"slugs":["bajandar","banik-bania","barua","haluadas","kanda","mahisyadas","malakar","sabar","saha","shabdakar","tripuri-namasudra"]},
{"id":"india-up","iso":"IN-UP","kind":"state","name":"Uttar Pradesh","shapeName":"Uttar Pradesh","bbox":[77.084,23.87,84.635,30.406],"count":23,"slugs":["ahmadiya","arain-kannauji","badhik","badi","balahar","bargala","bargujar","bhuiyar","bisati","charwaha","gadhia","habura","hijra","kankali","khangar-bundeli","mujavir","pankhiya","patari","putliwale","rai-bhat-muslims","rastogi","rave","sheikhzade"]},
{"id":"india-ut","iso":"IN-UT","kind":"state","name":"Uttarakhand","shapeName":"Uttarākhand","bbox":[77.573,28.723,81.045,31.456],"count":7,"slugs":["anwal","chaudangsi","jimdar","kuthliya-bora","raji-india","sayyid-garhwali","tagewale"]},
{"id":"india-wb","iso":"IN-WB","kind":"state","name":"West Bengal","shapeName":"West Bengal","bbox":[85.82,21.484,89.874,27.221],"count":30,"slugs":["angarok","ansari-momin-bengali","bagti","baida","baisya","bhakta","bhalla","bhujel","byadh","chunari","dhal-kstriya","dhimal","ghazi","hajjam-bengali","jains-bengali","kakmara","kapuria","kasar-bengali","khairwar-bengali","khalifa","khatik-bengali","kurol","mirshikar","paharia","pinjara-dhunia-bengali","siyalgar","sunuwar","sutradhar","tamboli-bengali","tripuri-kumhar"]}
]}

View file

@ -76,7 +76,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -81,7 +81,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-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -200,7 +200,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0",
"integrity": "sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A",
"elements": [
"country-posters-map"
],
@ -223,8 +223,8 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -240,7 +240,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-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -256,8 +256,8 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -881,7 +881,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-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB",
"integrity": "sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba",
"elements": [
"my-upg-100-list-map"
],
@ -904,7 +904,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-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -920,8 +920,8 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -937,8 +937,8 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]
@ -1180,7 +1180,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx",
"integrity": "sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM",
"elements": [
"regions-dashboard"
],
@ -1203,8 +1203,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1220,7 +1220,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-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1236,8 +1236,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]
@ -1473,7 +1473,7 @@
"bundle": "wall-of-people-groups",
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"output": "dx-maps/wall-of-people-groups/wall-of-people-groups.js",
"integrity": "sha384-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t",
"integrity": "sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh",
"elements": [
"wall-of-people-groups"
],
@ -1492,7 +1492,7 @@
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"embed": {
"iframe": "<iframe src=\"./wall-of-people-groups/wall-of-people-groups/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=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
},
"bundle": "wall-of-people-groups"
}

View file

@ -200,7 +200,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
"integrity": "sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0",
"integrity": "sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A",
"elements": [
"country-posters-map"
],
@ -223,8 +223,8 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -240,7 +240,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-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -256,8 +256,8 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-bMGgtFg/If1Fgbug2AbgpFSlAM44IvvNQewrfB30e8e+UqP/1yR4gVK4seN3LZz0\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-LHY6hzhphKMTlAzfb/Ty2uCu+O87CtDgqYayok4PZINsY/h42Fc4jwpVZ2jcEe2A\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -881,7 +881,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-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB",
"integrity": "sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba",
"elements": [
"my-upg-100-list-map"
],
@ -904,7 +904,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-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -920,8 +920,8 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -937,8 +937,8 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]
@ -1180,7 +1180,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx",
"integrity": "sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM",
"elements": [
"regions-dashboard"
],
@ -1203,8 +1203,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1220,7 +1220,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-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1236,8 +1236,8 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>",
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]
@ -1473,7 +1473,7 @@
"bundle": "wall-of-people-groups",
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"output": "dx-maps/wall-of-people-groups/wall-of-people-groups.js",
"integrity": "sha384-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t",
"integrity": "sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh",
"elements": [
"wall-of-people-groups"
],
@ -1492,7 +1492,7 @@
"scriptUrl": "./wall-of-people-groups/wall-of-people-groups.js",
"embed": {
"iframe": "<iframe src=\"./wall-of-people-groups/wall-of-people-groups/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=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./wall-of-people-groups/wall-of-people-groups.js\" integrity=\"sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<wall-of-people-groups profile-config='{\"profile\":\"wall-of-people-groups\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></wall-of-people-groups>"
},
"bundle": "wall-of-people-groups"
}

File diff suppressed because one or more lines are too long

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -76,7 +76,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -81,7 +81,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-FSloUQyV3AhmpUsDx+GhnLsYZONh5N0w/8iCm8w6gMK6z3MQCCZXnVPi8yY0UgwB" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-cJE79obFBKiaPMjchy0migTIZs98CYGbMBZzqIw0LhuGtjn/ak6ItaRx7YNWSqba" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -76,7 +76,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -77,7 +77,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-ip4uMMacs2WBhvxncgJl309qFPWbkX2Zr3bKpXXNoMBPM2TILjTpNcd9x5IRrRFx" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-iIeVdcfWpe2CnYJKFP7/ANLc+QaBWug0eZzk78gX9ymL05/ZNkgiUDE2cu+qHfCM" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -67,7 +67,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <wall-of-people-groups> custom element. -->
<script src="../wall-of-people-groups.js" integrity="sha384-S2g83dVn7Vb2ynBlin0ySD8rO/cFEdA60yPYJ+04ObqzXmiojWOZ5ufGPj4URG9t" crossorigin="anonymous"></script>
<script src="../wall-of-people-groups.js" integrity="sha384-+0FF2Yam1UsHfo2turc9M9IORYEfg/eKrOjl3VLUymNUK7P8/AnL8/p9zlrf/NEh" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: