doxa-maps/library/components/LargerScreenNotice.vue
Doxa Maps 51ef6f7ec4 One phone flag per map, and the leaf page follows it as well as the Gallery
A map says how it behaves on a narrow screen once, in its <profile>.meta.json:
"mobile": "desktop-only" (blocked on a phone) or "larger-screens" (opens,
under a dismissible notice). Absent means phone-ready. Until now the rule
lived only in the Research Gallery, as two hard-coded id sets, so a map
opened standalone on its own leaf page on a phone got neither the block nor
the notice.

- profile-meta.mjs knows the vocabulary; generate-tree and the dev manifest
  carry the flag into each profile's manifest entry, and generate-tree into
  the leaf page's BASE_CONFIG (with the map's name for the desktop-only
  sentence).
- ProfileLoader mounts library/components/LargerScreenNotice.vue on a screen
  under 640px: for desktop-only the profile is never mounted and a centred
  message says why; for larger-screens a slim in-flow bar sits above the map
  (it cannot cover the map's controls), dismissible per profile for the
  session, in the element's own locale and direction. Inside the Gallery it
  stays quiet: every Gallery pane now carries data-doxa-gallery.
- One text: the Gallery's two sentences moved into the library catalogue as
  messages.largerScreenNotice / desktopOnlyNotice (+ noticeDismiss), all 11
  locales moved as they were, and the Gallery reads them from there.
- The Gallery derives its rules from the manifest's flags. On a phone it
  mounts nothing until the manifest has landed: its baked first tab (Big
  Picture) would otherwise mount there before the flags say it is
  desktop-only.
- dashboard-plus-grid is desktop-only and gospel-internet/board is
  larger-screens, the two sets the Gallery held; nothing changes for either.
- check:mobile-gating now asserts the vocabulary, that each flagged leaf page
  carries its flag, the Gallery's derivation and its closed-before-manifest
  rule, that Big Picture stays desktop-only, the 11 locales, and the
  notice's mounting. Its self-test plants one failure per rule.
2026-09-29 07:31:16 +00:00

94 lines
3.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
LargerScreenNotice — what a map says on a phone when its <profile>.meta.json declares `mobile`
(SPEC-MOBILE-FLAG). ProfileLoader decides WHETHER it shows (narrow screen, not inside the Research
Gallery); this component only draws the two forms:
mode="larger-screens" a slim bar IN FLOW at the top of the plugin: the map still opens below it.
Not an overlay, so it can never cover the map's own controls. The × emits
`dismiss`; ProfileLoader remembers that per profile for the session.
mode="desktop-only" the whole plugin area: the map is NOT mounted, and the message says why.
ONE TEXT: messages.largerScreenNotice / messages.desktopOnlyNotice are the Research Gallery's own
sentences, moved into the library catalogue so the Gallery and a standalone leaf say the same thing
in all eleven locales. tStatic with the element's locale, not useI18n: this mounts inside elements
that may install no i18n plugin (the same reason PinHint reads tStatic).
-->
<template>
<div v-if="mode === 'desktop-only'" class="lsn-block" role="status">
<p class="lsn-block__text">{{ blockParts[0] }}<bdi v-if="blockParts.length > 1">{{ label }}</bdi>{{ blockParts[1] || '' }}</p>
</div>
<div v-else class="lsn-bar" role="note">
<p class="lsn-bar__text">{{ adviseText }}</p>
<button type="button" class="lsn-bar__x" :aria-label="dismissText" :title="dismissText" @click="$emit('dismiss')">×</button>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { tStatic } from '@map/i18n/index.js'
const props = defineProps({
mode: { type: String, required: true },
// The map's name for the desktop-only sentence ("{label} only supports desktop …").
label: { type: String, default: '' },
// The locale in force for THIS element (ProfileLoader's effectiveLocale), so the notice follows a
// host's language switch instead of the page's boot language.
locale: { type: String, default: '' },
})
defineEmits(['dismiss'])
const T = (key, en) => tStatic(key, props.locale || undefined) || en
const adviseText = computed(() => T('messages.largerScreenNotice',
'This experimental map is built for larger screens. It opens here, but parts of it may not fit or work well on a phone.'))
const dismissText = computed(() => T('messages.noticeDismiss', 'Dismiss'))
// Split on the placeholder rather than replacing it, so the name can sit in its own <bdi>: a Latin map
// name inside an Arabic sentence must not take the sentence's direction.
const blockParts = computed(() => T('messages.desktopOnlyNotice',
'{label} only supports desktop — open this page on a desktop to use it.').split('{label}'))
</script>
<style scoped>
.lsn-bar {
display: flex;
align-items: flex-start;
gap: 8px;
flex: none;
margin: 0;
padding: 8px 8px 8px 12px;
background: #fff8e1;
border-block-end: 1px solid #f0d68a;
color: #4a3b00;
font: 400 13px/1.4 system-ui, -apple-system, "Segoe UI", sans-serif;
}
.lsn-bar__text { flex: 1 1 auto; margin: 0; }
/* 24px is the smallest tap target the fleet accepts; the glyph stays small inside it. */
.lsn-bar__x {
flex: none;
min-width: 24px;
min-height: 24px;
padding: 0;
border: 0;
border-radius: 4px;
background: transparent;
color: inherit;
font: 600 18px/1 system-ui, sans-serif;
cursor: pointer;
}
.lsn-bar__x:focus-visible { outline: 2px solid #1a73e8; outline-offset: 2px; }
.lsn-block {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 200px;
padding: 24px;
background: #f7f7f5;
color: #333;
text-align: center;
font: 400 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}
.lsn-block__text { max-width: 28em; margin: 0; }
</style>