doxa-maps/plugin-designs/doxa-maps-spa/src/tabInfo.js
Doxa Maps d0de376f4a Big Picture header: rounded-square ⓘ left of a rounded-square Share
The dashboard header gains an info button ("What can this map do for you?") to
the left of Share. Both are rounded squares at the vertical tabs' radius
(--vtl-radius), matching the app's own roundedness; map controls stay round.

Built as a reusable, parameterized header-button form so other dashboards adopt
it by prop, not by copy:
- MapControlButton: new shape="rounded-square" (border-radius: var(--vtl-radius,8px)).
- ShareButton / MapInfoButton: a shape prop forwarded to MapControlButton;
  ShareButton also squares its rotating ring so a round ring never wraps a square.
  Defaults stay 'round' — every existing consumer is unchanged in behaviour.
- research-dashboard DashboardTopBar mounts the ⓘ and Share as one header-button
  group, sized to the bar with the 44px tap target kept for both. DOM order
  [ⓘ, Share] puts ⓘ at the logical start: left in English, right of Share in
  Arabic (the flex row reverses under rtl).
- doxa-maps-spa dashboard-plus-grid tab -> trigger:'control', so the tab's
  MapInfo is driven by the header ⓘ and the gallery's floating ⓘ is suppressed:
  exactly one ⓘ, fail-open. No new strings (reuses mapInfo.open, 11 locales).
2026-09-30 04:27:44 +00:00

139 lines
8.1 KiB
JavaScript

