feat: Social-Bild-Modal mit QR-Code für Kalender-Termine
Deploy / verify (push) Successful in 1m7s
Deploy / deploy (push) Successful in 1m39s

Single Button öffnet Vorschau-Modal mit beiden Formaten side-by-side.
Pro Format Download-Button. ESC + Click-outside schließen.

Endpoint generiert zusätzlich QR-Code (qrcode → PNG-Buffer), der
direkt auf den Termin im Kalender-Grid verweist
(/kalender/#event-<slug>). Pin-Icon, QR und Layout für OG (1200x630)
und Square (1080x1080).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-06-06 10:25:07 +02:00
parent f0d9fecfb8
commit ace3562111
4 changed files with 135 additions and 43 deletions
+93 -21
View File
@@ -446,6 +446,16 @@
let copyToastKey = $state<string | null>(null);
let modalImage = $state<{ src: string; alt: string } | null>(null);
let linkedSlug = $state<string | null>(null);
let socialModal = $state<{ slug: string; title: string } | null>(null);
function openSocial(item: EventCardItem) {
if (!item._slug) return;
socialModal = { slug: item._slug, title: item.title || "Termin" };
}
function closeSocial() {
socialModal = null;
}
function eventId(item: EventCardItem): string {
if (item._slug) return `event-${item._slug}`;
@@ -887,34 +897,20 @@
</button>
{/if}
{#if item._slug}
<a
href={`/api/social/calendar/${item._slug}?format=og`}
download={`termin-${item._slug}-og.png`}
<button
type="button"
onclick={() => openSocial(item)}
class="btn-outline btn-sm group"
aria-label="Social-Bild (Quer 1200×630)"
title="Social-Bild Quer (1200×630)"
aria-label="Social-Bild als Vorschau öffnen"
title="Social-Bild Vorschau"
>
<Icon
icon="mdi:image-outline"
class="size-3.5 shrink-0"
aria-hidden="true"
/>
<span class="whitespace-nowrap">Bild quer</span>
</a>
<a
href={`/api/social/calendar/${item._slug}?format=square`}
download={`termin-${item._slug}-square.png`}
class="btn-outline btn-sm group"
aria-label="Social-Bild (Quadrat 1080×1080)"
title="Social-Bild Quadrat (1080×1080)"
>
<Icon
icon="mdi:crop-square"
class="size-3.5 shrink-0"
aria-hidden="true"
/>
<span class="whitespace-nowrap">Bild quadr.</span>
</a>
<span class="whitespace-nowrap">Social-Bild</span>
</button>
{/if}
<button
type="button"
@@ -1326,6 +1322,82 @@
/>
{/if}
{#if socialModal}
{@const ogUrl = `/api/social/calendar/${socialModal.slug}?format=og`}
{@const sqUrl = `/api/social/calendar/${socialModal.slug}?format=square`}
<div
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 p-4"
role="dialog"
aria-modal="true"
aria-label="Social-Bild-Vorschau"
onclick={closeSocial}
onkeydown={(e) => { if (e.key === 'Escape') closeSocial(); }}
tabindex="-1"
>
<div
class="relative max-h-[92vh] w-full max-w-4xl overflow-y-auto rounded-lg bg-white p-5 shadow-2xl"
onclick={(e) => e.stopPropagation()}
role="presentation"
>
<div class="mb-4 flex items-start justify-between gap-3">
<div class="min-w-0">
<p class="text-xs uppercase tracking-wider text-stein-500">Social-Bild</p>
<p class="truncate text-sm font-semibold text-stein-800">{socialModal.title}</p>
</div>
<button
type="button"
onclick={closeSocial}
class="shrink-0 text-stein-400 hover:text-stein-700"
aria-label="Schließen"
>
<Icon icon="mdi:close" class="size-5" />
</button>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-2">
<p class="text-xs font-medium text-stein-600">Quer · 1200 × 630</p>
<img
src={ogUrl}
alt="Social-Bild Quer"
class="w-full rounded-sm border border-stein-200 bg-stein-50"
loading="eager"
/>
<a
href={ogUrl}
download={`termin-${socialModal.slug}-og.png`}
class="btn-outline btn-sm justify-center no-underline!"
>
<Icon icon="mdi:download" class="size-3.5" />
Quer herunterladen
</a>
</div>
<div class="flex flex-col gap-2">
<p class="text-xs font-medium text-stein-600">Quadrat · 1080 × 1080</p>
<img
src={sqUrl}
alt="Social-Bild Quadrat"
class="mx-auto w-full max-w-[300px] rounded-sm border border-stein-200 bg-stein-50 sm:max-w-none"
loading="eager"
/>
<a
href={sqUrl}
download={`termin-${socialModal.slug}-square.png`}
class="btn-outline btn-sm justify-center no-underline!"
>
<Icon icon="mdi:download" class="size-3.5" />
Quadrat herunterladen
</a>
</div>
</div>
<p class="mt-4 text-[11px] text-stein-400">
Die Bilder werden automatisch generiert. Für Posts auf Instagram, Facebook, WhatsApp oder als Hintergrund-Grafik.
</p>
</div>
</div>
{/if}
<style>
/* Heute-Markierung im Kalendergrid: dezenter Rand + leichtes Glow.
Ergänzt das Event-Badge, damit auch leere Tage als "Heute"