From 3b6cfc69b50423d1335d317692bf96b4f30f14f6 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 02:24:08 +0000 Subject: [PATCH] A validated WAGF region palette, where the one-palette gate requires it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Groundwork for the no-selection chooser: for the seven regions to read AS regions the moment the map loads, pins have to be colored by region, and no existing strategy does that (_registry.js and religion.js both reference a doxa-region.js that is not in this tree). PLACEMENT IS NOT A PREFERENCE HERE. check:one-palette requires every hex a consumer paints with to come from a strategy module in library/colors/, discovered by _registry.js. That gate exists because a dashboard composable once declared PRIVATE palettes the map had never seen — two surfaces then disagreed about what a color meant, and one keyed color by ARRAY INDEX over a count-sorted list, so a religion CHANGED COLOR as you filtered. A lookup table inside my component is the exact shape that caused it. COLOR FOLLOWS THE ENTITY, NEVER ITS RANK: the seven slots are assigned in a fixed ALPHABETICAL order of the region's own value, deliberately not by size. Asia has 1309 people groups today and Oceania 24; ordering by count would mean a cross-filter that moves the counts repaints the survivors. Alphabetical is arbitrary but STABLE, which is the property that matters. THE PALETTE WAS VALIDATED, NOT EYEBALLED. Run through the dataviz validator and re-stepped twice until every check passed with NO warning: lightness band PASS · chroma floor PASS (an earlier teal #0e7490 FAILED at 0.094, reading gray) · CVD separation PASS at dE 8.6 deutan for the worst adjacent pair (6–8 is a floor band that would have been legal only with secondary encoding) · normal-vision floor PASS at dE 30.6 · contrast vs surface PASS, all seven >= 3:1 (an earlier amber #f59e0b WARNed at 2.09:1, and the guidance is explicit that a contrast warning is not dismissable). Two notes on running that validator, because both cost time. Copied to a new filename it silently does nothing and exits 0 — its CLI guard requires process.argv[1] to end in "validate_palette.js", so a rename turns it into a no-op that looks like a pass. And it is an ES module, so it needs a directory with {"type":"module"} rather than a bare `node file.js`. The hex ORDER in the file IS the validated order: the CVD check measures ADJACENT pairs, so the legend must render regions in that same fixed order for the result to keep holding. Noted in the file so a future reorder re-validates. --- library/colors/wagf-region.js | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 library/colors/wagf-region.js diff --git a/library/colors/wagf-region.js b/library/colors/wagf-region.js new file mode 100644 index 000000000..7e465f3ef --- /dev/null +++ b/library/colors/wagf-region.js @@ -0,0 +1,94 @@ +/** + * WAGF region color strategy — colors pins by the region they belong to. + * + * WHY THIS EXISTS. The regions element's no-selection state is a CHOOSER: the Driver's brief was + * "some type of map that would show the regions and countries and let you select one". For the + * seven regions to read AS regions the moment the map loads, the pins have to be colored by + * region — which is a categorical encoding no existing strategy provides. (`_registry.js` and + * `religion.js` both mention a `doxa-region.js`; it does not exist in this tree.) + * + * WHY IT LIVES IN library/colors/ AND NOT IN THE PLUGIN. Not a preference — check:one-palette + * requires it. That gate exists because a dashboard composable once declared PRIVATE palettes the + * map had never seen, and two surfaces then disagreed about what a color meant; worse, one keyed + * color by ARRAY INDEX over a count-sorted list, so a religion CHANGED COLOR as you filtered. + * Every hex a consumer paints with must come from a strategy module here, discovered by + * `_registry.js`. A lookup table inside the component is the exact shape that caused the bug. + * + * COLOR FOLLOWS THE ENTITY, NEVER ITS RANK. The seven slots below are assigned in a FIXED + * ALPHABETICAL ORDER of the region's own `value`, deliberately NOT in count order: Asia has 1309 + * people groups and Oceania 24 today, and if the palette were ordered by size then a cross-filter + * that changed the counts would repaint the survivors. Alphabetical is arbitrary but STABLE, which + * is the property that matters. + * + * THE PALETTE WAS VALIDATED, NOT CHOSEN BY EYE. Run through the dataviz validator + * (scripts/validate_palette.js, light mode, surface #fcfcfb) and re-stepped twice until every + * check passed with no warning: + * Lightness band PASS all 7 inside L 0.43–0.77 + * Chroma floor PASS all 7 >= 0.1 (an earlier teal #0e7490 FAILED at 0.094 — read gray) + * CVD separation PASS worst adjacent #dc2626 <-> #15803d dE 8.6 deutan (>= 8; 6–8 is a floor + * band that would have been legal only with secondary encoding) + * Normal vision PASS worst adjacent dE 30.6 + * Contrast PASS all 7 >= 3:1 (an earlier amber #f59e0b WARNed at 2.09:1) + * The order of the hexes below IS the validated order — the CVD check measures ADJACENT pairs, so + * a legend must render the regions in this same fixed order for that result to still hold. + * + * Field on the Mapbox feature: `region` (the wagf_region VALUE, e.g. 'latin_america_&_caribbean'). + */ + +export const PROPERTY_KEY = 'region' + +const DEFAULT_COLOR = '#6d766e' // DX-life --color-brand-lighter: an unknown region reads as chrome + +/** Region value → display label, as the prayer API labels them. Used by the legend. */ +export const WAGF_REGIONS = { + 'africa': 'Africa', + 'asia': 'Asia', + 'europe': 'Europe', + 'latin_america_&_caribbean': 'Latin America & Caribbean', + 'middle_east': 'Middle East', + 'north_america_&_non-spanish_caribbean': 'North America & Non-Spanish Caribbean', + 'oceania': 'Oceania', +} + +/** Region value → color, in the validated order. Do not reorder without re-running the validator. */ +export const PALETTE = { + 'africa': '#2563eb', + 'asia': '#d97706', + 'europe': '#7c3aed', + 'latin_america_&_caribbean': '#15803d', + 'middle_east': '#dc2626', + 'north_america_&_non-spanish_caribbean': '#0891b2', + 'oceania': '#be185d', +} + +/** The fixed render order — the same order the palette was validated in. */ +export const REGION_ORDER = Object.keys(PALETTE) + +/** Per-feature color resolver, for the JS layer. */ +export function getColor(properties) { + const v = properties?.region || properties?._raw?.wagf_region?.value || properties?._raw?.wagf_region + return PALETTE[v] || DEFAULT_COLOR +} + +/** Mapbox match expression colored by region value. */ +export function applyColor() { + const match = ['match', ['coalesce', ['get', 'region'], '']] + for (const key of REGION_ORDER) match.push(key, PALETTE[key]) + match.push(/* default */ DEFAULT_COLOR) + return match +} + +// Registry-level alias, mirroring religion.js / language-family.js. +export const buildColorExpression = applyColor + +export default { + name: 'WAGF Region', + propertyKey: PROPERTY_KEY, + palette: PALETTE, + regions: WAGF_REGIONS, + order: REGION_ORDER, + getColor, + applyColor, + buildColorExpression, + fallback: DEFAULT_COLOR, +}