Commit Graph

219 Commits

Author SHA1 Message Date
Peter Meier ace3562111 feat: Social-Bild-Modal mit QR-Code für Kalender-Termine
Deploy / verify (push) Successful in 1m7s
Deploy / deploy (push) Successful in 1m39s
Single Button öffnet Vorschau-Modal mit beiden Formaten side-by-side.
Pro Format Download-Button. ESC + Click-outside schließen.

Endpoint generiert zusätzlich QR-Code (qrcode → PNG-Buffer), der
direkt auf den Termin im Kalender-Grid verweist
(/kalender/#event-<slug>). Pin-Icon, QR und Layout für OG (1200x630)
und Square (1080x1080).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 10:25:07 +02:00
Peter Meier f0d9fecfb8 feat: Social-Media-Bild-Generator für Kalender-Termine
Neuer Endpoint /api/social/calendar/[slug] rendert Termin via satori +
resvg zu PNG. Formate: og (1200x630) und square (1080x1080).
Gebrandetes Layout mit Datum, Titel, Ort. Inter-Font lazy von gstatic,
10min In-Memory-Cache pro Slug+Format.

CalendarBlock: zwei Download-Buttons pro Termin (Bild quer / quadr.).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 09:28:00 +02:00
Peter Meier d921b2758b refactor: AI-Prompt für Stellungnahme nachgeschärft
Klare Rolle, zwingende Regeln (Argumente erhalten, keine Erfindungen,
Briefkopf-Struktur, ≥70 % neu formuliert), explizites Ausgabeformat.
Kontext zum Windvorranggebiet und persönliche Betroffenheit prominent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 09:22:06 +02:00
Peter Meier 415e772e57 fix: Windkarte verbirgt Labels ausgeblendeter Status
Per-Status LayerGroups statt einer globalen labelLayer. syncLabels
respektiert hiddenStatuses und entfernt Labels für versteckte
Status-Kategorien beim Zoomen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 09:22:00 +02:00
Peter Meier 894263e5c4 fix: OrganisationsMap erst bei Klick interaktiv, kein Page-Scroll-Hijack
Deploy / verify (push) Successful in 1m1s
Deploy / deploy (push) Successful in 1m25s
Initial Drag/Zoom deaktiviert. Overlay 'Karte aktivieren' freigibt
Interaktion. Marker-Klick aktiviert automatisch mit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 13:17:15 +02:00
Peter Meier aebe5f4854 fix: kleinere Map-Höhe auf Mobile (320px statt 400px+)
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 1m8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 13:06:53 +02:00
Peter Meier fc81d973a5 feat: Ansprechpartner im OrganisationsMap Detail-Panel
Deploy / verify (push) Successful in 1m2s
Deploy / deploy (push) Successful in 1m11s
Marker-Klick zeigt jetzt zusätzlich Name, Rolle, Email und Telefon
der Kontaktpersonen pro Organisation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 13:02:47 +02:00
Peter Meier c9ba88ab80 feat: OrganisationsMapBlock — Karte der Bürgerinitiativen
Deploy / verify (push) Successful in 1m5s
Deploy / deploy (push) Successful in 1m13s
Neuer Block-Typ `organisations_map`: Leaflet-Karte mit CircleMarkern für alle
Organisationen die ein location.lat/lng haben. Klick auf Marker öffnet
Detail-Panel (Desktop: rechte Sidebar / Mobile: Bottom Sheet) mit Name,
Standort, Beschreibung und Website-Link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 12:03:26 +02:00
Peter Meier 88b2df0c05 feat: location-Feld an Organisation, Header-Tooltips
Deploy / verify (push) Successful in 1m0s
Deploy / deploy (push) Successful in 1m13s
- block-types: OrganisationEntry.location { text, lat, lng }
- Header: title-Attribut auf Social-Icons + Suche-Button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:54:13 +02:00
Peter Meier 6c774c4913 refactor: SearchableTextBlock Redesign, Ort-Badge Truncate
Deploy / verify (push) Successful in 59s
Deploy / deploy (push) Successful in 1m12s
- SearchableTextBlock: Farbverlauf-Header entfernt, Tag/Tags durch .chip ersetzt,
  Suche auf Adressbuch-Style ausgerichtet, Kopier-Buttons auf Icon-only reduziert,
  border-l-4 Akzent entfernt
- EventBadges: whitespace-nowrap entfernt, truncate max-w-[16rem] + title für lange Ortstexte
- PostCard: min-w-0 auf Badges-Container für korrekte Truncate-Propagation
- iconify-mdi-subset: qrcode + download Icons ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:25:34 +02:00
Peter Meier 982a7a3b73 feat: QR-Code, vCard-Export, Org-Gruppenansicht im Adressbuch
Deploy / verify (push) Successful in 1m10s
Deploy / deploy (push) Successful in 1m45s
- vcard.ts: shared vCard-Generator (downloadVCard, downloadVCardAll, buildVCardString)
- QrModal.svelte + QrButton.svelte: wiederverwendbare QR-Komponenten mit PNG-Download
- AdressbuchBlock: Org-Gruppenansicht (Toggle), QR + vCard pro Kontakt,
  Buchstaben-Trennzeilen, Such-Highlight, "Alle exportieren", Print-Styles
- ContactCard: vCard-Download-Button
- PostActions: QR-Button für Seiten-URL
- qrcode npm-Paket hinzugefügt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:15:57 +02:00
Peter Meier 4642b32271 refactor: Adressbuch-Kontaktinfo als Text mit Icon-Button statt vollem Button
Deploy / verify (push) Successful in 59s
Deploy / deploy (push) Successful in 1m12s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:54:44 +02:00
Peter Meier df1895bb91 refactor: einheitliche .chip-Klasse für Filter-Chips, .btn-outline.btn-sm für Adressbuch-Actions
Deploy / verify (push) Successful in 1m6s
Deploy / deploy (push) Successful in 1m15s
- app.css: neue .chip Utility (rounded-xs, border, text-[11px])
- AdressbuchBlock: Org-/Letter-Filter auf .chip, Action-Buttons auf .btn-outline.btn-sm
- CalendarBlock: Tag-Filter auf .chip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:36:26 +02:00
Peter Meier 31f1c8586c feat: WhatsApp/YouTube-Links auf Organisation
Deploy / verify (push) Successful in 1m2s
Deploy / deploy (push) Successful in 1m15s
Neue Felder whatsapp/youtube (string) auf OrganisationEntry.
OrganisationsCard zeigt Icon-Buttons wenn gesetzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:20:41 +02:00
Peter Meier 80ee877ae0 feat: render subheadlines as inline markdown
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 1m8s
Subheadlines in page headers and post cards support inline markdown
(bold, italic, links) via marked.parseInline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:11:07 +02:00
Peter Meier 363d4edb76 feat: Adressbuch-Block mit Suche, Org- und Buchstabenfilter
Deploy / verify (push) Successful in 1m14s
Deploy / deploy (push) Successful in 1m18s
- AdressbuchBlock: listet alle Kontakte alphabetisch mit Suche,
  Org-Filter-Chips und A–Z-Buchstabenfilter; /adressbuch-Seite
- ContactCard: neues Design passend zu OrganisationsCard (Badges, Links)
- ContactsBlock: Karussell mit snap-scroll, volle Breite
- block-types, blog-utils, cms: AdressbuchBlockData, resolveAdressbuchBlocks,
  getContacts; BlockRenderer registriert adressbuch
- Kontakt-Seite: Link auf /adressbuch statt eingebettetem Block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:08:54 +02:00
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 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 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 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 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 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 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