diff --git a/app/pages/deep-research/index.vue b/app/pages/deep-research/index.vue index c6a2f1fe..d3e7d506 100644 --- a/app/pages/deep-research/index.vue +++ b/app/pages/deep-research/index.vue @@ -17,6 +17,8 @@ import { mapboxTokenIfChosen } from '~/utils/mapProvider' // ONE scrollbar: no footer here, and the page itself never scrolls; the gallery's own content does. +// The header still leaves as on every other page: it hides while the gallery is scrolled down +// (see watchPaneForHeader) and the gallery then takes the whole screen. definePageMeta({ hideFooter: true }) const { t, locale } = useI18n() @@ -36,6 +38,7 @@ useHead({ const host = ref(null) let gallery: HTMLElement | null = null let resizeObs: ResizeObserver | null = null +let unwatchPane: (() => void) | null = null // The gallery fills the viewport below the header. The header's height differs between desktop // and phone (and with the language), so it is measured, not assumed. @@ -45,6 +48,31 @@ function fitBelowHeader() { document.documentElement.style.setProperty('--rg-header-h', h + 'px') } +// The gallery scrolls inside its own main pane, so a finger (or a wheel) over it scrolls that +// pane and never the page. The header leaves the way it does on every other page: once the pane +// is scrolled down a little, gets rg-header-hidden (the header hides and the gallery takes +// the full screen); back at the pane's top, the header returns. One scroller, so nothing fights +// the pane's own momentum. Only a scroller at least half the gallery's height counts (its main +// pane), not a small list such as a legend sheet's. +function watchPaneForHeader(el: HTMLElement, root: ShadowRoot) { + const onScroll = (e: Event) => { + const pane = e.target + if (!(pane instanceof HTMLElement) || pane.clientHeight < el.clientHeight / 2) return + const cls = document.documentElement.classList + if (pane.scrollTop > 8) cls.add('rg-header-hidden') + else if (pane.scrollTop <= 0) cls.remove('rg-header-hidden') + } + // Scroll events don't bubble, but they do pass the shadow root in the capture phase. + root.addEventListener('scroll', onScroll, { capture: true, passive: true }) + unwatchPane = () => root.removeEventListener('scroll', onScroll, true) +} + +// The gallery attaches its shadow root once its script has defined the element. +function whenShadowRoot(el: HTMLElement, done: (root: ShadowRoot) => void, tries = 50) { + if (el.shadowRoot) done(el.shadowRoot) + else if (tries > 0) setTimeout(() => whenShadowRoot(el, done, tries - 1), 100) +} + onMounted(() => { fitBelowHeader() // The gallery is sized to the viewport below the header; a sub-pixel header height must not add a @@ -76,6 +104,10 @@ onMounted(() => { gallery.setAttribute('provider', provider) gallery.setAttribute('profile-config', JSON.stringify(galleryConfig)) host.value?.appendChild(gallery) + customElements.whenDefined('doxa-maps-spa').then(() => { + const el = gallery + if (el) whenShadowRoot(el, root => watchPaneForHeader(el, root)) + }) }) watch(locale, (lang) => { @@ -83,9 +115,10 @@ watch(locale, (lang) => { }) onBeforeUnmount(() => { - document.documentElement.classList.remove('rg-no-page-scroll') - document.documentElement.classList.remove('rg-gallery-mounted') + document.documentElement.classList.remove('rg-no-page-scroll', 'rg-gallery-mounted', 'rg-header-hidden') resizeObs?.disconnect() + unwatchPane?.() + unwatchPane = null gallery?.remove() gallery = null }) @@ -131,6 +164,15 @@ html.rg-no-page-scroll body { overflow: hidden; } +/* While the gallery is scrolled down, the header is away and the gallery takes the whole screen, + as the header leaves on every other page. Back at the gallery's top, the header returns. */ +html.rg-header-hidden #masthead { + display: none; +} +html.rg-header-hidden .research-gallery-page { + height: 100svh; +} + /* The site header is z-index 1500 (blocks/_header.scss); the gallery's info dialog is a full-viewport layer at z-index 1000, full-screen on a phone, so the header covered the dialog's title and close button there. While the gallery is shown, the header sits just under the dialog: 999 still keeps it, diff --git a/app/pages/research/index.vue b/app/pages/research/index.vue index 4624dcf5..19e8e342 100644 --- a/app/pages/research/index.vue +++ b/app/pages/research/index.vue @@ -51,8 +51,9 @@ useTextHighlight() :profile-config="researchProfileConfig" class="rounded-xlg" /> - -

+ +

{{ t('Want Deeper Research?') }} + +