Each gallery tab can explain itself: the MapInfo button, per tab, from src/tabInfo.js

The library MapInfo (an info button, a one-time pointer and a popup of real, pre-generated output)
is mounted in each pane whose tab has an entry in src/tabInfo.js, with the gallery's live locale and
its assets base. UPG-100 is the first entry, with its poster; its packet pages and card deck join
when their files are in the tree, because a missing file would show "not available" to the audience.
Regions and the other tabs follow as their owners' keys land. profile-config info:false removes the
buttons.
This commit is contained in:
Doxa Maps 2026-09-29 19:00:06 +00:00
parent 2a7a0d88e3
commit 462c0babf7
7 changed files with 146 additions and 17 deletions

View file

@ -136,6 +136,10 @@
<button type="button" class="dms-mobile-notice-x" :aria-label="tr('dismiss')" @click="dismissMobileNotice(t.id)">×</button>
</div>
<div class="dms-slot" :ref="el => setSlot(i, el)"></div>
<!-- The tab's "what can this do for you?" button (library MapInfo), from src/tabInfo.js. It sits
over the pane, top-right; profile-config info:false removes it. -->
<MapInfo v-if="infoFor(t)" :key="'info-' + t.id" v-bind="infoFor(t)"
:assets-base="assetsBase" :locale="effectiveLocale" />
<div v-if="t.state === 'loading'" class="dms-hint">{{ tr('loading', { label: tabLabel(t) }) }}</div>
<div v-else-if="t.state === 'error'" class="dms-hint err">
{{ tr('loadFailed', { label: tabLabel(t) }) }}<br><small>{{ t.error }}</small>
@ -198,6 +202,8 @@ import { SWITCHER_LANGUAGES, loadLocale, saveLocale, getLanguage } from '@map/la
import { translate, LOCALES, hasKey } from './src/strings.js'
import { tStatic } from '@map/i18n/index.js'
import { helpFor } from './src/experimentalHelp.js'
import { TAB_INFO } from './src/tabInfo.js'
import MapInfo from '@map/components/info/MapInfo.vue'
import { loadScriptOnce, whenTagDefined } from '@map/utils/embedScript.js'
// THE HOST PAGE'S LANGUAGE, by the same ladder every nested map already uses (library/i18n
@ -746,7 +752,7 @@ export default defineComponent({
// (measured 2026-09-25). .vtl survives today only because it declares display:grid !important.
// Everything the layout needs (dir, lang, labels, …) is passed explicitly below.
inheritAttrs: false,
components: { VerticalTabsLayout, BackToTopButton },
components: { VerticalTabsLayout, BackToTopButton, MapInfo },
// profile-config props the standalone leaf page + gallery hand every element. treeBase lets a
// host pin the tree root explicitly; the rest are accepted so the element is a well-behaved
// member of the bundler (profile/instanceId/assetsBase/tk/shareUrl/qa are ignored here — the
@ -926,6 +932,7 @@ export default defineComponent({
// gone — every row is in the rail on every width, same order, always). The ONLY width-driven
// thing left is which row is DISABLED (Big Picture, on mobile — see isDesktopOnly) and
// which row auto-opens (see firstVisibleIndex) — both presentation, neither touches NAV_MODEL.
showInfo() { const v = this.cfg.info; return !(v === false || v === 'false' || v === 'off') },
headingText() { return String(this.cfg.heading || this.heading || '').trim() || 'Deep Research' },
// profile-config `experimental: false` (or "hidden") hides the Experimental folder: no discovery, no
// rows, and a tab= request for an experimental map falls back to the first visible tab. OFF only on
@ -1090,6 +1097,7 @@ export default defineComponent({
this.$nextTick(() => back && back.focus && back.focus())
},
// Does this tab have what/how text? Marks its pane, so a map's hosted "?" knows to show.
infoFor(t) { return (this.showInfo && t && TAB_INFO[t.id]) || null },
tabHasHelp(t) { return !!(t && t.discovered && helpFor(t.id, this.uiLocale)) },
// A map's "?" (library HelpButton) asked for help. Answer only for the active tab, and only when
// it has text; remember the exact button (composedPath reaches into the map's shadow root) so

View file

@ -0,0 +1,25 @@
/**
* 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. The words are keys in the tab
* owner's own catalogue area, in all eleven locales; this file only chooses them. Each OWNER supplies
* its entry (the keys and the example files), and the example files are real, pre-generated output
* under the tree's assets folder, so the popup opens instantly.
*
* ★ ADD AN EXAMPLE ONLY WHEN ITS FILE IS IN THE TREE. A missing file renders the popup's "not
* available" box in front of the audience; an example that is not there yet is simply left out.
* ★ 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.
*/
export const TAB_INFO = {
'upg-100': {
storageKey: 'upg-100',
leadKey: 'upgMap.infoLead',
takeawayKey: 'upgMap.infoTakeaway',
// The packet pages (12) and the card deck (70) join this list when their pre-generated shots land
// in assets/previews/examples/middle-east-core/.
examples: [
{ kind: 'image', src: 'previews/middle-east-core.webp', full: 'posters/middle-east-core.html' },
],
},
}

File diff suppressed because one or more lines are too long

View file

@ -61,7 +61,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
<script src="../../doxa-maps-spa.js" integrity="sha384-fcZL1E2HBskJzSeQWcuOZOzYzrWgmPOuCA7A1HUbRiwlMLO6MBVljVETnQ7sV+7t" crossorigin="anonymous"></script>
<script src="../../doxa-maps-spa.js" integrity="sha384-O+IEAMO2MwjG73ciiwXmUPpz7F2LvZY9lVd2strz701vBz/lT4yQEJzzzl+TdcaP" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -61,7 +61,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
<script src="../doxa-maps-spa.js" integrity="sha384-fcZL1E2HBskJzSeQWcuOZOzYzrWgmPOuCA7A1HUbRiwlMLO6MBVljVETnQ7sV+7t" crossorigin="anonymous"></script>
<script src="../doxa-maps-spa.js" integrity="sha384-O+IEAMO2MwjG73ciiwXmUPpz7F2LvZY9lVd2strz701vBz/lT4yQEJzzzl+TdcaP" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -361,7 +361,7 @@
"bundle": "doxa-maps-spa",
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"output": "dx-maps/doxa-maps-spa/doxa-maps-spa.js",
"integrity": "sha384-fcZL1E2HBskJzSeQWcuOZOzYzrWgmPOuCA7A1HUbRiwlMLO6MBVljVETnQ7sV+7t",
"integrity": "sha384-O+IEAMO2MwjG73ciiwXmUPpz7F2LvZY9lVd2strz701vBz/lT4yQEJzzzl+TdcaP",
"elements": [
"doxa-maps-spa"
],
@ -389,7 +389,7 @@
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"embed": {
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-fcZL1E2HBskJzSeQWcuOZOzYzrWgmPOuCA7A1HUbRiwlMLO6MBVljVETnQ7sV+7t\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-O+IEAMO2MwjG73ciiwXmUPpz7F2LvZY9lVd2strz701vBz/lT4yQEJzzzl+TdcaP\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
},
"instances": [
{

View file

@ -361,7 +361,7 @@
"bundle": "doxa-maps-spa",
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"output": "dx-maps/doxa-maps-spa/doxa-maps-spa.js",
"integrity": "sha384-fcZL1E2HBskJzSeQWcuOZOzYzrWgmPOuCA7A1HUbRiwlMLO6MBVljVETnQ7sV+7t",
"integrity": "sha384-O+IEAMO2MwjG73ciiwXmUPpz7F2LvZY9lVd2strz701vBz/lT4yQEJzzzl+TdcaP",
"elements": [
"doxa-maps-spa"
],
@ -389,7 +389,7 @@
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
"embed": {
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-fcZL1E2HBskJzSeQWcuOZOzYzrWgmPOuCA7A1HUbRiwlMLO6MBVljVETnQ7sV+7t\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-O+IEAMO2MwjG73ciiwXmUPpz7F2LvZY9lVd2strz701vBz/lT4yQEJzzzl+TdcaP\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
},
"instances": [
{