diff --git a/src/lib/components/blocks/InternalComponentBlock.svelte b/src/lib/components/blocks/InternalComponentBlock.svelte
index 8bf76c5..2d6c29c 100644
--- a/src/lib/components/blocks/InternalComponentBlock.svelte
+++ b/src/lib/components/blocks/InternalComponentBlock.svelte
@@ -3,6 +3,7 @@
import type { InternalComponentBlockData } from "$lib/block-types";
import ContactFormComponent from "$lib/components/internal/ContactFormComponent.svelte";
import MitmachenFormComponent from "$lib/components/internal/MitmachenFormComponent.svelte";
+ import FormCard from "$lib/components/internal/FormCard.svelte";
let { block }: { block: InternalComponentBlockData } = $props();
@@ -12,9 +13,13 @@
{#if component === "form-contact"}
-
+
+
+
{:else if component === "form-mitmachen"}
-
+
+
+
{:else}
Unbekannte interne Komponente: {component}
{/if}
diff --git a/src/lib/components/internal/FormCard.svelte b/src/lib/components/internal/FormCard.svelte
new file mode 100644
index 0000000..cbd11fa
--- /dev/null
+++ b/src/lib/components/internal/FormCard.svelte
@@ -0,0 +1,27 @@
+
+
+
+ {#if title}
+
+
{title}
+ {#if subtitle}
+
{subtitle}
+ {/if}
+
+ {/if}
+ {@render children()}
+
diff --git a/src/lib/components/internal/mitmachen-form-validation.ts b/src/lib/components/internal/mitmachen-form-validation.ts
index 5152ea5..f702094 100644
--- a/src/lib/components/internal/mitmachen-form-validation.ts
+++ b/src/lib/components/internal/mitmachen-form-validation.ts
@@ -52,8 +52,18 @@ const URL_RE = /\b(?:https?:\/\/|www\.)\S+/gi;
const CONTROL_CHAR_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F]/;
const PHONE_RE = /^[0-9+()/\-.\s]*$/;
+/** Unsichtbare Format-Zeichen: Zero-Width, Bidi-Marks (U+202A–E), Word-Joiner,
+ * BOM. Landen beim Kopieren aus iOS-Kontakten/WhatsApp in Telefonnummern und
+ * lassen sonst die Validierung durchfallen. */
+const INVISIBLE_RE = /[\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF]/g;
+
+/** Entfernt unsichtbare Format-Zeichen + trimmt. Für Validierung UND Payload. */
+export function cleanText(s: unknown): string {
+ return typeof s === "string" ? s.replace(INVISIBLE_RE, "").trim() : "";
+}
+
function trim(s: unknown): string {
- return typeof s === "string" ? s.trim() : "";
+ return cleanText(s);
}
export function validateMitmachen(input: Partial
): MitmachenErrors {
diff --git a/src/routes/api/mitmachen/+server.ts b/src/routes/api/mitmachen/+server.ts
index ab20afd..6fa27d3 100644
--- a/src/routes/api/mitmachen/+server.ts
+++ b/src/routes/api/mitmachen/+server.ts
@@ -3,6 +3,7 @@ import { env as publicEnv } from "$env/dynamic/public";
import { env as privateEnv } from "$env/dynamic/private";
import type { RequestHandler } from "./$types";
import {
+ cleanText,
HONEYPOT_FIELDS,
LIMITS,
validateFiles,
@@ -108,12 +109,14 @@ export const POST: RequestHandler = async ({ request, getClientAddress }) => {
? `${cmsBase}/api/forms/mitmachen/submit-multipart?_environment=${encodeURIComponent(cmsEnv)}`
: `${cmsBase}/api/forms/mitmachen/submit-multipart`;
+ // cleanText: unsichtbare Format-Zeichen (Bidi-Marks aus Copy-Paste) raus —
+ // gleiche Normalisierung wie in der Validierung.
const payload = {
- title: fields.title.trim(),
- description: fields.description.trim(),
- contact_name: fields.contactName.trim(),
- phone: fields.phone.trim(),
- email: fields.email.trim(),
+ title: cleanText(fields.title),
+ description: cleanText(fields.description),
+ contact_name: cleanText(fields.contactName),
+ phone: cleanText(fields.phone),
+ email: cleanText(fields.email),
consent_at: new Date().toISOString(),
submitted_via: "windwiderstand.de/api/mitmachen",
user_agent: (request.headers.get("user-agent") ?? "").slice(0, 200),