/posterverse mounts the Posterverse shell (<posterverse-app>) from the site's own /dx-maps tree as a full-page app with no site header, the same way /deep-research mounts the Research Gallery. It is in no menu and not indexed; ?tab=<row id> opens one plugin. The site's language drives the shell's, and the shell's "Back to homepage" leads to this site's home in the reader's language.
141 lines
5.5 KiB
Vue
141 lines
5.5 KiB
Vue
<script setup lang="ts">
|
|
// Posterverse: every poster plugin (UPG 100 List Posters, Country Posters, Poster Design and the poster
|
|
// shelf of experimental plugins) in one full-page app, the poster twin of Deep Research. Like Deep
|
|
// Research, this page is HIDDEN: it is in no menu and links nowhere, and robots are told not to index
|
|
// it. It is reached by its address, and ?tab=<row id> opens one plugin directly (the shell reads it).
|
|
//
|
|
// The shell is a web component served from this site's OWN /dx-maps/ tree, the same copy the Deep
|
|
// Research page uses (see DX-MAPS-GALLERY.md): one script, one element, <posterverse-app>. A custom
|
|
// element's name needs a hyphen, which is why the tag is not just <posterverse>.
|
|
//
|
|
// The element is created in onMounted rather than written in the template: it is a custom element
|
|
// that only exists in the browser, so this keeps it out of SSR and out of Vue's component resolver
|
|
// without any nuxt.config change.
|
|
|
|
import { mapboxTokenIfChosen } from '~/utils/mapProvider'
|
|
|
|
// A full-page app on every screen: no layout, so no site header and no footer. The way back to the site
|
|
// is the shell's own "Back to homepage" button, directly above its Collapse button (home-href below),
|
|
// so the shell fills the whole screen. The page itself never scrolls (the shell's own panes scroll).
|
|
definePageMeta({ layout: false })
|
|
|
|
const { t, locale, locales } = useI18n()
|
|
const localePath = useLocalePath()
|
|
const runtimeConfig = useRuntimeConfig()
|
|
|
|
// The dx-maps tree: NUXT_PUBLIC_DX_MAPS_BASE when set (a maps CDN), otherwise this site's own
|
|
// /dx-maps/ copy. The script and every embed URL come from the same tree.
|
|
const DX_MAPS_BASE = ((runtimeConfig.public as { dxMapsBase?: string }).dxMapsBase || '').replace(/\/+$/, '')
|
|
const POSTERVERSE_SCRIPT = `${DX_MAPS_BASE || '/dx-maps'}/posterverse/posterverse.js`
|
|
|
|
useHead({
|
|
title: () => t('Posterverse'),
|
|
meta: [{ name: 'robots', content: 'noindex' }],
|
|
script: [{ src: POSTERVERSE_SCRIPT, defer: true, key: 'posterverse' }]
|
|
})
|
|
|
|
// With no layout, this page sets <html lang dir> itself, as the public layout does for every other
|
|
// page: in Arabic the shell reads right to left.
|
|
const dir = computed(() => {
|
|
const loc = locales.value.find(l => typeof l === 'object' && 'code' in l && l.code === locale.value)
|
|
return (typeof loc === 'object' && loc && 'dir' in loc && loc.dir) ? loc.dir : 'ltr'
|
|
})
|
|
useHead(() => ({
|
|
htmlAttrs: {
|
|
lang: locale.value,
|
|
dir: dir.value
|
|
}
|
|
}))
|
|
|
|
const host = ref<HTMLElement | null>(null)
|
|
let shell: HTMLElement | null = null
|
|
|
|
onMounted(() => {
|
|
shell = document.createElement('posterverse-app')
|
|
// Every embed URL is built at RUNTIME: from NUXT_PUBLIC_DX_MAPS_BASE when set, otherwise from the
|
|
// origin serving this page, so the same build works on any host (laptop, staging, production) with
|
|
// nothing hardcoded.
|
|
shell.setAttribute('tree-base', DX_MAPS_BASE || `${window.location.origin}/dx-maps`)
|
|
// The site's language drives the shell's (and every plugin's); it follows the language switcher.
|
|
shell.setAttribute('language', locale.value)
|
|
// The way back to the site: the shell draws it above its Collapse button, in the site's own words.
|
|
shell.setAttribute('home-href', localePath('/'))
|
|
shell.setAttribute('home-label', t('Back to homepage'))
|
|
// MapLibre (keyless) by default; Mapbox only when NUXT_PUBLIC_MAP_PROVIDER=mapbox and a token is set.
|
|
// The provider is always explicit: a token alone never selects Mapbox in dx-maps.
|
|
const shellConfig: Record<string, unknown> = {}
|
|
const token = mapboxTokenIfChosen(runtimeConfig.public)
|
|
const provider = token ? 'mapbox' : 'maplibre'
|
|
shellConfig.provider = provider
|
|
if (token) shellConfig.tk = token
|
|
shell.setAttribute('provider', provider)
|
|
shell.setAttribute('profile-config', JSON.stringify(shellConfig))
|
|
host.value?.appendChild(shell)
|
|
})
|
|
|
|
watch(locale, (lang) => {
|
|
shell?.setAttribute('language', lang)
|
|
shell?.setAttribute('home-href', localePath('/'))
|
|
shell?.setAttribute('home-label', t('Back to homepage'))
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
shell?.remove()
|
|
shell = null
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="posterverse-page">
|
|
<h1 class="visually-hidden">
|
|
{{ t('Posterverse') }}
|
|
</h1>
|
|
<div
|
|
ref="host"
|
|
class="posterverse-host"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.posterverse-page {
|
|
/* 100svh: the phone's SMALL viewport, so the browser's own toolbars never cover the shell. */
|
|
height: 100svh;
|
|
}
|
|
/* The shell takes the whole screen, so there is no page scrollbar. */
|
|
.posterverse-host {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
/* 100%, not the element's own 85vh default: here the shell is the whole page. */
|
|
.posterverse-host :deep(posterverse-app) {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.visually-hidden {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
/* Not scoped, and keyed to this page's own element, so it applies only while this page is shown and
|
|
no other page changes. With no layout, the site's stylesheet may not be loaded here: the page sets
|
|
the two things it needs itself, no body margin and no page scroll. */
|
|
html:has(.posterverse-page),
|
|
html:has(.posterverse-page) body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
/* The site keeps room for a scrollbar on every page (scrollbar-gutter: stable, base/_globals.scss).
|
|
This page never scrolls, so on a desktop that room would only be an empty strip at the right edge. */
|
|
html:has(.posterverse-page) {
|
|
scrollbar-gutter: auto;
|
|
}
|
|
</style>
|