web: focus modal, slice 8 of M3

Clicking a panel's network (cursor zoom-in, a real button for keyboard
users) or its menu's Focus item opens the modal: the same SVG at the
same global round rendered up to 90vw/85vh over the spec scrim, with a
second PlayerBar docked in the footer sharing the store. Only the page
PlayerBar owns the global keyboard map, so keys are never handled
twice.

role=dialog with aria-modal, labelled by the panel name, focus moves in
and is trapped, Esc/X/scrim close, and focus returns to the opener.
focusPanelId already lives in the URL, so shared links open
pre-focused; on mobile the modal becomes a full-screen sheet.
This commit is contained in:
Justin Visser 2026-06-10 17:55:36 +02:00
parent eb1f7427c4
commit 8929eeddb0
4 changed files with 260 additions and 3 deletions

View file

@ -3,6 +3,7 @@ import { onMounted } from 'vue'
import AppBar from '@/components/AppBar.vue'
import ControlsCard from '@/components/ControlsCard.vue'
import ErrorBanner from '@/components/ErrorBanner.vue'
import FocusModal from '@/components/FocusModal.vue'
import FooterDisclaimer from '@/components/FooterDisclaimer.vue'
import HeroHeadline from '@/components/HeroHeadline.vue'
import LegendRow from '@/components/LegendRow.vue'
@ -45,6 +46,7 @@ onMounted(async () => {
/>
</main>
<FooterDisclaimer />
<FocusModal v-if="store.state.focusPanelId" />
<div class="visually-hidden" aria-live="polite">{{ store.state.announcement }}</div>
</template>