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, +}