/**
* tabInfo.js — what each gallery tab's info button (library MapInfo) says and shows.
*
* Keyed by the gallery's TAB id. A tab with no entry gets no button. Each OWNER supplies its words and
* example files; the example files are real, pre-generated output under the tree's assets folder, so the
* popup opens instantly.
*
* ★ EVERY KEY IS IN THE 'mapInfo' AREA, as mapInfo.tab.<tabId>.<field>, and never in the map's own area.
* The build gives each bundle only the catalogue areas its source names (internal/i18n-areas.mjs): a key
* in 'regions' or 'upgMap' would either render blank here or, named literally in this file, pull that
* map's WHOLE catalogue, in eleven locales, into the gallery bundle. upg100 owns 'mapInfo' and merges
* every tab's translated strings into it. Name keys LITERALLY here: that is what the area scan reads.
*
* ★ pos PLACES THE BUTTON CLEAR OF THE MAP'S OWN CONTROLS (--map-info-top / --map-info-side, default
* 12px each). The button sits at the END of the line (right in English, left in Arabic, see MapInfo),
* which is also where most of these maps keep their zoom column; measured on rega's 1440 captures in
* en and ar (2026-09-29), 12px covered the zoom-in button on Prayer Progress, Cities and Gospel, Big
* Picture's header buttons and Simple Research's tab bar. side 56px sits beside a zoom column; top
* 46px / 56px clears Big Picture's 38px header bar / Simple Research's tab bar. Regions and UPG-100
* open on a view-switch strip whose END holds their Share button, which the default covered exactly
* (found by geometry, not visible in a screenshot: only Share's ring showed around the button).
* pos.narrow overrides on a phone (the shell's `narrow`, max-width:760px), where the chrome differs:
* Regions' and UPG-100's strip is full at its END (selector + Share) and empty at its START, so the
* button goes to the start (placement 'top-left', which MapInfo resolves to the logical start, so
* Arabic mirrors); Gospel's and Cities' legend panels span to 344px of 390, so their button takes the corner
* above the zoom column. Measured with the same probe at 390 in en and ar.
* ★ AN EXAMPLE'S FILES SHIP WITH IT. MapInfo drops an example whose lead image fails to load (quietly,
* title and all), so a link can land before its files; but a popup is only worth opening once they are
* in the tree, so land the two together.
* ★ KEYS MUST NOT CONTAIN A DOT beyond the area separator: the gallery has no vue-i18n, so MapInfo
* resolves through tStatic, which splits the path on "." and would never find a sentence-shaped key.
*/
// The UPG-100 zone's pre-generated packet ('full' variant) and card deck, shot once and shared: Simple
// Research's packet is the same renderer, method and variant (usePosterPrints.printPacket, 'full'), so it
// shows these pages too, CAPTIONED as an example zone, because their data is a UPG-100 zone and not a
// Simple Research selection.
const MEC = 'previews/examples/middle-east-core'
const MEC_PAGES = Array.from({ length: 12 }, (_, i) => `${MEC}/packet-p${String(i + 1).padStart(2, '0')}.webp`)
const MEC_CARDS = Array.from({ length: 70 }, (_, i) => `${MEC}/card-${String(i + 1).padStart(2, '0')}.webp`)
export const TAB_INFO = {
'upg-100': {
storageKey: 'upg-100',
pos: { side: '56px', narrow: { placement: 'top-left', side: '12px' } },
leadKey: 'mapInfo.tab.upg-100.lead',
takeawayKey: 'mapInfo.tab.upg-100.takeaway',
examples: [
{ kind: 'image', src: 'previews/middle-east-core.webp', full: 'posters/middle-east-core.html' },
{ kind: 'pages', pages: MEC_PAGES },
{ kind: 'deck', cards: MEC_CARDS },
],
},
'regions-dashboard': {
storageKey: 'regions-dashboard',
pos: { side: '56px', narrow: { placement: 'top-left', side: '8px' } },
leadKey: 'mapInfo.tab.regions-dashboard.lead',
takeawayKey: 'mapInfo.tab.regions-dashboard.takeaway',
// "except India and China": India is genuinely unsupported -- no sheet, no geometry, and 855
// people groups, too many to fit one poster legibly (library/data/country-posters.json carries
// that reason itself). CHINA IS DIFFERENT: it is technically supported TODAY (sheet 3x4,
// geometry present, 154 groups -- the 3x4 tier exists precisely so it renders) and is withheld
// deliberately, not because the pipeline cannot do it. Do not "fix" this by enabling China;
// that is a decision above this file.
noteKey: 'mapInfo.tab.regions-dashboard.note',
// The Sudan poster, packet pages and card deck join when rega's shots are in the tree.
examples: [],
},
'dashboard-plus-grid': {
storageKey: 'dashboard-plus-grid',
// ⓘ IN THE HEADER, NOT FLOATING (design rule, 2026-09-30). Big Picture draws its own ⓘ as a rounded-square
// header button beside Share (DashboardTopBar), announcing itself through MapInfoButton's control
// bridge. trigger:'control' hands this MapInfo to that header button and SUPPRESSES the floating ⓘ
// whenever it is on screen (fail-open: the grace period keeps the floating one off while the composite
// loads, so there is no flash, and it still shows if the header control never appears). The pos below
// stays as the FALLBACK placement for that floating button (a leaf without the header control).
trigger: 'control',
// top 46px is the empty END of Big Picture's filter area at every width (1 row at 1440, 2 at 1024,
// 3 at 800), but that bar is a positioned layer (z-index 55) painted above the button, which left it
// unclickable (rega, 1440). raise lifts the BUTTON over the bar; the pointer stays under it.
pos: { top: '46px', raise: true },
leadKey: 'mapInfo.tab.dashboard-plus-grid.lead',
takeawayKey: 'mapInfo.tab.dashboard-plus-grid.takeaway',
// Big Picture's export is a spreadsheet, so its example is a real excerpt of one (sr): the six
// largest groups by population among those joinable locally, of the full list's 2,107. The column
// headers are the live table's own words, COPIED verbatim from dashboard.json into mapInfo.col (all
// eleven locales): naming dashboard.* here pulled the whole 'dashboard' area into this bundle
// (+50 KB raw). If the table's headers are reworded, copy them again. Cell text stays untranslated
// because the live table's cells are not translated either.
examples: [{
kind: 'table',
columns: [{ key: 'mapInfo.col.peopleGroup' }, { key: 'mapInfo.col.country' },
{ key: 'mapInfo.col.region' }, { key: 'mapInfo.col.population' }],
rows: [
['Hararghe Oromo', 'Ethiopia', 'Africa', 6355000],
['Gaaliin', 'Sudan', 'Middle East', 4520000],
['White Moor', 'Morocco', 'Middle East', 3120000],
['Baloch, Western', 'Iran', 'Middle East', 3035000],
['Baloch, Eastern', 'Pakistan', 'Asia', 2980000],
['Bhuiyar', 'India', 'Asia', 2925000],
],
total: 2107,
}],
},
'research-map-plus-grid': {
storageKey: 'research-map-plus-grid',
pos: { top: '56px', side: '56px' },
leadKey: 'mapInfo.tab.research-map-plus-grid.lead',
takeawayKey: 'mapInfo.tab.research-map-plus-grid.takeaway',
// Its own poster joins when rega's shot is in the tree.
examples: [
{ kind: 'pages', pages: MEC_PAGES, titleKey: 'mapInfo.tab.research-map-plus-grid.examplePacket' },
],
},
'prayer-map': {
storageKey: 'prayer-map',
// The map draws the button as the top of its own control column (MapInfoButton); pos still places
// this component's fallback button, shown whenever that control is not on screen.
trigger: 'control',
pointerKeep: true,
pos: { side: '56px' },
leadKey: 'mapInfo.tab.prayer-map.lead',
takeawayKey: 'mapInfo.tab.prayer-map.takeaway',
examples: [],
},
'cities-internet': {
storageKey: 'cities-internet',
pos: { side: '56px', narrow: { side: '4px' } },
leadKey: 'mapInfo.tab.cities-internet.lead',
takeawayKey: 'mapInfo.tab.cities-internet.takeaway',
examples: [],
},
'gospel-resources': {
storageKey: 'gospel-resources',
pos: { side: '56px', narrow: { side: '4px' } },
leadKey: 'mapInfo.tab.gospel-resources.lead',
takeawayKey: 'mapInfo.tab.gospel-resources.takeaway',
examples: [],
},
}