diff --git a/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue b/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue index 0b8956fce..468c6d862 100644 --- a/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue +++ b/plugin-designs/research-map-plus-grid/research-map-plus-grid.vue @@ -226,7 +226,36 @@ const SHARE_URL = absoluteEmbedUrl('research-map-plus-grid/research-map-plus-gri // Pinning min === max gives it a definite height to fill while the page keeps growing below. // svh (small viewport height) avoids the mobile URL-bar jump — which matters MORE here than on the // desktop sibling, because this composite is the mobile one. -const MAP_H = '88svh' +// ★ AND IT ENDS ON THE FIRST SCREEN. 88svh assumes the band starts near the top of the window. +// Under a host page's header it does not: measured 2026-09-30 on DX Life at 390x844, the band ran +// from y132 to y875. The legend sheet's bottom and its COLLAPSED strip began 31px below the fold, +// so a reader who collapsed the legend could not reopen it without scrolling first. sizeBand() below +// shortens the band to what is visible beneath its own top (never more than 88svh). The fallback is +// the old 88svh. It is measured once, and again only when the WIDTH changes (a rotation), never on a +// height change: the URL bar showing and hiding must not resize the map. +const MAP_H = 'var(--sr-band-h, 88svh)' +function scrollParentOf(el) { + for (let n = el.parentElement || el.getRootNode?.()?.host; n; n = n.parentElement || n.getRootNode?.()?.host) { + const oy = getComputedStyle(n).overflowY + if (oy === 'auto' || oy === 'scroll') return n + } + return null +} +let _bandWidth = 0 +function sizeBand() { + try { + const host = _selfInstance?.proxy?.$el?.getRootNode?.()?.host + if (!host || !host.style || typeof window === 'undefined') return + const box = scrollParentOf(host) + const bottom = Math.min(window.innerHeight, box ? box.getBoundingClientRect().bottom : window.innerHeight) + const h = Math.floor(Math.min(bottom - Math.max(0, host.getBoundingClientRect().top), window.innerHeight * 0.88)) + // Below 320px the map is worse than a band that runs past the fold, so keep the fallback there. + if (h >= 320) host.style.setProperty('--sr-band-h', h + 'px') + else host.style.removeProperty('--sr-band-h') + _bandWidth = window.innerWidth + } catch (_) { /* no layout (SSR/tests): the 88svh fallback stands */ } +} +function onBandResize() { if (window.innerWidth !== _bandWidth) sizeBand() } // src is resolved by the browser relative to THIS leaf page, which is served two levels deep // (doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html) — so go UP TWO to reach a @@ -494,10 +523,14 @@ onMounted(() => { rootEl = getCurrentInstance()?.proxy?.$el?.getRootNode?.() || null rootEl?.addEventListener?.('groups-filtered', onGridFiltered) rootEl?.addEventListener?.('map-filtered', onMapFiltered) + // After the first layout, so the host page's header has its height. + requestAnimationFrame(sizeBand) + window.addEventListener('resize', onBandResize) }) onBeforeUnmount(() => { rootEl?.removeEventListener?.('groups-filtered', onGridFiltered) rootEl?.removeEventListener?.('map-filtered', onMapFiltered) + window.removeEventListener('resize', onBandResize) }) const sections = computed(() => [