Update environment configuration, enhance component functionality, and improve project structure. Added Umami analytics configuration to .env.example and updated astro.config.mjs to use the new site URL. Introduced pagination component in BlogOverview.svelte, refactored image handling in ImageGalleryBlock.svelte, and improved SearchableTextBlock.svelte with tooltip support. Updated package.json with new scripts for Firebase deployment and added new dependencies for Storybook and Firebase. Enhanced styling and layout across various components for better user experience.

This commit is contained in:
Peter Meier
2026-02-27 14:13:38 +01:00
parent bb6ec20d45
commit 1f5454d04e
68 changed files with 3668 additions and 265 deletions
+78 -11
View File
@@ -21,7 +21,7 @@
);
const withUrl = $derived(
images
.map((img) => ({ img, url: imageUrl(img) }))
.map((img) => ({ img, url: img.resolvedSrc ?? imageUrl(img) }))
.filter((x): x is { img: ImageGalleryImage; url: string } => x.url != null),
);
@@ -40,6 +40,61 @@
function next() {
currentIndex = (currentIndex + 1) % withUrl.length;
}
const SWIPE_THRESHOLD = 50;
let startX = $state(0);
let isDragging = $state(false);
let dragDelta = $state(0);
function onPointerDown(e: PointerEvent) {
if (withUrl.length <= 1) return;
startX = e.clientX;
isDragging = true;
dragDelta = 0;
(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
}
function onPointerMove(e: PointerEvent) {
if (!isDragging) return;
dragDelta = e.clientX - startX;
if (e.pointerType === "touch" && Math.abs(dragDelta) > 10) e.preventDefault();
}
function onPointerUp() {
if (!isDragging) return;
if (Math.abs(dragDelta) > SWIPE_THRESHOLD) {
if (dragDelta < 0) next();
else prev();
}
isDragging = false;
dragDelta = 0;
startX = 0;
}
function onTouchStart(e: TouchEvent) {
if (withUrl.length <= 1) return;
startX = e.touches[0].clientX;
isDragging = true;
dragDelta = 0;
}
function onTouchMove(e: TouchEvent) {
if (!isDragging || !e.touches[0]) return;
const delta = e.touches[0].clientX - startX;
dragDelta = delta;
if (Math.abs(delta) > 10) e.preventDefault();
}
function onTouchEnd() {
if (!isDragging) return;
if (Math.abs(dragDelta) > SWIPE_THRESHOLD) {
if (dragDelta < 0) next();
else prev();
}
isDragging = false;
dragDelta = 0;
startX = 0;
}
</script>
<div class={layoutClasses} data-block-type="image_gallery" data-block-slug={block._slug}>
@@ -50,22 +105,34 @@
{/if}
{#if withUrl.length === 0}
<p class="text-sm text-zinc-500">Keine Bilder in der Galerie.</p>
<p class="text-sm text-stein-500">Keine Bilder in der Galerie.</p>
{:else if withUrl.length === 1}
<figure class="m-0">
<img
src={withUrl[0].url}
alt={withUrl[0].img.description ?? ""}
class="w-full rounded-sm object-cover aspect-4/3"
class="w-full rounded-sm object-cover aspect-video"
loading="eager"
/>
{#if withUrl[0].img.description}
<figcaption class="mt-1 text-sm text-zinc-600">{withUrl[0].img.description}</figcaption>
<figcaption class="mt-1 text-sm text-stein-600">{withUrl[0].img.description}</figcaption>
{/if}
</figure>
{:else}
<div class="relative">
<div class="relative overflow-hidden rounded-sm bg-zinc-100 aspect-4/3">
<div class="group relative">
<div
class="relative overflow-hidden rounded-sm bg-stein-100 aspect-video touch-pan-y cursor-grab active:cursor-grabbing"
role="region"
aria-label="Galerie: wischen zum Wechseln"
onpointerdown={onPointerDown}
onpointermove={onPointerMove}
onpointerup={onPointerUp}
onpointercancel={onPointerUp}
ontouchstart={onTouchStart}
ontouchmove={onTouchMove}
ontouchend={onTouchEnd}
ontouchcancel={onTouchEnd}
>
{#key currentIndex}
{@const current = withUrl[currentIndex]}
<figure
@@ -80,7 +147,7 @@
/>
{#if current.img.description}
<figcaption
class="absolute bottom-0 left-0 right-0 py-2 px-3 text-sm text-white bg-black/50 rounded-b-sm"
class="absolute bottom-0 left-0 right-0 py-2 px-3 text-xs text-white bg-black/50 rounded-b-sm transition-opacity lg:opacity-0 lg:group-hover:opacity-100"
>
{current.img.description}
</figcaption>
@@ -91,7 +158,7 @@
<button
type="button"
class="absolute left-2 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70 transition-colors focus:outline-none focus:ring-2 focus:ring-green-500"
class="absolute left-2 top-1/2 -translate-y-1/2 w-6 h-6 flex items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70 transition-opacity focus:outline-none focus:ring-2 focus:ring-wald-500 lg:opacity-0 lg:group-hover:opacity-100"
aria-label="Vorheriges Bild"
onclick={prev}
>
@@ -99,7 +166,7 @@
</button>
<button
type="button"
class="absolute right-2 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70 transition-colors focus:outline-none focus:ring-2 focus:ring-green-500"
class="absolute right-2 top-1/2 -translate-y-1/2 w-6 h-6 flex items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70 transition-opacity focus:outline-none focus:ring-2 focus:ring-wald-500 lg:opacity-0 lg:group-hover:opacity-100"
aria-label="Nächstes Bild"
onclick={next}
>
@@ -114,8 +181,8 @@
aria-label="Bild {i + 1}"
aria-selected={i === currentIndex}
class="w-2 h-2 rounded-full transition-colors {i === currentIndex
? "bg-green-600"
: "bg-zinc-300 hover:bg-zinc-400"}"
? "bg-wald-600"
: "bg-stein-300 hover:bg-stein-400"}"
onclick={() => (currentIndex = i)}
></button>
{/each}