307 Commits

Author SHA1 Message Date
Peter Meier 7bd62b75b0 feat(contacts): ContactCard + ContactsBlock-Komponente, BlockRenderer registriert
Deploy / verify (push) Successful in 1m8s
Deploy / deploy (push) Successful in 1m13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 08:49:23 +02:00
Peter Meier 1827d9772a feat(stellungnahme): Grüner Output-Hintergrund, KI-Anleitung, Aktualisieren-Button entfernt
Deploy / verify (push) Successful in 59s
Deploy / deploy (push) Successful in 1m35s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 15:26:36 +02:00
Peter Meier 5d2988fa1b feat(page): Kommentar-Badge oben mit Scroll-to-Comments
Deploy / verify (push) Successful in 1m0s
Deploy / deploy (push) Successful in 1m22s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 09:33:22 +02:00
Peter Meier df4380409e feat(comments): Auto-open accordion wenn Kommentare vorhanden, Pages-Support
Deploy / verify (push) Successful in 1m1s
Deploy / deploy (push) Successful in 1m11s
- Post: Kommentar-Count server-seitig laden, Accordion offen wenn count > 0
- Page-Route: optionale Kommentare via commentsEnabled-Flag (Schema: default false)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 09:22:03 +02:00
Peter Meier 6546f7d0a3 feat(stellungnahme): KI-Prompt, editierbarer Text, Stepper-Füllstand, DnD-Reihenfolge
Deploy / verify (push) Successful in 1m3s
Deploy / deploy (push) Successful in 1m17s
- KI-Prompt kopieren mit Ton/Haltung-Slidern und persönlichem Hinweis
- Output-Textarea editierbar (field-sizing: content, auto-height)
- Stepper zeigt Füllstand: Haken (komplett), gelb (leer), grau (unbesucht)
- Weiter-Buttons mit Tooltips bei leerem Schritt
- Auto-Generate beim Betreten von Step 4, live bei Eingabe-Änderungen
- Drag & Drop Umsortierung der Argumente in Step 4
- Step in localStorage persistiert (HMR-Fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 22:13:11 +02:00
Peter Meier f2379c0e28 feat(stellungnahme): Tag-Pill-Filter, flache Gruppen, Sticky-Footer, Auto-Save, Auto-Focus
Deploy / verify (push) Successful in 1m1s
Deploy / deploy (push) Successful in 1m9s
Filter:
- Tag-Pill-Reihe in Step 2 oberhalb der Suche. Multi-Select, OR-Logik
  zwischen Pills, AND mit Text-Suche. Pills aus allen Nicht-Fetch-Tags
  der deduplizierten Fragmente, alphabetisch sortiert. "Filter
  zurücksetzen" erscheint bei ≥1 aktivem Pill. Empty-State erklärt
  was nicht matcht (Text/Pills/beides).

Akkordeons raus (Variante B):
- Gruppen-Header bleiben als sichtbare Trenner (Label + Anzahl + "Alle"-
  Toggle), aber Click-to-Expand-Mechanik weg — Items immer offen.
  expandedGroups + toggleGroup als dead code entfernt.

Bedienkomfort:
- Sticky Mobile-Footer für alle 5 Step-Nav-Rows: bottom-2 mit
  weißem BG + Shadow + Backdrop-Blur. Auf Desktop unverändert (sm:static).
- Auto-Save-Indikator "✓ Gespeichert" rechts oben, blitzt 2s nach
  jeder Mutation. Pure UI — wir speichern eh schon in localStorage.
- Auto-Focus per Svelte-Action beim Step-Wechsel: Step 2 → Such-Input,
  Step 3 → Betroffenheit-Textarea, Step 4 → Name-Input.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 08:49:20 +02:00
Peter Meier 77fee9b1c2 feat(stellungnahme): Step-0 als Cards, Step-3 ohne Mindest-Auswahl, Steckbrief default offen
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m3s
Step 0 (Gebietskontext):
- Gebiets-Nummer (W-37) deutlich größer: text-4xl tracking-tight
- Detail-Felder als Cards: rounded-lg border-wald-200 bg-white/70 p-3 shadow-sm
- Icons (Gemeinden, Fläche, Anlagen geplant, Max. Höhe, Investor, Status)
  oben links als eigenes Element statt inline; size-5 text-wald-600
- Labels: text-sm sm:text-base font-semibold text-wald-800 whitespace-nowrap
  (kleiner auf Mobile damit "Anlagen geplant" in der Card bleibt)
- Werte: text-stein-600 (grau) mit mt-1 Abstand
- Steckbrief-<details> default open

Step 2 (Allgemeine Argumente):
- "Weiter"-Button nicht mehr disabled bei 0 Auswahl — Mindest-Auswahl
  weggefallen, Anzahl nur im Label wenn >0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:43:01 +02:00
Peter Meier 3f39b5b1a8 fix(stellungnahme): Mini-Map mobile square + Gebiet zentriert (fitBoundsPadding-Prop)
Deploy / verify (push) Successful in 1m5s
Deploy / deploy (push) Successful in 1m12s
2026-05-30 11:25:36 +02:00
Peter Meier bd67f0c568 feat(nav): navgroup-Dropdowns im Header + Fix Overflow-Clipping
Deploy / verify (push) Successful in 1m3s
Deploy / deploy (push) Successful in 1m12s
- layout.server.ts: NavLink um optional children?: NavLink[] erweitert.
  navHeader-Resolve von 'links' auf 'all' (navgroup-sub-links werden mit
  aufgelöst). Resolver-Loop erkennt navgroup-Entries (_type==='navgroup'
  oder Shape label+links) und baut NavLink mit Children + Parent-URL.
- Header.svelte: Desktop-Dropdown mit Hover/Focus-Open + ARIA (haspopup,
  expanded, role=menu/menuitem). Mobile: collapsible Submenu via
  openSubmenu-State, slide-animiert; "– Übersicht"-Link wenn Parent
  selbst eine URL hat. isActiveTree(): Parent gilt aktiv wenn ein Child
  aktiv ist.
- Overflow-Fix: .gradient-blob-bg setzt overflow:hidden → clipte den
  Desktop-Dropdown. Neue Variante .gradient-blob-bg-inner als absoluter
  Inner-Layer (eigenes overflow-hidden), Header selbst bleibt
  overflow-visible. Bestehende .gradient-blob-bg-Nutzungen (z.B.
  DeadlineBannerBlock) unverändert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 08:50:51 +02:00
Peter Meier c58f418c1a perf(cms-client+ssr): O(1)-Slug-Lookup, Sparse-Pages, höhere TTLs, Cache-Control, Parallel-Resolver
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 1m12s
- getPageBySlug: neuer Filter-Lookup-Pfad (?slug=X&_fields=_slug) als
  primärer Fallback — O(1) statt komplette Page-Liste laden. Vollständige
  Liste nur noch als Last-Resort-Safety-Net. Skaliert egal wieviele Pages.
- getPages: Sparse-Projektion via _fields (8 Felder statt Voll-Resolve-
  Tree) → ~90 % kleinere Payload pro Call.
- CACHE_TTL hoch (Webhooks purgen eh): page/page_config/navigation/footer/
  fullwidth_banner/translation/openapi → 1h; tag/text_fragment → 30min;
  post → 10min. Spart Cold-Loads + CMS-Roundtrips.
- [...slug]/+page.server.ts: Cache-Control public, s-maxage=60,
  stale-while-revalidate=600 für öffentliche Pages (Caddy/Browser cachen).
  Preview-Branch bleibt private/no-store.
- Resolver parallelisiert: Calendar/Deadline/StellingnahmeGenerator
  starten sofort parallel zu PostOverview, nicht erst danach.
- Icon-Subset um clock-remove-outline ergänzt (Frist-abgelaufen-Banner).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 00:09:26 +02:00
Peter Meier f05ee6b9d7 fix(cms): getPages paginiert alle Seiten (Slug-Fallback fand Seiten jenseits per_page=50 nicht → 404)
Deploy / verify (push) Successful in 1m4s
Deploy / deploy (push) Successful in 1m15s
2026-05-29 23:28:35 +02:00
Peter Meier 940b9beb7f feat(windkarte): stellungnahmeUrl-Feld + Einwendung-erstellen-Button im Detail-Panel
Deploy / verify (push) Successful in 1m7s
Deploy / deploy (push) Successful in 1m19s
2026-05-29 09:14:36 +02:00
Peter Meier 0be73adc51 feat(stellungnahme): a11y, Suche, Frist-Status, Steckbrief & Map mobil
Deploy / verify (push) Successful in 57s
Deploy / deploy (push) Successful in 1m38s
- Titel klickbar als <label> (Tastatur/Screenreader), aria-live auf Ausgabe
- Neustart mit Rückfrage, mailto-Längen-Guard (>1900 Zeichen)
- Frist-abgelaufen-Banner statt kommentarlos verschwinden
- Suchfeld filtert allgemeine Argumente live
- <details>-Chevron dreht beim Aufklappen
- Steckbrief einklappbar + scrollbar, Single-Newlines als Hard-Break
- Mini-Map responsive Höhe (h-56 mobil → h-80 desktop)
- Ausgabe-Buttons text-sm/text-[11px], mobil umbrechend; Mail-Link no-underline
- gebietsBez-Deklaration vor Verwendung gezogen (svelte-check Fehler)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:41:43 +02:00
Peter Meier 5d0a634359 fix(stellungnahme): Mail-Link no-underline, Ausgabe-Buttons kleiner
Deploy / verify (push) Failing after 47s
Deploy / deploy (push) Has been skipped
2026-05-28 14:27:10 +02:00
Peter Meier e80d9eef7e feat(stellungnahme): E-Mail-Button immer zeigen (leerer Empfänger = an sich selbst)
Deploy / verify (push) Failing after 51s
Deploy / deploy (push) Has been skipped
2026-05-28 14:25:21 +02:00
Peter Meier a872a015d9 fix(stellungnahme): Ausgabe-Buttons mobil umbrechen statt overflowen
Deploy / verify (push) Failing after 47s
Deploy / deploy (push) Has been skipped
2026-05-28 14:23:47 +02:00
Peter Meier 8194d8639d feat(stellungnahme): Einleitung/Abschluss via CMS-Felder pflegbar
Deploy / verify (push) Failing after 46s
Deploy / deploy (push) Has been skipped
2026-05-28 13:35:31 +02:00
Peter Meier 4e46e82770 feat(stellungnahme): Button subtiles Wiggle-Rotate statt bounce
Deploy / verify (push) Failing after 50s
Deploy / deploy (push) Has been skipped
2026-05-28 13:32:08 +02:00
Peter Meier 2bae9b3e5d fix(stellungnahme): Button animate-bounce statt ping
Deploy / verify (push) Failing after 47s
Deploy / deploy (push) Has been skipped
2026-05-28 13:31:07 +02:00
Peter Meier 16bb8af12c feat(stellungnahme): Text-erstellen-Button pulsiert (animate-ping)
Deploy / verify (push) Failing after 47s
Deploy / deploy (push) Has been skipped
2026-05-28 13:29:44 +02:00
Peter Meier 6bc6781b3c fix(stellungnahme): Zurück-Button in Step 5 links
Deploy / verify (push) Failing after 44s
Deploy / deploy (push) Has been skipped
2026-05-28 13:29:01 +02:00
Peter Meier d45e691171 feat(stellungnahme): Notizfeld pro Argument + editierbare Einleitung/Abschluss
Deploy / verify (push) Failing after 50s
Deploy / deploy (push) Has been skipped
- Notizfeld an jedem Argument (expanded) → erscheint im Output als "Anmerkung:"
- Einleitung und Abschluss in Step 5 via <details> editierbar
- Beide mit Zurücksetzen-Button auf Default
- Alles in localStorage persistiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 13:27:07 +02:00
Peter Meier e239c85961 feat(markdown): span[style] in Markdown erlauben, alles andere HTML strippen
Deploy / verify (push) Successful in 1m4s
Deploy / deploy (push) Successful in 1m21s
2026-05-28 13:14:58 +02:00
Peter Meier 28170a8549 fix(stellungnahme): Disclaimer prominenter mit Rahmen und Großbuchstaben
Deploy / verify (push) Successful in 52s
Deploy / deploy (push) Successful in 1m5s
2026-05-27 16:37:54 +02:00
Peter Meier 2d1967ec38 feat(stellungnahme): Entwurfs-Disclaimer im generierten Dokument
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 1m37s
2026-05-27 16:36:53 +02:00
Peter Meier 8f260044e6 fix(stellungnahme): bulletPoints statt Prosatext im generierten Dokument
Deploy / verify (push) Successful in 57s
Deploy / deploy (push) Successful in 1m36s
2026-05-27 16:34:43 +02:00
Peter Meier 5f5bd06a78 feat(stellungnahme): E-Mail, Name in Grußformel, Datum-Fix, UX-Polish
Deploy / verify (push) Successful in 57s
Deploy / deploy (push) Successful in 1m9s
- E-Mail-Button: mailto-Link wenn recipientEmail im Block gesetzt
- Name in Grußformel: "Mit freundlichen Grüßen\n\nMax Mustermann"
- Datum-Fix: heute in buildText() berechnet, nicht beim Render
- Disabled-Hint: roter Hinweis wenn Name fehlt
- "Alle ab" klappt Gruppe ein (Symmetrie zu "Alle" öffnet)
- Schritt X von Y unter Step-Indikator
- recipientEmail-Feld in StellingnahmeGeneratorBlockData

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 16:32:25 +02:00
Peter Meier ff383b8b3b fix(stellungnahme): mode const nicht in resetAll zuweisen
Deploy / verify (push) Successful in 55s
Deploy / deploy (push) Successful in 1m5s
2026-05-27 16:02:53 +02:00
Peter Meier cfb74096d1 feat(stellungnahme): Modus-Auswahl entfernen, immer Fertigtext
Deploy / verify (push) Failing after 45s
Deploy / deploy (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 16:00:39 +02:00
Peter Meier 96dd88247d feat(stellungnahme): UX-Verbesserungen, localStorage, Mini-Map, Druck
Deploy / verify (push) Successful in 55s
Deploy / deploy (push) Successful in 1m39s
- localStorage: Formularstand + Ausgabe-Historie (max 5) persistieren
- Fortsetzen-Banner: gespeicherte Sitzung erkannt → Sprung zu letztem Schritt
- Neustart-Button: alles inkl. localStorage leeren
- Frist-Banner: deadline-Feld in StellingnahmeGeneratorBlockData, Ampelfarben
- Step 1 überspringen wenn keine ortskonkreten Argumente vorhanden
- Step-Indikator: goTo() statt step= (scrollt jetzt korrekt)
- "Alle wählen" öffnet Gruppe automatisch
- Download als .txt neben Kopieren/Drucken
- <pre> → <div> für konsistentes Schriftbild im Output
- Mini-Map für Vorranggebiet in Step 0 (tap-to-activate, fitBoundsMaxZoom=13)
- WindkarteMap: fitBoundsMaxZoom-Prop ergänzt
- Druckausgabe: 13pt Serif, 2cm Ränder, kein Markdown im Fertigtext

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 15:57:27 +02:00
Peter Meier 6d0861bb0a feat(stellungnahme): Einwendungs-Assistent für Windvorranggebiete
Deploy / verify (push) Successful in 59s
Deploy / deploy (push) Successful in 2m37s
5-Schritt-Wizard: Gebietskontext → Argumente wählen → Ausgabeform →
persönliche Betroffenheit → Absender + Ausgabe. Alle Eingaben bleiben
lokal im Browser (kein Server-Transfer). Fertigtext- und Leitfaden-Modus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 14:41:00 +02:00
Peter Meier d781b3140e feat(calendar-compact): polish + global not-prose opt-out support
Deploy / verify (push) Successful in 1m4s
Deploy / deploy (push) Successful in 1m9s
CalendarCompact:
- denser layout (smaller padding/font/badge), header icon size-8
- icon stein-900 (black), subtle wald gradient background
- rounded-sm to match design tokens (was rounded-lg)
- list items link to linkTo + "#event-<slug>" anchor; <svelte:element>
  falls back to div when no linkTo is set
- not-prose on root so global content margins/list/link rules don't
  apply to the card subtree

blog-utils.resolveCalendarBlocks:
- when items array is empty, fall back to the full calendar_item pool
  (both default and compact variant get this) — saves editors from
  re-listing items on every page.

app.css: add `:where(:not(.not-prose *))` to .content p/h1-h4,
main a (link colour + underline), main ul/ol so subtrees marked
not-prose stop inheriting the global prose-like defaults.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:16:21 +02:00
Peter Meier b7bcdd76ee feat(calendar): compact variant for next-events teaser
Deploy / verify (push) Successful in 1m8s
Deploy / deploy (push) Successful in 1m13s
Calendar block now supports variant: "default" | "compact".
- default = full calendar (unchanged)
- compact = next N events (default 3) + link to a target page

New CalendarCompactBlock.svelte: small card with month/day badge,
title, date/time/location line, "Alle kommenden Termine →" footer
link resolved from the schema's linkTo reference. Filters to future
events (12h tolerance for running ones).

BlockRenderer dispatches by block.variant inside the calendar case.
Schema (cms_content/_types/core/calendar.json5) adds variant + linkTo
+ limit fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:49:08 +02:00
Peter Meier 055f001174 feat(page): inline topImage field, fallback to topFullwidthBanner
Deploy / verify (push) Successful in 1m3s
Deploy / deploy (push) Successful in 1m4s
Reads page.topImage first (new direct image field on the page schema);
falls back to legacy topFullwidthBanner.image during transition. Same
ResponsiveImage build, social-image transform and warm-up as before.
Banner-object still passed through so existing rendering keeps working
when headline/subheadline/variant are wanted; pure-image migration
needs no further code changes.

Schema + 21 pages migrated CMS-side in the same change set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:14:41 +02:00
Peter Meier c329658992 feat(page): use topFullwidthBanner as og:image source
Deploy / verify (push) Successful in 55s
Deploy / deploy (push) Successful in 1m4s
Pages now expose `socialImage` from the topFullwidthBanner field —
same 1200×630 cover transform with focal-aware crop that posts already
use. Layout meta-tag pipeline picks it up automatically; pages without
a banner still fall back to the global default social image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:06:48 +02:00
Peter Meier 6a1069f908 fix(form): shrink sm inputs to text-xs, align consent checkbox top
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m3s
- TextInput/Textarea size="sm": text-xs, tighter padding (h-9, px-2.5
  py-1.5) so placeholder/value match label scale.
- Checkbox: new size prop (sm = h-4 w-4 + text-xs leading-snug).
  Switched container to flex/items-start so multi-line labels align
  with checkbox top instead of vertically centering and pushing the
  box halfway down the paragraph.
- ContactForm: consent checkbox uses size="sm"; privacy note + error
  hint at text-[0.7rem] leading-snug to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:33:34 +02:00
Peter Meier f7a547874b fix(css): drop aggressive word-break in link content
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 1m6s
Global rule `main a, .markdown a { word-break: break-all }` broke
German words mid-character whenever the anchor wrapped non-URL content
(e.g. PostCard wraps title/excerpt in <a href>). Visible in PostOverview
as "schützt" → "sc / hützt", "überall" → "über / all".

Replaced with overflow-wrap: anywhere — still wraps long URLs softly,
but only when nothing else fits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:26:52 +02:00
Peter Meier 81c82c33e6 perf: review-fixes (N+1, waterfalls, sanitize-cache, lazy strommix)
Deploy / verify (push) Successful in 56s
Deploy / deploy (push) Successful in 1m3s
- blog-utils.resolvePostOverviewBlocks: parallel getPostBySlug via
  Promise.all + .catch(null) instead of sequential await loop.
- blog-utils.resolveCalendarBlocks: same fix for calendar items.
- [...slug] page load: SearchableText/Calendar/DeadlineBanner now run
  parallel after tagsMap (was strict waterfall).
- sanitize-blocks: LRU cache keyed on raw html input — sanitize-html is
  ~ms/KB, the same content repeats across pages and cache hits.
- cms.getNormalizedTranslations: WeakMap-memoised the per-request
  Object.entries normalize loop on top of the existing 5-min bundle TTL.
- cms.cached: opt-in dev cache via DEV_CACHE_CMS=1 (default still off).
- rusty-image.ensureTransformedImage: was async-wrapping a sync URL
  builder; made sync, removed awaits at call sites — saves microtasks.
- BlockRenderer: lazy-load StrommixBlock (~220 KB) via dynamic import
  in the template — pages without the live-strommix widget no longer
  ship its bundle.

Also: enabled Caddy zstd+gzip on windwiderstand.de vhost (server-side
edit; verified `content-encoding: zstd` on response).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:30:15 +02:00
Peter Meier c00eb26689 feat: wire CMS redirects plugin into hooks.server.ts
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 1m16s
New $lib/redirects.server.ts: lazy-loads GET /api/redirects, exact-match
index with trailing-slash normalisation, 60s TTL with background refresh
(cold start blocks once, subsequent requests get stale cache instantly).

hooks.server.ts: before legacy-redirect fallback, runs lookupRedirect for
GET requests. Editor-defined rules win over hardcoded /blog→/posts etc.
External targets (http://…) drop query; internal targets keep ?search.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:51:45 +02:00
Peter Meier 0aa3898e5b fix(ui): make Button.children optional in props type
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 1m7s
svelte-check (strict) flagged ContactFormComponent for not passing
children to <Button>. The component already renders label as fallback;
typed children as optional Snippet so callers don't need to pass it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:47:20 +02:00
Peter Meier bcf1502093 fix(contact): drop Origin header when forwarding to CMS
Deploy / verify (push) Failing after 43s
Deploy / deploy (push) Has been skipped
SvelteKit already validates Origin at /api/contact; forwarding the
client Origin upstream caused the prod CMS to reject local dev
submissions (allowlist is prod-only). CMS forms plugin accepts
no-Origin requests when REQUIRE_ORIGIN=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:44:06 +02:00
Peter Meier d03afe780e feat(contact): add Cloudflare Turnstile captcha
Deploy / verify (push) Failing after 44s
Deploy / deploy (push) Has been skipped
Client renders explicit Turnstile widget (flexible, light theme) when
PUBLIC_TURNSTILE_SITE_KEY is set. Token sent with form payload, server
verifies via siteverify endpoint before forwarding to CMS. Skipped
entirely when no site key configured (dev fallback). New i18n keys:
contact_captcha_pending, contact_server_error_captcha. Deploy workflow
forwards PUBLIC_TURNSTILE_SITE_KEY + TURNSTILE_SECRET.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:37:09 +02:00
Peter Meier 94a8feeb67 feat(contact): full validation, CMS-translated strings, smaller UI
Deploy / verify (push) Failing after 47s
Deploy / deploy (push) Has been skipped
- contact-form-validation.ts: shared client+server validator returning
  {key, params} so messages can be translated via the CMS bundle.
- ContactFormComponent: 3 honeypots, subject field, GDPR consent
  checkbox, character counter, inline per-field errors after submit,
  uses useTranslate() everywhere.
- /api/contact: origin allowlist, multiple honeypot check, client-age
  gate, server-side re-validation, forwards X-Forwarded-For + UA to
  CMS forms plugin with consent timestamp in payload.
- TextInput/Textarea: new size="sm" variant (rounded-sm, shadow-sm,
  h-10, smaller padding/text). Default "md" unchanged.
- translations.ts: 37 new contact_* keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:28:23 +02:00
Peter Meier 8575572183 refactor: consolidate block dispatch into BlockRenderer
ContentRows now delegates to BlockRenderer instead of maintaining its
own parallel if-else chain. Single source of truth for all block types.
Adds internal_component support (ContactFormComponent via /api/contact).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:12:49 +02:00
Peter Meier 9e05fbb2ba feat: add internal_component block type with contact form
InternalComponentBlock dispatches on `component` field value.
ContactFormComponent submits via /api/contact (SvelteKit proxy →
CMS /api/forms/kontakt/submit) with honeypot + client_age_ms.
BlockRenderer and block-types.ts updated accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:01:47 +02:00
Peter Meier 50d68a17ca fix(windkarte): Kriterien-Accordion text-xs, Titel 2-zeilig
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m5s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:52:03 +02:00
Peter Meier 8e43169b23 feat(windkarte): Stellungnahme-Kriterien per Windvorranggebiet
Deploy / verify (push) Successful in 56s
Deploy / deploy (push) Successful in 1m38s
Jedes Windvorranggebiet kann jetzt 1–n text_fragment-Einträge als
Stellungnahme-Kriterien referenzieren. Das Panel lädt diese lazy beim
Öffnen und zeigt sie als aufklappbare Accordion-Sektion.

Außerdem: alle hardcodierten Strings im Karten-Block (Panel, Map,
Block) durch t()-Aufrufe ersetzt; 7 neue Translation-Keys ergänzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:22:35 +02:00
Peter Meier a3f545d727 feat(windkarte): add per-area geo export + dynamic favicon
Deploy / verify (push) Successful in 1m1s
Deploy / deploy (push) Successful in 1m8s
- /api/vorranggebiete.geojson + .kml endpoints (filter via ?id=W-XX)
- Download-Buttons in WindAreaPanel pro selektiertes Gebiet
- Hover-Tooltip auf Polygon zeigt Fläche, geplante Anlagen, Höhe, Gemeinden
- Favicon + apple-touch-icon dynamisch aus data.logoUrl (CMS-Logo)
- alte windrad-PNGs in static/favicons/ entfernt
- WindkarteBlock list-fetch: _limit statt limit (RustyCMS-Param)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 12:14:58 +02:00
Peter Meier 61d2093d0b seo: use banner PNG as default OG image, skip SVG logos for social
Deploy / verify (push) Successful in 55s
Deploy / deploy (push) Successful in 1m16s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 15:06:38 +02:00
Peter Meier 12e0d4e3b8 ui: carousel swipe, org peek, deadline banner fixes
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 1m3s
- Carousel: touch swipe support (40px threshold)
- OrganisationsBlock: 1.5 cards on mobile to hint at more content
- DeadlineBanner: left-aligned text, shrink-0 close button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:46:23 +02:00