- 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>
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>
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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
- /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>
- 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>