feat(T02): Static pages – home, referenzen, kontakt, legal (impressum/datenschutz/agb)

This commit is contained in:
Implementation Engineer
2026-07-10 01:02:27 +02:00
parent e9da21b57e
commit db5080df48
15 changed files with 1618 additions and 109 deletions
+109
View File
@@ -0,0 +1,109 @@
<template>
<Teleport to="body">
<div
v-if="isOpen"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/90"
data-testid="lightbox"
@click.self="$emit('close')"
@keydown.esc="$emit('close')"
tabindex="0"
ref="lightboxRef"
>
<!-- Close Button -->
<button
class="absolute top-4 right-4 text-text text-3xl leading-none p-2 hover:text-accent transition-colors duration-fast"
data-testid="lightbox-close"
aria-label="Schließen"
@click="$emit('close')"
>
&times;
</button>
<!-- Prev Button -->
<button
class="absolute left-4 top-1/2 -translate-y-1/2 text-text text-3xl leading-none p-3 hover:text-accent transition-colors duration-fast"
data-testid="lightbox-prev"
aria-label="Vorheriges Bild"
@click="$emit('prev')"
>
&#8249;
</button>
<!-- Image -->
<div class="max-w-4xl max-h-[80vh] px-16">
<img
:src="image.src"
:alt="image.alt"
class="max-w-full max-h-[80vh] object-contain rounded-lg"
/>
<p class="text-center text-text-muted text-sm mt-4">{{ image.caption }}</p>
</div>
<!-- Next Button -->
<button
class="absolute right-4 top-1/2 -translate-y-1/2 text-text text-3xl leading-none p-3 hover:text-accent transition-colors duration-fast"
data-testid="lightbox-next"
aria-label="Nächstes Bild"
@click="$emit('next')"
>
&#8250;
</button>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted, nextTick } from "vue";
interface LightboxImage {
src: string;
alt: string;
caption: string;
}
const props = defineProps<{
isOpen: boolean;
image: LightboxImage;
}>();
const lightboxRef = ref<HTMLElement | null>(null);
const emit = defineEmits<{
close: [];
prev: [];
next: [];
}>();
const handleKeydown = (e: KeyboardEvent) => {
if (!props.isOpen) return;
if (e.key === "Escape") {
emit("close");
} else if (e.key === "ArrowLeft") {
emit("prev");
} else if (e.key === "ArrowRight") {
emit("next");
}
};
onMounted(() => {
window.addEventListener("keydown", handleKeydown);
});
onUnmounted(() => {
window.removeEventListener("keydown", handleKeydown);
});
watch(
() => props.isOpen,
(open) => {
if (open) {
document.body.style.overflow = "hidden";
nextTick(() => {
lightboxRef.value?.focus();
});
} else {
document.body.style.overflow = "";
}
}
);
</script>