Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-3/work
Some checks are pending
build-gate / build (push) Waiting to run

# Conflicts:
#	public/doxa-maps/index.html
This commit is contained in:
doxa-maps-88655c70 2026-09-24 21:26:47 +00:00
commit 558786ddf7
84 changed files with 939 additions and 422 deletions

View file

@ -12,7 +12,7 @@ capabilities, five deployment methods, one shared library, one build.
| **One app nests every map** — and each stays embeddable on its own | [capability 5](#capability-5--app-shell-the-research-gallery) · `bun run check:composite-reg` |
| **5 capabilities × 5 deployment methods** | `bun run verify:5-caps` |
| **Shared library, private plugins** — reuse without breaking each other | `bun run check:library-index` |
| **Many agents, one green `main`** | [agent swarm](#build-it-with-an-agent-swarm) |
| **One agent per plugin, one shepherd, one green `main`** | [agent swarm](#build-it-with-an-agent-swarm) |
---
@ -99,12 +99,11 @@ One `<iframe>` tag, no build step. Copy the `embed.iframe` snippet from `manifes
![Method 1 — the prayer map embedded in a host page's iframe](documentation/readme-images/deploy1-iframe.png)
### 2. Mobile app — Simple Research on a phone ✅ built
### 2. Mobile app — UPG-100 on a phone 🛠️ in progress
The Simple Research dashboard (research map + people-groups grid), shipped on its own as a
Capacitor app. `bun run check:mobile` · `bun run phone` (needs Android SDK + JDK 17)
<p align="center"><img src="documentation/readme-images/deploy2-mobile-simple-research.png" alt="Method 2 — the Simple Research dashboard at phone size" width="320"></p>
The UPG-100 list, shipped on its own as a Capacitor app. It is being finished for phones now;
its screenshot lands here when it is ready. `bun run check:mobile` · `bun run phone` (needs
Android SDK + JDK 17)
### 3. JavaScript embed — the Research Gallery on dx-life ✅ production
@ -133,11 +132,12 @@ not production-ready.
## Build it with an agent swarm
Trunk-based development with a **merge queue** — the model behind GitHub's merge queue and GitLab's
merge trains. Many agents build in parallel, each in its own worktree; changes land on `main` one
at a time, rebased and gated, so `main` stays green.
Every plugin has **one agent** responsible for building it, working in its own git worktree. The
plugins live together in the **Plugin Studio** — one repo, one shared library — and **one shepherd**
makes sure every agent is doing its job well. Changes land on `main` through a **merge queue** (the
model behind GitHub's merge queue and GitLab's merge trains): one at a time, rebased and gated.
![The delegation model: agents in their own worktrees send changes through one merge queue into main](documentation/readme-images/delegation-model.png)
![The delegation model: the Plugin Studio at the centre, a ring of plugins around it, one agent per plugin outside that, and one shepherd over all the agents](documentation/readme-images/delegation-model.png)
```bash
# one worktree + one agent per plugin
@ -154,8 +154,8 @@ flock /tmp/dx-maps-main.lock sh -c '
```
One agent owns the full build (it wipes the shared output first); everyone else checks their own
bundle with `BUNDLE=<name> bun x vite build`. A **maintainer** reviews what lands and keeps shared
code shared. Agents joining this repo: read [AGENT-BRIEF.md](./AGENT-BRIEF.md).
bundle with `BUNDLE=<name> bun x vite build`. Agents joining this repo: read
[AGENT-BRIEF.md](./AGENT-BRIEF.md).
Diagram source: [`delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -1,68 +1,65 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1010" viewBox="0 0 1200 1010" font-family="Inter, 'Helvetica Neue', Arial, sans-serif">
<svg xmlns="http://www.w3.org/2000/svg" width="1300" height="1300" viewBox="0 0 1300 1300" font-family="Inter, 'Helvetica Neue', Arial, sans-serif">
<defs>
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#64748b"/></marker>
<marker id="arM" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#b91c1c"/></marker>
<radialGradient id="gM" cx="45%" cy="40%"><stop offset="0" stop-color="#16a34a"/><stop offset="1" stop-color="#166534"/></radialGradient>
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#2563eb"/></marker>
<marker id="arS" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#7c3aed"/></marker>
<path id="stLabel" d="M 526,720 A 124,124 0 0 1 774,720"/>
<path id="plLabel" d="M 342,720 A 308,308 0 0 1 958,720"/>
<path id="agLabel" d="M 184,720 A 466,466 0 0 1 1116,720"/>
</defs>
<rect width="1200" height="1010" fill="#ffffff"/>
<circle cx="600" cy="475" r="450" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2.5"/>
<circle cx="600" cy="475" r="365" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<circle cx="600" cy="475" r="262" fill="#fffbeb" stroke="#d97706" stroke-width="2.5"/>
<circle cx="600" cy="475" r="168" fill="#fef2f2" stroke="#dc2626" stroke-width="2.5" stroke-dasharray="8 6"/>
<rect x="578.0" y="297.0" width="44" height="20" fill="#fef2f2"/>
<line x1="578.0" y1="291.0" x2="578.0" y2="323.0" stroke="#dc2626" stroke-width="5" stroke-linecap="round"/>
<line x1="622.0" y1="291.0" x2="622.0" y2="323.0" stroke="#dc2626" stroke-width="5" stroke-linecap="round"/>
<text x="600.0" y="283.0" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">ONE GATE</text>
<line x1="600.0" y1="319.0" x2="600" y2="389" stroke="#b91c1c" stroke-width="3" marker-end="url(#arM)"/>
<text x="610" y="355.0" font-size="12" font-weight="700" fill="#b91c1c">merge</text>
<rect x="515.7941844763895" y="305.1584883484701" width="48" height="26" rx="13" fill="#ffffff" stroke="#dc2626" stroke-width="2"/>
<text x="539.7941844763895" y="323.1584883484701" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">#1</text>
<rect x="465.7820831295948" y="335.2087905225743" width="48" height="26" rx="13" fill="#ffffff" stroke="#dc2626" stroke-width="2"/>
<text x="489.7820831295948" y="353.2087905225743" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">#2</text>
<rect x="429.0638892005815" y="380.55198379861537" width="48" height="26" rx="13" fill="#ffffff" stroke="#dc2626" stroke-width="2"/>
<text x="453.0638892005815" y="398.55198379861537" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">#3</text>
<text x="431.7820831295948" y="328.2087905225743" text-anchor="middle" font-size="12" fill="#b91c1c" font-style="italic">waiting in line</text>
<text x="600" y="581" text-anchor="middle" font-size="16" font-weight="800" fill="#b91c1c">MERGE QUEUE</text>
<text x="600" y="599" text-anchor="middle" font-size="12" fill="#7f1d1d">one change at a time · rebased on main</text>
<text x="600" y="614" text-anchor="middle" font-size="12" fill="#7f1d1d">lands only if the gates pass</text>
<circle cx="600" cy="475" r="82" fill="url(#gM)" stroke="#14532d" stroke-width="2"/>
<text x="600" y="473" text-anchor="middle" font-size="30" font-weight="800" fill="#ffffff">main</text>
<text x="600" y="497" text-anchor="middle" font-size="13" fill="#dcfce7">trunk · always green</text>
<rect x="405.70000000000005" y="683.0" width="388.59999999999997" height="30" rx="15" fill="#ffffff" stroke="#d97706" stroke-width="1.5"/>
<text x="600" y="703.0" text-anchor="middle" font-size="14" font-weight="700" fill="#92400e">PLUGIN STUDIO · one repo, shared library/</text>
<rect x="371.3" y="777.5" width="457.4" height="30" rx="15" fill="#ffffff" stroke="#2563eb" stroke-width="1.5"/>
<text x="600" y="797.5" text-anchor="middle" font-size="14" font-weight="700" fill="#1e40af">AGENTS · one per plugin, each in its own worktree</text>
<rect x="336.9" y="867.5" width="526.2" height="30" rx="15" fill="#ffffff" stroke="#7c3aed" stroke-width="1.5"/>
<text x="600" y="887.5" text-anchor="middle" font-size="14" font-weight="700" fill="#5b21b6">MAINTAINER · reviews what lands, keeps shared code shared</text>
<circle cx="834.6174775355869" cy="195.393778261573" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
<text x="834.6174775355869" y="189.393778261573" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Research</text>
<text x="834.6174775355869" y="206.393778261573" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Gallery</text>
<text x="834.6174775355869" y="224.393778261573" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
<line x1="796.0502209543945" y1="241.3564448487117" x2="711.8450440854579" y2="341.7082668972978" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
<circle cx="965.0" cy="475.0" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
<text x="965.0" y="469.0" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Big</text>
<text x="965.0" y="486.0" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Picture</text>
<text x="965.0" y="504.0" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
<line x1="905.0" y1="475.0" x2="774.0" y2="475.0" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
<circle cx="858.0939751330899" cy="733.0939751330898" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
<text x="858.0939751330899" y="736.0939751330898" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Regions</text>
<text x="858.0939751330899" y="762.0939751330898" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
<line x1="815.667568261897" y1="690.667568261897" x2="723.0365799264592" y2="598.0365799264592" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
<circle cx="341.9060248669102" cy="733.0939751330899" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
<text x="341.9060248669102" y="727.0939751330899" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">UPG-100</text>
<text x="341.9060248669102" y="744.0939751330899" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">List</text>
<text x="341.9060248669102" y="762.0939751330899" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
<line x1="384.332431738103" y1="690.667568261897" x2="476.96342007354076" y2="598.0365799264592" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
<circle cx="235.0" cy="475.00000000000006" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
<text x="235.0" y="469.00000000000006" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Gospel</text>
<text x="235.0" y="486.00000000000006" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Resources</text>
<text x="235.0" y="504.00000000000006" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
<line x1="295.0" y1="475.00000000000006" x2="426.0" y2="475.0" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
<circle cx="320.39377826157306" cy="240.38252246441309" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2" stroke-dasharray="5 4"/>
<text x="320.39377826157306" y="234.38252246441309" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">your next</text>
<text x="320.39377826157306" y="251.38252246441309" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">plugin</text>
<text x="320.39377826157306" y="269.3825224644131" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
<text x="600" y="25" dy="-0" />
<text x="600" y="968" text-anchor="middle" font-size="18" font-weight="800" fill="#0f172a">Many agents build in parallel. Changes land one at a time. main stays green.</text>
<text x="600" y="992" text-anchor="middle" font-size="13" fill="#475569">Each arrow is a change entering the merge queue; only the queue writes to main.</text>
<rect width="1300" height="1300" fill="#ffffff"/>
<circle cx="650" cy="720" r="450" fill="none" stroke="#2563eb" stroke-width="1.5" stroke-dasharray="3 7" opacity=".7"/>
<circle cx="650" cy="720" r="290" fill="none" stroke="#15803d" stroke-width="2" opacity=".8"/>
<circle cx="650" cy="720" r="150" fill="#fffbeb" stroke="#d97706" stroke-width="3"/>
<text font-size="22" font-weight="800" fill="#d97706" letter-spacing="3"><textPath href="#stLabel" startOffset="50%" text-anchor="middle">PLUGIN STUDIO</textPath></text>
<text x="650" y="728" text-anchor="middle" font-size="16" fill="#92400e">one repo · one shared library</text>
<text x="650" y="752" text-anchor="middle" font-size="16" fill="#92400e">every plugin in one place</text>
<text font-size="15" font-weight="700" fill="#15803d" letter-spacing="2"><textPath href="#plLabel" startOffset="50%" text-anchor="middle">PLUGINS</textPath></text>
<text font-size="15" font-weight="700" fill="#2563eb" letter-spacing="2"><textPath href="#agLabel" startOffset="50%" text-anchor="middle">AGENTS</textPath></text>
<line x1="827.9" y1="350.6" x2="800.1" y2="408.3" stroke="#2563eb" stroke-width="2.2" marker-end="url(#ar)"/>
<circle cx="775.8" cy="458.7" r="50" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="775.8" y="455.7" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Big</text>
<text x="775.8" y="472.7" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Picture</text>
<circle cx="845.2" cy="314.6" r="38" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="845.2" y="319.6" text-anchor="middle" font-size="13" font-weight="700" fill="#1e3a8a">agent</text>
<line x1="1049.7" y1="628.8" x2="987.3" y2="643.0" stroke="#2563eb" stroke-width="2.2" marker-end="url(#ar)"/>
<circle cx="932.7" cy="655.5" r="50" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="932.7" y="652.5" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Simple</text>
<text x="932.7" y="669.5" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Research</text>
<circle cx="1088.7" cy="619.9" r="38" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="1088.7" y="624.9" text-anchor="middle" font-size="13" font-weight="700" fill="#1e3a8a">agent</text>
<line x1="970.6" y1="975.6" x2="920.5" y2="935.7" stroke="#2563eb" stroke-width="2.2" marker-end="url(#ar)"/>
<circle cx="876.7" cy="900.8" r="50" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="876.7" y="905.8" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Regions</text>
<circle cx="1001.8" cy="1000.6" r="38" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="1001.8" y="1005.6" text-anchor="middle" font-size="13" font-weight="700" fill="#1e3a8a">agent</text>
<line x1="650.0" y1="1130.0" x2="650.0" y2="1066.0" stroke="#2563eb" stroke-width="2.2" marker-end="url(#ar)"/>
<circle cx="650.0" cy="1010.0" r="50" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="650.0" y="1007.0" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">UPG-100</text>
<text x="650.0" y="1024.0" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">List</text>
<circle cx="650.0" cy="1170.0" r="38" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="650.0" y="1175.0" text-anchor="middle" font-size="13" font-weight="700" fill="#1e3a8a">agent</text>
<line x1="329.4" y1="975.6" x2="379.5" y2="935.7" stroke="#2563eb" stroke-width="2.2" marker-end="url(#ar)"/>
<circle cx="423.3" cy="900.8" r="50" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="423.3" y="897.8" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Prayer</text>
<text x="423.3" y="914.8" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Progress</text>
<circle cx="298.2" cy="1000.6" r="38" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="298.2" y="1005.6" text-anchor="middle" font-size="13" font-weight="700" fill="#1e3a8a">agent</text>
<line x1="250.3" y1="628.8" x2="312.7" y2="643.0" stroke="#2563eb" stroke-width="2.2" marker-end="url(#ar)"/>
<circle cx="367.3" cy="655.5" r="50" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="367.3" y="652.5" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Cities with</text>
<text x="367.3" y="669.5" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Internet</text>
<circle cx="211.3" cy="619.9" r="38" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="211.3" y="624.9" text-anchor="middle" font-size="13" font-weight="700" fill="#1e3a8a">agent</text>
<line x1="472.1" y1="350.6" x2="499.9" y2="408.3" stroke="#2563eb" stroke-width="2.2" marker-end="url(#ar)"/>
<circle cx="524.2" cy="458.7" r="50" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="524.2" y="455.7" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Gospel</text>
<text x="524.2" y="472.7" text-anchor="middle" font-size="13" font-weight="700" fill="#14532d">Resources</text>
<circle cx="454.8" cy="314.6" r="38" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="454.8" y="319.6" text-anchor="middle" font-size="13" font-weight="700" fill="#1e3a8a">agent</text>
<circle cx="650" cy="120" r="62" fill="#f5f3ff" stroke="#7c3aed" stroke-width="3"/>
<text x="650" y="126" text-anchor="middle" font-size="17" font-weight="800" fill="#4c1d95">shepherd</text>
<line x1="650" y1="184" x2="650" y2="228" stroke="#7c3aed" stroke-width="2.2" stroke-dasharray="6 5" marker-end="url(#arS)"/>
<text x="734" y="112" font-size="17" fill="#4c1d95">one shepherd makes sure all plugin agents</text>
<text x="734" y="136" font-size="17" fill="#4c1d95">are doing their job building the plugins well</text>
<text x="650" y="1250" text-anchor="middle" font-size="19" font-weight="700" fill="#1e3a8a">each plugin has one agent responsible for building it</text>
</svg>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -330,12 +330,13 @@ virtualPages.set('/__readme-iframe-host.html', `<!doctype html>
await shot('deploy1-iframe', '/__readme-iframe-host.html',
{ vp: { width: 1000, height: 700 }, wait: 11000, countFrames: true, mapboxReferer: false })
// ---- DEPLOY2: mobile — the Simple Research dashboard as the phone app ----
// The mobile app ships ONE dashboard, not the whole gallery rail: research-map-plus-grid (the cap-4
// "Simple Research" composite — research map stacked over the people-groups grid), shot at a phone
// viewport. It mounts no iframe, so it keeps the Referer the map tiles need.
await shot('deploy2-mobile-simple-research', '/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html',
{ vp: { width: 390, height: 844 }, wait: 12000, dsf: 3 })
// ---- DEPLOY2: mobile — deliberately NOT shot yet ----
// The mobile example is UPG-100 (ruled 2026-09-24). Simple Research was retired as the example:
// it was never built for phones (legend, a bottom button cut off by the page edge, unreadable
// prompt). Once its phone layout is finished, add a shot named deploy2-mobile-upg100 of
// /doxa-maps/my-upg-100-list/upg-100-mfe/index.html at vp 390x844, dsf 3, and embed it in
// README.md under method 2. (Not written as code here: check:shots parses shot calls, even in
// comments, and would demand a PNG that does not exist yet.)
// ---- DEPLOY3: JS embed — the Research Gallery on a real host site ----
// The whole cap-5 gallery JS-embedded on dx-life's research page (a <script src> plus one element,

View file

@ -38,8 +38,10 @@
* dropped from the custom element's shadow DOM (see WARNING.md).
*/
import { ref, inject, computed, onMounted, onBeforeUnmount, getCurrentInstance, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useShadowStyles } from '../../composables/useShadowStyles.js'
const { t, te } = useI18n()
const uiStore = inject('uiStore', null)
const profileConfig = inject('profileConfig', null)
const isDark = computed(() => uiStore?.theme === 'dark')
@ -54,8 +56,21 @@ const isDark = computed(() => uiStore?.theme === 'dark')
// The fix is the DEFAULT, not per-profile config, because the defect came from a default that
// asserted more than it could know. A shared component cannot know what colour a given map's dots
// are, so it must not claim one; profiles that DO want to name a colour still can, via pinHintLabel.
const label = computed(() =>
profileConfig?.value?.pinHintLabel || 'Tap a coloured dot — someone still needs prayer')
// ★ AND IT WAS NEVER TRANSLATED — on ANY map, in ANY locale. The colour half of this defect was
// found and fixed (the default no longer says "red"), and the note above records that carefully.
// What nobody checked is that the sentence is a JAVASCRIPT STRING LITERAL: this component has no
// useI18n, and no locale file anywhere carries the text. A reader in Arabic, Russian or Chinese
// has been shown an English sentence on every map that renders the hint. That is the same shape as
// the Russian-heading bug next door — wrong only for readers who do not file bug reports in
// English, and therefore invisible to everyone who could.
// The key falls back to the English sentence when a locale lacks it, so a missing translation
// degrades to today's behaviour rather than to "messages.pinHint.tapDot".
const label = computed(() => {
const override = profileConfig?.value?.pinHintLabel
if (override) return override
const EN = 'Tap a coloured dot — someone still needs prayer'
try { return te('messages.pinHint.tapDot') ? t('messages.pinHint.tapDot') : EN } catch (_) { return EN }
})
const visible = ref(false)
const hintEl = ref(null)

View file

@ -1225,8 +1225,19 @@ useShadowStyles(`
@click="selectNode(row.node)">
<span v-for="(seg, i) in row.trunks" :key="`tr${i}`"
class="stl-trunk" :class="`stl-trunk-${seg}`"></span>
<!-- ★ AN ICON-ONLY DISCLOSURE NEEDS A NAME AND A STATE. This button held a 9x9 <svg> and
nothing else — no text, no aria-label, not even a title — so assistive technology
announced "button" seven times per legend with no indication of what any of them
did. check:a11y-button-names found 14 visible nameless buttons across two leaves and
these are all of them: country-posters-mfe COMPOSES countries-map, so it is the same
seven counted twice. Naming them here fixes every bundle that renders this legend,
which is the point of it living in library/.
aria-expanded comes with it, because a name alone tells a screen-reader user what
the control is for and still not whether the branch is open. -->
<button v-if="row.node.children?.length"
class="stl-caret" :class="{ open: expandedIds.has(row.node.id) }"
:aria-expanded="expandedIds.has(row.node.id) ? 'true' : 'false'"
:aria-label="t(expandedIds.has(row.node.id) ? 'legend.a11y.collapseRow' : 'legend.a11y.expandRow', { label: row.node.label })"
@click.stop="toggleExpand(row.node.id, $event)">
<svg width="9" height="9" viewBox="0 0 12 12" fill="none">
<path d="M4.5 3L7.5 6L4.5 9" stroke="currentColor" stroke-width="1.6"
@ -1240,8 +1251,15 @@ useShadowStyles(`
<span v-if="hasCount" class="stl-num">{{ fmtCount(row.node.count) }}</span>
<span v-if="hasPop" class="stl-num">{{ fmtPop(row.node.pop) }}</span>
<span class="stl-x-slot">
<!-- The same defect, latent: this one carries a `title`, and a title is a TOOLTIP —
unreliably announced and never shown to touch users — so its accessible name was
also empty. It escaped the gate only because it renders while a row is SELECTED
and the sweep measured an unselected legend. Hardcoded English besides, on a
component whose every other string is translated. -->
<button v-if="selectedId === row.node.id"
class="stl-x-chip" title="Clear selection"
class="stl-x-chip"
:title="t('legend.a11y.clearSelection')"
:aria-label="t('legend.a11y.clearSelection')"
@click.stop="deselect($event)">
<svg width="8" height="8" viewBox="0 0 12 12" fill="none">
<path d="M2 2L10 10M10 2L2 10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>

View file

@ -101,3 +101,28 @@ export const POPPINS_FACES = [
{ weight: '700', src: 'fonts/poppins/poppins-700-latin.woff2', unicodeRange: POPPINS_LATIN },
{ weight: '700', src: 'fonts/poppins/poppins-700-latin-ext.woff2', unicodeRange: POPPINS_LATIN_EXT },
]
// Oswald, as DX-life serves it for CYRILLIC HEADINGS. base/_globals.scss:35-44 switches every
// h1/h2/h3 to --font-family-cyrillic under [lang="ru-RU"], because Bebas Neue has no Cyrillic
// glyphs at all — without the switch a Russian heading leaves the family entirely. Google's own
// unicode-range values (css2 v57), carried verbatim so only the needed subset is fetched; latin and
// latin-ext are included because a Russian heading routinely contains Latin characters and a
// Cyrillic-only face would drop those back to the fallback mid-word.
export const OSWALD_FACES = [
{
src: 'fonts/oswald/oswald-400-cyrillic.woff2',
unicodeRange: 'U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116',
},
{
src: 'fonts/oswald/oswald-400-cyrillic-ext.woff2',
unicodeRange: 'U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F',
},
{
src: 'fonts/oswald/oswald-400-latin.woff2',
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
},
{
src: 'fonts/oswald/oswald-400-latin-ext.woff2',
unicodeRange: 'U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF',
},
]

View file

@ -59,5 +59,10 @@
"upg100Region": "المنطقة — ترتّب قوائم UPG-100 حسب منطقة العالم (أفريقيا، الهند، الشرق الأوسط، الصمّ، …). الأعداد هي مجموع UUPG والسكان لكل قائمة تحتها.",
"upg100Zone": "قوائم UPG-100 — كل واحدة هي قائمة صلاة للشعوب غير الموصول إليها في منطقة ما، وهي ما ترسمه الخريطة وتلوّنه. تُطبع كل قائمة ككُتيّب خاص بها.",
"upg100Group": "مجموعة شعبية — المجموعات غير الموصول إليها داخل القائمة، الأكثر حاجة للصلاة أولاً. كل نقطة على الخريطة هي واحدة منها."
},
"a11y": {
"expandRow": "توسيع {label}",
"collapseRow": "طي {label}",
"clearSelection": "إلغاء التحديد"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "القائمة قادمة قريباً"
"menuComingSoon": "القائمة قادمة قريباً",
"pinHint": {
"tapDot": "اضغط على نقطة ملوّنة — لا يزال هناك من يحتاج إلى الصلاة"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "خارج هذه المنطقة",
"Pray": "صلّي",
"Loading region dashboards…": "جارٍ تحميل لوحات المناطق…",
"No regions found.": "لم يتم العثور على مناطق."
"No regions found.": "لم يتم العثور على مناطق.",
"Region data could not be loaded. This is a connection problem, not a filter.": "تعذّر تحميل بيانات المناطق. هذه مشكلة في الاتصال، وليست تصفية.",
"People groups could not be loaded. This is a connection problem, not a filter.": "تعذّر تحميل الشعوب. هذه مشكلة في الاتصال، وليست تصفية.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "تعذّر تشغيل الخريطة. هذه مشكلة في رمز الخريطة أو في الاتصال، وليست تصفية — والأرقام أعلاه غير متأثرة."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Region — sortiert die UPG-100-Listen nach Weltgegend (Afrika, Indien, Naher Osten, Gehörlose, …). Die Zahlen sind die UUPGs und die Bevölkerung aller darunterliegenden Listen zusammen.",
"upg100Zone": "UPG-100-Listen — jede ist eine Gebetsliste der unerreichten Volksgruppen eines Gebiets und das, was die Karte zeichnet und einfärbt. Jede Liste wird als eigenes Heft gedruckt.",
"upg100Group": "Volksgruppe — die einzelnen unerreichten Gruppen innerhalb einer Liste, größter Gebetsbedarf zuerst. Jeder Kartenpunkt ist eine davon."
},
"a11y": {
"expandRow": "{label} ausklappen",
"collapseRow": "{label} einklappen",
"clearSelection": "Auswahl aufheben"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Menü folgt in Kürze"
"menuComingSoon": "Menü folgt in Kürze",
"pinHint": {
"tapDot": "Tippe auf einen farbigen Punkt — jemand braucht noch Gebet"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "Außerhalb dieser Region",
"Pray": "Ich bete",
"Loading region dashboards…": "Regions-Dashboards werden geladen…",
"No regions found.": "Keine Regionen gefunden."
"No regions found.": "Keine Regionen gefunden.",
"Region data could not be loaded. This is a connection problem, not a filter.": "Regionsdaten konnten nicht geladen werden. Das ist ein Verbindungsproblem, kein Filter.",
"People groups could not be loaded. This is a connection problem, not a filter.": "Volksgruppen konnten nicht geladen werden. Das ist ein Verbindungsproblem, kein Filter.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "Die Karte konnte nicht starten. Das ist ein Karten-Token- oder Verbindungsproblem, kein Filter — die Zahlen oben sind davon nicht betroffen."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Region — sort the UPG-100 lists by world area (Africa, India, Middle East, Deaf, …). Counts are the UUPGs and population summed across every list beneath it.",
"upg100Zone": "UPG-100 Lists — each one is a prayer list of the unreached people groups in an area, and is what the map paints and colours. Every list prints as its own packet.",
"upg100Group": "People Group — the individual unreached groups inside a list, largest prayer need first. Each map dot is one of these."
},
"a11y": {
"expandRow": "Expand {label}",
"collapseRow": "Collapse {label}",
"clearSelection": "Clear selection"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Menu coming soon"
"menuComingSoon": "Menu coming soon",
"pinHint": {
"tapDot": "Tap a coloured dot — someone still needs prayer"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "Outside this region",
"Pray": "Pray",
"Loading region dashboards…": "Loading region dashboards…",
"No regions found.": "No regions found."
"No regions found.": "No regions found.",
"Region data could not be loaded. This is a connection problem, not a filter.": "Region data could not be loaded. This is a connection problem, not a filter.",
"People groups could not be loaded. This is a connection problem, not a filter.": "People groups could not be loaded. This is a connection problem, not a filter.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Región — ordena las listas UPG-100 por área mundial (África, India, Oriente Medio, Sordos, …). Los totales son los UUPG y la población sumados de todas las listas que contiene.",
"upg100Zone": "Listas UPG-100 — cada una es una lista de oración de los grupos étnicos no alcanzados de un área, y es lo que el mapa pinta y colorea. Cada lista se imprime como su propio paquete.",
"upg100Group": "Grupo étnico — los grupos no alcanzados dentro de una lista, primero los de mayor necesidad de oración. Cada punto del mapa es uno de ellos."
},
"a11y": {
"expandRow": "Expandir {label}",
"collapseRow": "Contraer {label}",
"clearSelection": "Borrar la selección"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Menú próximamente"
"menuComingSoon": "Menú próximamente",
"pinHint": {
"tapDot": "Toca un punto de color: alguien todavía necesita oración"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "Fuera de esta región",
"Pray": "Oración",
"Loading region dashboards…": "Cargando los paneles de regiones…",
"No regions found.": "No se encontraron regiones."
"No regions found.": "No se encontraron regiones.",
"Region data could not be loaded. This is a connection problem, not a filter.": "No se pudieron cargar los datos de las regiones. Es un problema de conexión, no un filtro.",
"People groups could not be loaded. This is a connection problem, not a filter.": "No se pudieron cargar los pueblos. Es un problema de conexión, no un filtro.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "El mapa no pudo iniciarse. Es un problema del token del mapa o de conexión, no un filtro: las cifras de arriba no se ven afectadas."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Région — trie les listes UPG-100 par zone du monde (Afrique, Inde, Moyen-Orient, Sourds, …). Les totaux sont les UUPG et la population additionnés sur toutes les listes qu’elle contient.",
"upg100Zone": "Listes UPG-100 — chacune est une liste de prière des groupes de peuples non atteints d’une zone, et c’est ce que la carte dessine et colore. Chaque liste s’imprime comme son propre livret.",
"upg100Group": "Groupe de peuples — les groupes non atteints au sein d’une liste, les plus grands besoins de prière en premier. Chaque point de la carte en est un."
},
"a11y": {
"expandRow": "Développer {label}",
"collapseRow": "Réduire {label}",
"clearSelection": "Effacer la sélection"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Menu bientôt disponible"
"menuComingSoon": "Menu bientôt disponible",
"pinHint": {
"tapDot": "Touchez un point coloré — quelqu’un a encore besoin de prière"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "Hors de cette région",
"Pray": "Prier",
"Loading region dashboards…": "Chargement des tableaux de bord des régions…",
"No regions found.": "Aucune région trouvée."
"No regions found.": "Aucune région trouvée.",
"Region data could not be loaded. This is a connection problem, not a filter.": "Les données des régions n'ont pas pu être chargées. Il s'agit d'un problème de connexion, pas d'un filtre.",
"People groups could not be loaded. This is a connection problem, not a filter.": "Les peuples n'ont pas pu être chargés. Il s'agit d'un problème de connexion, pas d'un filtre.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "La carte n'a pas pu démarrer. Il s'agit d'un problème de jeton de carte ou de connexion, pas d'un filtre — les chiffres ci-dessus ne sont pas affectés."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "क्षेत्र — UPG-100 सूचियों को विश्व क्षेत्र के अनुसार क्रमित करता है (अफ़्रीका, भारत, मध्य पूर्व, बधिर, …)। संख्याएँ उसके नीचे की हर सूची के UUPG और जनसंख्या का योग हैं।",
"upg100Zone": "UPG-100 सूचियाँ — हर एक किसी क्षेत्र के अनपहुँचे जनसमूहों की प्रार्थना सूची है, और यही मानचित्र बनाता और रंगता है। हर सूची अपने अलग पैकेट के रूप में छपती है।",
"upg100Group": "जनसमूह — किसी सूची के भीतर के अलग-अलग अनपहुँचे समूह, सबसे बड़ी प्रार्थना आवश्यकता पहले। मानचित्र का हर बिंदु इनमें से एक है।"
},
"a11y": {
"expandRow": "{label} का विस्तार करें",
"collapseRow": "{label} को छोटा करें",
"clearSelection": "चयन हटाएँ"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "मेनू शीघ्र आ रहा है"
"menuComingSoon": "मेनू शीघ्र आ रहा है",
"pinHint": {
"tapDot": "किसी रंगीन बिंदु पर टैप करें — किसी को अभी भी प्रार्थना की आवश्यकता है"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "इस क्षेत्र के बाहर",
"Pray": "प्रार्थना करें",
"Loading region dashboards…": "क्षेत्र डैशबोर्ड लोड हो रहे हैं…",
"No regions found.": "कोई क्षेत्र नहीं मिला।"
"No regions found.": "कोई क्षेत्र नहीं मिला।",
"Region data could not be loaded. This is a connection problem, not a filter.": "क्षेत्र डेटा लोड नहीं हो सका। यह कनेक्शन की समस्या है, फ़िल्टर की नहीं।",
"People groups could not be loaded. This is a connection problem, not a filter.": "लोकसमूह लोड नहीं हो सके। यह कनेक्शन की समस्या है, फ़िल्टर की नहीं।",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "नक्शा शुरू नहीं हो सका। यह नक्शा-टोकन या कनेक्शन की समस्या है, फ़िल्टर की नहीं — ऊपर के आंकड़े प्रभावित नहीं हैं।"
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Regione — ordina le liste UPG-100 per area del mondo (Africa, India, Medio Oriente, Sordi, …). I totali sono gli UUPG e la popolazione sommati di ogni lista sottostante.",
"upg100Zone": "Liste UPG-100 — ognuna è una lista di preghiera dei gruppi etnici non raggiunti di un’area, ed è ciò che la mappa disegna e colora. Ogni lista si stampa come il proprio fascicolo.",
"upg100Group": "Gruppo etnico — i singoli gruppi non raggiunti dentro una lista, prima quelli con maggiore bisogno di preghiera. Ogni punto della mappa è uno di questi."
},
"a11y": {
"expandRow": "Espandi {label}",
"collapseRow": "Comprimi {label}",
"clearSelection": "Cancella la selezione"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Menu in arrivo"
"menuComingSoon": "Menu in arrivo",
"pinHint": {
"tapDot": "Tocca un punto colorato — qualcuno ha ancora bisogno di preghiera"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "Fuori da questa regione",
"Pray": "Prega",
"Loading region dashboards…": "Caricamento delle dashboard delle regioni…",
"No regions found.": "Nessuna regione trovata."
"No regions found.": "Nessuna regione trovata.",
"Region data could not be loaded. This is a connection problem, not a filter.": "Non è stato possibile caricare i dati delle regioni. È un problema di connessione, non un filtro.",
"People groups could not be loaded. This is a connection problem, not a filter.": "Non è stato possibile caricare i gruppi di popolazione. È un problema di connessione, non un filtro.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "La mappa non è potuta avviare. È un problema di token della mappa o di connessione, non un filtro: i dati sopra non sono interessati."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Região — ordena as listas UPG-100 por área do mundo (África, Índia, Oriente Médio, Surdos, …). Os totais são os UUPG e a população somados de todas as listas abaixo dela.",
"upg100Zone": "Listas UPG-100 — cada uma é uma lista de oração dos grupos étnicos não alcançados de uma área, e é o que o mapa pinta e colore. Cada lista é impressa como seu próprio pacote.",
"upg100Group": "Grupo étnico — os grupos não alcançados dentro de uma lista, primeiro os de maior necessidade de oração. Cada ponto do mapa é um deles."
},
"a11y": {
"expandRow": "Expandir {label}",
"collapseRow": "Recolher {label}",
"clearSelection": "Limpar a seleção"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Menu em breve"
"menuComingSoon": "Menu em breve",
"pinHint": {
"tapDot": "Toque num ponto colorido — alguém ainda precisa de oração"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "Fora desta região",
"Pray": "Ore",
"Loading region dashboards…": "Carregando os painéis das regiões…",
"No regions found.": "Nenhuma região encontrada."
"No regions found.": "Nenhuma região encontrada.",
"Region data could not be loaded. This is a connection problem, not a filter.": "Não foi possível carregar os dados das regiões. É um problema de conexão, não um filtro.",
"People groups could not be loaded. This is a connection problem, not a filter.": "Não foi possível carregar os povos. É um problema de conexão, não um filtro.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "O mapa não pôde iniciar. É um problema do token do mapa ou de conexão, não um filtro — os números acima não são afetados."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Regiune — sortează listele UPG-100 după zona lumii (Africa, India, Orientul Mijlociu, Surzi, …). Totalurile sunt UUPG-urile și populația însumate din fiecare listă de dedesubt.",
"upg100Zone": "Liste UPG-100 — fiecare este o listă de rugăciune a grupurilor etnice neatinse dintr-o zonă și este ceea ce harta desenează și colorează. Fiecare listă se tipărește ca propriul pachet.",
"upg100Group": "Grup etnic — grupurile neatinse dintr-o listă, mai întâi cele cu cea mai mare nevoie de rugăciune. Fiecare punct de pe hartă este unul dintre ele."
},
"a11y": {
"expandRow": "Extinde {label}",
"collapseRow": "Restrânge {label}",
"clearSelection": "Șterge selecția"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Meniu în curând"
"menuComingSoon": "Meniu în curând",
"pinHint": {
"tapDot": "Atinge un punct colorat — cineva are încă nevoie de rugăciune"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "În afara acestei regiuni",
"Pray": "Roagă-te",
"Loading region dashboards…": "Se încarcă panourile regiunilor…",
"No regions found.": "Nu au fost găsite regiuni."
"No regions found.": "Nu au fost găsite regiuni.",
"Region data could not be loaded. This is a connection problem, not a filter.": "Datele regiunilor nu au putut fi încărcate. Este o problemă de conexiune, nu un filtru.",
"People groups could not be loaded. This is a connection problem, not a filter.": "Grupurile de popoare nu au putut fi încărcate. Este o problemă de conexiune, nu un filtru.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "Harta nu a putut porni. Este o problemă de token al hărții sau de conexiune, nu un filtru — cifrele de mai sus nu sunt afectate."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "Регион — сортирует списки UPG-100 по частям света (Африка, Индия, Ближний Восток, Глухие, …). Числа — это UUPG и население, просуммированные по всем спискам внутри.",
"upg100Zone": "Списки UPG-100 — каждый представляет собой молитвенный список непросвещённых народов области и именно он отображается и раскрашивается на карте. Каждый список печатается отдельным буклетом.",
"upg100Group": "Народ — отдельные непросвещённые народы внутри списка, сначала с наибольшей нуждой в молитве. Каждая точка на карте — один из них."
},
"a11y": {
"expandRow": "Развернуть {label}",
"collapseRow": "Свернуть {label}",
"clearSelection": "Снять выделение"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "Меню скоро появится"
"menuComingSoon": "Меню скоро появится",
"pinHint": {
"tapDot": "Нажмите на цветную точку — кому-то всё ещё нужна молитва"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "За пределами этого региона",
"Pray": "Молиться",
"Loading region dashboards…": "Загрузка панелей по регионам…",
"No regions found.": "Регионы не найдены."
"No regions found.": "Регионы не найдены.",
"Region data could not be loaded. This is a connection problem, not a filter.": "Не удалось загрузить данные по регионам. Это проблема соединения, а не фильтра.",
"People groups could not be loaded. This is a connection problem, not a filter.": "Не удалось загрузить народы. Это проблема соединения, а не фильтра.",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "Карта не запустилась. Это проблема токена карты или соединения, а не фильтра — цифры выше не затронуты."
}

View file

@ -59,5 +59,10 @@
"upg100Region": "区域 — 按世界区域排列 UPG-100 名单(非洲、印度、中东、聋人……)。数字是其下每份名单的 UUPG 与人口总和。",
"upg100Zone": "UPG-100 名单 — 每一份都是某地区未得之民的祷告名单,也是地图所绘制和上色的内容。每份名单印成独立的祷告册。",
"upg100Group": "族群 — 名单内各个未得之民群体,祷告需要最大的排在前面。地图上每个点就是其中之一。"
},
"a11y": {
"expandRow": "展开{label}",
"collapseRow": "折叠{label}",
"clearSelection": "清除选择"
}
}
}

View file

@ -1,3 +1,6 @@
{
"menuComingSoon": "菜单即将推出"
"menuComingSoon": "菜单即将推出",
"pinHint": {
"tapDot": "点按一个彩色圆点——仍有人需要祷告"
}
}

View file

@ -65,5 +65,8 @@
"Outside this region": "在此区域之外",
"Pray": "祷告",
"Loading region dashboards…": "正在加载区域面板…",
"No regions found.": "未找到区域。"
"No regions found.": "未找到区域。",
"Region data could not be loaded. This is a connection problem, not a filter.": "无法加载区域数据。这是连接问题,不是筛选问题。",
"People groups could not be loaded. This is a connection problem, not a filter.": "无法加载民族群体。这是连接问题,不是筛选问题。",
"The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.": "地图无法启动。这是地图令牌或连接问题,不是筛选问题——上方的数字不受影响。"
}

View file

@ -28,7 +28,8 @@ import { useShadowStyles } from '@map/composables/useShadowStyles.js'
// house pattern (doxa-simple-map.vue:18, :246, :1346) — an EMBEDDED element must not reach for
// document.documentElement.dir, which belongs to the host page.
import { RTL_LOCALES } from '@map/i18n/index.js'
import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES } from '@map/composables/useDocumentFont.js'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES, OSWALD_FACES } from '@map/composables/useDocumentFont.js'
import FilterDropdown from '@map/components/search/FilterDropdown.vue'
import UupgsList from './src/UupgsList.vue'
// Their strings, via their own English keys (see src/useRegionsI18n.js). The goal sentences,
@ -95,16 +96,37 @@ const assetsBase = computed(() => cfg.value.assetsBase || './assets')
// origin — no CORS headers — so it is vendored under public/assets/fonts/bebas-neue (OFL,
// provenance in LICENSE.txt beside it). Loading it deleted an uppercase imitation and two
// stepped-down font-sizes that only existed because the fallback face was wider.
useDocumentFont('Bebas Neue', BEBAS_NEUE_FACES, assetsBase.value)
// ★ THE FONT BASE MUST BE ABSOLUTE, and this is the one place where that is not merely safer.
// @font-face is DOCUMENT-scoped: useDocumentFont injects a <style> into the HOST document, so a
// RELATIVE src resolves against the HOST PAGE, not against this bundle. On our own leaf that is
// invisible — the leaf's generated config says '../../assets' and the leaf really is two
// directories deep, so it resolves correctly. On a THIRD-PARTY PAGE it resolves against the
// partner's URL, the file is not there, and every face fails.
// MEASURED, which is the only reason I know: the cross-origin embed probe reported every
// FontFace in the partner document as status 'error' while the woff2 responses were 200 and the
// asset host was sending both access-control-allow-origin:* and content-type: font/woff2. Two
// wrong hypotheses died first — a missing CORS header (the server already sends it) and a check
// made before document.fonts.ready (fixed, still error). The URL was the answer.
// resolveAssetsBase exists for exactly this and its own header documents this bug class; the
// poster previews next door have used it all along. The font loader was the last consumer still
// taking the raw value.
const fontAssetsBase = resolveAssetsBase(cfg.value)
useDocumentFont('Bebas Neue', BEBAS_NEUE_FACES, fontAssetsBase)
// Poppins is their --font-family-base — the face of every line of BODY text on these
// pages. It was the larger of the two typography gaps and completely invisible to a
// computed-style diff, which read 68/68 while our body text was a system fallback 5.5%
// wider per glyph. Registered the same way and for the same reason as Bebas.
useDocumentFont('Poppins', POPPINS_FACES, assetsBase.value)
useDocumentFont('Poppins', POPPINS_FACES, fontAssetsBase)
// Figtree stands in for Brandon Grotesque, which is commercial and must not ship in a public
// giveaway (Driver, 2026-09-24). @font-face is DOCUMENT-scoped, so it registers here beside
// the other two rather than inside the shadow root that uses it.
useDocumentFont('Figtree', FIGTREE_FACES, assetsBase.value)
useDocumentFont('Figtree', FIGTREE_FACES, fontAssetsBase)
// Oswald, for CYRILLIC HEADINGS ONLY. base/_globals.scss:35-44 switches every h1/h2/h3 to
// --font-family-cyrillic under [lang="ru-RU"], because BEBAS NEUE HAS NO CYRILLIC GLYPHS — their
// heading face cannot render Russian at all, so without the switch the heading leaves the family.
// We ship a `ru` locale and had neither the rule nor the font, so every Russian heading here was
// rendering in whatever condensed system face the browser picked.
useDocumentFont('Oswald', OSWALD_FACES, fontAssetsBase)
// ── state ───────────────────────────────────────────────────────────────────
const { t, tf , locale } = useRegionsI18n()
@ -824,6 +846,19 @@ useShadowStyles(
' font-family:"Bebas Neue","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
' font-weight:400; }' +
'.rd-h2--start{ text-align:start; }' +
// ★ CYRILLIC HEADINGS SWITCH FACE, as theirs do. base/_globals.scss:35-44:
// h1,h2,h3 { [lang="ru-RU"] & { font-family: var(--font-family-cyrillic); letter-spacing: -0.02em } }
// and --font-family-cyrillic is Oswald. The switch is not decoration: BEBAS NEUE HAS NO CYRILLIC
// GLYPHS, so a Russian heading in it leaves the family entirely and lands on whatever condensed
// system face the browser picks. We ship a `ru` locale and had neither the rule nor the font.
// ONLY the headings, exactly as theirs — their .region-stat__value is a <p> and keeps Bebas even in
// Russian, which is their behaviour and so is ours.
// BOTH TAG FORMS, deliberately: their site tags the document ru-RU while our i18n locale is `ru`,
// and a host page embedding this element may set either. A selector that matched only one would
// work on their site and fail on ours, or the reverse, depending on who set the attribute.
'[lang="ru"] .rd-title, [lang="ru-RU"] .rd-title,' +
'[lang="ru"] .rd-h2, [lang="ru-RU"] .rd-h2{ font-family:"Oswald","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
' letter-spacing:-0.02em; }' +
// .font-size-4xl .font-weight-medium / .font-size-lg
// Their .font-size-4xl (var(--font-size-4xl)) verified live. Same substitution consequence as the h2:
// in our wider stand-in face 'Adoption Progress' 21 / 2,107 breaks after the slash inside a
@ -912,14 +947,16 @@ useShadowStyles(
</script>
<template>
<div class="rd-root" :dir="isRTL ? 'rtl' : 'ltr'">
<!-- :lang is here for the SAME REASON :dir is: a [lang=] selector in this shadow root cannot
see the host document's attribute, so the switch their global sheet performs on
[lang="ru-RU"] can only fire if the attribute is INSIDE the root. It is also simply
correct HTML — screen readers and hyphenation read it. -->
<div class="rd-root" :lang="locale" :dir="isRTL ? 'rtl' : 'ltr'">
<!-- a landmark, so a screen reader can jump to the dashboard body; the app probe
measured none on the page and it costs nothing to name the region. -->
<main class="rd-scroll" :aria-label="t('Region dashboard')">
<div v-if="loading" class="rd-status">{{ t('Loading region dashboards…') }}</div>
<div v-else-if="loadFailed" class="rd-status rd-failed" role="status">
Region data could not be loaded. This is a connection problem, not a filter.
</div>
<div v-else-if="loadFailed" class="rd-status rd-failed" role="status">{{ t('Region data could not be loaded. This is a connection problem, not a filter.') }}</div>
<!-- ★ ONE TEMPLATE, ONE MAP INSTANCE — not two branches each owning a <RegionCountryMap>.
MEASURED: with a branch per subject kind, switching region -> country UNMOUNTED one map

View file

@ -38,7 +38,7 @@ import { useRegionsI18n } from './src/useRegionsI18n.js'
// document.documentElement.dir, which belongs to the host page.
import { RTL_LOCALES } from '@map/i18n/index.js'
import { useI18n } from 'vue-i18n'
import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES } from '@map/composables/useDocumentFont.js'
import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES, OSWALD_FACES } from '@map/composables/useDocumentFont.js'
import { summarizeRegions, PEOPLE_GROUP_LIST_FIELDS } from '@map/utils/countries-meta.js'
// DX-life's spacing/type scales under THEIR token names. Prepended to this
// component's shadow CSS so every var() below resolves inside the shadow root.
@ -113,16 +113,37 @@ const headTitle = computed(() => splitLastWord(t('Regions')))
// only tab 2, because its own leaf page (regions-list/index.html) is a standalone surface where
// tab 2 is not present — relying on a sibling would leave THIS page on the fallback face.
// Idempotent per document, so the composite still registers exactly one.
useDocumentFont('Bebas Neue', BEBAS_NEUE_FACES, assetsBase.value)
// ★ THE FONT BASE MUST BE ABSOLUTE, and this is the one place where that is not merely safer.
// @font-face is DOCUMENT-scoped: useDocumentFont injects a <style> into the HOST document, so a
// RELATIVE src resolves against the HOST PAGE, not against this bundle. On our own leaf that is
// invisible — the leaf's generated config says '../../assets' and the leaf really is two
// directories deep, so it resolves correctly. On a THIRD-PARTY PAGE it resolves against the
// partner's URL, the file is not there, and every face fails.
// MEASURED, which is the only reason I know: the cross-origin embed probe reported every
// FontFace in the partner document as status 'error' while the woff2 responses were 200 and the
// asset host was sending both access-control-allow-origin:* and content-type: font/woff2. Two
// wrong hypotheses died first — a missing CORS header (the server already sends it) and a check
// made before document.fonts.ready (fixed, still error). The URL was the answer.
// resolveAssetsBase exists for exactly this and its own header documents this bug class; the
// poster previews next door have used it all along. The font loader was the last consumer still
// taking the raw value.
const fontAssetsBase = resolveAssetsBase(cfg.value)
useDocumentFont('Bebas Neue', BEBAS_NEUE_FACES, fontAssetsBase)
// Poppins is their --font-family-base — the face of every line of BODY text on these
// pages. It was the larger of the two typography gaps and completely invisible to a
// computed-style diff, which read 68/68 while our body text was a system fallback 5.5%
// wider per glyph. Registered the same way and for the same reason as Bebas.
useDocumentFont('Poppins', POPPINS_FACES, assetsBase.value)
useDocumentFont('Poppins', POPPINS_FACES, fontAssetsBase)
// Figtree stands in for Brandon Grotesque, which is commercial and must not ship in a public
// giveaway (Driver, 2026-09-24). @font-face is DOCUMENT-scoped, so it registers here beside
// the other two rather than inside the shadow root that uses it.
useDocumentFont('Figtree', FIGTREE_FACES, assetsBase.value)
useDocumentFont('Figtree', FIGTREE_FACES, fontAssetsBase)
// Oswald, for CYRILLIC HEADINGS ONLY. base/_globals.scss:35-44 switches every h1/h2/h3 to
// --font-family-cyrillic under [lang="ru-RU"], because BEBAS NEUE HAS NO CYRILLIC GLYPHS — their
// heading face cannot render Russian at all, so without the switch the heading leaves the family.
// We ship a `ru` locale and had neither the rule nor the font, so every Russian heading here was
// rendering in whatever condensed system face the browser picked.
useDocumentFont('Oswald', OSWALD_FACES, fontAssetsBase)
// ── state ───────────────────────────────────────────────────────────────────
const regions = ref([])
@ -427,6 +448,19 @@ useShadowStyles(
' margin-inline-start:10px; }' +
// .region-card__summary: --font-size-md (var(--font-size-md)), 0.85 opacity, on the header band.
'.region-card__summary{ font-size:var(--font-size-md); opacity:0.85; margin:0; }' +
// ★ CYRILLIC HEADINGS SWITCH FACE, as theirs do. base/_globals.scss:35-44:
// h1,h2,h3 { [lang="ru-RU"] & { font-family: var(--font-family-cyrillic); letter-spacing: -0.02em } }
// and --font-family-cyrillic is Oswald. The switch is not decoration: BEBAS NEUE HAS NO CYRILLIC
// GLYPHS, so a Russian heading in it leaves the family entirely and lands on whatever condensed
// system face the browser picks. We ship a `ru` locale and had neither the rule nor the font.
// ONLY the headings, exactly as theirs — their .region-stat__value is a <p> and keeps Bebas even in
// Russian, which is their behaviour and so is ours.
// BOTH TAG FORMS, deliberately: their site tags the document ru-RU while our i18n locale is `ru`,
// and a host page embedding this element may set either. A selector that matched only one would
// work on their site and fail on ours, or the reverse, depending on who set the attribute.
'[lang="ru"] .rl-title, [lang="ru-RU"] .rl-title,' +
'[lang="ru"] .region-card__title, [lang="ru-RU"] .region-card__title{ font-family:"Oswald","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
' letter-spacing:-0.02em; }' +
// .region-card__body: padding --spacing-lg / clamp(...) / --spacing-xl -> var(--spacing-lg) / clamp / var(--spacing-xl)
'.region-card__body{ padding:var(--spacing-lg) clamp(var(--spacing-md), 4cqw, var(--spacing-2xl)) var(--spacing-xl); }' +
// .countries-grid: DX-life's track is minmax(220px,1fr). Gap --spacing-xs / --spacing-xl
@ -505,7 +539,11 @@ useShadowStyles(
</script>
<template>
<div class="rl-root" :dir="isRTL ? 'rtl' : 'ltr'">
<!-- :lang is here for the SAME REASON :dir is: a [lang=] selector in this shadow root cannot
see the host document's attribute, so the switch their global sheet performs on
[lang="ru-RU"] can only fire if the attribute is INSIDE the root. It is also simply
correct HTML — screen readers and hyphenation read it. -->
<div class="rl-root" :lang="i18nLocale" :dir="isRTL ? 'rtl' : 'ltr'">
<!-- THEIR ICON, DELIVERED THE WAY THEIR MARKUP DELIVERS IT. Theirs is
<svg class="icon | rotate-90 right"><use href="/assets/icons/arrow-chevron.svg#chevron-up"/></svg>
— one <use> reference, not a path repeated per call site.
@ -537,9 +575,7 @@ useShadowStyles(
already translated in all eight locales. Their /regions is server-rendered and has no
loading state of its own, but reusing their wording beats inventing English here. -->
<div v-if="loading" class="rl-status">{{ t('Loading results...') }}</div>
<div v-else-if="loadFailed" class="rl-status rl-failed" role="status">
Region data could not be loaded. This is a connection problem, not a filter.
</div>
<div v-else-if="loadFailed" class="rl-status rl-failed" role="status">{{ t('Region data could not be loaded. This is a connection problem, not a filter.') }}</div>
<div v-else-if="!regions.length" class="rl-status">{{ t('No regions found.') }}</div>
<template v-else>

View file

@ -565,10 +565,7 @@ useShadowStyles(
from my-upg-100-list/upg-100-list.vue:1889 rather than invented. -->
<div class="map-scroll-edge map-scroll-edge--left" />
<div class="map-scroll-edge map-scroll-edge--right" />
<p v-if="mapFailed" class="rcm__note" role="status">
The map could not start. This is a map-token or connection problem, not a filter — the
figures above are unaffected.
</p>
<p v-if="mapFailed" class="rcm__note" role="status">{{ t('The map could not start. This is a map-token or connection problem, not a filter — the figures above are unaffected.') }}</p>
<div v-if="colorMode === 'prayer' && !mapFailed" class="rcm__legend">
<span><i :style="{ background: C.prayerFull }" />{{ t('100+ People Praying') }}</span>
<span><i :style="{ background: C.prayerPartial }" />{{ t('1+ People Praying') }}</span>

View file

@ -385,6 +385,8 @@ useShadowStyles(
// .repel — their space-between row (composition/_repel.scss), at --font-size-md gap.
'.ul-total{ display:flex; gap:var(--spacing-md); justify-content:space-between; align-items:center; }' +
'.ul-total__count{ font-size:var(--font-size-sm); }' +
// utilities/_visibility.scss:17 — theirs exactly.
'.invisible-placeholder{ visibility:hidden; overflow:hidden; }' +
// .grid[data-width-lg] — auto-fit, 400px min track, --spacing-2xl gap
'.grid{ display:grid; grid-gap:var(--spacing-2xl);' +
' grid-template-columns:repeat(auto-fit, minmax(min(400px,100%), 1fr)); }' +
@ -535,13 +537,22 @@ useShadowStyles(
hide-see-all-link, so only the total shows. What was here before ("N People Groups ·
Cameroon") was MINE, not theirs, and the scope rule says anything that exists on their
page must match rather than be improved on. -->
<div v-if="!loading && !failed" class="ul-total">
<div class="ul-total__count">{{ T.total }}: {{ filtered.length }}</div>
<!-- ★ THE ROW STAYS, AND HOLDS ITS HEIGHT WHILE LOADING. Theirs never removes it: it swaps
the total for <span class="invisible-placeholder">Placeholder</span>, which
utilities/_visibility.scss:17 makes visibility:hidden + overflow:hidden — occupying its
line so the results below do not jump down when the count arrives. Ours removed the whole
row with a v-if and the layout shifted on every load.
Found by the class-coverage sweep only AFTER its extractor was widened to the whole file
(the class is in a branch my template-only scan reached, but the sweep had never seen the
name at all, so nobody asked what it was for). -->
<div v-if="!failed" class="ul-total">
<div class="ul-total__count">
<template v-if="!loading">{{ T.total }}: {{ filtered.length }}</template>
<span v-else class="invisible-placeholder">{{ T.total }}</span>
</div>
</div>
<div v-if="failed" class="ul-status ul-failed" role="status">
People groups could not be loaded. This is a connection problem, not a filter.
</div>
<div v-if="failed" class="ul-status ul-failed" role="status">{{ t('People groups could not be loaded. This is a connection problem, not a filter.') }}</div>
<div id="results" class="grid" data-width-lg>
<div v-for="p in displayed" :key="p.slug" class="uupg__card">

126
public/assets/fonts/oswald/LICENSE vendored Normal file
View file

@ -0,0 +1,126 @@
Oswald — SIL Open Font License, Version 1.1
Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)
Full text below, and at https://openfontlicense.org/ (OFL-1.1)
WHY THIS FILE IS IN THIS REPO
DX-life sets --font-family-cyrillic to 'Oswald' and base/_globals.scss:35-44 switches every
h1/h2/h3 to it under [lang="ru-RU"], with letter-spacing -0.02em. That switch exists because
BEBAS NEUE HAS NO CYRILLIC GLYPHS AT ALL — their heading face simply cannot render Russian, so
without the switch a Russian heading falls out of the family entirely. Ours did exactly that:
we ship a `ru` locale, we had neither the rule nor the font, so every Russian heading rendered
in whatever condensed system face the browser chose.
WHICH SUBSETS, AND WHY FOUR
cyrillic and cyrillic-ext are the point. latin and latin-ext are carried too because a Russian
heading routinely contains Latin characters — a country name, a figure, an ampersand — and a
face that covers only Cyrillic drops those glyphs back to the fallback MID-WORD, which looks
worse than not switching at all. Google's own unicode-range values are carried verbatim
(css2 v57), so the browser fetches only the subset a given heading needs.
oswald-400-cyrillic.woff2 6700 B
oswald-400-cyrillic-ext.woff2 7688 B
oswald-400-latin.woff2 12248 B
oswald-400-latin-ext.woff2 11624 B
Each verified to begin with the wOF2 magic (774f4632) after download, because a fetch that
silently returns an HTML error page also "succeeds" and a font that is really a 404 page fails
at render time with no network error to find later.
Weight 400 only: their --font-weight-regular is what h1/h2/h3 use, and nothing on the regions
surfaces asks Oswald for another weight.
────────────────────────────────────────────────────────────────────────────────
Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,126 @@
Oswald — SIL Open Font License, Version 1.1
Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)
Full text below, and at https://openfontlicense.org/ (OFL-1.1)
WHY THIS FILE IS IN THIS REPO
DX-life sets --font-family-cyrillic to 'Oswald' and base/_globals.scss:35-44 switches every
h1/h2/h3 to it under [lang="ru-RU"], with letter-spacing -0.02em. That switch exists because
BEBAS NEUE HAS NO CYRILLIC GLYPHS AT ALL — their heading face simply cannot render Russian, so
without the switch a Russian heading falls out of the family entirely. Ours did exactly that:
we ship a `ru` locale, we had neither the rule nor the font, so every Russian heading rendered
in whatever condensed system face the browser chose.
WHICH SUBSETS, AND WHY FOUR
cyrillic and cyrillic-ext are the point. latin and latin-ext are carried too because a Russian
heading routinely contains Latin characters — a country name, a figure, an ampersand — and a
face that covers only Cyrillic drops those glyphs back to the fallback MID-WORD, which looks
worse than not switching at all. Google's own unicode-range values are carried verbatim
(css2 v57), so the browser fetches only the subset a given heading needs.
oswald-400-cyrillic.woff2 6700 B
oswald-400-cyrillic-ext.woff2 7688 B
oswald-400-latin.woff2 12248 B
oswald-400-latin-ext.woff2 11624 B
Each verified to begin with the wOF2 magic (774f4632) after download, because a fetch that
silently returns an HTML error page also "succeeds" and a font that is really a 404 page fails
at render time with no network error to find later.
Weight 400 only: their --font-weight-regular is what h1/h2/h3 use, and nothing on the regions
surfaces asks Oswald for another weight.
────────────────────────────────────────────────────────────────────────────────
Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
<script src="../country-posters.js" integrity="sha384-S0O8LKY8FDeykxEoMkbMbI7dHp8DZQlNAxW+xj+9rJ/XDvlGUvY4t9zEnuRym7hd" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-zA/pbofsasr4aEgMczbqePae9kmb2Xh2wp3V4UIp0T8ZRgAH3CSUx6dA/8IfwkFH" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,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-S0O8LKY8FDeykxEoMkbMbI7dHp8DZQlNAxW+xj+9rJ/XDvlGUvY4t9zEnuRym7hd" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-zA/pbofsasr4aEgMczbqePae9kmb2Xh2wp3V4UIp0T8ZRgAH3CSUx6dA/8IfwkFH" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,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-S0O8LKY8FDeykxEoMkbMbI7dHp8DZQlNAxW+xj+9rJ/XDvlGUvY4t9zEnuRym7hd" crossorigin="anonymous"></script>
<script src="../country-posters.js" integrity="sha384-zA/pbofsasr4aEgMczbqePae9kmb2Xh2wp3V4UIp0T8ZRgAH3CSUx6dA/8IfwkFH" 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

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c" 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

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c" 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

@ -158,7 +158,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "doxa-maps/country-posters/country-posters.js",
"integrity": "sha384-S0O8LKY8FDeykxEoMkbMbI7dHp8DZQlNAxW+xj+9rJ/XDvlGUvY4t9zEnuRym7hd",
"integrity": "sha384-zA/pbofsasr4aEgMczbqePae9kmb2Xh2wp3V4UIp0T8ZRgAH3CSUx6dA/8IfwkFH",
"elements": [
"country-posters-map"
],
@ -178,7 +178,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/countries-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-S0O8LKY8FDeykxEoMkbMbI7dHp8DZQlNAxW+xj+9rJ/XDvlGUvY4t9zEnuRym7hd\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-zA/pbofsasr4aEgMczbqePae9kmb2Xh2wp3V4UIp0T8ZRgAH3CSUx6dA/8IfwkFH\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -192,7 +192,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-S0O8LKY8FDeykxEoMkbMbI7dHp8DZQlNAxW+xj+9rJ/XDvlGUvY4t9zEnuRym7hd\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-zA/pbofsasr4aEgMczbqePae9kmb2Xh2wp3V4UIp0T8ZRgAH3CSUx6dA/8IfwkFH\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
},
{
@ -206,7 +206,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "<iframe src=\"./country-posters/country-posters-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-S0O8LKY8FDeykxEoMkbMbI7dHp8DZQlNAxW+xj+9rJ/XDvlGUvY4t9zEnuRym7hd\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-zA/pbofsasr4aEgMczbqePae9kmb2Xh2wp3V4UIp0T8ZRgAH3CSUx6dA/8IfwkFH\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
}
}
]
@ -596,7 +596,7 @@
"bundle": "gospel-resources",
"scriptUrl": "./gospel-resources/gospel-resources.js",
"output": "doxa-maps/gospel-resources/gospel-resources.js",
"integrity": "sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at",
"integrity": "sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c",
"elements": [
"gospel-resources-map"
],
@ -616,7 +616,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -630,7 +630,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -644,7 +644,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/gospel-dashboard/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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -658,7 +658,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -672,7 +672,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-omkh4A+NQYcdZtNZzEM509Yrmbv3aG6OI4/vtkHzKaZA3NBSNkTLLu28/MM6F8at\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-VSJBa0rj+G/h4+0MOKOTCyB726MtzIp4EahuDM0P8rPU+27JIRcy7g8ILkiIZa7c\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
}
]
@ -799,7 +799,7 @@
"bundle": "plugin-studio",
"scriptUrl": "./plugin-studio/plugin-studio.js",
"output": "doxa-maps/plugin-studio/plugin-studio.js",
"integrity": "sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS",
"integrity": "sha384-6Dmz25GPZmlHiB0aEzeuaYQvFfvy+1IMNy8Tsk9VflO2lH2543P/Q2W6WvetrU9o",
"elements": [
"plugin-studio"
],
@ -827,7 +827,7 @@
"scriptUrl": "./plugin-studio/plugin-studio.js",
"embed": {
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/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=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-6Dmz25GPZmlHiB0aEzeuaYQvFfvy+1IMNy8Tsk9VflO2lH2543P/Q2W6WvetrU9o\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
},
"instances": [
{
@ -879,7 +879,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-RROi/svcB/vhqo1W8SwZCYhJbi2HbGft9lGxxJQ4UAYjNzrbHKjSzJ6MmS7imr6o",
"integrity": "sha384-XG46qT7xD/y8M4550zPkaAWJv14HqHmTI/BcVLZ0Uxs19ByuNcxNX8svr6AaYSD2",
"elements": [
"regions-dashboard"
],
@ -899,7 +899,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-RROi/svcB/vhqo1W8SwZCYhJbi2HbGft9lGxxJQ4UAYjNzrbHKjSzJ6MmS7imr6o\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-XG46qT7xD/y8M4550zPkaAWJv14HqHmTI/BcVLZ0Uxs19ByuNcxNX8svr6AaYSD2\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -913,7 +913,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-RROi/svcB/vhqo1W8SwZCYhJbi2HbGft9lGxxJQ4UAYjNzrbHKjSzJ6MmS7imr6o\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-XG46qT7xD/y8M4550zPkaAWJv14HqHmTI/BcVLZ0Uxs19ByuNcxNX8svr6AaYSD2\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -927,7 +927,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-RROi/svcB/vhqo1W8SwZCYhJbi2HbGft9lGxxJQ4UAYjNzrbHKjSzJ6MmS7imr6o\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-XG46qT7xD/y8M4550zPkaAWJv14HqHmTI/BcVLZ0Uxs19ByuNcxNX8svr6AaYSD2\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
<script src="../plugin-studio.js" integrity="sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS" crossorigin="anonymous"></script>
<script src="../plugin-studio.js" integrity="sha384-6Dmz25GPZmlHiB0aEzeuaYQvFfvy+1IMNy8Tsk9VflO2lH2543P/Q2W6WvetrU9o" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
<script src="../../plugin-studio.js" integrity="sha384-D++rzhYeOQb6jBAa8CIB+8VvmvRij/YX1WF9TVHHLkGmUZUZSoOwUx6cgFuzkHOS" crossorigin="anonymous"></script>
<script src="../../plugin-studio.js" integrity="sha384-6Dmz25GPZmlHiB0aEzeuaYQvFfvy+1IMNy8Tsk9VflO2lH2543P/Q2W6WvetrU9o" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-RROi/svcB/vhqo1W8SwZCYhJbi2HbGft9lGxxJQ4UAYjNzrbHKjSzJ6MmS7imr6o" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-XG46qT7xD/y8M4550zPkaAWJv14HqHmTI/BcVLZ0Uxs19ByuNcxNX8svr6AaYSD2" 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

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-RROi/svcB/vhqo1W8SwZCYhJbi2HbGft9lGxxJQ4UAYjNzrbHKjSzJ6MmS7imr6o" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-XG46qT7xD/y8M4550zPkaAWJv14HqHmTI/BcVLZ0Uxs19ByuNcxNX8svr6AaYSD2" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-RROi/svcB/vhqo1W8SwZCYhJbi2HbGft9lGxxJQ4UAYjNzrbHKjSzJ6MmS7imr6o" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-XG46qT7xD/y8M4550zPkaAWJv14HqHmTI/BcVLZ0Uxs19ByuNcxNX8svr6AaYSD2" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: