Basemap follows the tab: prayer stays night, every other map is day in monochrome white

- useMapTheme: one look rule (any prayer strategy → night/default; everything else →
  day/monochrome) applied at boot and on every tab switch in the simple map and the
  research map; an unknown strategy keeps the current look
- Legends and the research search pill are 380px again; desktop numeric columns 70px at
  13px so every row stays on one line with no overlap
- Map containers carry their map instance for gates (no global)
This commit is contained in:
swift-river542 2026-09-16 20:50:51 -05:00
parent 1ae532481f
commit 1b22fa419b
5 changed files with 40 additions and 16 deletions

View file

@ -427,6 +427,10 @@ const showTabBar = computed(() => true)
const activeTabId = ref(null)
const activeTab = computed(() => tabs.value.find(t => t.id === activeTabId.value) ?? tabs.value[0])
// Basemap look follows the active tab: prayer → night, everything else → day (monochrome).
// Re-evaluated whenever the tab changes, including when the tab list arrives after boot.
mapTheme.setLook(mapTheme.lookFor(activeTab.value?.colorStrategy))
watch(() => activeTab.value?.colorStrategy, (strategy) => mapTheme.setLook(mapTheme.lookFor(strategy), map.value))
const activeLegendType = computed(() => activeTab.value?.legend ?? 'default')
const activePopupAction = computed(() => activeTab.value?.popup ?? 'pray')
@ -1327,10 +1331,8 @@ function switchTab(tabId) {
if (!FEATURES.geocoderPersistAcrossTabs) clearGeocoderProgrammatic()
const tab = tabs.value.find(t => t.id === tabId)
if (!tab || !map.value) return
// Re-assert the persisted theme's basemap lightPreset/theme on EVERY tab switch — defensive
// so dark mode can't appear "not working" on any tab (bug-dark-mode-multi-tab). Cheap: it's
// a config prop (no setStyle).
mapTheme.applyTheme(map.value)
// The basemap look follows the tab (prayer → night, others → day); config prop only, no reload.
mapTheme.setLook(mapTheme.lookFor(tab.colorStrategy), map.value)
// Lazy-load regions GeoJSON the first time the user lands on this tab.
if (tab.id === 'doxa-regions') void ensureRegionsLoaded()
@ -1929,7 +1931,7 @@ function applyContainerScale(width) {
// cards). SemanticTreeLegend's .stl-panel follows this variable too.
// 400px (was 380) since the desktop type scale widened the numeric
// columns; keeps "Full Prayer Coverage" on one line (2026-09-16).
el.style.setProperty('--map-legend-width', '400px')
el.style.setProperty('--map-legend-width', '380px')
} else if (width >= 500) {
el.style.setProperty('--map-btn-size', '36px')
el.style.setProperty('--map-toolbar-gap', '6px')

View file

@ -419,7 +419,7 @@ function applyContainerScale(width) {
el.style.setProperty('--map-toolbar-gap', '8px')
el.style.setProperty('--map-font-md', '13px')
el.style.setProperty('--map-font-sm', '11px')
el.style.setProperty('--map-legend-width', '400px')
el.style.setProperty('--map-legend-width', '380px')
} else if (width >= 500) {
el.style.setProperty('--map-btn-size', '36px')
el.style.setProperty('--map-toolbar-gap', '6px')
@ -447,6 +447,10 @@ const mapStore = inject('mapStore')
const dataStore = inject('dataStore')
const uiStore = inject('uiStore')
const mapTheme = useMapTheme(uiStore)
// Basemap look follows the active tab: prayer → night, everything else → day (monochrome).
// Re-evaluated whenever the tab changes, including when the tab list arrives after boot.
mapTheme.setLook(mapTheme.lookFor(activeTab.value?.colorStrategy))
watch(() => activeTab.value?.colorStrategy, (strategy) => mapTheme.setLook(mapTheme.lookFor(strategy), map.value))
// loc-012: feed the active locale into the bulk/list data fetch so the data the
// search bar searches comes back in the selected language — the same `lang` param
// the people-group detail fetch uses (PeopleGroupDetail.vue). null → API default.
@ -837,9 +841,8 @@ function switchTab(tabId) {
activeTabId.value = tabId
const tab = tabs.value.find(t => t.id === tabId)
if (!tab || !map.value) return
// Re-assert the persisted theme's basemap lightPreset on every tab switch (defensive,
// config-prop only — keeps dark mode consistent across tabs). bug-dark-mode-multi-tab.
mapTheme.applyTheme(map.value)
// The basemap look follows the tab (prayer → night, others → day); config prop only, no reload.
mapTheme.setLook(mapTheme.lookFor(tab.colorStrategy), map.value)
const strategyKey = tab.colorStrategy
const colorMode =
strategyKey === 'engagement' ? COLOR_MODES.ENGAGEMENT :

View file

@ -977,11 +977,11 @@ useShadowStyles(`
.stl-panel:not(.legend-mobile-sheet *) .stl-hdr-name { font-size: 13px; }
.stl-panel:not(.legend-mobile-sheet *) .stl-hdr-num { font-size: 10px; }
.stl-panel:not(.legend-mobile-sheet *) .stl-name { font-size: 15px; }
.stl-panel:not(.legend-mobile-sheet *) .stl-num { font-size: 13.5px; }
.stl-panel:not(.legend-mobile-sheet *) .stl-num { font-size: 13px; }
/* The numeric columns must grow with the type: "POPULATION" at 10px is ~65px, ~76px with
its sort arrow; "139.0M" at 13.5px is ~49px. A fixed 44px column let the header collide
with "UUPGs" and let values spill into the neighbouring column (Driver, 2026-09-16). */
.stl-panel:not(.legend-mobile-sheet *) .stl-inner { --stl-num-w: 76px; } /* the vars live on .stl-inner */
.stl-panel:not(.legend-mobile-sheet *) .stl-inner { --stl-num-w: 70px; } /* the vars live on .stl-inner */
.stl-panel:not(.legend-mobile-sheet *) .stl-hdr-num,
.stl-panel:not(.legend-mobile-sheet *) .stl-num { white-space: nowrap; }
.stl-panel:not(.legend-mobile-sheet *) .stl-info-pop { font-size: 13px; }

View file

@ -366,6 +366,8 @@ export function useMapInstance({
attributionControl: true // Mapbox's own attribution on every map
})
claimLogo(map.value)
// Probe hook: the container element carries its map so gates can read live config (no global).
try { containerRef.value.__doxaMap = map.value } catch (_) {}
} else {
// Fragment config for THIS boot (a thunk re-reads the theme on every re-init).
const initialConfig = typeof config === 'function' ? config() : config
@ -394,6 +396,8 @@ export function useMapInstance({
attributionControl: true // Mapbox's own attribution on every map
})
claimLogo(map.value)
// Probe hook: the container element carries its map so gates can read live config (no global).
try { containerRef.value.__doxaMap = map.value } catch (_) {}
}
// Belt-and-suspenders to dragRotate:false — kills the two-finger twist→rotate

View file

@ -44,6 +44,12 @@ export const THEME_TOGGLE = false
// The basemap look that is pinned when THEME_TOGGLE is false. These are the exact
// values the ORIGINAL dark mode used, so the map is visually unchanged.
export const FIXED_BASEMAP = { lightPreset: 'night', theme: 'default' }
// The light look: the same Standard style lit by day in the monochrome (white) theme, so the
// pins stand out on every map that informs rather than prays. Prayer maps keep the night look (light and darkness).
export const LIGHT_BASEMAP = { lightPreset: 'day', theme: 'monochrome' }
// Which look a colour strategy asks for: prayer → dark, everything else → light.
// Unknown strategy (tabs not loaded yet) → null: the current look is kept.
export function lookForStrategy(strategy) { return !strategy ? null : (/prayer/i.test(String(strategy)) ? 'dark' : 'light') } // 'prayer', 'prayerProgress', …
export function useMapTheme(uiStore) {
// Single pending 'idle' re-assert guard (per map instance). Prevents stacking one
@ -69,11 +75,20 @@ export function useMapTheme(uiStore) {
// both the boot `config` (bootConfig) and every live re-pin (applyTheme), so the first
// painted frame and a later re-assert can never disagree. Recomputed fresh on every call
// (rapid toggles can't desync — last call wins).
// The pinned look for this map instance: 'dark' (prayer) or 'light' (every other tab).
// Set from the active tab's colour strategy at boot and on each tab switch (setLook).
let look = 'dark'
function lookFor(strategy) { return lookForStrategy(strategy) }
function setLook(next, map) {
if (next !== 'light' && next !== 'dark') return // unknown → keep the current look
look = next
if (map) applyTheme(map)
}
function basemapConfig() {
// FLAG OFF → the basemap is PINNED to the dark/colourful look and does NOT follow the
// UI theme. This is what lets the chrome be light while the map keeps its dark-green
// colour strategy.
if (!THEME_TOGGLE) return { ...FIXED_BASEMAP }
// FLAG OFF → the basemap is PINNED per look and does NOT follow the UI theme. This is
// what lets the chrome stay light while a prayer map keeps its dark-green look and an
// informational map its day-lit green look.
if (!THEME_TOGGLE) return { ...(look === 'light' ? LIGHT_BASEMAP : FIXED_BASEMAP) }
const dark = isDarkTheme()
// lightPreset = day/night lighting. theme = the Standard colour theme: light mode uses
// the LIGHTER, cleaner 'monochrome' theme (closer to a flat white map); dark keeps the
@ -189,5 +204,5 @@ export function useMapTheme(uiStore) {
applyTheme(map)
}
return { STANDARD_STYLE, LIGHT_STYLE, DARK_STYLE, isDarkTheme, lightPreset, basemapConfig, bootStyle, bootConfig, applyTheme, swapTheme }
return { setLook, lookFor, STANDARD_STYLE, LIGHT_STYLE, DARK_STYLE, isDarkTheme, lightPreset, basemapConfig, bootStyle, bootConfig, applyTheme, swapTheme }
}