-
+ variant="primary"
+ loading={submitting}
+ disabled={!body.trim() || !author.trim()}
+ label={submitting ? t(T.comments_sending) : t(T.comments_send)}
+ class="text-sm"
+ />
{#if requireApproval}
diff --git a/src/lib/components/ContactCard.stories.ts b/src/lib/components/ContactCard.stories.ts
new file mode 100644
index 0000000..2fbfb6e
--- /dev/null
+++ b/src/lib/components/ContactCard.stories.ts
@@ -0,0 +1,28 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import ContactCard from './ContactCard.svelte';
+
+const meta = {
+ title: 'Molecules/ContactCard',
+ component: ContactCard,
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Standard: Story = {
+ args: {
+ contact: {
+ name: 'Maria Müller',
+ role: 'Sprecherin',
+ email: 'maria@example.org',
+ phone: '01234 567890',
+ note: 'Erreichbar Mo–Fr',
+ organisations: [{ name: 'BI Steigerwald' }],
+ },
+ },
+};
+
+export const Minimal: Story = {
+ args: { contact: { name: 'Hans Weber', role: 'Presse', email: 'presse@example.org' } },
+};
diff --git a/src/lib/components/EventBadges.stories.ts b/src/lib/components/EventBadges.stories.ts
new file mode 100644
index 0000000..7c9b599
--- /dev/null
+++ b/src/lib/components/EventBadges.stories.ts
@@ -0,0 +1,23 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import EventBadges from './EventBadges.svelte';
+
+const meta = {
+ title: 'Molecules/EventBadges',
+ component: EventBadges,
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const MitOrt: Story = {
+ args: {
+ eventDate: '2026-08-15T18:00:00Z',
+ locationText: 'Rathaus Schleusingen',
+ locationHref: 'https://www.openstreetmap.org',
+ },
+};
+
+export const NurDatum: Story = {
+ args: { eventDate: '2026-09-05T19:00:00Z' },
+};
diff --git a/src/lib/components/EventMap.stories.ts b/src/lib/components/EventMap.stories.ts
new file mode 100644
index 0000000..bd8c8cb
--- /dev/null
+++ b/src/lib/components/EventMap.stories.ts
@@ -0,0 +1,22 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import EventMap from './EventMap.svelte';
+
+// Karten-Embed für Termine (iframe + Link). Tiles laden extern bei Netzzugriff.
+const meta = {
+ title: 'Molecules/EventMap',
+ component: EventMap,
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Standard: Story = {
+ args: {
+ embedUrl:
+ 'https://www.openstreetmap.org/export/embed.html?bbox=10.6,50.4,10.9,50.6&layer=mapnik',
+ linkUrl: 'https://www.openstreetmap.org/#map=13/50.51/10.75',
+ locationText: 'Rathaus Schleusingen',
+ label: 'Veranstaltungsort',
+ },
+};
diff --git a/src/lib/components/Header.stories.ts b/src/lib/components/Header.stories.ts
new file mode 100644
index 0000000..61f5e84
--- /dev/null
+++ b/src/lib/components/Header.stories.ts
@@ -0,0 +1,37 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import Header from './Header.svelte';
+
+// Nutzt den SvelteKit-page-Store (aktiver Link) — vom sveltekit-Framework
+// gemockt. Logo als Inline-SVG-Platzhalter.
+const meta = {
+ title: 'Organisms/Header',
+ component: Header,
+ tags: ['autodocs'],
+ parameters: { layout: 'fullscreen' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Standard: Story = {
+ args: {
+ logoSvgHtml: 'Windwiderstand',
+ links: [
+ { href: '/', label: 'Start' },
+ {
+ href: '/verfahren',
+ label: 'Verfahren',
+ children: [
+ { href: '/verfahren/ablauf', label: 'Ablauf' },
+ { href: '/verfahren/fristen', label: 'Fristen' },
+ ],
+ },
+ { href: '/posts', label: 'Aktuelles' },
+ { href: '/mitmachen', label: 'Mitmachen' },
+ ],
+ socialLinks: [
+ { href: 'https://example.org', icon: 'mdi:instagram', label: 'Instagram' },
+ { href: 'https://example.org', icon: 'mdi:youtube', label: 'YouTube' },
+ ],
+ },
+};
diff --git a/src/lib/components/InfoAccordion.stories.ts b/src/lib/components/InfoAccordion.stories.ts
new file mode 100644
index 0000000..ba47866
--- /dev/null
+++ b/src/lib/components/InfoAccordion.stories.ts
@@ -0,0 +1,21 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import InfoAccordion from './InfoAccordion.svelte';
+
+const meta = {
+ title: 'Molecules/InfoAccordion',
+ component: InfoAccordion,
+ tags: ['autodocs'],
+ args: {
+ title: 'Wie läuft das Verfahren ab?',
+ body: 'Der Regionalplan wird ausgelegt, Einwendungen sind bis zum Fristende möglich.',
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Geschlossen: Story = {};
+export const Offen: Story = { args: { open: true } };
+export const Warnung: Story = {
+ args: { variant: 'amber', iconName: 'lucide:alert-triangle', title: 'Frist endet bald', open: true },
+};
diff --git a/src/lib/components/Pagination.stories.ts b/src/lib/components/Pagination.stories.ts
new file mode 100644
index 0000000..1d9f4fd
--- /dev/null
+++ b/src/lib/components/Pagination.stories.ts
@@ -0,0 +1,17 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import Pagination from './Pagination.svelte';
+
+const meta = {
+ title: 'Molecules/Pagination',
+ component: Pagination,
+ tags: ['autodocs'],
+ args: { currentPage: 2, totalPages: 5, basePath: '/posts' },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Mitte: Story = {};
+export const ErsteSeite: Story = { args: { currentPage: 1 } };
+export const LetzteSeite: Story = { args: { currentPage: 5 } };
+export const Viele: Story = { args: { currentPage: 7, totalPages: 20 } };
diff --git a/src/lib/components/PostCard.stories.ts b/src/lib/components/PostCard.stories.ts
new file mode 100644
index 0000000..b1459aa
--- /dev/null
+++ b/src/lib/components/PostCard.stories.ts
@@ -0,0 +1,32 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import PostCard from './PostCard.svelte';
+
+const CMS_ASSET =
+ 'https://cms.pm86.de/api/assets/pages/img_2814-ar4x2.webp?_environment=windwiderstand';
+
+const meta = {
+ title: 'Organisms/PostCard',
+ component: PostCard,
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+const post = {
+ _slug: 'infoabend-schleusingen',
+ slug: 'infoabend-schleusingen',
+ headline: 'Infoabend in Schleusingen',
+ subheadline: 'Über 80 Bürgerinnen und Bürger kamen zusammen.',
+ created: '2026-06-12T18:00:00Z',
+ postTag: [{ name: 'Veranstaltung' }, { name: 'Verfahren' }],
+ _resolvedImageUrl: CMS_ASSET,
+} as never;
+
+export const MitBild: Story = {
+ args: { post, href: '/posts/infoabend-schleusingen', commentCount: 4 },
+};
+
+export const OhneKommentare: Story = {
+ args: { post, href: '/posts/infoabend-schleusingen', commentCount: null },
+};
diff --git a/src/lib/components/PostCardCompact.stories.ts b/src/lib/components/PostCardCompact.stories.ts
new file mode 100644
index 0000000..e339fce
--- /dev/null
+++ b/src/lib/components/PostCardCompact.stories.ts
@@ -0,0 +1,28 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import PostCardCompact from './PostCardCompact.svelte';
+
+const CMS_ASSET =
+ 'https://cms.pm86.de/api/assets/pages/img_2814-ar4x2.webp?_environment=windwiderstand';
+
+const meta = {
+ title: 'Organisms/PostCardCompact',
+ component: PostCardCompact,
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Standard: Story = {
+ args: {
+ post: {
+ _slug: 'frist-laeuft',
+ slug: 'frist-laeuft',
+ headline: 'Frist für Einwendungen läuft',
+ created: '2026-06-01T09:00:00Z',
+ postTag: [{ name: 'Verfahren' }],
+ _resolvedImageUrl: CMS_ASSET,
+ } as never,
+ href: '/posts/frist-laeuft',
+ },
+};
diff --git a/src/lib/components/PostMeta.stories.ts b/src/lib/components/PostMeta.stories.ts
new file mode 100644
index 0000000..781265b
--- /dev/null
+++ b/src/lib/components/PostMeta.stories.ts
@@ -0,0 +1,20 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import PostMeta from './PostMeta.svelte';
+
+const meta = {
+ title: 'Molecules/PostMeta',
+ component: PostMeta,
+ tags: ['autodocs'],
+ args: {
+ date: '2026-06-12T18:00:00Z',
+ tags: [{ name: 'Verfahren' }, { name: 'Natur' }],
+ tagBase: '/posts/tag',
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Standard: Story = {};
+export const NurDatum: Story = { args: { tags: [] } };
+export const NurTags: Story = { args: { date: null } };
diff --git a/src/lib/components/QrButton.stories.ts b/src/lib/components/QrButton.stories.ts
new file mode 100644
index 0000000..97f7ece
--- /dev/null
+++ b/src/lib/components/QrButton.stories.ts
@@ -0,0 +1,19 @@
+import type { Meta, StoryObj } from '@storybook/sveltekit';
+import QrButton from './QrButton.svelte';
+
+// Button, der auf Klick ein QR-Modal öffnet (aus `value`).
+const meta = {
+ title: 'Molecules/QrButton',
+ component: QrButton,
+ tags: ['autodocs'],
+ args: {
+ value: 'https://windwiderstand.de',
+ label: 'QR-Code',
+ sublabel: 'Seite teilen',
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Standard: Story = {};
diff --git a/src/lib/components/QrModal.svelte b/src/lib/components/QrModal.svelte
index 08f47e7..253335d 100644
--- a/src/lib/components/QrModal.svelte
+++ b/src/lib/components/QrModal.svelte
@@ -3,6 +3,7 @@
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import QRCode from "qrcode";
+ import Button from "$lib/ui/Button.svelte";
let {
value,
@@ -87,15 +88,16 @@
Generiere…
{/if}
QR-Code scannen
-
+