Commit Graph

244 Commits

Author SHA1 Message Date
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
Peter Meier 9d58c92e94 ui: card-surface utility, rounded corners, calendar polish, banner layout
Deploy / verify (push) Successful in 1m1s
Deploy / deploy (push) Successful in 1m9s
- Add .card-surface utility (@layer components): rounded-sm border shadow-sm
- Card.svelte, CalendarBlock, SearchableTextBlock, BlogOverview use card-surface
- OrganisationsBlock: logo h-12 rounded-sm, description style cleanup, pt-1 for hover clip fix
- DeadlineBanner: container-custom width constraint, text left-aligned
- WindAreaPanel stats: card-surface
- CalendarBlock: rounded-sm day headers, past days opacity-20 + diagonal strike
- Add .day-past pseudo-element utility for diagonal line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 14:32:42 +02:00
Peter Meier f08e3bb790 windkarte: scroll map into view when URL hash selects an area
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m4s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:45:46 +02:00
Peter Meier cd362fb811 windkarte: Rechtskräftig default hidden, remove Entwurf-2-voraussichtlich, 2km/5km rings on by default
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 1m6s
- Hide status badge for rechtskraeftig in WindAreaPanel
- Default hiddenStatuses=["rechtskraeftig"] so polygons start hidden
- Fix: call restyleAll after geojsonLayer init so initial hidden state applies
- Default hiddenRings=[] so 2km+5km buffer rings show on selection
- Remove entwurf_2_voraussichtlich from legend
- Lower maxZoom 12→11 for more context on feature select

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:26:19 +02:00
Peter Meier 4a72142555 WindAreaPanel: compact key-value rows instead of cards
Label left, value right, divided rows. No more padded bg cards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:18:38 +02:00
Peter Meier 823adde1fa WindAreaPanel: stats single column layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:18:06 +02:00
Peter Meier f55f409158 WindAreaPanel: render notizen as HTML with prose styles
Was output as escaped plain text. Field contains HTML from the CMS editor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:16:46 +02:00
Peter Meier 250865893e FilesBlock: icon size-4 → size-5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:46:52 +02:00
Peter Meier 07b6317b94 FilesBlock: mt-0.5 on icon for top alignment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:46:38 +02:00
Peter Meier 07f17e880e FilesBlock: top-align icon with text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:45:48 +02:00
Peter Meier baa4b9aae4 FilesBlock: allow description to wrap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:45:17 +02:00
Peter Meier 61e01c127f FilesBlock: size as inline badge after filename
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:44:46 +02:00
Peter Meier 887a6bda3e FilesBlock: remove default link underline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:43:57 +02:00
Peter Meier 774ac893ec FilesBlock: description on second line below filename
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:43:10 +02:00