From daa757d972c4f81f52fbf7d97e6fd4a9348bbd3c Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 3 Oct 2026 00:20:35 +0000 Subject: [PATCH 1/2] No India tab on the pilot by dev mode alone: the India preview (its tab and its whole-country poster) needs an explicit opt-in too, ?preview=india or localStorage 'dxm-preview', and every built bundle still compiles the whole read away. The gates hold the opt-in and that dev mode alone stays withheld. --- library/packet/countrySheet.js | 19 ++++++++++++----- .../country-posters/country-posters-mfe.vue | 13 +++++++----- .../scripts/check-country-sheet-one-path.mjs | 5 +++-- .../scripts/check-poster-section.mjs | 21 ++++++++++++++++--- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/library/packet/countrySheet.js b/library/packet/countrySheet.js index 4d05f8c51..95bff38a4 100644 --- a/library/packet/countrySheet.js +++ b/library/packet/countrySheet.js @@ -92,12 +92,21 @@ const largeOf = (item) => (item && LARGE_SHEETS[String(item.iso3 || '').toUpperC */ // WHAT of a whole country is withheld: its POSTER, and/or its ROWS (its prayer list, packet and cards). // India's whole-country poster is withheld EVERYWHERE BY DEFAULT (it is not released yet). It prints only in an -// explicit PREVIEW context: the Vite DEV server (the pilot's Plugin Studio, where it is reviewed), whose element -// bootstrap sets globalThis.DXM_PREVIEW_UNRELEASED. The read is gated on import.meta.env.DEV too, so every BUILT bundle -// compiles it away: no page, embed or console can set a global and print it from production. Never by a URL -// parameter. Its rows (about a thousand pages) stay withheld everywhere. +// explicit PREVIEW context, which needs ALL of: +// - the Vite DEV server (the pilot), whose element bootstrap sets globalThis.DXM_PREVIEW_UNRELEASED; +// - an explicit OPT-IN on that page: ?preview=india in its URL, or localStorage 'dxm-preview' containing 'india'. +// The pilot runs in dev mode, so dev mode alone must NOT show it (the pilot shows what the release shows). +// The whole read is gated on import.meta.env.DEV first, so every BUILT bundle compiles it away (the opt-in included): +// no page, URL, embed or console can print it from production. Its rows (about a thousand pages) stay withheld +// everywhere. export const WITHHELD = { IND: { poster: true, rows: true, preview: true } } -export const previewUnreleased = () => Boolean(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true) +export const PREVIEW_KEY = 'india' +export function previewOptedIn() { + const has = (v) => String(v || '').split(',').map((x) => x.trim()).includes(PREVIEW_KEY) + try { if (new URLSearchParams(globalThis.location?.search || '').getAll('preview').some(has)) return true } catch (_) { /* no URL */ } + try { return has(globalThis.localStorage?.getItem('dxm-preview')) } catch (_) { return false } +} +export const previewUnreleased = () => Boolean(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true && previewOptedIn()) const withheldOf = (item) => (item && !item.indiaState && WITHHELD[String(item.iso3 || '').toUpperCase()]) || null // No caller can open it: the preview state is read HERE, never passed in (so a built bundle cannot print it). export function posterWithheld(item) { const w = withheldOf(item); return Boolean(w?.poster) && !(w.preview && previewUnreleased()) } diff --git a/plugin-designs/country-posters/country-posters-mfe.vue b/plugin-designs/country-posters/country-posters-mfe.vue index 88bc2dd64..3d17c37c1 100644 --- a/plugin-designs/country-posters/country-posters-mfe.vue +++ b/plugin-designs/country-posters/country-posters-mfe.vue @@ -35,6 +35,7 @@ import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pan import { useShadowStyles } from '@map/composables/useShadowStyles.js' import ViewSelect from '@map/components/ViewSelect.vue' import { countryName } from '@map/utils/countryName.js' +import { previewOptedIn } from '@map/packet/countrySheet.js' import { getRuntimeLocale, detectLocale } from '@map/i18n/index.js' import ShareButton from '@map/components/map-controls/ShareButton.vue' import { useI18n } from 'vue-i18n' @@ -70,8 +71,9 @@ const spec = computed(() => ({ // strip desynchronises from the scroll column. panes: [ { id: 'wall', label: t('tabs.countryPosters'), at: [0, 0], config: { profile: 'country-poster-wall' } }, - // the same DEV-gated read as countrySheet.js previewUnreleased(), INLINE so every build drops the pane entirely - ...(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true ? [INDIA_PANE] : []), + // the same DEV-gated read as countrySheet.js previewUnreleased() (dev server AND the explicit opt-in), INLINE so + // every build drops the pane entirely + ...(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true && previewOptedIn() ? [INDIA_PANE] : []), { id: 'map', label: t('tabs.map'), at: [2, 0], config: { profile: 'countries-map' } }, ], // The switcher lives in this element's own header, so the layout's "… below ↓" @@ -80,9 +82,10 @@ const spec = computed(() => ({ })) // INDIA: the whole-India poster (66x66 by default) on its own tab, the same wall filtered to India, labelled with -// India's own name in the reader's language (Intl). Shown ONLY in a preview context (countrySheet.js -// previewUnreleased(): the Plugin Studio on the pilot), because the poster is not released; everywhere else the tab -// is absent and India's card keeps the withheld sentence. +// India's own name in the reader's language (Intl). Shown ONLY in an opted-in preview (countrySheet.js +// previewUnreleased(): the pilot's dev server AND ?preview=india or localStorage 'dxm-preview'), because the poster is +// not released; everywhere else (the pilot without the opt-in included) the tab is absent and India's card keeps the +// withheld sentence. const INDIA_PANE = { id: 'india', label: countryName('IND', getRuntimeLocale() || detectLocale() || 'en') || 'India', at: [1, 0], config: { profile: 'country-poster-wall', countries: ['IND'] } } const stage = ref(null) diff --git a/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs b/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs index 8bc30174e..b56aba054 100644 --- a/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs +++ b/plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs @@ -221,7 +221,7 @@ export async function armG(cs, srcs, mfeSrc = '') { if (!/rowsWithheld\((?:c|item)\) \? (?:withheld\(\)|Promise\.reject\(keyedError\('messages\.countryPosterTooMany')/.test(src)) fails.push(`${name}: withheld ROWS are not withheld (its list/packet/cards would print)`) if (/sheetWithheld\(/.test(src)) fails.push(`${name}: still reads sheetWithheld() (ambiguous: poster or rows?)`) } - if (mfeSrc && !/\.\.\.\(import\.meta\.env && import\.meta\.env\.DEV && globalThis\.DXM_PREVIEW_UNRELEASED === true \? \[INDIA_PANE\] : \[\]\)/.test(mfeSrc)) fails.push('country-posters-mfe: the India tab is not gated on the DEV-only preview read') + if (mfeSrc && !/\.\.\.\(import\.meta\.env && import\.meta\.env\.DEV && globalThis\.DXM_PREVIEW_UNRELEASED === true && previewOptedIn\(\) \? \[INDIA_PANE\] : \[\]\)/.test(mfeSrc)) fails.push('country-posters-mfe: the India tab is not gated on the DEV-only preview read AND the explicit opt-in (previewOptedIn())') // the BUILT tree never mentions the flag (every read is compiled away) try { const pub = join(ROOT, 'public/dx-maps') @@ -298,7 +298,8 @@ async function main() { ['an opener argument comes back', async () => armG({ ...CS, posterWithheld: (it, open) => CS.posterWithheld(it) }, lists(files[wall]), MFE)], ['the preview read without DEV (a global opens production)', async () => armG({ ...CS, previewUnreleased: () => globalThis.DXM_PREVIEW_UNRELEASED === true }, lists(files[wall]), MFE)], ['India\'s rows un-withheld', async () => armG({ ...CS, rowsWithheld: () => false }, lists(files[wall]), MFE)], - ['the India tab shown without the preview flag', async () => armG(CS, lists(files[wall]), MFE.replace('...(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true ? [INDIA_PANE] : []),', 'INDIA_PANE,'))], + ['the India tab shown without the preview flag', async () => armG(CS, lists(files[wall]), MFE.replace('...(import.meta.env && import.meta.env.DEV && globalThis.DXM_PREVIEW_UNRELEASED === true && previewOptedIn() ? [INDIA_PANE] : []),', 'INDIA_PANE,'))], + ['the India tab shown on the pilot by dev mode alone', async () => armG(CS, lists(files[wall]), MFE.replace(' && previewOptedIn() ? [INDIA_PANE]', ' ? [INDIA_PANE]'))], ['the wall prints the list of a country whose rows are withheld', async () => armG(CS, lists(files[wall].replace('rowsWithheld(c) ? withheld() : ', '')))], ['the library type prints withheld rows', () => armH(TYPE.replace('rowsWithheld(c) ? ', 'false ? '))], ['the library type shows a withheld poster', () => armH(TYPE.replace('posterWithheld(c) ||', 'false ||'))], diff --git a/plugin-designs/country-posters/scripts/check-poster-section.mjs b/plugin-designs/country-posters/scripts/check-poster-section.mjs index b432f21c7..942504689 100644 --- a/plugin-designs/country-posters/scripts/check-poster-section.mjs +++ b/plugin-designs/country-posters/scripts/check-poster-section.mjs @@ -51,17 +51,31 @@ async function arms({ types, country, element, sectionSrc, viewSrc, cs }) { if (!/countryPosterTooMany/.test(String(why))) fails.push(`C: withheld India's rows are not withheld (${why})`) if (fetched) fails.push('C: withheld India fetched records') } - // C2. THE PREVIEW, under Vite (import.meta.env.DEV is true here, as on the pilot's dev server): only with the global - // set does India's whole-country poster open, at 60x96; its rows stay withheld; without it, withheld. + // C2. THE PREVIEW, under Vite (import.meta.env.DEV is true here, as on the pilot's dev server): DEV MODE ALONE (the + // global set by the dev bootstrap) does NOT open India: the pilot shows what the release shows. Only with the + // explicit opt-in too (?preview=india, or localStorage 'dxm-preview') does the poster open, at 60x96; its rows + // stay withheld; without the global, withheld. const was = globalThis.DXM_PREVIEW_UNRELEASED + const loc = Object.getOwnPropertyDescriptor(globalThis, 'location'), ls = Object.getOwnPropertyDescriptor(globalThis, 'localStorage') + const setLoc = (search) => Object.defineProperty(globalThis, 'location', { value: { search }, configurable: true, writable: true }) + const setLs = (v) => Object.defineProperty(globalThis, 'localStorage', { value: { getItem: (k) => (k === 'dxm-preview' ? v : null) }, configurable: true, writable: true }) try { globalThis.DXM_PREVIEW_UNRELEASED = true + setLoc(''); setLs(null) + if (!cs.posterWithheld({ iso3: 'IND' }) || cs.largeSheet({ iso3: 'IND' })) fails.push('C2: dev mode ALONE (no opt-in) opens India: the pilot would show the withheld poster') + setLs('india') + if (cs.posterWithheld({ iso3: 'IND' })) fails.push("C2: localStorage 'dxm-preview'='india' does not open the preview") + setLs(null); setLoc('?lang=en&preview=india') const big = cs.largeSheet({ iso3: 'IND' }) if (cs.posterWithheld({ iso3: 'IND' }) || !big || big.format !== '60x96') fails.push(`C2: in a DEV preview India's poster does not open at 60x96 (${big && big.format})`) if (!cs.rowsWithheld({ iso3: 'IND' })) fails.push("C2: the preview also released India's rows") delete globalThis.DXM_PREVIEW_UNRELEASED if (!cs.posterWithheld({ iso3: 'IND' })) fails.push('C2: without the preview global India is not withheld') - } finally { if (was === undefined) delete globalThis.DXM_PREVIEW_UNRELEASED; else globalThis.DXM_PREVIEW_UNRELEASED = was } + } finally { + if (was === undefined) delete globalThis.DXM_PREVIEW_UNRELEASED; else globalThis.DXM_PREVIEW_UNRELEASED = was + if (loc) Object.defineProperty(globalThis, 'location', loc); else delete globalThis.location + if (ls) Object.defineProperty(globalThis, 'localStorage', ls); else delete globalThis.localStorage + } // D const fake = (attrs) => ({ getAttribute: (n) => (n in attrs ? attrs[n] : null), hasAttribute: (n) => n in attrs }) const d0 = element.posterSectionConfig(fake({ scope: 'africa' })) @@ -121,6 +135,7 @@ try { ["a registry without 'country'", { ...real, types: { ...types, posterTypeNames: () => [] } }, /^A:/], ['a region key without "&" → "and"', { ...real, country: { ...country, items: (s, l) => (/-and-/.test(String(s)) ? [] : country.items(s, l)) } }, /^B:/], ['the preview ignored under DEV', { ...real, cs: { ...cs, posterWithheld: () => true, largeSheet: () => null } }, /^C2:/], + ['dev mode alone opens India (no opt-in)', { ...real, cs: { ...cs, posterWithheld: () => false, largeSheet: () => ({ format: '60x96', formats: ['60x96'] }) } }, /^C2: dev mode ALONE/], ['a section that uses a name it does not inject', { ...real, sectionSrc: sectionSrc.replace('', '{{ notProvided }}') }, /^E:/], ['an element config that drops show-header', { ...real, element: { posterSectionConfig: (el) => { const c = element.posterSectionConfig(el); delete c.showHeader; return c } } }, /^D: show-header="false"/], ['a view that does not pass show-header to the wall', { ...real, viewSrc: viewSrc.replace(':section-heads="showHeader"', '') }, /^F:/], From 0777bd6d1cf5256ce9812606c9f5087c3c707a07 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 3 Oct 2026 04:17:42 +0000 Subject: [PATCH 2/2] Regions dashboard: two views again, Regions List and Region Dashboards; the Country Posters view and its profile are removed (the list's per-region country posters stay) --- .../reference-capability-exemplars.md | 1 - .../element-census/regions-dashboard.json | 2 +- .../country-poster-sections.meta.json | 5 - .../country-poster-sections.vue | 66 ----- .../regions-dashboard/regions-list.vue | 9 +- .../regions-dashboard/regions-mfe.vue | 28 +- public/README.md | 2 +- public/dx-maps/doxa-maps-spa/manifest.json | 30 +-- public/dx-maps/manifest.json | 30 +-- .../country-poster-sections/index.html | 250 ------------------ .../region-dashboards/index.html | 2 +- .../regions-dashboard/regions-dashboard.js | 186 ++++++------- .../regions-dashboard/regions-list/index.html | 2 +- .../regions-dashboard/regions-mfe/index.html | 2 +- 14 files changed, 118 insertions(+), 497 deletions(-) delete mode 100644 plugin-designs/regions-dashboard/country-poster-sections.meta.json delete mode 100644 plugin-designs/regions-dashboard/country-poster-sections.vue delete mode 100644 public/dx-maps/regions-dashboard/country-poster-sections/index.html diff --git a/documentation/reference-capability-exemplars.md b/documentation/reference-capability-exemplars.md index 5fdf202cb..2a5baa455 100644 --- a/documentation/reference-capability-exemplars.md +++ b/documentation/reference-capability-exemplars.md @@ -57,7 +57,6 @@ Generated from 30 bundle(s). | `poster-design` | `poster-sizes` | `single` | | `poster-design` | `poster-styles` | `single` | | `prayer-walk-planner` | `prayer-walk-planner` | `single` | -| `regions-dashboard` | `country-poster-sections` | `single` | | `regions-dashboard` | `region-dashboards` | `single` | | `regions-dashboard` | `regions-list` | `single` | diff --git a/internal/element-census/regions-dashboard.json b/internal/element-census/regions-dashboard.json index 024118b31..5b63c1d5a 100644 --- a/internal/element-census/regions-dashboard.json +++ b/internal/element-census/regions-dashboard.json @@ -8,7 +8,7 @@ "neg": { "leaf": "regions-dashboard/regions-list/index.html", "text": "View country posters" }, "sanctioned": [ { - "match": "^(Select view|Regions List|Dashboard|Country Posters)$", "flags": "i", + "match": "^(Select view|Regions List|Dashboard)$", "flags": "i", "what": "the SELECT VIEW tab strip — sanctioned addition #1", "ruling": "the tabs added, and what the dashboard tab shows before a region or country is selected", "date": "2026-09-24", "card": "SPEC-REGIONS-IS-A-CLONE-PLUS-EXACTLY-TWO-ADDITIONS" diff --git a/plugin-designs/regions-dashboard/country-poster-sections.meta.json b/plugin-designs/regions-dashboard/country-poster-sections.meta.json deleted file mode 100644 index 9f3005b33..000000000 --- a/plugin-designs/regions-dashboard/country-poster-sections.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "single", - "label": "Regions — country posters by region", - "note": "The Regions dashboard's third view: one collapsible per region (the library's element, the same cards and print options as the Country Posters wall), starting shut, in the page's locale. The regions are the ones the poster data files its countries under, in the dashboard's region order." -} diff --git a/plugin-designs/regions-dashboard/country-poster-sections.vue b/plugin-designs/regions-dashboard/country-poster-sections.vue deleted file mode 100644 index 9dae22f4b..000000000 --- a/plugin-designs/regions-dashboard/country-poster-sections.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - diff --git a/plugin-designs/regions-dashboard/regions-list.vue b/plugin-designs/regions-dashboard/regions-list.vue index 61da5969c..b659b9270 100644 --- a/plugin-designs/regions-dashboard/regions-list.vue +++ b/plugin-designs/regions-dashboard/regions-list.vue @@ -61,8 +61,8 @@ import { DXLIFE_TOKENS } from './dxlife-tokens.js' // // NOTHING HERE IS REBUILT. Each region's posters are the library's // (library/elements/posterSectionElement.js), the same section, cards and print options as the Country Posters -// wall and the Regions dashboard's third view, so the list is composed from the shared element rather than -// drawing a wall of its own. This file supplies only the toggle and the region. +// wall, so the list is composed from the shared element rather than drawing a wall of its own. This file supplies +// only the toggle and the region. import RegionSectionCard from '@map/components/regions/RegionSectionCard.vue' import { resolveAssetsBase } from '@map/utils/embedBase.js' import { registerPosterSectionElement } from '@map/elements/posterSectionElement.js' @@ -309,9 +309,8 @@ const postersOpen = (region) => openPosters.value.has(region.slug) // the country record and reusing selectCountry() is what makes the button open the dashboard // tab for that country: the requirement is that the view-on-map button opens the country // dashboard page for that country. -// The library's poster sections name themselves 'poster-section-country-' (PosterSectionView.vue). In the -// Regions composite this list is always mounted, so it also answers the third view's sections (Country Posters): -// ONE handler for both, which keeps the region context in the selection. +// The library's poster sections name themselves 'poster-section-country-' (PosterSectionView.vue); selecting +// through selectCountry() keeps the region context in the selection. function onPosterShowOnMap(ev) { const d = ev?.detail || {} if (!String(d.instanceId || '').startsWith('poster-section-country-')) return diff --git a/plugin-designs/regions-dashboard/regions-mfe.vue b/plugin-designs/regions-dashboard/regions-mfe.vue index 34de2472b..2efe33d2f 100644 --- a/plugin-designs/regions-dashboard/regions-mfe.vue +++ b/plugin-designs/regions-dashboard/regions-mfe.vue @@ -24,7 +24,7 @@ * listens for, and scrolls to tab 2. That is the same seam upg-100-mfe uses for the poster * wall's 'show-on-map', and it keeps each pane under its own ProfileLoader/Pinia. */ -import { ref, computed, inject, onMounted, onBeforeUnmount, watch, nextTick } from 'vue' +import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue' import { usePaneConfig } from '@map/composables/usePaneConfig.js' import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js' import { useShadowStyles } from '@map/composables/useShadowStyles.js' @@ -47,11 +47,8 @@ import MapInfoButton from '@map/components/map-controls/MapInfoButton.vue' // them to 97 and fail the gate. The moment someone adds these three strings to all locales, // the strip localises with no code change here. import { useRegionsI18n } from '@map/i18n/useRegionsI18n.js' -// The third view's label is the gallery's own tab name for country posters (tabs.countryPosters, all eleven locales). -import { useOptionalT } from '@map/composables/useOptionalT.js' const { t } = useRegionsI18n() -const tKey = useOptionalT('') const cfg = inject('profileConfig', ref(null)) const uiStore = inject('uiStore', null) const isDark = computed(() => !!uiStore?.isDark) @@ -73,8 +70,6 @@ const spec = computed(() => ({ panes: [ { id: 'list', label: t('Regions List'), at: [0, 0], config: { profile: 'regions-list' } }, { id: 'dashboard', label: t('Region Dashboards'), at: [1, 0], config: { profile: 'region-dashboards' } }, - // THE THIRD VIEW: country posters, one collapsible section per region (the library's ). - { id: 'posters', label: tKey('tabs.countryPosters'), at: [2, 0], config: { profile: 'country-poster-sections' } }, ], // The View switcher lives in this element's own header, so the layout's "… below ↓" pill // would be a second, redundant control (same call upg-100-mfe makes). @@ -151,13 +146,6 @@ function go(id) { column?.scrollTo(id) } -// On a phone the three tabs scroll sideways (see the 600px rule below): keep the current one in view. -const hdr = ref(null) -watch(view, () => nextTick(() => { - const b = hdr.value && hdr.value.querySelector('.seg button[aria-pressed="true"]') - if (b && b.scrollIntoView) b.scrollIntoView({ inline: 'center', block: 'nearest' }) -})) - // ── tab 1 -> tab 2: forward the selection, then swap the visible pane ─────── const SELECT_EVENT = 'doxa-regions-dashboard:select' @@ -181,10 +169,6 @@ function onCountrySelected(e) { forwardSelection({ regionSlug: d.region?.slug || '', countrySlug: d.slug || '' }) } -// A card's "Show on map" in the third view (Country Posters) opens that country's dashboard: the Regions list pane -// (always mounted here) answers every poster section's event with its own selectCountry(), which arrives above as -// 'country-selected'. One handler for both views (regions-list.vue onPosterShowOnMap). - onMounted(() => { if (!stage.value) return column = buildPaneColumn(spec.value, { host: stage.value }) @@ -250,14 +234,6 @@ useShadowStyles(SHADOW_BOX_SIZING_RESET + PANE_GRID_STYLES + '\n' + // ON A PHONE the centred view switcher (about 290 px at 390) leaves no room for two buttons at one end, so the // ⓘ takes the header's START edge, where the floating ⓘ sat before; Share keeps the end. '@media (max-width: 600px){ .rmfe .hdr .hdr-btns > :first-child{ position:absolute; inset-inline-start:12px; top:50%; transform:translateY(-50%); } }' + - // THREE VIEWS DO NOT FIT A PHONE'S HEADER (measured at 390: the tabs need about 390 px and ran under the corner - // buttons). The switcher keeps the room between the two 36 px buttons (12 + 36 + 8 on each side) and its tab row - // scrolls sideways, every label whole; the current tab is scrolled into view on each change. A fieldset is - // min-content wide by default, hence min-width:0. Desktop is unchanged. - '@media (max-width: 600px){ .rmfe .hdr .viewsel{ min-width:0; max-width:calc(100% - 112px); }' + - ' .rmfe .hdr .viewsel .seg{ overflow-x:auto; scrollbar-width:none; scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch; }' + - ' .rmfe .hdr .viewsel .seg::-webkit-scrollbar{ display:none; }' + - ' .rmfe .hdr .viewsel .seg button{ flex:0 0 auto; scroll-snap-align:center; } }' + // Stage: the pane-wrapper column mounts here (its .pane-col is absolute). '.rmfe .stage{ position:relative; flex:1 1 auto; min-height:0; }' + '.pane > .pane-mount > *{ position:absolute; inset:0; width:100%; height:100%; display:block; }', @@ -267,7 +243,7 @@ useShadowStyles(SHADOW_BOX_SIZING_RESET + PANE_GRID_STYLES + '\n' +