Merge origin/development (d3b879c94) into studio/pinhint

This commit is contained in:
Doxa Maps 2026-10-01 06:32:47 +00:00
commit ba022f68f9
639 changed files with 3915 additions and 20567 deletions

138
README.md
View file

@ -6,7 +6,9 @@ Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His
## Bottom line up front
- **Why:** dx-maps exists to give disciples, churches and leaders what they need to bless the
- **Why:** To Bless the Nations
- **Great Commission:** dx-maps exists to give disciples, churches and leaders what they need to bless the
nations. We believe the deeper answer to the Great Commission is indigenous disciples, churches
and leaders multiplying among their own peoples and blessing them, so that the Commission is
fulfilled through love. The maps serve that in three ways: praying for the unreached peoples of
@ -32,6 +34,12 @@ Made by Vibe Coders for Vibe Coders to prepare the way of the Lord and make His
Built with love, with the prayer that these maps bless the nations.
**A blessing for you.** These maps and posters are given to bless the people who bless the nations.
We love you, and we are thankful for your work. We bless you in Jesus' name.
Every poster is in Posterverse (`plugin-designs/posterverse/`): the UPG 100 List, Country Posters,
Poster Design and Wall of People Groups. They are all in the Plugin Studio (`bun run dev`) too.
# dx-maps — one build, maps that embed everywhere
**Write a map once in `plugin-designs/<your-map>/`, run one build, and drop it on any site** —
@ -62,7 +70,7 @@ capabilities, three outputs, 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, every one shippable in every output** | `bun run verify:5-caps` |
| **Shared library, private plugins** — reuse without breaking each other | `bun run check:library-index` |
| **One agent per plugin, one shepherd, one working `development`** | [agent swarm](#build-it-with-an-agent-swarm) |
| **One owner per plugin, a second reader for every change, one landing at a time** | [agent swarm](#build-it-with-an-agent-swarm) |
## For vibe coders: build, open the Studio, embed a map
@ -169,11 +177,11 @@ the Research Gallery, which DX Life publishes as **Deep Research**. An eighth pr
| Capability | Example | iframe | Mobile app | JavaScript embed | WordPress / Disciple Tools | Desktop |
|---|---|:-:|:-:|:-:|:-:|:-:|
| **5 — app shell** | Research Gallery | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| **4 — composite** | UPG-100 List | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| **3 — multi-view** | research map | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| **2 — presets** | Prayer Progress | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| **1 — single element** | all people groups | ✅ | ✅ | ✅ | 🧪 | 🧪 |
| **5 — app shell** | Research Gallery | ✅ | ✅ | ✅ | 🧪 | ✅ |
| **4 — composite** | UPG-100 List | ✅ | ✅ | ✅ | 🧪 | ✅ |
| **3 — multi-view** | research map | ✅ | ✅ | ✅ | 🧪 | ✅ |
| **2 — presets** | Prayer Progress | ✅ | ✅ | ✅ | 🧪 | ✅ |
| **1 — single element** | all people groups | ✅ | ✅ | ✅ | 🧪 | ✅ |
<sub>✅ works today · 🧪 proven, not yet in production. Every plugin builds into the same folder, and that folder is what all five methods ship — so a capability never limits where a plugin can go.</sub>
@ -425,9 +433,9 @@ put it* and *how a page shows it*.
| # | Output | What it is | Status |
|---|---|---|---|
| **1** | **The web folder** `public/dx-maps/` | every map, each embeddable on its own, all working together — 1205 files, 154 MiB (about 160 MB) | ✅ production |
| **1** | **The web folder** `public/dx-maps/` | every map, each embeddable on its own, all working together — 1367 files, 163 MiB (about 170 MB) | ✅ production |
| **2** | **Mobile app** | the same folder, wrapped as an Android app by Capacitor | ✅ built |
| **3** | **Desktop app** | the same folder, wrapped as a desktop app by Tauri | 🧪 proven, not yet in production |
| **3** | **Desktop app** | the same folder, wrapped as a desktop app by Tauri | ✅ works today |
### Output 1 — the web folder
@ -581,16 +589,16 @@ bun run phone # builds the app and installs it on a phone or emulator
</details>
### Output 3 — the desktop app 🧪 proven, not yet in production
### Output 3 — the desktop app ✅ works today
**We got the JAMstack map app running as a desktop app.**
![Desktop app (Tauri) — proven, not yet in production](documentation/readme-images/deploy5-desktop.png)
![Desktop app (Tauri), working](documentation/readme-images/deploy5-desktop.png)
**Rudder, the voice-to-text plugin, runs as its own desktop app too**, built with
`bun run desktop:build rudder-voice` ([HOWTO-build-the-desktop-app](./documentation/HOWTO-build-the-desktop-app.md)).
![Rudder as a desktop app on Linux: offline voice-to-text](documentation/readme-images/deploy5-desktop-rudder.png)
![Rudder, the offline voice-to-text desktop app, running](documentation/readme-images/deploy5-desktop-rudder.png)
<details>
<summary><b>More about the desktop app</b></summary>
@ -687,11 +695,62 @@ your own page, set `"locale"` in its `profile-config`. Arabic reads right to lef
## Build it with an agent swarm
**Many AI agents can build many plugins at once without breaking each other's work.** Every plugin
has one agent building it, and one shepherd agent makes sure every agent does its job well. All the
plugins live in one project with one shared library, and changes are added to `development`, the shared
working version, one at a time. `main` is the production version, and it moves only when a release is published.
has one agent that owns it, and changes are added to `development`, the shared working version, one
at a time. `main` is the production version: it moves only when you publish a release.
![The delegation model: main at the centre guarded by a merge queue, inside the Plugin Studio; eight plugins around it; one agent per plugin; and one shepherd over all the agents](documentation/readme-images/delegation-model.png)
**Bring your own harness.** dx-maps works with whatever AI coding agent or agent harness you already
use. The repo gives you the branch strategy below, the checks (`bun run gates:pre`) and the Plugin
Studio; it does not ship a harness. Roles such as a coordinator that puts the landings in order, or a
gate-keeper that reads each change before it lands, are jobs your harness gives to its agents.
![The delegation model: development at the centre behind a landing lock, inside the Plugin Studio; eight plugins around it, each owned by one agent; a coordinator and a gate-keeper over the agents; below, how a change lands in four steps, and the three outputs](documentation/readme-images/delegation-model.png)
<sub>[Open the diagram full size](documentation/readme-images/delegation-model.png): on a phone its small text needs it.</sub>
### Set up the branch strategy
1. **`main` is production.** It moves only when you publish a release (step 7).
2. **`development` is the shared working branch.** Create it once, from `main`:
```bash
git fetch origin && git switch -c development origin/main && git push -u origin development
```
3. **One branch and worktree per plugin agent**, from `origin/development`, then install once in each:
```bash
git worktree add -b regions/work ../dx-maps-regions origin/development
git worktree add -b upg100/work ../dx-maps-upg100 origin/development
cd ../dx-maps-regions && bun install --frozen-lockfile
```
4. **One preview server per agent, each on its own port:**
```bash
bun x vite --port 5301 --strictPort # the next agent uses 5302, and so on
```
5. **Land one at a time, through a lock.** Catch up with `development`, run the checks, and push only
if nothing fails (`gates:pre` exits non-zero when a check fails, so the push never runs then):
```bash
flock /tmp/dx-maps-development.lock sh -c '
git fetch origin && git rebase origin/development &&
bun run gates:pre && git push origin HEAD:development'
```
`flock` guards one machine. Agents on several machines need a shared lock, such as one your
harness provides.
6. **One agent owns the full build**; the others build only their own plugin:
```bash
BUILD_ALLOW=1 bun run build # the build owner: every plugin
BUNDLE=<name> bun x vite build && bun run tree && bun run fix:sri # everyone else: one plugin
```
7. **Release:** fast-forward `main` to a `development` commit that passed the checks, tag it, and
deploy from the tag:
```bash
git fetch origin && git switch main && git merge --ff-only origin/main
git merge --ff-only <development-commit> # a commit that passed the checks
bun run gates:pre # the checks must pass on exactly this commit
git tag -a v1.0.0 -m "Release v1.0.0" && git push origin main v1.0.0
```
The tag's `public/dx-maps/` folder is the built site: deploy that
([how to deploy](./documentation/DEPLOY.md)).
How to contribute, and the bar for shared code: [documentation/CONTRIBUTING.md](./documentation/CONTRIBUTING.md).
Diagram source: [`delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
<details>
<summary><b>What problem does this solve? (in plain words)</b></summary>
@ -702,15 +761,17 @@ map breaks at once**. And when several agents work in the same project at the sa
each other — two agents edit the same file, one change quietly erases another, or half-finished work
lands in the version everyone else is using.
So the project works like a busy kitchen with one head chef:
So the project works like a busy kitchen:
1. **Each agent works in its own copy of the project** — its own counter, so nobody bumps anybody.
2. **Before adding its work, each agent catches up**: it pulls in everyone else's latest work and
fixes any clashes with its own. The agent fixes the clash, not the next person.
3. **Only one agent adds work at a time.** They wait in a line (the *merge queue*), the checks run,
and only then does the change go into `development`. `development` always works.
4. **A shepherd agent watches everyone**: plugins built in the studio, shared parts really shared,
and every change going through the line.
1. **Each agent works in its own branch and copy of the project** — its own counter, so nobody bumps
anybody. One owner per plugin, and one per shared file.
2. **Only one change lands at a time.** Whoever holds the *landing lock* first catches up with
everyone else's latest work, fixes any clash itself and runs every check; the change goes into
`development` only if nothing fails. `development` always works.
3. **Someone keeps order.** A coordinator can put the landings in order, and a gate-keeper can read
each change by its content before it lands. Your harness gives these jobs to its agents.
4. **`main` moves only when you publish a release**, after the checks pass on that exact version.
A preview can follow `development`; the published site follows `main`.
That's how many plugins get built at the same time, in one project, without the maps breaking.
@ -727,31 +788,8 @@ That's how many plugins get built at the same time, in one project, without the
| **worktree** | a private copy of the project for one agent to work in |
| **merge conflict** | two changes touched the same lines; someone has to decide how to combine them |
| **rebase** | catching your work up with everyone else's latest before adding it |
| **merge queue** | the line agents wait in, so only one change goes into `main` at a time |
</details>
<details>
<summary><b>The commands (for the curious)</b></summary>
```bash
# one private copy (worktree) + one agent per plugin
git worktree add -b regions/work ../dx-maps-regions origin/development
git worktree add -b upg100/work ../dx-maps-upg100 origin/development
# one preview server per agent, each on its own port
bun x vite --port 5301 --strictPort
# add work through the line: one at a time, caught up first, checks must pass
flock /tmp/dx-maps-development.lock sh -c '
git fetch origin && git rebase origin/development &&
bun run gates:pre && git push origin HEAD:development'
```
One agent owns the full build (it rebuilds everyone's output); the others check just their own
plugin with `BUNDLE=<name> bun x vite build`. How to contribute, and the bar for shared code:
[documentation/CONTRIBUTING.md](./documentation/CONTRIBUTING.md). Diagram source:
[`delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
| **landing lock** | only one change lands on `development` at a time; its holder catches up and runs every check first |
| **tag** | a fixed name for one release commit, such as `v1.0.0`, so you can always deploy exactly it |
</details>
@ -773,7 +811,7 @@ and choose `"provider": "mapbox"` (see *No Mapbox token?* below). For Rudder's v
`bash backend/rudder-engine/setup-runtime.sh` once; without Go, everything except voice works.
```bash
git clone --depth 1 https://git.nuxtbase.app/doxa-open/doxa-maps.git # latest code only: seconds, not hundreds of MB of history
git clone --depth 1 <your-repo-url> # latest code only: seconds, not hundreds of MB of history
cd doxa-maps
bun install --frozen-lockfile
cp .env.example .env.local # optional: paste your pk. token after VITE_MAPBOX_TOKEN= (.env works too; .env.local wins)

View file

@ -423,7 +423,7 @@ func (e *engine) startWith(lang, device string, src *recorderProc, sink func(eve
recMu.Unlock()
writeMu.Lock()
// The streaming model holds back the last fraction of a second (its right context) until more audio
// or a pause arrives, and a commit alone does not flush it (rega: a Stop mid-sentence lost "what
// or a pause arrives, and a commit alone does not flush it (measured: a Stop mid-sentence lost "what
// your country…"). 1.2 s of silence first lets it finish the words already spoken.
silence := make([]byte, 3200)
for i := 0; i < 12; i++ {
@ -750,7 +750,7 @@ func (e *engine) startWith(lang, device string, src *recorderProc, sink func(eve
e.logs.add("speech server error: %s", string(msg))
emit(event{"type": "error", "message": string(msg)})
case t == "input_audio_buffer.committed":
// The commit acknowledges Stop; the words for the audio before it can still follow (rega: a
// The commit acknowledges Stop; the words for the audio before it can still follow (measured: a
// Stop mid-word lost "what your country…"). Keep reading until the final text or 6 s of quiet (a loaded server can take seconds per backlog chunk).
committed = true
e.logs.add("speech server committed the stream; reading its last words")

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="512" width="840" height="1208" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="489" width="840" height="1231" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="627" width="840" height="1093" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

BIN
data/posters/india-an.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
data/posters/india-ap.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
data/posters/india-ar.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
data/posters/india-as.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
data/posters/india-br.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
data/posters/india-ch.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
data/posters/india-ct.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
data/posters/india-dh.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
data/posters/india-dl.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
data/posters/india-ga.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
data/posters/india-gj.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
data/posters/india-hp.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
data/posters/india-hr.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
data/posters/india-jh.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
data/posters/india-jk.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
data/posters/india-ka.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
data/posters/india-kl.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
data/posters/india-la.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
data/posters/india-ld.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
data/posters/india-mh.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
data/posters/india-ml.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
data/posters/india-mn.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
data/posters/india-mp.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
data/posters/india-or.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
data/posters/india-pb.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
data/posters/india-py.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
data/posters/india-rj.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
data/posters/india-sk.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
data/posters/india-tg.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

BIN
data/posters/india-tn.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
data/posters/india-tr.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
data/posters/india-up.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
data/posters/india-ut.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
data/posters/india-wb.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -194,7 +194,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -214,8 +214,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -286,15 +286,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="351" width="840" height="1333" fill="var(--sea)"
@ -318,7 +316,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -342,8 +340,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1925" x2="1474" y2="1925" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="512" width="840" height="1208" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="443" width="840" height="1277" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

View file

@ -184,7 +184,7 @@
.hseries{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:14px;fill:#7E7562;letter-spacing:.01em}
.hseries-em{font-weight:700;fill:#6B4A78}
.hseries-dim{fill:#A79E8A}
/* The site reference on its OWN line — Brief: "so that it is less wide". A
/* The site reference on its OWN line, so the series block is less wide. A
single long line was forcing the series block wider than the map. */
.hseries-dim2{font-family:Lato,'Segoe UI',system-ui,sans-serif;font-size:12px;
fill:#A79E8A;letter-spacing:.02em}
@ -204,8 +204,8 @@
the map's width (~1100px); the band is now 1388px, so the type follows.
15px measures ~1263px — fills the band while keeping headroom over the width
estimator's error rather than sitting one glyph from overflow. */
/* 13.5px bold. Brief: "this is too large, it should not span full width —
there needs to be gaps." At 15px bold it measured 1396px of the 1448px band,
/* 13.5px bold. At full width it was too large: the line needs gaps at both
ends. At 15px bold it measured 1396px of the 1448px band,
i.e. touching both rules; 13.5 measures ~1256px and leaves ~96px of paper at
each end, so the line reads as SET INSIDE the band rather than filling it.
Bold + dark ink still carry the weight. */
@ -276,15 +276,13 @@
<feDropShadow dx="0" dy="3" stdDeviation="5" flood-color="#2A2721" flood-opacity="0.34"/>
</filter>
</defs>
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating."
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating.
A soft drop shadow under a crisp 1.5px border lifts the map off the paper,
so it reads as an object ON the sheet rather than a window cut INTO it —
which is also what makes the surrounding text feel like a mount around it. -->
<!-- THE MAP FLOATS. Brief: "a border around the map itself like a border line
and a shadow around the outside of that line that makes the map itself look
like it is floating." The white mat was tried and rejected — the shadow-only
<!-- THE MAP FLOATS: a border line around the map itself, with a shadow outside
that line, makes the map look like it is floating. The white mat was tried and rejected — the shadow-only
border reads better, and the mat also cut every leader line with a band of
white where the lines crossed it. -->
<rect x="330" y="466" width="840" height="1254" fill="var(--sea)"
@ -308,7 +306,7 @@
down to where the region name stops being the loudest thing on the poster.
Splitting it lets the CATEGORY sit quietly above and the REGION stay big:
the region is what a passer-by uses to find their sheet on a wall of 30. -->
<!-- TITLE, two-tone. Brief: the title reads "Unengaged of <Region>", with
<!-- TITLE, two-tone. The title reads "Unengaged of <Region>", with
"Unengaged of" in a DIFFERENT COLOUR from the region name — so a passer-by
reads at a glance that it is these PEOPLE who are unengaged, while the
region stays the loudest thing on the sheet. The overline above it is now
@ -332,8 +330,8 @@
shadow now define that edge far more clearly, so the rule just floated as a
stray horizontal line above the map.) -->
<line x1="26" y1="1936" x2="1474" y2="1936" class="rule"/>
<!-- FOOTER SCRIPTURE — TIER 1. Brief: "should be larger text bold and not two
lines but one line with scripture reference on the right." It is one of the
<!-- FOOTER SCRIPTURE — TIER 1: larger, bold, on ONE line, with the scripture
reference on the right. It is one of the
three things that must read when a reader steps closer, alongside the title
and the people-group names, so it carries display weight. The reference is
right-aligned to the map's own edge, which is what keeps it on ONE line. -->

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -42,7 +42,7 @@ const refuse = (msg) => (PUBLIC
// fetch() hands back the body DECOMPRESSED, so the upstream Content-Encoding and Content-Length no longer describe
// what is sent on: forwarding them made browsers try to gunzip plain HTML, and PocketBase's dashboard never rendered
// (rega, 2026-09-29). And PocketBase redirects to its own root (/_ -> /_/), which is put back under the mount (/pb/_/).
// (measured 2026-09-29). And PocketBase redirects to its own root (/_ -> /_/), which is put back under the mount (/pb/_/).
// A backend path as the backend will read it: percent-decoded, runs of slashes collapsed. The admin gates run on
// THIS form, so /pb/%5F/ (PocketBase decodes it to /_/) meets the same check as /pb/_/ (studio, 2026-09-29).
// Encoded slashes or backslashes and undecodable input are refused: nothing legitimate sends them.
@ -179,7 +179,7 @@ Bun.serve({
const bp = backendPath(p)
if (bp === null) return text('bad path', 400)
if (!p.startsWith('/api-prod/api/') || !bp.startsWith('/api-prod/api/')) return text('only /api/ is public', 403)
// As on /pb: the superuser and every _system collection stay closed. No public client uses them (dx-base, V2 review).
// As on /pb: the superuser and every _system collection stay closed. No public client uses them (a platform security review).
if (bp.startsWith('/api-prod/api/collections/_')) return text('for admins only', 403)
return proxy(req, url, '/api-prod/', 8091)
}

View file

@ -11,7 +11,6 @@
AGENT-BRIEF.md
_archive
data-private
library/scripts/posters/snapshots/_granular-experiment.mjs.shelved
plugin-designs/key-cities
name:*field-access*
name:*visa*.json

View file

@ -17,7 +17,7 @@ building ones run the live source with hot reload; the showing ones serve the **
## 1. Your laptop — `bun run dev`
```bash
git clone --depth 1 https://git.nuxtbase.app/doxa-open/doxa-maps.git
git clone --depth 1 <your-repo-url>
cd doxa-maps
bun install --frozen-lockfile
bun run dev # → http://localhost:5173/ is the Plugin Studio

View file

@ -49,7 +49,7 @@ The scripts these commands are grounded in (from this branch's `package.json`):
"build": "bash internal/build-lock.sh",
"build:unlocked":"bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post",
"gates:pre":"node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts",
"gates:post":"node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed",
"gates:post":"node internal/run-parallel.mjs --label=post -- check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed",
"build:bundles": "node internal/run-parallel.mjs --bundles", // the 22 vite builds, 4 at a time
"preview": "bun x vite preview",
"serve": "node internal/serve-strict.mjs", // 404s a directory, like production

View file

@ -16,7 +16,7 @@ served folder". The licence travels with the asset; the explanation travels with
| `boundaries/gb-CHN-ADM1.geojson` | geoBoundaries gbOpen, boundaryID `CHN-ADM1-43563684` | **Public Domain**, licenceSource `commons.wikimedia.org`, year 2019 | recorded in `boundaries/ATTRIBUTION` | none — upstream records no attribution duty |
| `boundaries/world-adm0*.geojson` (2 files, 162 countries) | geoBoundaries gbOpen, assembled per country (its features carry geoBoundaries' own `iso3`/`shapeGroup`/`shapeName`; corrected — this register first guessed Natural Earth) | **MIXED, and ESTABLISHED per country as of 2026-09-28**: 162 upstream licences. **32** duty-free (28 Public Domain + 4 CC0) · **65** attribution-only · **65 SHARE-ALIKE CLASS** (58 ODbL 1.0 + 7 explicit ShareAlike: AGO GAB GEO GIN MMR NER TUR) | **Yes** — the per-country table (licence + licenseSource + boundaryID + boundarySource, fetched verbatim from the geoBoundaries API, 162 of 162, 0 failures) ships at `plugin-designs/my-upg-100-list/data/adm0/PROVENANCE.json`. `boundaries/ATTRIBUTION` references it by path rather than copying it | **OPEN, legal — not a measurement**: whether a redistributed boundary file is a "Derivative Database" under ODbL is unanswered, and half the duty-bearing countries turn on it. It needs an owner's decision on data licensing |
| `people-groups/*` (3 files, 8 MB) | peoplegroups.org | **not established** | no terms ship | **OPEN, judgment** — redistribution terms unknown |
| `pplr/*` (6 files, 16 MB) | **PPLR ArcGIS Online org** `services2.arcgis.com/S4ydGgujXcif36k3`, aggregating IMB (peoplegroups.org), Joshua Project, the ROP registry, and carrying WCD (World Christian Database) IDs. Built from a local 78 MB extract not present in this repo — evidence from exp-shepherd | **UNRESOLVED.** `aCountry` and `aIMBPeople` publish no `copyrightText`, description or licence (queried `f=json`) | nothing ships | **OPEN, judgment** — redistribution not established. WCD is a proprietary Brill database, so even the IDs need a check |
| `pplr/*` (6 files, 16 MB) | **PPLR ArcGIS Online org** `services2.arcgis.com/S4ydGgujXcif36k3`, aggregating IMB (peoplegroups.org), Joshua Project, the ROP registry, and carrying WCD (World Christian Database) IDs. Built from a local 78 MB extract not present in this repo | **UNRESOLVED.** `aCountry` and `aIMBPeople` publish no `copyrightText`, description or licence (queried `f=json`) | nothing ships | **OPEN, judgment** — redistribution not established. WCD is a proprietary Brill database, so even the IDs need a check |
| `tables/*` (304 files, 36 MB) | derived by `library/scripts/research/fetch-*.mjs` from several upstream APIs | **varies, not established per table** | nothing ships; there is no source or licence doc in `tables/` | **OPEN, judgment** — the largest gap by volume |
| `images/worldmap.svg` | recorded in `images/PROVENANCE` | see that file | the PROVENANCE file ships and explains why no licence file sits beside it | none claimed; reasoning recorded rather than assumed |
| `previews/*` (378 files) | our own poster previews | ours | n/a | none |

View file

@ -42,7 +42,6 @@ the app is rebuilt.**
| `build-gallery.mjs` | `out/index.html` — browsable preview grid, sectioned by region. |
| `build-hd-gallery.mjs` | `out/hd.html` — full-resolution review + copy-to-clipboard. |
| `export-public.mjs` | Copies PNGs into region folders OUTSIDE the repo (see below). |
| `snapshots/` | Frozen template versions. v5 is the approved current; v1–v4 are the lineage. |
## Environment

View file

@ -1,10 +1,7 @@
# needs-eyes — partner-embed fixtures
Under the team's Playwright policy (2026-09-25) only **rega** runs a browser on the VPS, and a
maintainer's laptop is the other pair of eyes. So a check that can only be seen has to be *openable by
someone else* — not a construct that lives inside one agent's gate process.
These pages are generated, not hand-written:
A check that can only be seen has to be *openable by someone else*, not a construct that lives inside
one gate's process. These pages are generated on demand and are not committed; write them with:
```
node internal/check-embedded-rtl.mjs --emit

View file

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>partner</title>
<style>html,body{margin:0}#w{height:900px}</style></head>
<body><h1 id="partner-heading">Partner page</h1><div id="w"></div>
<!-- A REAL PARTNER EMBED SETS THIS. The generated leaf pages bake window.MAP_APP_API_URL, and
without it every data fetch resolves same-origin against the partner and fails, so the wall
renders zero cards and the locale has nothing to translate. The first run of this gate
omitted it and reported "never mounted" — a harness gap, not the RTL defect. -->
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<script src="http://127.0.0.1:47395/dx-maps/my-upg-100-list/my-upg-100-list.js" crossorigin="anonymous"></script>
<script>
var el = document.createElement("my-upg-100-list-map");
el.setAttribute('profile-config', JSON.stringify({ profile: "poster-wall",
assetsBase: "http://127.0.0.1:47395/dx-maps/assets" }));
el.style.cssText = 'display:block;width:100%;height:900px';
document.getElementById('w').appendChild(el);
</script></body></html>

View file

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>partner</title>
<style>html,body{margin:0}#w{height:900px}</style></head>
<body><h1 id="partner-heading">Partner page</h1><div id="w"></div>
<!-- A REAL PARTNER EMBED SETS THIS. The generated leaf pages bake window.MAP_APP_API_URL, and
without it every data fetch resolves same-origin against the partner and fails, so the wall
renders zero cards and the locale has nothing to translate. The first run of this gate
omitted it and reported "never mounted" — a harness gap, not the RTL defect. -->
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<script src="http://127.0.0.1:47395/dx-maps/my-upg-100-list/my-upg-100-list.js" crossorigin="anonymous"></script>
<script>
var el = document.createElement("my-upg-100-list-map");
el.setAttribute('profile-config', JSON.stringify({ profile: "poster-wall", locale: 'ar',
assetsBase: "http://127.0.0.1:47395/dx-maps/assets" }));
el.style.cssText = 'display:block;width:100%;height:900px';
document.getElementById('w').appendChild(el);
</script></body></html>

View file

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>partner</title>
<style>html,body{margin:0}#w{height:900px}</style></head>
<body><h1 id="partner-heading">Partner page</h1><div id="w"></div>
<!-- A REAL PARTNER EMBED SETS THIS. The generated leaf pages bake window.MAP_APP_API_URL, and
without it every data fetch resolves same-origin against the partner and fails, so the wall
renders zero cards and the locale has nothing to translate. The first run of this gate
omitted it and reported "never mounted" — a harness gap, not the RTL defect. -->
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<script src="http://127.0.0.1:47395/dx-maps/my-upg-100-list/my-upg-100-list.js" crossorigin="anonymous"></script>
<script>
var el = document.createElement("my-upg-100-list-map");
el.setAttribute('profile-config', JSON.stringify({ profile: "upg-100-mfe", locale: 'ar',
assetsBase: "http://127.0.0.1:47395/dx-maps/assets" }));
el.style.cssText = 'display:block;width:100%;height:900px';
document.getElementById('w').appendChild(el);
</script></body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 KiB

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1500" height="2030" viewBox="0 0 1500 2030" font-family="Inter, 'Helvetica Neue', Arial, sans-serif">
<svg xmlns="http://www.w3.org/2000/svg" width="1500" height="2086" viewBox="0 0 1500 2086" font-family="Inter, 'Helvetica Neue', Arial, sans-serif">
<defs>
<marker id="aA" 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="aS" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#7c3aed"/></marker>
@ -9,8 +9,8 @@
<path id="agL" d="M 194,850 A 556,556 0 0 1 1306,850"/>
<path id="mqL" d="M 679,884 A 71,71 0 0 0 821,884"/>
</defs>
<rect width="1500" height="2030" fill="#ffffff"/>
<text x="750" y="54" text-anchor="middle" font-size="34" font-weight="800" fill="#0f172a">How dx-maps is built: one studio, one agent per plugin, one shepherd</text>
<rect width="1500" height="2086" fill="#ffffff"/>
<text x="750" y="54" text-anchor="middle" font-size="34" font-weight="800" fill="#0f172a">How dx-maps is built: one owner per plugin, one landing at a time</text>
<circle cx="750" cy="850" r="540" fill="none" stroke="#2563eb" stroke-width="1.6" stroke-dasharray="3 7" opacity=".75"/>
<circle cx="750" cy="850" r="355" fill="none" stroke="#15803d" stroke-width="2.2" opacity=".85"/>
<circle cx="750" cy="850" r="606" fill="none" stroke="#7c3aed" stroke-width="2" stroke-dasharray="8 7" opacity=".6"/>
@ -29,8 +29,8 @@
<circle cx="750" cy="850" r="120" fill="#fef2f2" stroke="#dc2626" stroke-width="3" stroke-dasharray="10 6"/>
<path id="mqT" d="M 646,850 A 104,104 0 0 1 854,850" fill="none"/>
<path id="mqB" d="M 644,850 A 106,106 0 0 0 856,850" fill="none"/>
<text font-size="13" font-weight="800" fill="#b91c1c" letter-spacing="1.6"><textPath href="#mqT" startOffset="50%" text-anchor="middle">MERGE QUEUE · GUARD</textPath></text>
<text font-size="12" font-weight="700" fill="#b91c1c" letter-spacing="1.2"><textPath href="#mqB" startOffset="50%" text-anchor="middle">ONE AGENT AT A TIME</textPath></text>
<text font-size="13" font-weight="800" fill="#b91c1c" letter-spacing="1.6"><textPath href="#mqT" startOffset="50%" text-anchor="middle">LANDING LOCK</textPath></text>
<text font-size="12" font-weight="700" fill="#b91c1c" letter-spacing="1.2"><textPath href="#mqB" startOffset="50%" text-anchor="middle">ONE LANDING AT A TIME</textPath></text>
<rect x="617" y="846" width="26" height="20" rx="4" fill="#dc2626"/>
<path d="M 622,846 v -6 a 8,8 0 0 1 16,0 v 6" fill="none" stroke="#dc2626" stroke-width="3.5"/>
<circle cx="630" cy="856" r="3" fill="#fff"/>
@ -38,9 +38,9 @@
<path d="M 862,846 v -6 a 8,8 0 0 1 16,0 v 6" fill="none" stroke="#dc2626" stroke-width="3.5"/>
<circle cx="870" cy="856" r="3" fill="#fff"/>
<circle cx="750" cy="850" r="72" fill="#166534" stroke="#14532d" stroke-width="3"/>
<text x="750" y="852" text-anchor="middle" font-size="34" font-weight="800" fill="#fff">main</text>
<text x="750" y="876" text-anchor="middle" font-size="12.5" fill="#dcfce7">the one branch</text>
<text x="750" y="892" text-anchor="middle" font-size="12.5" fill="#dcfce7">always green</text>
<text x="750" y="850" text-anchor="middle" font-size="18" font-weight="800" fill="#fff">development</text>
<text x="750" y="872" text-anchor="middle" font-size="12.5" fill="#dcfce7">every change</text>
<text x="750" y="888" text-anchor="middle" font-size="12.5" fill="#dcfce7">lands here</text>
<text font-size="16" font-weight="800" fill="#15803d" letter-spacing="3"><textPath href="#plL" startOffset="50%" text-anchor="middle">PLUGINS</textPath></text>
<text font-size="16" font-weight="800" fill="#2563eb" letter-spacing="3"><textPath href="#agL" startOffset="18%" text-anchor="middle">AGENTS</textPath></text>
<line x1="862.1" y1="579.3" x2="822.7" y2="674.5" stroke="#15803d" stroke-width="1.4" opacity=".45"/>
@ -117,67 +117,83 @@
<circle cx="543.4" cy="351.1" r="40" fill="#ecfeff" stroke="#0891b2" stroke-width="2.5"/>
<text x="543.4" y="349.1" text-anchor="middle" font-size="12" font-weight="700" fill="#155e75">roaming</text>
<text x="543.4" y="364.1" text-anchor="middle" font-size="12" font-weight="700" fill="#155e75">agent</text>
<circle cx="750" cy="244" r="60" fill="#f5f3ff" stroke="#7c3aed" stroke-width="3.5"/>
<text x="750" y="251" text-anchor="middle" font-size="19" font-weight="800" fill="#4c1d95">shepherd</text>
<text x="834" y="192" font-size="17" fill="#4c1d95">one shepherd makes sure all plugin agents</text>
<text x="834" y="216" font-size="17" fill="#4c1d95">are doing their job building the plugins well</text>
<text x="750" y="1502" text-anchor="middle" font-size="20" font-weight="800" fill="#1e3a8a">each plugin has one agent responsible for building it</text>
<rect x="36" y="110" width="300" height="280" rx="14" fill="#f8fafc" stroke="#e2e8f0" stroke-width="2"/>
<circle cx="64" cy="144" r="12" fill="#166534" stroke="#14532d" stroke-width="2.5"/>
<text x="88" y="149" font-size="15" fill="#334155">main — the one branch</text>
<circle cx="64" cy="180" r="12" fill="#fef2f2" stroke="#dc2626" stroke-width="2.5"/>
<text x="88" y="185" font-size="15" fill="#334155">merge queue — one at a time</text>
<circle cx="64" cy="216" r="12" fill="#fffbeb" stroke="#d97706" stroke-width="2.5"/>
<text x="88" y="221" font-size="15" fill="#334155">Plugin Studio — the one repo</text>
<circle cx="64" cy="252" r="12" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="88" y="257" font-size="15" fill="#334155">plugin (badge = capability)</text>
<circle cx="64" cy="288" r="12" fill="#ecfeff" stroke="#0891b2" stroke-width="2.5"/>
<text x="88" y="293" font-size="15" fill="#334155">Experimental shelf</text>
<circle cx="64" cy="324" r="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="88" y="329" font-size="15" fill="#334155">agent — builds one plugin</text>
<circle cx="64" cy="360" r="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2.5"/>
<text x="88" y="365" font-size="15" fill="#334155">shepherd — oversees all</text>
<rect x="60" y="1536" width="1380" height="218" rx="20" fill="#fef2f2" stroke="#fca5a5" stroke-width="2"/>
<text x="750" y="1578" text-anchor="middle" font-size="22" font-weight="800" fill="#7f1d1d">How main is protected — so many plugins can be built at once</text>
<rect x="80.0" y="1602" width="410" height="130" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="116.0" cy="1638" r="20" fill="#dc2626"/>
<text x="116.0" y="1645" text-anchor="middle" font-size="20" font-weight="800" fill="#fff">1</text>
<text x="148.0" y="1645" font-size="20" font-weight="800" fill="#0f172a">Work in your own copy</text>
<text x="102.0" y="1686" font-size="16" fill="#475569">each agent builds its own plugin,</text>
<text x="102.0" y="1710" font-size="16" fill="#475569">in its own private copy of the project</text>
<text x="517.5" y="1677" text-anchor="middle" font-size="30" font-weight="800" fill="#dc2626">→</text>
<rect x="545.0" y="1602" width="410" height="130" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="581.0" cy="1638" r="20" fill="#dc2626"/>
<text x="581.0" y="1645" text-anchor="middle" font-size="20" font-weight="800" fill="#fff">2</text>
<text x="613.0" y="1645" font-size="20" font-weight="800" fill="#0f172a">Catch up, fix the clashes</text>
<text x="567.0" y="1686" font-size="16" fill="#475569">pull in everyone else's latest work and</text>
<text x="567.0" y="1710" font-size="16" fill="#475569">fix any clashes before adding yours</text>
<text x="982.5" y="1677" text-anchor="middle" font-size="30" font-weight="800" fill="#dc2626">→</text>
<rect x="1010.0" y="1602" width="410" height="130" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="1046.0" cy="1638" r="20" fill="#dc2626"/>
<text x="1046.0" y="1645" text-anchor="middle" font-size="20" font-weight="800" fill="#fff">3</text>
<text x="1078.0" y="1645" font-size="20" font-weight="800" fill="#0f172a">Add changes one at a time</text>
<text x="1032.0" y="1686" font-size="16" fill="#475569">the queue lets ONE agent in at a time;</text>
<text x="1032.0" y="1710" font-size="16" fill="#475569">the checks must pass, main stays working</text>
<circle cx="690" cy="248" r="56" fill="#f5f3ff" stroke="#7c3aed" stroke-width="3.5"/>
<text x="690" y="254" text-anchor="middle" font-size="14" font-weight="800" fill="#4c1d95">coordinator</text>
<circle cx="812" cy="248" r="56" fill="#f1f5f9" stroke="#334155" stroke-width="3.5"/>
<text x="812" y="254" text-anchor="middle" font-size="14" font-weight="800" fill="#1e293b">gate-keeper</text>
<text x="884" y="192" font-size="17" fill="#4c1d95">a coordinator puts the landings in order;</text>
<text x="884" y="216" font-size="17" fill="#1e293b">a gate-keeper checks each one by its content</text>
<text x="750" y="1502" text-anchor="middle" font-size="20" font-weight="800" fill="#1e3a8a">one owner per plugin, in its own branch · a second reader for every change</text>
<rect x="36" y="110" width="304" height="280" rx="14" fill="#f8fafc" stroke="#e2e8f0" stroke-width="2"/>
<circle cx="64" cy="142" r="11" fill="#166534" stroke="#14532d" stroke-width="2.5"/>
<text x="88" y="147" font-size="15" fill="#334155">development — shared branch</text>
<circle cx="64" cy="174" r="11" fill="#fef2f2" stroke="#dc2626" stroke-width="2.5"/>
<text x="88" y="179" font-size="15" fill="#334155">landing lock — one at a time</text>
<circle cx="64" cy="206" r="11" fill="#fffbeb" stroke="#d97706" stroke-width="2.5"/>
<text x="88" y="211" font-size="15" fill="#334155">Plugin Studio — the one repo</text>
<circle cx="64" cy="238" r="11" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
<text x="88" y="243" font-size="15" fill="#334155">plugin (badge = capability)</text>
<circle cx="64" cy="270" r="11" fill="#ecfeff" stroke="#0891b2" stroke-width="2.5"/>
<text x="88" y="275" font-size="15" fill="#334155">Experimental shelf</text>
<circle cx="64" cy="302" r="11" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="88" y="307" font-size="15" fill="#334155">agent — owns one plugin</text>
<circle cx="64" cy="334" r="11" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2.5"/>
<text x="88" y="339" font-size="15" fill="#334155">coordinator — orders landings</text>
<circle cx="64" cy="366" r="11" fill="#f1f5f9" stroke="#334155" stroke-width="2.5"/>
<text x="88" y="371" font-size="15" fill="#334155">gate-keeper — checks landings</text>
<rect x="60" y="1536" width="1380" height="232" rx="20" fill="#fef2f2" stroke="#fca5a5" stroke-width="2"/>
<text x="750" y="1578" text-anchor="middle" font-size="22" font-weight="800" fill="#7f1d1d">How a change lands — one at a time, so many plugins can be built at once</text>
<rect x="80.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="114.0" cy="1634" r="18" fill="#dc2626"/>
<text x="114.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">1</text>
<text x="142.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Own branch</text>
<text x="100.0" y="1680" font-size="15" fill="#475569">each owner builds its plugin</text>
<text x="100.0" y="1702" font-size="15" fill="#475569">in its own branch and copy;</text>
<text x="100.0" y="1724" font-size="15" fill="#475569">one owner per shared file</text>
<text x="405.0" y="1682" text-anchor="middle" font-size="26" font-weight="800" fill="#dc2626">→</text>
<rect x="425.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="459.0" cy="1634" r="18" fill="#dc2626"/>
<text x="459.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">2</text>
<text x="487.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Second reader</text>
<text x="445.0" y="1680" font-size="15" fill="#475569">another agent reads every</text>
<text x="445.0" y="1702" font-size="15" fill="#475569">change by its content</text>
<text x="445.0" y="1724" font-size="15" fill="#475569">before it may land</text>
<text x="750.0" y="1682" text-anchor="middle" font-size="26" font-weight="800" fill="#dc2626">→</text>
<rect x="770.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="804.0" cy="1634" r="18" fill="#dc2626"/>
<text x="804.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">3</text>
<text x="832.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">One at a time</text>
<text x="790.0" y="1680" font-size="15" fill="#475569">in the lock: merge development,</text>
<text x="790.0" y="1702" font-size="15" fill="#475569">rebuild, run every check;</text>
<text x="790.0" y="1724" font-size="15" fill="#475569">push only if nothing fails</text>
<text x="1095.0" y="1682" text-anchor="middle" font-size="26" font-weight="800" fill="#dc2626">→</text>
<rect x="1115.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="1149.0" cy="1634" r="18" fill="#dc2626"/>
<text x="1149.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">4</text>
<text x="1177.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Promote to main</text>
<text x="1135.0" y="1680" font-size="15" fill="#475569">main is production: it moves</text>
<text x="1135.0" y="1702" font-size="15" fill="#475569">only when the owner approves,</text>
<text x="1135.0" y="1724" font-size="15" fill="#475569">after full checks on that version</text>
<line x1="750" y1="956" x2="750" y2="1786" stroke="#334155" stroke-width="2.5" stroke-dasharray="2 5" opacity=".0"/>
<rect x="60" y="1780" width="1380" height="230" rx="20" fill="#f8fafc" stroke="#cbd5e1" stroke-width="2"/>
<text x="750" y="1824" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">main → ONE build → one web folder → three outputs</text>
<rect x="80.0" y="1852" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
<text x="290.0" y="1874" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 1</text>
<text x="290.0" y="1906" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">The web folder</text>
<text x="290.0" y="1932" text-anchor="middle" font-size="15" fill="#475569">iframe · JS embed · WordPress (for fun)</text>
<rect x="190.0" y="1946" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
<text x="290.0" y="1964" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">PRODUCTION</text>
<rect x="540.0" y="1852" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
<text x="750.0" y="1874" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 2</text>
<text x="750.0" y="1906" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Mobile app</text>
<text x="750.0" y="1932" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Capacitor</text>
<rect x="650.0" y="1946" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
<text x="750.0" y="1964" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">BUILT</text>
<rect x="1000.0" y="1852" width="420" height="130" rx="14" fill="#fff" stroke="#d97706" stroke-width="2.5"/>
<text x="1210.0" y="1874" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 3</text>
<text x="1210.0" y="1906" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Desktop app</text>
<text x="1210.0" y="1932" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Tauri</text>
<rect x="1110.0" y="1946" width="200" height="26" rx="13" fill="#d97706" opacity=".13"/>
<text x="1210.0" y="1964" text-anchor="middle" font-size="12" font-weight="800" fill="#d97706">PROVEN · NOT YET LIVE</text>
<rect x="60" y="1812" width="1380" height="254" rx="20" fill="#f8fafc" stroke="#cbd5e1" stroke-width="2"/>
<text x="750" y="1856" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">main → ONE build → one web folder → three outputs</text>
<text x="750" y="1882" text-anchor="middle" font-size="16" fill="#475569">the dev preview follows development · the published site follows main</text>
<rect x="80.0" y="1908" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
<text x="290.0" y="1930" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 1</text>
<text x="290.0" y="1962" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">The web folder</text>
<text x="290.0" y="1988" text-anchor="middle" font-size="15" fill="#475569">iframe · JS embed · WordPress (for fun)</text>
<rect x="190.0" y="2002" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
<text x="290.0" y="2020" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">PRODUCTION</text>
<rect x="540.0" y="1908" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
<text x="750.0" y="1930" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 2</text>
<text x="750.0" y="1962" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Mobile app</text>
<text x="750.0" y="1988" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Capacitor</text>
<rect x="650.0" y="2002" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
<text x="750.0" y="2020" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">BUILT</text>
<rect x="1000.0" y="1908" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
<text x="1210.0" y="1930" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 3</text>
<text x="1210.0" y="1962" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">Desktop app</text>
<text x="1210.0" y="1988" text-anchor="middle" font-size="15" fill="#475569">the same folder, wrapped by Tauri</text>
<rect x="1110.0" y="2002" width="200" height="26" rx="13" fill="#15803d" opacity=".13"/>
<text x="1210.0" y="2020" text-anchor="middle" font-size="12" font-weight="800" fill="#15803d">WORKING</text>
</svg>

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -158,7 +158,7 @@ the IMB / peoplegroups.org dataset (never a hand-kept roster). Aliases name them
## Open points found while writing this down
Each is a defect or a decision, routed through dx-shepherd.
Each is a defect or a decision, still open.
1. **`profile-config.tokenUrl` has no reader.** It is written by the studio and the gallery and
read by nothing. The reader's own URL is also never forwarded as `tokenUrl`, only as the token

View file

@ -356,8 +356,6 @@ The measurements behind the rules above, kept for whoever needs the reasoning.
- **Undocumented gates (2026-09-22).** 26 gates lived outside the build and only nine were listed; two
unlisted ones had been failing for two sessions. A standalone gate nobody knows about does not run —
which is why the table above is the list to run before publishing.
- **`check:dxlife`.** It exits 2 ("clone not found") on any machine without a sibling DX-life clone — a
deliberate third value that `bun run` reports as a failure. It is not in the build, so a clone still builds.
- **Screenshot distinctness.** Two README slots once showed the same view under two captions: the shot
chased a "Next" control that did not exist inside a `try/catch`, and the PNGs differed by 0.16% render
noise, so no checksum caught them.

View file

@ -194,7 +194,7 @@ export function backendDevPlugin(root, env = process.env) {
name: 'dx-backend-dev',
apply: 'serve',
configureServer(server) {
// ENGINE NOT RUNNING, QUIETLY (dx-shepherd, R500): with no backend process the proxy answered
// ENGINE NOT RUNNING, QUIETLY (R500): with no backend process the proxy answered
// 500 ECONNREFUSED, which every page showed as console errors. While a backend is down its routes get
// well-formed answers instead: /events streams ONE status event saying the engine is offline (the
// plugin renders its own "engine not running" state from it), anything else returns an empty JSON

View file

@ -168,7 +168,7 @@ done
# Everything busy: keep POLLING EVERY SLOT until the deadline, and report a timeout as UNMEASURED
# rather than as a verdict. This used to queue on slot 1 alone (flock -w on slot 1), so a slot that
# freed up was never taken: measured 2026-09-25 (dx-shepherd), ten waiters were piled on slot 1 while
# freed up was never taken: measured 2026-09-25, ten waiters were piled on slot 1 while
# slot 2 sat free, and with slot 1 held 12 s and slot 2 freed at ~3 s a caller waited 11.5 s — the
# two-slot semaphore had collapsed into ONE queue. Polling takes the first slot that frees (~4 s in
# the same test). Trade-off, accepted: polling is not first-come-first-served, so under heavy

View file

@ -30,7 +30,7 @@ check('a GeoJSON feature is matched to rows on the same point', colocatedWith(id
check('a lone point shares with nobody', colocatedWith(idx, rows[3]).length === 0, colocatedWith(idx, rows[3]))
check('a row without a position is ignored', colocatedWith(idx, rows[4]).length === 0, colocatedWith(idx, rows[4]))
// A click hands over the RENDERED feature: the engine rebuilt its geometry from tile coordinates, so it is
// near the roster point but not equal to it: ~0.001 degree at low zoom (rega, row 46, a shared point that never matched).
// near the roster point but not equal to it: ~0.001 degree at low zoom (a browser check, row 46: a shared point that never matched).
const rendered = { type: 'Feature', properties: { name: 'Koraga' }, geometry: { type: 'Point', coordinates: [75.2486, 12.8431] } }
check('a rendered (tile-rounded) feature still finds its co-located groups', colocatedWith(idx, rendered).sort().join() === 'Godda,Maila', colocatedWith(idx, rendered))
// The same people in two countries: the nearest roster row wins, not the first.
@ -39,7 +39,7 @@ const renderedFar = { type: 'Feature', properties: { name: 'Koraga' }, geometry:
check('a repeated name resolves to the nearest roster point', colocatedWith(twin, renderedFar).join() === 'Other', colocatedWith(twin, renderedFar))
// The rows a map DRAWS are normalized people groups (name + latitude/longitude, no geometry), and every
// map built on useMapData must offer them to its legends: the prayer map did not, so its legends read the
// raw API cache and neither the shared-point sentence nor the standing note appeared (rega, row 46 at 835ec1ac).
// raw API cache and neither the shared-point sentence nor the standing note appeared (a browser check, row 46 at 835ec1ac).
const drawn = [{ name: 'A', latitude: 12.85, longitude: 75.25 }, { name: 'B', latitude: '12.85', longitude: '75.25' }]
const clickedA = { type: 'Feature', properties: { name: 'A' }, geometry: { type: 'Point', coordinates: [75.2504, 12.8497] } }
check('normalized rows (no geometry) index and match a clicked pin', colocatedWith(buildColocationIndex(drawn), clickedA).join() === 'B', colocatedWith(buildColocationIndex(drawn), clickedA))

View file

@ -1,101 +0,0 @@
#!/usr/bin/env node
/**
* check-backlog-refs.mjs — a card may not point at a card that does not exist.
*
* THE DEFECT THIS CATCHES. `depends_on` is the board's readiness mechanism: a card is ready when
* its dependencies are done. A dependency naming a card that is NOT IN THE BACKLOG can never be
* done, so the card can never become ready — and nothing says so. It reads as ordinary blocked
* work forever.
*
* MEASURED 2026-09-24, three dangling references across the builder backlog:
* IDEA-SLIM-MAP-SEARCHBAR-HOW-DO-FILTERS-FIT <- BUILD-PEOPLE-GROUP-SEARCH-…depends_on
* IDEA-REGIONS-DASHBOARD-REVERSE-ENGINEER-DXLIFE <- two regions cards' depends_on
* PRINCIPLE-MAPS-ARE-SECONDARY-LISTS-AND-GRIDS-ARE-PRIMARY <- two cards' parent
* The first one matters most: that card is status:todo and waiting on something unfindable.
* The last is cited by the loop prompt itself as an open card ("PRINCIPLE-MA"), so a reader is
* being pointed at work that has no record.
*
* ★ SEVERITY IS NOT UNIFORM, so this gate grades it. A dangling `depends_on` on an OPEN card is a
* FAILURE — that card is unreachable. A dangling `parent`, or a dangling anything on a card that is
* already done, is REPORTED but does not fail: losing a parent is a filing problem, not a blocked
* one, and failing on it would only teach people to delete the reference rather than restore the
* card. Naming the difference is the point.
*
* ⚠ IT ONLY CHECKS REFERENCES THAT LOOK LIKE CARD IDS. Fields like `parent` also legitimately hold
* prose in some cards ("the regions epic"), and treating those as broken links would drown the
* real ones. The shape is SCREAMING-KEBAB, 9+ chars — the convention every id in this board uses.
*
* Usage: node internal/check-backlog-refs.mjs
* SKIPs when _qa-cycle is absent (it lives outside every repo, by design).
*/
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const QA = process.env.QA_CYCLE || resolve(HERE, '..', '..', '_qa-cycle')
if (!existsSync(QA)) {
console.log('BACKLOG-REFS: SKIP — _qa-cycle not present (expected in CI; it is in no repo).')
console.log(' This gate reads the AGENTIC BACKLOGS, which live outside the repo and are not')
console.log(' cloned with it. Absence is expected; it is NOT a pass.')
process.exit(0)
}
const PHASES = ['01-ideation-research', '02-builder-cycle', '03-deployment-cycle']
const REF_FIELDS = ['parent', 'depends_on', 'blocks', 'supersedes', 'supersedes_mechanism_of']
// The id convention every card in this board follows. Prose in a `parent` field must NOT match.
const ID_SHAPE = /^[A-Z][A-Z0-9-]{8,}$/
const cards = []
for (const phase of PHASES) {
const p = join(QA, phase, 'backlog.json')
if (!existsSync(p)) continue
const j = JSON.parse(readFileSync(p, 'utf8'))
for (const c of j.cards || []) cards.push({ ...c, _phase: phase })
}
if (!cards.length) {
console.log('BACKLOG-REFS: SKIP — no cards found in any phase backlog.')
process.exit(0)
}
const ids = new Set(cards.map((c) => c.id))
const OPEN = new Set(['todo', 'doing', 'in-progress', 'blocked'])
const blocking = [] // dangling depends_on on an OPEN card — the card can never become ready
const noted = [] // everything else dangling — filing debt, reported not failed
for (const c of cards) {
const f = c.fields || {}
for (const key of REF_FIELDS) {
const raw = f[key] !== undefined ? f[key] : c[key]
if (!raw) continue
for (const ref of (Array.isArray(raw) ? raw : [raw])) {
if (typeof ref !== 'string' || !ID_SHAPE.test(ref)) continue
if (ids.has(ref)) continue
const entry = { from: c.id, key, ref, status: c.status || '(none)' }
if (key === 'depends_on' && OPEN.has(String(c.status || '').toLowerCase())) blocking.push(entry)
else noted.push(entry)
}
}
}
console.log(`BACKLOG-REFS: ${cards.length} card(s) across ${PHASES.length} backlog(s), `
+ `${REF_FIELDS.length} reference field(s) checked.`)
if (noted.length) {
console.log(` ${noted.length} dangling reference(s) that do NOT block anything:`)
for (const n of noted) console.log(` ${n.from} [${n.status}] .${n.key} -> ${n.ref}`)
console.log(' Filing debt: a lost parent or a stale link on a finished card. Restore the card')
console.log(' or drop the reference — but do not delete it just to quiet this line.')
}
if (blocking.length) {
console.error(`\nBACKLOG-REFS: FAIL — ${blocking.length} OPEN card(s) depend on a card that does not exist.`)
for (const b of blocking) {
console.error(` ✗ ${b.from} [${b.status}] depends_on ${b.ref}`)
}
console.error('\n A dependency that is not in the backlog can never be done, so these cards can')
console.error(' never become ready — and nothing says so. They read as ordinary blocked work.')
console.error(' Fix: restore the missing card, or remove the dependency if it is obsolete.')
process.exit(1)
}
console.log('BACKLOG-REFS: PASS — every open card\'s dependencies name a card that exists.')

View file

@ -1,121 +0,0 @@
#!/usr/bin/env node
/**
* check-backlog-status-enum.mjs — a card's `status` must be a machine-readable enum, not prose.
*
* THE DEFECT THIS CATCHES, and it was MY OWN reporting error for an entire flight. Cards were being
* written with explanatory statuses:
*
* "done — reconciled: big-picture + dashboard-plus-grid are current, unified-research-dashboard…"
* "blocked — Steps 1+3 DONE and re-verified 2026-09-22 at 19/19 bundles…"
* "ready"
*
* The prose is genuinely valuable — several of those strings carry measurements — but it made
* `status` unqueryable. Every count I reported used `status !== 'done'`, which is FALSE for
* `"done — reconciled: …"`, so finished cards kept being counted as open.
*
* MEASURED 2026-09-22 across the three phase backlogs: 130 of 395 cards carried a prose status, and
* 71 of those were ALREADY DONE. Reported open was 176; the real number was 105. The P0 figure I had
* been quoting every tick was 21; it was actually 14.
*
* ★ WHY THIS IS WORSE THAN AN ORDINARY OFF-BY-N. A backlog is a decision instrument — it answers
* "what is left" and "is the remaining work human-gated". Inflating the open count makes a drained
* board look stalled, which pushes toward manufacturing work rather than holding honestly. And the
* error is self-concealing: every individual card reads correctly to a human, because the prose says
* exactly what is true. Only the AGGREGATE is wrong.
*
* THE FIX PRESERVES THE PROSE. Migration moved it to `fields.status_detail` and normalised `status`
* to the enum; 130/130 preserved, 0 lost. The explanation was never the problem — putting it in the
* field that tooling reads was.
*
* WHAT IT ASSERTS: every card's `status` is ONE WORD from a closed set — the five workflow
* states plus four terminal ones (superseded / deferred / decided / verified-already-true).
* The point is not the particular words: it is that `status` stays machine-readable so the
* open-count is right. Prose belongs in fields.status_detail.
*
* It deliberately does NOT require `status_detail` to exist. Most cards need no explanation, and
* demanding one would turn a real check into paperwork that gets satisfied with filler.
*
* NEGATIVE-TESTED: setting one card's status back to "done — reconciled: …" fails by name.
*/
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const QA = join(ROOT, '../_qa-cycle')
const PHASES = ['01-ideation-research', '02-builder-cycle', '03-deployment-cycle']
// ★ THE ENUM WAS TOO NARROW, not the cards wrong. MEASURED 2026-09-24 across 633 cards:
// EIGHT distinct status values are in use and EVERY ONE IS A SINGLE WORD — there is no
// prose left anywhere in the field. The nine this gate was failing carried legitimate
// TERMINAL states the original enum simply predated: superseded (5), deferred (2),
// decided, verified-already-true.
//
// This gate exists to keep the AGGREGATE honest — "a prose status makes every open-count
// wrong while each card still READS correctly". A one-word terminal state serves that
// purpose perfectly; it is exactly what the 130-card migration was FOR. Rewriting nine
// accurate statuses to 'done' would have destroyed real distinctions (a SUPERSEDED card
// was replaced by another; a DEFERRED one was consciously postponed; neither was
// finished) to satisfy a list — losing information to make a checker green.
//
// OPEN vs CLOSED is the property that matters, and all four additions are CLOSED:
// open = todo + doing + in-progress (66), closed = done + these four (507), blocked (60).
const TERMINAL_EXTRA = ['superseded', 'deferred', 'decided', 'verified-already-true']
const ALLOWED = new Set(['todo', 'doing', 'in-progress', 'blocked', 'done', ...TERMINAL_EXTRA])
if (!existsSync(QA)) {
// _qa-cycle lives outside every repo the CI clones, so absence is EXPECTED there. Say so loudly
// rather than passing silently — a gate that reports success when it never ran is the failure
// mode this repo has already been bitten by.
console.log('BACKLOG-STATUS: SKIP — _qa-cycle not present (expected in CI; it is in no repo).')
process.exit(0)
}
const offenders = []
let total = 0
const counts = {}
for (const ph of PHASES) {
const p = join(QA, ph, 'backlog.json')
if (!existsSync(p)) continue
let cards
try {
cards = JSON.parse(readFileSync(p, 'utf8')).cards || []
} catch (e) {
console.error(`BACKLOG-STATUS: FAIL — ${ph}/backlog.json does not parse: ${e.message}`)
process.exit(1)
}
for (const c of cards) {
total++
const s = String(c.status)
counts[s] = (counts[s] || 0) + 1
if (!ALLOWED.has(s)) offenders.push({ ph, id: c.id, s })
}
}
if (!total) {
console.error('BACKLOG-STATUS: FAIL — found 0 cards across three phase backlogs.')
console.error(' A gate with nothing to assert is not passing; it is not running.')
process.exit(1)
}
if (offenders.length) {
console.error(`BACKLOG-STATUS: FAIL — ${offenders.length} of ${total} card(s) carry a non-enum status.\n`)
for (const o of offenders.slice(0, 12)) {
console.error(` ${o.ph} ${o.id}`)
console.error(` status: "${o.s.slice(0, 84)}${o.s.length > 84 ? '…' : ''}"`)
}
if (offenders.length > 12) console.error(` …and ${offenders.length - 12} more`)
console.error('\n Allowed: ' + [...ALLOWED].join(' / '))
console.error(' Keep the explanation — move it to fields.status_detail. The prose is valuable and')
console.error(' often carries measurements; it just must not live in the field tooling filters on.')
console.error(' A prose status makes every open-count wrong while each card still READS correctly,')
console.error(' so the error hides in the aggregate: 130 of 395 cards once carried one, 71 of them')
console.error(' already done, and the reported open count was 176 against a real 105.')
process.exit(1)
}
console.log(`BACKLOG-STATUS: PASS — ${total} card(s), every status is a queryable enum.`)
const order = ['todo', 'doing', 'in-progress', 'blocked', 'done']
console.log(' ' + order.filter((k) => counts[k]).map((k) => `${k} ${counts[k]}`).join(' · '))
console.log(' Explanations live in fields.status_detail, which is not required — most cards need')
console.log(' none, and demanding one would turn this into paperwork satisfied with filler.')

View file

@ -86,6 +86,32 @@ ok(dry(formatClipboard(arabic, { color: '#0072b2' })) !== 'ok', 'NEGATIVE CONTRO
const indiaText = X.selectionClipboard([{ key: 'adm1:Mahārāshtra', name: 'Mahārāshtra', count: 54, kind: 'adm1' }, { key: 'adm1:Goa', name: 'Goa', count: 5, kind: 'adm1' }], {})
ok(dry(indiaText) === 'ok', `apply-grouping's dry run refuses an India state selection: ${dry(indiaText)}`, 'an India state selection resolves too (shape names are canonical)')
// ── A3. the client's regroupable() agrees with apply-grouping's own verdict ─────────────────────────────
// Copy warns when its first line will not regroup (a mix of kinds, or a legend zone). The warning is only honest if
// regroupable() says exactly what the tool's dry run says, so each case is run through BOTH.
const T = (key, name, count, kind, country) => ({ key, name, count, kind, ...(country ? { country } : {}) })
const REGROUP_CASES = {
'India states': [T('adm1:Mahārāshtra', 'Mahārāshtra', 54, 'adm1', 'IND'), T('adm1:Goa', 'Goa', 5, 'adm1', 'IND')],
'a China province': [T('adm1:Gansu Province', 'Gansu Province', 3, 'adm1', 'CHN')],
'countries': [T('adm0:NPL', 'Nepal', 34, 'adm0'), T('adm0:BTN', 'Bhutan', 13, 'adm0')],
'a state + a country': [T('adm1:Goa', 'Goa', 5, 'adm1', 'IND'), T('adm0:NPL', 'Nepal', 34, 'adm0')],
'India + China': [T('adm1:Goa', 'Goa', 5, 'adm1', 'IND'), T('adm1:Gansu Province', 'Gansu Province', 3, 'adm1', 'CHN')],
'a legend zone': [T('zone:test', 'Test Zone', 12, 'zone')],
}
const disagree = []
for (const [name, sel] of Object.entries(REGROUP_CASES)) {
const tool = dry(X.selectionClipboard(sel, {})) === 'ok'
if (X.regroupable(sel) !== tool) disagree.push(`${name}: regroupable ${X.regroupable(sel)}, apply-grouping ${tool ? 'accepts' : 'refuses'}`)
}
// …and the COUNTRY an ADM1 territory needs for that verdict is always known: from the feature's own props on a
// map tap, else from the baked table (a props-only toggle once left it null, which read as a false warning).
const { resolveTerritory, ADM1_COUNTS, ADM1_COUNTRY } = await import(pathToFileURL(P('plugin-designs/my-upg-100-list/data/territory-counts.js')).href)
const noCountry = Object.keys(ADM1_COUNTS).filter((n) => !['IND', 'CHN'].includes(ADM1_COUNTRY[n]))
ok(!noCountry.length && resolveTerritory({ shapeName: 'Mahārāshtra' }).country === 'IND' && resolveTerritory({ shapeName: 'Guizhou Province' }).country === 'CHN',
`an ADM1 territory without a known country: ${noCountry.slice(0, 5).join(', ') || 'a props-only toggle'}`, 'every ADM1 name resolves its country, even from a props-only toggle')
ok(!disagree.length, `Copy's regroup warning disagrees with apply-grouping: ${disagree.join('; ')}`,
`the regroup warning matches apply-grouping's own verdict in all ${Object.keys(REGROUP_CASES).length} cases (India, China, countries, and the three it refuses)`)
// ── B. the CSV ───────────────────────────────────────────────────────────────────────────────────
function parseCsv(s) { // RFC 4180: quoted fields, doubled quotes, CRLF records
const rows = []; let row = [], cell = '', q = false
@ -124,6 +150,17 @@ const wired = {
'the prefetch on batch mode': /if \(next\) selectionRecordsLoad\(\)/.test(vue),
'one detection for every action': (vue.match(/await selectionPeople\(\)/g) || []).length >= 2 && /selectionPeople\(\)\.then/.test(vue),
// India's states resolve through the served table (library/data/indiaStates.js), fetched only once a state is selected
// In batch mode a tap on a pin opens NO detail sheet (it covered the whole HUD on a phone); the tapestry
// listener under it toggles the territory. The real-tap proof is a browser check; this pins the guard in the source.
'a pin tap in batch mode opens no sheet': /map\.on\('click', 'upg-pins-hit', \(e\) => \{[\s\S]{0,700}?if \(batchMode\.value\) return\s*\n\s*const f = e && e\.features/.test(vue),
// …and that tap toggles the territory EXACTLY ONCE: the tapestry listener already does it, so the pin handler
// must not toggle a second time (select-then-deselect in one tap).
'the pin handler adds no second toggle': (() => {
const at = vue.indexOf("map.on('click', 'upg-pins-hit', (e) => {"); if (at < 0) return false
const body = vue.slice(at, vue.indexOf('\n })', at))
return !/toggleTerritory|toggleByProps|toggleZoneSelection/.test(body)
})(),
'the regroup warning on Copy': /const warn = regroupable\(sel\) \? '' : t\('upgMap\.copyNotRegroupable'\)/.test(vue),
'India states detected from the served table': /import \{ loadIndiaStates \} from '@map\/data\/indiaStates\.js'/.test(vue)
&& /india\.byShapeName\.get\(t\.name\)/.test(vue) && /t\.kind === 'adm1'\) indiaStates\(\)\.catch/.test(vue),
}
@ -133,7 +170,7 @@ ok(!missingWires.length, `the HUD is missing: ${missingWires.join(', ')}`, 'the
// ── D. the strings, ×11 ──────────────────────────────────────────────────────────────────────────
const LANGS = ['en', 'es', 'fr', 'pt', 'de', 'it', 'ro', 'ru', 'ar', 'hi', 'zh']
const KEYS = {
upgMap: ['copySelection', 'exportCsv', 'selectionColourGroup', 'selectionColour', 'customColour', 'copiedSelection', 'copiedTerritoriesOnly', 'csvSaved', 'csvFailed'] }
upgMap: ['copySelection', 'exportCsv', 'copyNotRegroupable', 'selectionColourGroup', 'selectionColour', 'customColour', 'copiedSelection', 'copiedTerritoriesOnly', 'csvSaved', 'csvFailed'] }
const cat = (l, f) => JSON.parse(readFileSync(P('library/i18n/locales', l, `${f}.json`), 'utf8'))
const bad = []
for (const [f, keys] of Object.entries(KEYS)) {

View file

@ -111,8 +111,8 @@ if (!targets.length) {
// The token to hand the partner page is the one the tree already bakes into its own leaves: this
// gate must not invent a key, and a token that is not this tree's would 401 and look like our bug.
// The tree bakes no token since the auto engine (the repo ships none), so every tk case SKIPPED and every
// provider case went UNMEASURED: a hollow PASS (rega, ded4b3c1). A seat that has a real public token passes
// it as TEST_MAPBOX_TOKEN (as rega's checks do); otherwise a baked one is still used if a tree has it.
// provider case went UNMEASURED: a hollow PASS (measured at ded4b3c1). A seat that has a real public token passes
// it as TEST_MAPBOX_TOKEN (as the browser checks do); otherwise a baked one is still used if a tree has it.
const bakedToken = process.env.TEST_MAPBOX_TOKEN || (() => {
for (const b of manifest.bundles || []) {
for (const p of b.profiles || []) {
@ -170,10 +170,10 @@ try {
lines.push(` · ${t.element} [${c.knob}] SKIPPED — no baked token in this tree to hand the host`)
continue
}
// The composite's OWN profile rides with the knob under test (rega, 1a761741: gospel-resources,
// The composite's OWN profile rides with the knob under test (measured at 1a761741: gospel-resources,
// my-upg-100-list, regions-dashboard and two more drew no map under a knob-only config, because a
// multi-profile element needs to be told which profile is the composite). A knob still wins.
// A composite that opens on a non-map view must be told to show its map (exp-shepherd: country-posters-mfe
// A composite that opens on a non-map view must be told to show its map (country-posters-mfe
// reads profile-config `view`), or there is no canvas to measure.
const VIEW = { 'country-posters-mfe': { view: 'map' } }
const cfg = JSON.stringify({ profile: t.profile, ...(VIEW[t.element] || {}), ...c.config(bakedToken) }).replace(/'/g, '&#39;')

View file

@ -11,7 +11,7 @@
*
* ★ WHY IT NOW REBUILDS AND COMPARES BYTES, NOT MTIMES (2026-09-25). The first version compared
* the newest child SOURCE mtime with the parent BUNDLE mtime. File times are not build facts:
* a git merge or checkout rewrites sources with checkout-time mtimes, so rega's publish of
* a git merge or checkout rewrites sources with checkout-time mtimes, so a publish of
* beb13da3 stopped on "doxa-maps-spa / plugin-studio / stack-demo STALE vs gospel-resources /
* research-dashboard" although a rebuild reproduced the committed bytes exactly — and two of
* those parents mount their children BY TAG at runtime, so they compile none of the child in
@ -61,7 +61,7 @@ const TREE = join(ROOT, 'public', TREE_NAME)
const argv = process.argv.slice(2)
const oi = argv.indexOf('--only')
const ONLY = oi >= 0 && argv[oi + 1] ? new Set(argv[oi + 1].split(',')) : null
// NOT ONLY COMPOSITES (rega, 2026-09-25): a library change merged into main reached prayer-walk-planner,
// NOT ONLY COMPOSITES (measured 2026-09-25): a library change merged into main reached prayer-walk-planner,
// which composes nothing, so this gate could not see it was stale. --all checks EVERY bundle; --bundles a,b
// checks those (the landing precondition passes the ones a landing can have changed).
const ALL = argv.includes('--all')

View file

@ -26,7 +26,7 @@
*
* NOT A SUBSTITUTE FOR A BROWSER. Both ends existing is necessary, not sufficient: UPG-100's
* select-zone listener exists and still loses the first click, because the lazy Map pane attaches it
* too late (rega, 2026-09-26). The runtime half is check:gallery-embed's `linked` phase.
* too late (measured 2026-09-26). The runtime half is check:gallery-embed's `linked` phase.
*
* NEGATIVE-TESTED (2026-09-26, main 521f8b18, PASS with 17 links and the 3 LATE_KNOWN sites), each FAILS:
* a receiver renamed in source ('map-filtered' -> 'map-filter' in research-map-plus-grid.vue);

View file

@ -19,7 +19,7 @@
* `tk` to a baked-in child, at production stage. See the comment at the scan below.
*
* Source-level and headless BY DESIGN: the behavioural twin (check:composite-embed-knobs) needs a
* browser and only rega may run one. Exit 0 pass · 1 fail.
* browser, and this gate runs without one. Exit 0 pass · 1 fail.
*/
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'

View file

@ -10,7 +10,7 @@
* and Simple Research, both in the Research Gallery). Every structural gate passed. This gate runs
* the thing that failed: the composite's own setup, and its mounted hooks.
*
* HOW. No browser (fleet policy: only rega launches one). Vite loads each composite .vue through
* HOW. No browser (by design: this gate runs headless). Vite loads each composite .vue through
* ssrLoadModule with the repo's alias and the same vue({ customElement: true }) plugin, and
* @vue/runtime-core mounts it on a no-op renderer. The SSR compile gives the component an
* ssrRender rather than a render, so the renderer draws nothing and no CHILD is set up: this

View file

@ -1,149 +0,0 @@
#!/usr/bin/env node
/**
* check-done-evidence.mjs — a card may not reach status:done with NO evidence.
*
* WHY (2026-09-21)
* Two quality cards were status:done with an EMPTY bluf. Both turned out genuinely done — but
* only because I opened a browser and read the source. A board-wide count then found 18 done
* cards carrying NEITHER a bluf NOR any fields: a status word and nothing else.
*
* ★ THE HAND-OFF IS THE POINT. The Doxa team will read this board to learn what exists. A card
* reading "done" with no evidence cannot be reconciled against the code at all — they cannot
* tell a shipped deliverable from an abandoned one someone closed. This flight found SIX cards
* whose stated status disagreed with reality; an evidence-less card is the unfalsifiable case.
*
* WHAT COUNTS AS EVIDENCE (deliberately generous — this is a floor, not a style guide)
* EITHER a non-empty `bluf` OR a non-empty `fields` object. Both are free to write at the moment
* the work is fresh, which is the only moment the evidence is cheap and accurate.
*
* BASELINE, NOT A CLIFF
* The 10 pre-existing offenders are allow-listed by id. A gate that fails on day one for history
* nobody can fix gets disabled, and then it protects nothing. This fails ONLY on a NEW one — the
* same shape as check-label-joins, which allow-lists two known occurrences.
* ★ TO CLEAR AN ENTRY: add real evidence to the card, then delete its id here. The list should
* only ever shrink; a PR that grows it is doing the thing this gate exists to stop.
*
* NEGATIVE-TESTED: adding a fresh done card with no bluf/fields makes this FAIL naming it,
* EXIT=1; removing it returns PASS.
*/
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
// ★ DEPTH 2, NOT 1 — and that is deliberate but load-bearing. Every sibling gate in internal/
// resolves ROOT one level up (the repo). This one goes TWO levels up, because the backlogs it
// checks live in `_qa-cycle/`, which is AGENTIC SCRATCH OUTSIDE THE REPO — tracked by no git
// repository, and never cloned with the studio.
//
// THE HAND-OFF DEFECT THIS GUARD FIXES (measured 2026-09-22). On this machine _qa-cycle happens
// to sit beside the repo, so the gate passes. An inheritor who clones ONLY the studio has no
// sibling _qa-cycle — and the gate did NOT fail there. It printed
// "0 card(s) across 3 backlog(s), 0 done" ... "PASS"
// and exited 0. A VACUOUSLY GREEN gate is worse than no gate: it certifies nothing while looking
// healthy, which is exactly the failure shape this repo keeps re-learning.
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
const QA = join(ROOT, '_qa-cycle')
if (!existsSync(QA)) {
console.log('DONE-EVIDENCE: SKIP — no _qa-cycle/ beside this repo, so there are no backlogs to check.')
console.log(' This gate reads the AGENTIC BACKLOGS, which live outside the repo and are not cloned')
console.log(' with it. Absence is expected for anyone who checked out the studio on its own; it is')
console.log(' NOT a pass. Run it from a workspace that has _qa-cycle/ as a sibling of this repo.')
process.exit(0)
}
const BACKLOGS = [
'01-ideation-research/backlog.json',
'02-builder-cycle/backlog.json',
'03-deployment-cycle/backlog.json',
]
// Pre-existing, from before the rule. Verified 2026-09-21. SHRINK ONLY.
// ★ THE BASELINE IS NOW EMPTY, and that is the strongest state this gate can be in.
// All ten exemptions had since gained evidence — the gate reported every one of them
// as STALE on 2026-09-24 — so the list was pure dead weight: ten ids that exempted
// nothing while implying ten cards still owed a record.
// An EMPTY set is not an unset one: every done card must now carry evidence, with no
// exemptions, and the gate fails outright for a single card that does not. Keeping
// `{ ... }` full of satisfied ids would mean the same today and quietly permit a
// regression the moment someone "reused" an entry.
// If a genuinely un-reconstructable card ever needs an exemption, add its id here WITH
// the reason — and the gate will tell you the day it becomes stale.
const BASELINE = new Set([
])
let checked = 0, done = 0
const offenders = []
const staleBaseline = []
// What counts as evidence on a done card, by schema family:
// 1. `bluf` or a non-empty `fields` object (the original schema);
// 2. `one_line` or `verify` (the second schema; see the note in the loop below);
// 3. a non-empty `evidence` LIST with at least one non-blank entry. 44 cards record their proof
// this way, and 12 of them carry none of the other fields, so each false-failed on closing
// (DESIGN-TEAM-ROLES-MEMBER-DESIGNER-ADMIN did, with its proof sitting in the list).
function carriesEvidence(c) {
const str = (v) => String(v || '').trim().length > 0
if (str(c.bluf) || str(c.one_line) || str(c.verify)) return true
if (c.fields && Object.keys(c.fields).length > 0) return true
if (Array.isArray(c.evidence) && c.evidence.some((e) => str(typeof e === 'string' ? e : JSON.stringify(e ?? '')))) return true
return false
}
// Self-test, both sides: a non-empty list passes; an empty list, or one of blanks, still fails.
{
const ok = carriesEvidence({ status: 'done', evidence: ['check:x PASS 12/12'] })
const empty = carriesEvidence({ status: 'done', evidence: [] })
const blanks = carriesEvidence({ status: 'done', evidence: ['', ' '] })
if (!ok || empty || blanks) {
console.error('DONE-EVIDENCE: SELF-TEST FAILED — an evidence list is not judged by its content')
process.exit(1)
}
}
for (const rel of BACKLOGS) {
const p = join(QA, rel)
if (!existsSync(p)) continue
let cards
try { cards = JSON.parse(readFileSync(p, 'utf8')).cards || [] } catch { continue }
for (const c of cards) {
checked++
if (c.status !== 'done') continue
done++
// ★ THE BOARD USES TWO CARD SCHEMAS, and this gate only knew one. Older cards carry
// `bluf` + `fields`; a second family carries `one_line` (what was done), `why`, and
// `verify` (the artifact and how it was checked). MEASURED 2026-09-24 across 443 done
// cards: 40 carry `verify` and 41 carry `one_line`, and ALL THREE cards this gate was
// failing had one or both — with named probes, assertion counts and negative tests.
// They were among the best-evidenced cards on the board.
// `verify` is if anything the STRONGER signal: it names how the claim was checked,
// which is exactly what "evidence" means here. Demanding a redundant `bluf` alongside
// it would have taught the next agent to pad the field the checker reads rather than
// write the record — the gate training the behaviour it exists to prevent.
if (carriesEvidence(c)) {
// A baselined id that now HAS evidence is stale — report it so the list shrinks.
if (BASELINE.has(c.id)) staleBaseline.push(`${c.id} (${rel})`)
continue
}
if (BASELINE.has(c.id)) continue
offenders.push(`${c.id} (${rel})`)
}
}
console.log(`DONE-EVIDENCE: ${checked} card(s) across ${BACKLOGS.length} backlog(s), ${done} done, ` +
`${BASELINE.size} baselined.`)
for (const s of staleBaseline) {
console.log(` i BASELINE NOW STALE — ${s} has evidence; remove its id from check-done-evidence.mjs`)
}
if (offenders.length) {
console.error(`\nDONE-EVIDENCE: FAIL — ${offenders.length} card(s) are done with NO evidence in any field:`)
for (const o of offenders) console.error(` ✗ ${o}`)
console.error(
'\n Add ONE of: `bluf` or `one_line` saying what was done, `verify` naming how it was checked,\n' +
' a `fields` entry naming the artifact, or an `evidence` list. Any ONE is enough — the point is a\n' +
' record a reader can reconcile against the code, not a particular field name.\n' +
' it was verified. Write it now, while the work is fresh — reconstructing it later produces a\n' +
' plausible story indistinguishable from a real record.'
)
process.exit(1)
}
console.log('DONE-EVIDENCE: PASS — every done card carries evidence (bluf, one_line, verify, fields or an evidence list).')

View file

@ -1,101 +0,0 @@
#!/usr/bin/env node
/**
* check-dxlife-untouched.mjs — evidence for the ZERO DX-LIFE EDITS standing law.
*
* ★ WHY THIS IS NOT `git status` (a real false-pass, 2026-09-20)
* I verified the invariant for several ticks with:
* cd root-dx-life && git status --porcelain | wc -l # -> 0
* and reported that 0 as proof. root-dx-life IS NOT A GIT REPOSITORY — it is a
* plain directory clone. `git status` prints "fatal: not a git repository" to STDERR and nothing
* to stdout, so `wc -l` counts 0. THE CHECK RETURNED THE PASS VALUE *BECAUSE IT FAILED*.
* Identical in shape to the settled-tree churn bug (bfs rejected the argument, stderr discarded,
* wc -l counted an empty stream): A COMMAND'S ERROR PATH PRODUCING THE PASS VALUE THROUGH AN
* UNCHECKED PIPE. A safety invariant is the worst possible place for that.
*
* ★ WHAT THIS DOES INSTEAD — a VCS-INDEPENDENT WITNESS.
* Walks the clone and records (path, size, mtime) for every file into a baseline JSON. On later
* runs it DIFFS against that baseline and reports added / removed / modified. No VCS required,
* so it cannot be defeated by the tree not being a repo.
*
* FIRST RUN writes the baseline and says so plainly — it does NOT claim the tree is unmodified,
* because a baseline taken now can only prove "unchanged SINCE NOW". Honest framing matters more
* than a green line: this gate earns its value over time, not on its first invocation.
*
* USAGE
* node internal/check-dxlife-untouched.mjs # create or verify the baseline
* DXLIFE=/path/to/clone node internal/check-dxlife-untouched.mjs
* ★ WHAT IT WALKS: the DX Life CLONES, not every directory under the umbrella (2026-09-29). root-dx-life
* also holds git WORKTREES of doxa-life (wt-rg-*, today's research-gallery branches) and a DANGLING
* one (research-map-clean, whose .git points at a path that no longer exists). Walking them counted
* sanctioned work and generated output as "added" and held this gate red, while the clones themselves
* were untouched. A directory whose .git is a FILE beginning `gitdir:` is a worktree and is skipped,
* keyed on that file rather than on `git worktree list`, which does not show a dangling one. Skipped,
* never deleted: research-map-clean's parent repo is gone, so it may hold the only copy of its work.
* Generated dirs are skipped too, and the baseline is compared only WITHIN this scope, so narrowing
* the scope reads as no change rather than as thousands of removals.
*
* EXIT 0 unchanged (or baseline created) · 1 drift detected · 75 not measured (no clone on this machine)
*/
import { readdirSync, statSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
import { join, resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const CLONE = process.env.DXLIFE || resolve(HERE, '..', '..', '..', 'root-dx-life')
const STORE = resolve(HERE, '..', '..', '_qa-cycle', '_scratch-coordinator', 'dxlife-baseline.json')
if (!existsSync(CLONE)) {
// Not measured, not failed: a machine without the DX Life umbrella has nothing this law covers.
console.log(`DXLIFE-UNTOUCHED: NOT MEASURED — no DX Life clone at ${CLONE} on this machine.`)
process.exit(75)
}
const SKIP = new Set(['node_modules', '.git', '.output', '.nuxt', '.cache', 'dist'])
const isWorktree = (dir) => {
try { const g = join(dir, '.git'); return statSync(g).isFile() && readFileSync(g, 'utf8').startsWith('gitdir:') } catch { return false }
}
const worktrees = [] // relative paths skipped as worktrees, reported below
const snap = {}
;(function walk(dir) {
if (dir !== CLONE && isWorktree(dir)) { worktrees.push(dir.slice(CLONE.length + 1)); return }
let entries
try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return }
for (const e of entries) {
if (SKIP.has(e.name)) continue
const p = join(dir, e.name)
if (e.isDirectory()) { walk(p); continue }
try {
const st = statSync(p)
snap[p.slice(CLONE.length + 1)] = `${st.size}:${Math.round(st.mtimeMs)}`
} catch { /* unreadable file — skip rather than fail the invariant check */ }
}
})(CLONE)
const n = Object.keys(snap).length
if (!existsSync(STORE)) {
mkdirSync(dirname(STORE), { recursive: true })
writeFileSync(STORE, JSON.stringify({ created: new Date().toISOString(), clone: CLONE, files: snap }, null, 0))
console.log(`DXLIFE-UNTOUCHED: BASELINE CREATED — ${n} files recorded at ${CLONE}.`)
console.log(' This run proves NOTHING about the past; it establishes the reference point.')
console.log(' From here on, any add/remove/modify under that tree will be reported.')
process.exit(0)
}
// Compare within the walked scope: a baseline entry under a skipped worktree or generated dir is out of scope.
const inScope = (rel) => !worktrees.some((w) => rel === w || rel.startsWith(w + '/')) && !rel.split('/').some((seg) => SKIP.has(seg))
const base = Object.fromEntries(Object.entries(JSON.parse(readFileSync(STORE, 'utf8')).files).filter(([k]) => inScope(k)))
console.log(`DXLIFE-UNTOUCHED: skipped ${worktrees.length} worktree(s) (a .git FILE): ${worktrees.join(', ') || 'none'}`)
const added = Object.keys(snap).filter((k) => !(k in base))
const removed = Object.keys(base).filter((k) => !(k in snap))
const changed = Object.keys(snap).filter((k) => k in base && base[k] !== snap[k])
console.log(`DXLIFE-UNTOUCHED: ${n} files walked · +${added.length} -${removed.length} ~${changed.length}`)
if (added.length || removed.length || changed.length) {
console.error('DXLIFE-UNTOUCHED: FAIL — the read-only clone CHANGED:')
for (const f of added.slice(0, 5)) console.error(` ADDED ${f}`)
for (const f of removed.slice(0, 5)) console.error(` REMOVED ${f}`)
for (const f of changed.slice(0, 5)) console.error(` MODIFIED ${f}`)
console.error(' ZERO DX-LIFE EDITS is a standing law. Investigate before doing anything else.')
process.exit(1)
}
console.log('DXLIFE-UNTOUCHED: PASS — no file added, removed or modified since the baseline.')

View file

@ -12,7 +12,7 @@
*
* Nothing throws, because the t() shim falls back to its key. So it reads as "a few strings not
* translated yet" and it survived in country-posters, key-cities, gospel-resources, cities-internet and
* my-upg-100-list simultaneously — confirmed independently by regions-plugin, who censused all 19 entry
* my-upg-100-list simultaneously — confirmed independently by a census of all 19 entry
* files and found the same five and no sixth.
*
* ★ AND A CENSUS THAT COUNTED FUNCTION BODIES SAW FOUR OF SIXTEEN. That is why this gate greps for the

View file

@ -4,7 +4,7 @@
*
* THE DEFECT (2026-09-28, found testing live): the link copied from Simple Research was
* `research-map-plus-grid/research-map-plus-grid/index.html` while the page was
* `http://127.0.0.1:5192/#f=…` — "the perfect example of how embeddability is not working".
* `http://127.0.0.1:5192/#f=…`, a clear case of an embed that could not work.
*
* WHY IT SURVIVED SO LONG, and it is the reason this is a gate rather than a fix: a relative share link
* WORKS WHEN CLICKED. The browser resolves it against the page you copied it from, so the person testing

View file

@ -196,7 +196,7 @@ const run = async () => {
const f = __srcFeatures(m, 'rd-bubbles'); return !!(f && f.length > 0);
})()`, { timeout: 90000 })
} catch (e) {
// ★ DISTINGUISH "I CANNOT MEASURE THIS ENGINE" FROM "THE PRODUCT IS BROKEN".
// ★ DISTINGUISH "THIS ENGINE CANNOT BE MEASURED HERE" FROM "THE PRODUCT IS BROKEN".
// This gate reads the source's CURRENT GeoJSON off `_data`, a private Mapbox internal. MEASURED
// on one dashboard, one build, only the engine differing, by calling the app's own setData() to
// shrink 2107 features to 5:

View file

@ -38,7 +38,7 @@ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
import { join, relative, resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { TREE_NAME } from './_tree-name.mjs'
// ★ SHARED WITH check:hardcoded-english (regions-plugin). Two gates must agree about WHICH files are
// ★ SHARED WITH check:hardcoded-english (the same file list). Two gates must agree about WHICH files are
// in scope even when they disagree about what they find in them; a second copy would drift on exactly
// the pair-form / transitive / cycle details below, and the drift would be silent.
import { effectiveStages, stageReason } from './lib/effective-stage.mjs'
@ -73,7 +73,7 @@ function findFrozen(ast, aliases) {
if (name && (TRANSLATORS.has(name) || aliases.has(name))) {
// tStatic(path, EXPLICIT_LOCALE) — the caller pinned it deliberately.
const pinned = name === 'tStatic' && node.arguments.length > 1
// ★ tStatic IS NEVER LIVE, WHEREVER IT SITS (exp, 2026-09-28). It resolves the locale via
// ★ tStatic IS NEVER LIVE, WHEREVER IT SITS (2026-09-28). It resolves the locale via
// getRuntimeLocale(), which reads a PLAIN window property — not a Vue ref — so a computed()
// wrapping it has NO reactive dependency and never invalidates. computed(() => tStatic(x))
// is strictly WORSE than the naked call: it looks like the fix, so nobody looks again.
@ -153,7 +153,7 @@ const DEAD_LOCALE_GETTER = /\b(getRuntimeLocale|detectLocale|localeFromUrl)\s*\(
/**
* Is offset `at` inside the parentheses of a computed()/watch()/watchEffect() that tracks the locale?
*
* ⚠ BRACE DEPTH, NOT A LOOKBACK WINDOW (gospel, GOS-FROZEN-SR). The first version of this asked
* ⚠ BRACE DEPTH, NOT A LOOKBACK WINDOW (GOS-FROZEN-SR). The first version of this asked
* whether a wrapper appeared in the preceding 260 characters. gospel hit exactly this in GiDrawer.vue
* — the enclosing `computed(` opens at line 35 and the call sites are at 95-100 — and reported SIX
* false positives on working code, nearly "fixing" it. Measured on the same shape here: 2,600
@ -206,7 +206,7 @@ function findStoredTreeFreeze(code) {
// function RETURNS A STRUCTURE whose values come from the translator — an options object or a
// node list — which a caller then stores. So require the translator call to sit inside a
// returned object/array literal, not merely somewhere in the body.
// ★ THE MECHANISM IS "BUILT CORRECTLY, THEN PARKED", not "built once at setup" (gospel, on
// ★ THE MECHANISM IS "BUILT CORRECTLY, THEN PARKED", not "built once at setup" (found on
// upg100's legendOpts). That function re-reads t() correctly on EVERY call — its own comment
// says the function shape is deliberate — and the freeze is one level out: the RESULT is
// assigned into a ref and nothing re-fills it. A rule that only looked for a translator outside
@ -282,7 +282,7 @@ function findDeadGetterComputed(code) {
}
/**
* SHAPE 3 — labels evaluated inside a provide() literal (gospel, GOS-FROZEN-SR; real in GiCombos.vue).
* SHAPE 3 — labels evaluated inside a provide() literal (GOS-FROZEN-SR; real in GiCombos.vue).
*
* provide('dashboardConfig', { facets: { access_level: { label: T('…') } } })
*
@ -388,7 +388,7 @@ const CASES = [
['GOOD inside an arrow', () => scanSource("const { t } = useI18n()\nconst f = () => t('a.b')").length, 0],
['GOOD tStatic with explicit locale', () => scanSource("const x = tStatic('a.b', 'en')").length, 0],
['GOOD watch(() => t())', () => scanSource("const { t } = useI18n()\nwatch(src, () => t('a.b'))").length, 0],
// ★ exp's false negative: a computed cannot make tStatic live.
// ★ A known false negative: a computed cannot make tStatic live.
['BAD computed(() => tStatic()) is STILL frozen', () => scanSource("const X = computed(() => tStatic('a.b'))").length, 1],
// A function BODY re-reads the global on every call (useOptionalT:74 returns such an arrow),
// so it is NOT frozen. Only a reactive wrapper caches it. I had this case backwards at first.

View file

@ -171,7 +171,7 @@ try {
the data was right and the feature was dead. */
/* ⚠ THE NAME, CASE-FOLDED, ANYWHERE IN THE CARD. This asserted `text.includes(slug.split('-')[0])`
— "abai" against a rendered "Abai Sungai" — and reported a false negative on a card that was
rendering correctly, which cost rega a round. A slug fragment is not the name; the roster has
rendering correctly, which cost a review round. A slug fragment is not the name; the roster has
the name, so the roster is what it compares. */
const anyRow = (JSON.parse(readFileSync(resolve(PUBLIC, TREE_NAME, 'assets/tables/doxa-people-groups.json'), 'utf8')).rows || [])
.find((r) => r.slug && r.mapped)

View file

@ -2,7 +2,7 @@
* check-gospel-engine-contract.mjs — the map must use the engine it was ASKED for, or say nothing
* at all. Never a different one.
*
* THE DEFECT THIS IS WRITTEN AGAINST (regions' C3 read + upg100's addendum, 2026-09-26).
* THE DEFECT THIS IS WRITTEN AGAINST (the C3 review and its addendum, 2026-09-26).
* resources-map resolved its engine as `engineGlobal(provider) || window.mapboxgl`. engineGlobal
* already defaults to mapbox, so that fallback could only fire for provider 'maplibre' — and then
* it returned MAPBOX. A page that chose the keyless engine silently got the one that needs a

Some files were not shown because too many files have changed in this diff Show more