Compare commits
2 commits
main
...
bundler-1/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
299c627c4f | ||
|
|
dcbfb63238 |
16 changed files with 47 additions and 476 deletions
|
|
@ -178,6 +178,10 @@ and a change here can break other maps.
|
|||
`import … from '@map/utils/embedBase.js'`
|
||||
- **geoUtils** — geoUtils.js — Geographic / GeoJSON Utility Functions
|
||||
`import … from '@map/utils/geoUtils.js'`
|
||||
- **html** — html.js — the two helpers every HTML string built in library/ must use (CRITIC-3, 2026-09-26).
|
||||
`import … from '@map/utils/html.js'`
|
||||
- **loadPinnedUmd** — loadPinnedUmd — load a PINNED, integrity-checked UMD library and capture it WITHOUT touching the host
|
||||
`import … from '@map/utils/loadPinnedUmd.js'`
|
||||
- **viewportScrim** — viewportScrim.js — make a position:fixed full-screen SCRIM actually cover the VIEWPORT,
|
||||
`import … from '@map/utils/viewportScrim.js'`
|
||||
|
||||
|
|
@ -192,6 +196,8 @@ and a change here can break other maps.
|
|||
|
||||
### Constants (never-change values)
|
||||
|
||||
- **cdn** — cdn.js — EVERY third-party script and stylesheet a plugin loads at runtime, pinned to an EXACT version
|
||||
`import … from '@map/constants/cdn.js'`
|
||||
- **mapDefaults** — mapDefaults.js — Default map initialization settings
|
||||
`import … from '@map/constants/mapDefaults.js'`
|
||||
- **posterDefaults** — posterDefaults.js — default margins, bleed, slot sizes, and typography
|
||||
|
|
|
|||
|
|
@ -42,6 +42,14 @@ export const AFFINITY_BLOCK_NAMES = {
|
|||
}
|
||||
|
||||
/** Primary mapping: ROP1 Code → Color */
|
||||
// ★ REJECTED CANDIDATE, RECORDED ABOVE THE PALETTE DELIBERATELY: #AF7AC5 was considered for A019
|
||||
// and scored 2.95 against the Material Light surface, just under WCAG 1.4.11's 3.0:1 non-text floor.
|
||||
// It lives here rather than beside A019 because check:one-palette slices from `export const PALETTE`
|
||||
// to END OF FILE and matches every hex it finds -- so a rejected colour mentioned BELOW the export
|
||||
// would be demanded of every consumer bundle, and the gate would fail on a colour we refused on
|
||||
// accessibility grounds. Keeping the note above the export preserves the reasoning without
|
||||
// creating that obligation. (The gate's own comment says it reads "only the PALETTE export"; its
|
||||
// implementation does not, which is reported separately.)
|
||||
export const PALETTE = {
|
||||
'A001': '#E74C3C', // Arab World - Red/Orange
|
||||
'A002': '#3498DB', // East Asian Peoples - Blue
|
||||
|
|
@ -68,7 +76,6 @@ export const PALETTE = {
|
|||
// A013 #8E44AD 5.28 (unchanged) A018 #6C3483 7.69 A019 #A569BD 3.53
|
||||
// Sibling separation so the three stay tellable apart: A018-vs-A019 2.61,
|
||||
// A013-vs-A018 1.46, A013-vs-A019 1.79.
|
||||
// REJECTED #AF7AC5 for A019 — scored 2.95, just under the 3.0 floor.
|
||||
'A018': '#6C3483', // Tibetan Peoples - deep purple (A013 family, darker)
|
||||
'A019': '#A569BD' // Himalayan Peoples - light purple (A013 family, lighter)
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* don tsee the you using htes colors on the reigosn dashbarod hwen you shoudl". Two surfaces
|
||||
* disagreeing about what Africa's colour means is a product defect whichever palette is prettier,
|
||||
* and he chose which one wins. Source of truth:
|
||||
* plugin-designs/doxa-research-map/src/colors/doxa-region.js, which the research dashboard and
|
||||
* library/colors/doxa-region.js (it lived at plugin-designs/doxa-research-map/src/colors/ until C1a), which the research dashboard and
|
||||
* three other consumers already paint from.
|
||||
*
|
||||
* WHAT THE CHANGE COST, measured rather than glossed (dataviz validate_palette.js, light mode,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { getColorStrategy, COLOR_MODES } from '../../colors/_registry.js'
|
|||
// (inside the builder, at call time) so registration has already run — a
|
||||
// module-load-time read could see an empty palette.
|
||||
const doxaRegionColors = () => getColorStrategy(COLOR_MODES.DOXA_REGION)?.palette ?? {}
|
||||
// resource is likewise research-only (plugin-designs/doxa-research-map/src/colors/
|
||||
// resource is likewise research-only, and now lives in library/colors/ (C1b) (was plugin-designs/doxa-research-map/src/colors/
|
||||
// resource.js), registry-resolved. Lazy + GUARDED on propertyKey — getColorStrategy
|
||||
// falls back to language-family for unregistered modes, and that palette must not
|
||||
// masquerade as resource colors.
|
||||
|
|
|
|||
|
|
@ -42,9 +42,12 @@ import { computed } from 'vue'
|
|||
// Single source of truth — pins paint with this same palette, keyed by ROP1 code.
|
||||
// Resolving the SAME strategy the pins use keeps legend ↔ map in lock-step (was a
|
||||
// separate name-keyed palette causing pin/legend drift). affinity-block is a per-map
|
||||
// (research-only) strategy that lives in plugin-designs/doxa-research-map/
|
||||
// src/colors/affinity-block.js and is resolved via the registry (registered by
|
||||
// the research bundle's local src/colors glob). Read LAZILY (at call time
|
||||
// strategy, which NOW LIVES IN library/colors/affinity-block.js (C1): it was private to
|
||||
// plugin-designs/doxa-research-map/ and duplicated in country-posters, where the stale copy
|
||||
// covered only A001-A017 and sent 134 Tibetan/Himalayan rows to the grey fallback.
|
||||
// It is resolved via the registry, which now discovers it from library/colors/ for EVERY
|
||||
// bundle rather than from one plugin's local glob -- that is the whole point of the move.
|
||||
// Read LAZILY (at call time
|
||||
// inside blocColor — this composable is only invoked by research surfaces) so
|
||||
// registration has already run, and GUARDED on propertyKey — getColorStrategy
|
||||
// silently falls back to language-family for unregistered modes, and that palette
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const COLUMNS_SIMPLE = [] // e.g. affinity-blocs — no numeric columns
|
|||
|
||||
// ─── Resource palette/names (research-only strategy) ──────────────────────────
|
||||
// resource is a per-map (research-only) strategy that lives in
|
||||
// plugin-designs/doxa-research-map/src/colors/resource.js and is resolved via
|
||||
// library/colors/resource.js (moved there in C1b) and is resolved via
|
||||
// the registry (registered by the research bundle's local src/colors glob).
|
||||
// Read LAZILY (inside the items computed, at call time) so registration has already
|
||||
// run, and GUARDED on propertyKey — getColorStrategy silently falls back to
|
||||
|
|
@ -42,7 +42,7 @@ function resourceStrategy() {
|
|||
|
||||
// ─── Region / Bloc palettes (registry strategies — single source of truth) ───
|
||||
// doxa-region and affinity-block are PER-MAP (research-only) strategies that
|
||||
// live in plugin-designs/doxa-research-map/src/colors/ and register via the
|
||||
// live in library/colors/ and are discovered by _registry.js for every bundle, not via a
|
||||
// research bundle's local src/colors glob. Read LAZILY (inside the items
|
||||
// computed, at call time) so registration has already run, and GUARDED on
|
||||
// propertyKey — getColorStrategy silently falls back to language-family when a
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { computed } from 'vue'
|
|||
// label-keyed lookup falls back to grey for every region except "Asia" (spelled
|
||||
// identically across locales) once the user switches language. The slug is
|
||||
// locale-independent. Mirrors SLUG_PALETTE in the research bundle's
|
||||
// src/colors/doxa-region.js.
|
||||
// library/colors/doxa-region.js (moved there in C1a).
|
||||
const REGION_COLORS = {
|
||||
'africa': '#ff2e2e',
|
||||
'asia': '#1a8cff',
|
||||
|
|
|
|||
|
|
@ -33,22 +33,14 @@ import { defineCustomElement } from 'vue'
|
|||
import { createPinia } from 'pinia'
|
||||
import ProfileLoader from '@map/ProfileLoader.vue'
|
||||
import { createAppI18n } from '@map/i18n/index.js'
|
||||
import { registerStrategies } from '@map/colors/_registry.js'
|
||||
|
||||
// ─── Bundle-private profile registry ─────────────────────────────────────────
|
||||
const profileModules = import.meta.glob('./*.vue')
|
||||
|
||||
// ─── Bundle-private color strategies ─────────────────────────────────────────
|
||||
// COPIED VERBATIM from doxa-research-map/src/colors/ — affinity-block.js,
|
||||
// doxa-region.js, resource.js. countries-map.vue is a clone of research-map.vue,
|
||||
// so it needs the same three research-only strategies; without doxaRegion in
|
||||
// particular the Countries tab would silently fall back to a different palette
|
||||
// (getColorStrategy() fails soft, so the symptom is wrong colours, not an error).
|
||||
//
|
||||
// Keys are auto-derived from filenames and merged OVER the shared library set.
|
||||
// This bundle inlines its own copy of the registry (IIFE build), so registering
|
||||
// here affects THIS bundle only.
|
||||
registerStrategies(import.meta.glob('./src/colors/*.js', { eager: true }))
|
||||
// ★ THE LOCAL COLOUR GLOB IS GONE (C1b). This bundle registered its own src/colors/*.js over the
|
||||
// shared set; all three modules (doxa-region, affinity-block, resource) now live in library/colors/
|
||||
// where _registry.js discovers them for every bundle, and src/colors/ holds no .js at all. The glob
|
||||
// matched nothing, so leaving it would be dead code that reads as a deliberate override.
|
||||
|
||||
// ─── Mapbox RTL text plugin — load ONCE before any map instance ──────────────
|
||||
if (typeof window !== 'undefined' && window.mapboxgl?.setRTLTextPlugin) {
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
/**
|
||||
* Affinity Block Color Strategy
|
||||
*
|
||||
* Colors pins based on their ROP1 code (A001, A002, etc.).
|
||||
* Uses ROP1 code as the primary key for consistency across data sources.
|
||||
*
|
||||
* ARCHITECTURE DECISION:
|
||||
* - Use ROP1 codes (A001, A002, etc.) as the PRIMARY KEY
|
||||
* - Store mappings from ROP1 → Name and ROP1 → Color
|
||||
* - Avoids issues with typos in affinity bloc names
|
||||
*/
|
||||
|
||||
export const PROPERTY_KEY = 'affinityBlock' // This is the ROP1 code
|
||||
|
||||
const DEFAULT_COLOR = '#95a5a6'
|
||||
|
||||
/** Primary mapping: ROP1 Code → Affinity Block Name */
|
||||
export const AFFINITY_BLOCK_NAMES = {
|
||||
'A001': 'Arab World',
|
||||
'A002': 'East Asian Peoples',
|
||||
'A003': 'Eurasian Peoples',
|
||||
'A004': 'Horn of Africa Peoples',
|
||||
'A005': 'Jewish',
|
||||
'A006': 'Malay Peoples',
|
||||
'A007': 'Persian-Median Peoples',
|
||||
'A008': 'South Asian Peoples',
|
||||
'A009': 'North American Peoples',
|
||||
'A010': 'Pacific Islanders',
|
||||
'A011': 'Southeast Asian Peoples',
|
||||
'A012': 'Sub-Saharan Peoples',
|
||||
'A013': 'Tibetan-Himalayan Peoples',
|
||||
'A014': 'Turkic Peoples',
|
||||
'A015': 'Latin-Caribbean Americans',
|
||||
'A017': 'Deaf'
|
||||
}
|
||||
|
||||
/** Primary mapping: ROP1 Code → Color */
|
||||
export const PALETTE = {
|
||||
'A001': '#E74C3C', // Arab World - Red/Orange
|
||||
'A002': '#3498DB', // East Asian Peoples - Blue
|
||||
'A003': '#2ECC71', // Eurasian Peoples - Green
|
||||
'A004': '#F39C12', // Horn of Africa Peoples - Orange
|
||||
'A005': '#9B59B6', // Jewish - Purple
|
||||
'A006': '#1ABC9C', // Malay Peoples - Teal
|
||||
'A007': '#E67E22', // Persian-Median Peoples - Dark orange
|
||||
'A008': '#34495E', // South Asian Peoples - Dark blue-grey
|
||||
'A009': '#95A5A6', // North American Peoples - Grey
|
||||
'A010': '#5DADE2', // Pacific Islanders - Light blue
|
||||
'A011': '#16A085', // Southeast Asian Peoples - Sea green
|
||||
'A012': '#C0392B', // Sub-Saharan Peoples - Deep red
|
||||
'A013': '#8E44AD', // Tibetan-Himalayan Peoples - Purple
|
||||
'A014': '#D35400', // Turkic Peoples - Burnt orange
|
||||
'A015': '#27AE60', // Latin-Caribbean Americans - Forest green
|
||||
'A017': '#000000' // Deaf - Black (universal accessibility)
|
||||
}
|
||||
|
||||
export const AFFINITY_BLOCK_COLORS = PALETTE
|
||||
|
||||
/** Reverse lookup: Name → ROP1 Code */
|
||||
export const AFFINITY_BLOCK_NAME_TO_CODE = Object.entries(AFFINITY_BLOCK_NAMES).reduce((acc, [code, name]) => {
|
||||
acc[name] = code
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
/** Legacy: Name-based color lookup (for backward compatibility) */
|
||||
export const AFFINITY_BLOCK_COLORS_BY_NAME = Object.entries(AFFINITY_BLOCK_NAMES).reduce((acc, [code, name]) => {
|
||||
acc[name] = AFFINITY_BLOCK_COLORS[code]
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
export const NAMES = AFFINITY_BLOCK_NAMES
|
||||
|
||||
export function getAffinityBlockName(code) {
|
||||
return AFFINITY_BLOCK_NAMES[code] || 'Unknown'
|
||||
}
|
||||
|
||||
export function getAffinityBlockColorByCode(code) {
|
||||
return AFFINITY_BLOCK_COLORS[code] || DEFAULT_COLOR
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for an affinity block (supports both ROP1 code and name).
|
||||
*/
|
||||
export function getAffinityBlockColor(blockNameOrCode) {
|
||||
if (!blockNameOrCode) return DEFAULT_COLOR
|
||||
|
||||
if (typeof blockNameOrCode === 'string' && /^A\d{3}$/.test(blockNameOrCode)) {
|
||||
return AFFINITY_BLOCK_COLORS[blockNameOrCode] || DEFAULT_COLOR
|
||||
}
|
||||
|
||||
return AFFINITY_BLOCK_COLORS_BY_NAME[blockNameOrCode] || DEFAULT_COLOR
|
||||
}
|
||||
|
||||
export function getAffinityBlocksArray() {
|
||||
return Object.entries(AFFINITY_BLOCK_NAMES).map(([code, name]) => ({
|
||||
code,
|
||||
name,
|
||||
color: AFFINITY_BLOCK_COLORS[code]
|
||||
}))
|
||||
}
|
||||
|
||||
export function getAffinityBlockData(code) {
|
||||
return {
|
||||
code,
|
||||
name: AFFINITY_BLOCK_NAMES[code] || 'Unknown',
|
||||
color: AFFINITY_BLOCK_COLORS[code] || DEFAULT_COLOR
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy-aligned getter — uniform name across all per-strategy modules.
|
||||
* Expects properties.affinityBlock to contain ROP1 code (e.g., "A007").
|
||||
*/
|
||||
export function getColor(properties) {
|
||||
const rop1Code = properties.affinityBlock || properties._raw?.ROP1
|
||||
return getAffinityBlockColorByCode(rop1Code)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display name for a feature.
|
||||
*/
|
||||
export function getName(properties) {
|
||||
const rop1Code = properties.affinityBlock || properties._raw?.ROP1
|
||||
return getAffinityBlockName(rop1Code)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Mapbox color expression for affinity blocks.
|
||||
* Uses ROP1 code (A001, A002, etc.) as the match key.
|
||||
*/
|
||||
export function applyColor() {
|
||||
const colorExpression = ['match', ['get', PROPERTY_KEY]]
|
||||
Object.entries(AFFINITY_BLOCK_COLORS).forEach(([code, color]) => {
|
||||
colorExpression.push(code, color)
|
||||
})
|
||||
colorExpression.push(DEFAULT_COLOR)
|
||||
return colorExpression
|
||||
}
|
||||
|
||||
export const buildColorExpression = applyColor
|
||||
|
||||
// Backward-compatibility aliases
|
||||
export const affinityBlockCodeToName = AFFINITY_BLOCK_NAMES
|
||||
export const affinityBlockCodeToColor = AFFINITY_BLOCK_COLORS
|
||||
export const affinityBlockNameToCode = AFFINITY_BLOCK_NAME_TO_CODE
|
||||
export const affinityBlockColors = AFFINITY_BLOCK_COLORS_BY_NAME
|
||||
export const affinityBlockCodeLookup = AFFINITY_BLOCK_NAMES
|
||||
|
||||
export default {
|
||||
name: 'Affinity Block',
|
||||
propertyKey: PROPERTY_KEY,
|
||||
palette: PALETTE,
|
||||
colors: PALETTE,
|
||||
names: NAMES,
|
||||
getColor,
|
||||
applyColor,
|
||||
buildColorExpression,
|
||||
getName,
|
||||
getAffinityBlockName,
|
||||
getAffinityBlockColor,
|
||||
getAffinityBlockColorByCode,
|
||||
getAffinityBlocksArray,
|
||||
getAffinityBlockData
|
||||
}
|
||||
|
|
@ -15,8 +15,13 @@ import { createPinia } from 'pinia'
|
|||
import { createAppI18n } from '@map/i18n/index.js'
|
||||
import { registerStrategies } from '@map/colors/_registry.js'
|
||||
import { installRtlTextPlugin } from '@map/maps/rtl-text.js'
|
||||
import affinityBlock from '../doxa-research-map/src/colors/affinity-block.js'
|
||||
import doxaRegion from '../doxa-research-map/src/colors/doxa-region.js'
|
||||
// ★ WAS A CROSS-PLUGIN RELATIVE IMPORT into another plugin's PRIVATE folder
|
||||
// ('../doxa-research-map/src/colors/...'). Both modules now live in library/colors/ (C1), so this
|
||||
// bundle no longer depends on doxa-research-map's internal layout -- which is the coupling the
|
||||
// library exists to remove, and the reason this migration had four consumers rather than the two
|
||||
// the spec counted.
|
||||
import affinityBlock from '@map/colors/affinity-block.js'
|
||||
import doxaRegion from '@map/colors/doxa-region.js'
|
||||
import DashboardPlusGrid from './dashboard-plus-grid.vue'
|
||||
|
||||
// ─── THE SAME BYPASS AS THE i18n BUG BELOW, SECOND LEG (card BUILD-MAP-STYLE-...) ────────────
|
||||
|
|
|
|||
|
|
@ -21,21 +21,16 @@ import { defineCustomElement } from 'vue'
|
|||
import { createPinia } from 'pinia'
|
||||
import ProfileLoader from '@map/ProfileLoader.vue'
|
||||
import { createAppI18n } from '@map/i18n/index.js'
|
||||
import { registerStrategies } from '@map/colors/_registry.js'
|
||||
|
||||
// ─── Bundle-private profile registry ─────────────────────────────────────────
|
||||
// import.meta.glob is evaluated by Vite at build time relative to THIS file —
|
||||
// so it captures only this bundle's profiles.
|
||||
const profileModules = import.meta.glob('./*.vue')
|
||||
|
||||
// ─── Bundle-private color strategies ─────────────────────────────────────────
|
||||
// Same seam as profileModules, for colors. Any `<name>.js` a contributor drops in
|
||||
// this bundle's `src/colors/` folder is merged OVER the shared library set
|
||||
// (same mode key = override; new key = profile-private). This bundle inlines its own
|
||||
// copy of the registry (IIFE build), so registering here affects THIS bundle only.
|
||||
// This bundle ships THREE research-only strategies: affinity-block.js,
|
||||
// doxa-region.js, resource.js (shared modes like religion stay in the library).
|
||||
registerStrategies(import.meta.glob('./src/colors/*.js', { eager: true }))
|
||||
// ★ THE LOCAL COLOUR GLOB IS GONE (C1b). This bundle registered its own src/colors/*.js over the
|
||||
// shared set; all three modules (doxa-region, affinity-block, resource) now live in library/colors/
|
||||
// where _registry.js discovers them for every bundle, and src/colors/ holds no .js at all. The glob
|
||||
// matched nothing, so leaving it would be dead code that reads as a deliberate override.
|
||||
|
||||
// ─── Mapbox RTL text plugin — load ONCE before any map instance ──────────────
|
||||
if (typeof window !== 'undefined' && window.mapboxgl?.setRTLTextPlugin) {
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
/**
|
||||
* DOXA Region Color Strategy
|
||||
*
|
||||
* Colors pins based on their DOXA / WAGF region.
|
||||
*
|
||||
* Drift note (vs simple-map):
|
||||
* - simple-map's 'No WAGF Region/Bloc' = '#95a5a6' (grey),
|
||||
* research = '#1a1a2e' (deep navy). Research wins.
|
||||
*
|
||||
* generateColorFromString fallback comes from utils/geoUtils.js — sister
|
||||
* subagent 1D owns that file. Cross-folder dep flagged.
|
||||
*/
|
||||
|
||||
import { generateColorFromString } from '@map/utils/geoUtils.js'
|
||||
|
||||
export const PROPERTY_KEY = 'doxaRegion'
|
||||
|
||||
const DEFAULT_COLOR = '#0098FF' // Bright blue for null/undefined region
|
||||
const FALLBACK_COLOR = '#95a5a6'
|
||||
|
||||
/**
|
||||
* DOXA Region Color Palette — defines colors for the 8 DOXA regions.
|
||||
*/
|
||||
export const PALETTE = {
|
||||
'Africa': '#ff2e2e',
|
||||
'Asia': '#1a8cff',
|
||||
'Europe': '#1ee05f',
|
||||
'Latin America & Caribbean': '#FFEB3B',
|
||||
'Middle East': '#a83dff',
|
||||
'No WAGF Region/Bloc': '#1a1a2e',
|
||||
'North America & Non-Spanish Caribbean': '#00e0bd',
|
||||
'Oceania': '#ff7f11'
|
||||
}
|
||||
|
||||
export const DOXA_REGION_COLORS = PALETTE
|
||||
|
||||
/**
|
||||
* SLUG-keyed palette — the AUTHORITATIVE map for coloring.
|
||||
*
|
||||
* The pray-tools API returns wagf_region as { value: <slug>, label: <localized> }.
|
||||
* DSM stores the stable slug in properties.doxaRegion (e.g. 'asia', 'middle_east').
|
||||
* The label is localized per `lang` ("Africa" → "África" → "Afrique"), so keying
|
||||
* colors on the display name silently breaks in every non-English locale (only
|
||||
* "Asia" — spelled identically across es/fr — survives). Key on the slug instead.
|
||||
* Slugs verified against the live API (2026-06-22).
|
||||
*/
|
||||
export const SLUG_PALETTE = {
|
||||
'africa': '#ff2e2e',
|
||||
'asia': '#1a8cff',
|
||||
'europe': '#1ee05f',
|
||||
'latin_america_&_caribbean': '#FFEB3B',
|
||||
'middle_east': '#a83dff',
|
||||
'north_america_&_non-spanish_caribbean': '#00e0bd',
|
||||
'oceania': '#ff7f11',
|
||||
'na': '#1a1a2e' // No WAGF Region/Bloc
|
||||
}
|
||||
|
||||
/** Color for a region by its stable slug (locale-independent). */
|
||||
export function getRegionColorBySlug(slug) {
|
||||
if (!slug) return DEFAULT_COLOR
|
||||
return SLUG_PALETTE[String(slug).toLowerCase()] || FALLBACK_COLOR
|
||||
}
|
||||
|
||||
/** Extended region aliases for flexible matching */
|
||||
export const REGION_COLOR_ALIASES = {
|
||||
'Africa': '#ff2e2e',
|
||||
'East Asia': '#1a8cff',
|
||||
'Asia': '#1a8cff',
|
||||
'Eurasia': '#1ee05f',
|
||||
'Europe': '#1ee05f',
|
||||
'Latin America': '#FFEB3B',
|
||||
'Latin America & Caribbean': '#FFEB3B',
|
||||
'Caribbean': '#FFEB3B',
|
||||
'Middle East': '#a83dff',
|
||||
'North America': '#00e0bd',
|
||||
'North America (non-spanish)': '#00e0bd',
|
||||
'North America & Non-Spanish Caribbean': '#00e0bd',
|
||||
'Pacific': '#ff7f11',
|
||||
'Oceania': '#ff7f11',
|
||||
'South Asia': '#ff7f11',
|
||||
'No WAGF Region/Bloc': '#1a1a2e',
|
||||
'Unknown': '#0098FF'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for a region — falls back to a deterministic
|
||||
* generateColorFromString() so unmapped regions still render.
|
||||
*/
|
||||
export function getRegionColor(regionName) {
|
||||
if (!regionName) {
|
||||
return DEFAULT_COLOR
|
||||
}
|
||||
|
||||
const color = REGION_COLOR_ALIASES[regionName]
|
||||
if (!color) {
|
||||
return generateColorFromString(regionName)
|
||||
}
|
||||
|
||||
return color
|
||||
}
|
||||
|
||||
export function getColor(properties) {
|
||||
// properties.doxaRegion is the stable slug (e.g. 'asia'), NOT the localized
|
||||
// label — color by slug so it survives language switches.
|
||||
const slug = properties.doxaRegion || properties._normalized?.doxaRegion
|
||||
return getRegionColorBySlug(slug)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Mapbox color expression for DOXA regions.
|
||||
*
|
||||
* Matches on the stable slug stored in `doxaRegion` (e.g. 'asia') so the
|
||||
* expression keeps coloring correctly when the API returns localized labels.
|
||||
*/
|
||||
export function applyColor() {
|
||||
const colorExpression = ['match', ['get', PROPERTY_KEY]]
|
||||
Object.entries(SLUG_PALETTE).forEach(([slug, color]) => {
|
||||
colorExpression.push(slug, color)
|
||||
})
|
||||
colorExpression.push(FALLBACK_COLOR)
|
||||
return colorExpression
|
||||
}
|
||||
|
||||
export const buildColorExpression = applyColor
|
||||
|
||||
export default {
|
||||
name: 'DOXA Region',
|
||||
propertyKey: PROPERTY_KEY,
|
||||
palette: PALETTE,
|
||||
colors: PALETTE,
|
||||
slugColors: SLUG_PALETTE,
|
||||
aliases: REGION_COLOR_ALIASES,
|
||||
fallback: FALLBACK_COLOR,
|
||||
getColor,
|
||||
applyColor,
|
||||
buildColorExpression,
|
||||
getRegionColor,
|
||||
getRegionColorBySlug
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
/**
|
||||
* Gospel Resources Color Strategy
|
||||
*
|
||||
* Colors pins based on available gospel resources.
|
||||
* DEFAULT: Binary coloring - Green (has ANY resource) / Black (no resources)
|
||||
* LEGEND COUNTS: Show TOTAL for each resource (can overlap)
|
||||
*/
|
||||
|
||||
export const PROPERTY_KEY = 'resourceType'
|
||||
|
||||
/**
|
||||
* Gospel Resources Colors
|
||||
* DEFAULT: Binary coloring - Green (has ANY resource) / Black (no resources)
|
||||
* LEGEND: Show TOTAL count for each resource (INCLUSIVE, can overlap)
|
||||
*/
|
||||
export const PALETTE = {
|
||||
bible: '#3498db', // Blue - Bible available
|
||||
jesusFilm: '#20c997', // Fisher Green (teal-green) - Jesus Film available
|
||||
radio: '#9b59b6', // Purple - Radio broadcast available
|
||||
gospel: '#f39c12', // Orange - Gospel recordings available
|
||||
audio: '#1abc9c', // Teal - Audio resources available
|
||||
stories: '#e74c3c', // Red - Stories available
|
||||
hasResources: '#27ae60', // Green - Has ANY gospel resource (default pin color)
|
||||
noResources: '#2c3e50' // Dark/Black - No gospel resources
|
||||
}
|
||||
|
||||
export const RESOURCE_COLORS = PALETTE
|
||||
|
||||
/** Resource priority order for pin coloring */
|
||||
export const RESOURCE_PRIORITY = ['bible', 'jesusFilm', 'radio', 'gospel', 'audio', 'stories']
|
||||
|
||||
/** Resource display names for legend */
|
||||
export const NAMES = {
|
||||
bible: 'Bible',
|
||||
jesusFilm: 'Jesus Film',
|
||||
radio: 'Radio',
|
||||
gospel: 'Gospel',
|
||||
audio: 'Audio',
|
||||
stories: 'Stories',
|
||||
hasResources: 'Has Resources',
|
||||
noResources: 'No Resources'
|
||||
}
|
||||
|
||||
export const RESOURCE_NAMES = NAMES
|
||||
|
||||
/**
|
||||
* Get CSV column name for a resource key.
|
||||
*/
|
||||
export function getCSVColumn(resourceKey) {
|
||||
const mapping = {
|
||||
bible: 'Bible',
|
||||
jesusFilm: 'Jesus',
|
||||
radio: 'Radio',
|
||||
gospel: 'Gospel',
|
||||
audio: 'Audio',
|
||||
stories: 'Stories'
|
||||
}
|
||||
return mapping[resourceKey]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for a feature based on resource availability.
|
||||
* Binary logic: Green if has ANY resource, Black if none.
|
||||
*/
|
||||
export function getColor(properties) {
|
||||
for (const resourceKey of RESOURCE_PRIORITY) {
|
||||
const value = properties[resourceKey] || properties._raw?.[getCSVColumn(resourceKey)]
|
||||
if (value === 'Available') {
|
||||
return PALETTE.hasResources // Green
|
||||
}
|
||||
}
|
||||
return PALETTE.noResources // Black
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if feature has ANY resource (for hasResources).
|
||||
*/
|
||||
export function hasAnyResource(properties) {
|
||||
for (const resourceKey of RESOURCE_PRIORITY) {
|
||||
const value = properties[resourceKey] || properties._raw?.[getCSVColumn(resourceKey)]
|
||||
if (value === 'Available') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highest priority resource type for a feature.
|
||||
* Returns the resource key that determines the pin color.
|
||||
*/
|
||||
export function getResourceType(properties) {
|
||||
for (const resourceKey of RESOURCE_PRIORITY) {
|
||||
const value = properties[resourceKey] || properties._raw?.[getCSVColumn(resourceKey)]
|
||||
if (value === 'Available') {
|
||||
return resourceKey
|
||||
}
|
||||
}
|
||||
return 'noResources'
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Mapbox color expression for gospel resources.
|
||||
* DEFAULT: Binary coloring - Green if has ANY resource, Black if none.
|
||||
*/
|
||||
export function applyColor() {
|
||||
return [
|
||||
'case',
|
||||
// If ANY resource is Available → Green (hasResources)
|
||||
['any',
|
||||
['==', ['get', 'bible'], 'Available'],
|
||||
['==', ['get', 'jesusFilm'], 'Available'],
|
||||
['==', ['get', 'radio'], 'Available'],
|
||||
['==', ['get', 'gospel'], 'Available'],
|
||||
['==', ['get', 'audio'], 'Available'],
|
||||
['==', ['get', 'stories'], 'Available']
|
||||
],
|
||||
PALETTE.hasResources, // Green
|
||||
// Default: No resources → Black
|
||||
PALETTE.noResources // Black
|
||||
]
|
||||
}
|
||||
|
||||
export const buildColorExpression = applyColor
|
||||
|
||||
export default {
|
||||
name: 'Gospel Resources',
|
||||
propertyKey: PROPERTY_KEY,
|
||||
palette: PALETTE,
|
||||
colors: PALETTE,
|
||||
names: NAMES,
|
||||
priority: RESOURCE_PRIORITY,
|
||||
getColor,
|
||||
applyColor,
|
||||
buildColorExpression,
|
||||
hasAnyResource,
|
||||
getResourceType,
|
||||
getCSVColumn
|
||||
}
|
||||
|
|
@ -25,8 +25,13 @@ import ProfileLoader from '@map/ProfileLoader.vue'
|
|||
import { createAppI18n } from '@map/i18n/index.js'
|
||||
import { registerStrategies } from '@map/colors/_registry.js'
|
||||
import { installRtlTextPlugin } from '@map/maps/rtl-text.js'
|
||||
import affinityBlock from '../doxa-research-map/src/colors/affinity-block.js'
|
||||
import doxaRegion from '../doxa-research-map/src/colors/doxa-region.js'
|
||||
// ★ WAS A CROSS-PLUGIN RELATIVE IMPORT into another plugin's PRIVATE folder
|
||||
// ('../doxa-research-map/src/colors/...'). Both modules now live in library/colors/ (C1), so this
|
||||
// bundle no longer depends on doxa-research-map's internal layout -- which is the coupling the
|
||||
// library exists to remove, and the reason this migration had four consumers rather than the two
|
||||
// the spec counted.
|
||||
import affinityBlock from '@map/colors/affinity-block.js'
|
||||
import doxaRegion from '@map/colors/doxa-region.js'
|
||||
|
||||
// ─── Bundle-private profile registry ─────────────────────────────────────────
|
||||
const profileModules = import.meta.glob('./*.vue')
|
||||
|
|
|
|||
Loading…
Reference in a new issue