feat(modal): route-targeted content modals on navigation
Deploy / verify (push) Successful in 1m12s
Deploy / deploy (push) Successful in 2m10s

Add a global modal/popup system driven entirely from the CMS. A new
`modal` config collection self-targets routes via a glob/regex urlPattern
(optional time window), and its `content` array of block references renders
through the existing BlockRenderer — so markdown, HTML and YouTube blocks
work exactly like a page row. A `modals` list collection holds the active
set behind an enable flag (analogous to `campaigns`).

- modal-match.ts: glob (`*`/`**`) + `~regex` pathname matcher, time window
  and modal selection helpers (+ unit tests)
- ContentModal.svelte: immediate/delay/scroll/exit_intent triggers, session
  vs. permanent dismiss (sessionStorage + localStorage, keyed by slug+version)
- +layout.server.ts: load `modals-global` in the bootstrap batch, resolve
  block images server-side
- +layout.svelte: mount on non-embed routes, match against the current path

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-07-06 09:47:49 +02:00
parent 121b593e4e
commit 84c41f80b1
5 changed files with 369 additions and 2 deletions
+7
View File
@@ -9,6 +9,7 @@
import AnnouncementBanner from '$lib/components/AnnouncementBanner.svelte';
import DeadlineBannerBlock from '$lib/components/blocks/DeadlineBannerBlock.svelte';
import TranslationProvider from '$lib/components/TranslationProvider.svelte';
import ContentModal from '$lib/components/ContentModal.svelte';
import Breadcrumbs from '$lib/ui/Breadcrumbs.svelte';
import type { LayoutData } from './$types';
import { ensureTransformedImage } from '$lib/rusty-image';
@@ -406,5 +407,11 @@
<Footer footer={data.footerData} translations={data.translations} />
</div>
</div>
<ContentModal
modals={data.modals}
pathname={$page.url.pathname}
translations={data.translations}
/>
{/if}
</TranslationProvider>