Update from source d8c67ca28
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

This commit is contained in:
Doxa Maps 2026-09-28 06:20:36 +00:00
parent 51728f96f7
commit 529bd65af5
6 changed files with 121 additions and 115 deletions

View file

@ -1092,6 +1092,43 @@ function _buildFlatLegendMatchExpr(node, legendType) {
}
return null
}
// ─── ONE WRITER FOR THE REGION POLYGONS ──────────────────────────────────────
// ★ MEASURED (rega, main 373b7979b): a single WAGF row click produced NINE regions-* paint writes —
// fill-opacity, line-color and line-width, three times each inside ~110 ms — because two paths painted
// the same highlight from DIFFERENT sources: onSemanticTreeSelect from node.isoCodes, applyDimFilter
// from mapLayers.regionIsoMap, each triggering the other through mapStore. Two sources for one visual
// is not just wasted work; it is how they drift, and it already cost a debugging cycle when we had to
// establish WHICH of them had cleared the highlight.
//
// ★ AND THE IDLE STATE IS DATA-DRIVEN ON PURPOSE. rega measured the row click compiling a new shader
// variant: 5.5 s on MapLibre, 11.1 s on Mapbox (SwiftShader), because that click was the first moment
// these three properties stopped being constants. Same defect as the pins, same fix: the idle value is
// ['case', ['has', 'iso_3166_1_alpha_3'], x, x] — both branches identical, so not one pixel changes —
// and the variant therefore compiles when the layer is ADDED rather than mid-gesture.
//
// The compare is asked of the MAP, not of a remembered key, so a layer torn down and re-added cannot
// leave a skip holding a paint that is no longer there.
const REGION_IDLE_FILL = ['case', ['has', 'iso_3166_1_alpha_3'], 0.7, 0.7]
const REGION_IDLE_COLOR = ['case', ['has', 'iso_3166_1_alpha_3'], 'rgba(60,60,80,0.35)', 'rgba(60,60,80,0.35)']
const REGION_IDLE_WIDTH = ['case', ['has', 'iso_3166_1_alpha_3'], 0.6, 0.6]
function paintRegionScope(m, isoCodes) {
if (!m || !m.getLayer('regions-fill')) return
const list = Array.isArray(isoCodes) ? isoCodes.filter(Boolean) : []
const isoArr = ['literal', list]
const fill = list.length ? ['case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], 0.85, 0.15] : REGION_IDLE_FILL
const color = list.length ? ['case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], '#111827', 'rgba(60,60,80,0.15)'] : REGION_IDLE_COLOR
const width = list.length ? ['case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], 2.5, 0.3] : REGION_IDLE_WIDTH
const put = (layer, prop, value) => {
if (!m.getLayer(layer)) return
if (JSON.stringify(m.getPaintProperty(layer, prop)) === JSON.stringify(value)) return
m.setPaintProperty(layer, prop, value)
}
try {
put('regions-fill', 'fill-opacity', fill)
put('regions-border', 'line-color', color)
put('regions-border', 'line-width', width)
} catch (_) { /* style still loading — the next paint carries it */ }
}
function onSemanticTreeSelect(node) {
const m = map.value
// — uniform legend-row camera: any non-null row click moves
@ -1195,25 +1232,7 @@ function onSemanticTreeSelect(node) {
// Mirror region selection into mapStore (for geocoder / external sync).
if (id.startsWith('region:')) mapStore.selectRegion?.(id.slice(7))
// Highlight matching polygons using isoCodes (alpha-3) on iso_3166_1_alpha_3.
if (m && m.getLayer('regions-fill')) {
const isoCodes = node.isoCodes || []
if (isoCodes.length) {
const isoArr = ['literal', isoCodes]
m.setPaintProperty('regions-fill', 'fill-opacity', [
'case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], 0.85, 0.15
])
m.setPaintProperty('regions-border', 'line-color', [
'case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], '#111827', 'rgba(60,60,80,0.15)'
])
m.setPaintProperty('regions-border', 'line-width', [
'case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], 2.5, 0.3
])
} else {
m.setPaintProperty('regions-fill', 'fill-opacity', 0.7)
m.setPaintProperty('regions-border', 'line-color', 'rgba(60,60,80,0.35)')
m.setPaintProperty('regions-border', 'line-width', 0.6)
}
}
paintRegionScope(m, node.isoCodes || [])
}
}
// Theme bridge: keep the SemanticTreeLegend in sync with the app's dark-mode
@ -1537,6 +1556,12 @@ async function ensureRegionsLoaded() {
const regionsData = await mapData.loadRegionsData?.()
if (regionsData && map.value) {
mapLayers.addRegionsLayer?.(regionsData, 'doxa-regions')
// ★ PRE-WARM HERE, which is the only moment that helps. These layers are added on first visit
// to the Regions tab, and rega measured the first ROW click paying 5.5 s (MapLibre) / 11.1 s
// (Mapbox) of shader compile because that click was when their paint first became data-driven.
// Painting the idle state through paintRegionScope makes it data-driven from the first draw, so
// the variant compiles here — where a 6.4 MB GeoJSON is already loading — instead of mid-gesture.
paintRegionScope(map.value, [])
// Hide regions on non-Doxa-Regions tabs so they don't show through.
if (map.value.getLayer('regions-fill') && activeTab.value?.id !== 'doxa-regions') {
map.value.setLayoutProperty('regions-fill', 'visibility', 'none')
@ -1733,14 +1758,10 @@ function clearAllHighlights(m) {
m.setFilter('language-family-pins', null)
_syncHitboxFilter(m, null)
mapLayers.syncGlowFilter(null)
if (m.getLayer('regions-fill')) {
m.setPaintProperty('regions-fill', 'fill-opacity', 0.7)
m.setPaintProperty('regions-fill', 'fill-outline-color', null)
}
if (m.getLayer('regions-border')) {
m.setPaintProperty('regions-border', 'line-color', 'rgba(60,60,80,0.35)')
m.setPaintProperty('regions-border', 'line-width', 0.6)
}
// Through the one writer, so "cleared" and "idle" are the same expression rather than two spellings
// of it — and so the idle state stays DATA-DRIVEN and the shader variant stays compiled.
paintRegionScope(m, [])
if (m.getLayer('regions-fill')) m.setPaintProperty('regions-fill', 'fill-outline-color', null)
_lastHL.family = _lastHL.language = _lastHL.dialect = _lastHL.region = null
_lastHL.prayer = _lastHL.engagement = _lastHL.adoption = null
if (uiStore.prayerFilter) uiStore.setPrayerFilter(null)
@ -1860,22 +1881,7 @@ function applyDimFilter(detail) {
if (norm(k) === ev) { isoCodes = v; break }
}
}
if (isoCodes.length) {
const isoArr = ['literal', isoCodes]
m.setPaintProperty('regions-fill', 'fill-opacity', [
'case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], 0.85, 0.15
])
m.setPaintProperty('regions-border', 'line-color', [
'case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], '#111827', 'rgba(60,60,80,0.15)'
])
m.setPaintProperty('regions-border', 'line-width', [
'case', ['in', ['get', 'iso_3166_1_alpha_3'], isoArr], 2.5, 0.3
])
} else {
m.setPaintProperty('regions-fill', 'fill-opacity', 0.7)
m.setPaintProperty('regions-border', 'line-color', 'rgba(60,60,80,0.35)')
m.setPaintProperty('regions-border', 'line-width', 0.6)
}
paintRegionScope(m, isoCodes)
}
} else {
// Family / language / prayer / engagement / adoption: keep all pins

View file

@ -376,7 +376,7 @@
"bundle": "doxa-research-map",
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"output": "dx-maps/doxa-research-map/doxa-research-map.js",
"integrity": "sha384-SgOn8LQR6r32218+9dS9e9ewxB9qBtwCoVph4HLDkGsNCrX2fLf5drETWeO+HnoU",
"integrity": "sha384-484w9lFNnAVr+yPbXIGMSP9DZ25gQ9lwdXC5mVig1WKxSTUnVsX0ARlgGZXQuoG9",
"elements": [
"doxa-research-map"
],
@ -395,7 +395,7 @@
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-SgOn8LQR6r32218+9dS9e9ewxB9qBtwCoVph4HLDkGsNCrX2fLf5drETWeO+HnoU\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-484w9lFNnAVr+yPbXIGMSP9DZ25gQ9lwdXC5mVig1WKxSTUnVsX0ARlgGZXQuoG9\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
},
"instances": [
{

File diff suppressed because one or more lines are too long

View file

@ -36,7 +36,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
<script src="../doxa-research-map.js" integrity="sha384-SgOn8LQR6r32218+9dS9e9ewxB9qBtwCoVph4HLDkGsNCrX2fLf5drETWeO+HnoU" crossorigin="anonymous"></script>
<script src="../doxa-research-map.js" integrity="sha384-484w9lFNnAVr+yPbXIGMSP9DZ25gQ9lwdXC5mVig1WKxSTUnVsX0ARlgGZXQuoG9" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -33,7 +33,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-research-map> custom element. -->
<script src="../../doxa-research-map.js" integrity="sha384-SgOn8LQR6r32218+9dS9e9ewxB9qBtwCoVph4HLDkGsNCrX2fLf5drETWeO+HnoU" crossorigin="anonymous"></script>
<script src="../../doxa-research-map.js" integrity="sha384-484w9lFNnAVr+yPbXIGMSP9DZ25gQ9lwdXC5mVig1WKxSTUnVsX0ARlgGZXQuoG9" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -376,7 +376,7 @@
"bundle": "doxa-research-map",
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"output": "dx-maps/doxa-research-map/doxa-research-map.js",
"integrity": "sha384-SgOn8LQR6r32218+9dS9e9ewxB9qBtwCoVph4HLDkGsNCrX2fLf5drETWeO+HnoU",
"integrity": "sha384-484w9lFNnAVr+yPbXIGMSP9DZ25gQ9lwdXC5mVig1WKxSTUnVsX0ARlgGZXQuoG9",
"elements": [
"doxa-research-map"
],
@ -395,7 +395,7 @@
"scriptUrl": "./doxa-research-map/doxa-research-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-research-map/research-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-SgOn8LQR6r32218+9dS9e9ewxB9qBtwCoVph4HLDkGsNCrX2fLf5drETWeO+HnoU\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-research-map/doxa-research-map.js\" integrity=\"sha384-484w9lFNnAVr+yPbXIGMSP9DZ25gQ9lwdXC5mVig1WKxSTUnVsX0ARlgGZXQuoG9\" crossorigin=\"anonymous\"></script>\n<doxa-research-map profile-config='{\"profile\":\"research-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-research-map>"
},
"instances": [
{