Commit Graph

35 Commits

Author SHA1 Message Date
Peter Meier 0ab2c58eb4 feat(header): icon nav redesign + migrate icons mdi→lucide
Deploy / verify (push) Successful in 1m26s
Deploy / deploy (push) Successful in 2m26s
- Header desktop nav: social icons grouped in a wald-tinted pill with
  labels, active-state (aria-current + bold), opacity hover (no per-item bg)
- Mobile overlay: social links as icon+label rows with active-state
- Search button: lucide:search, icon-only
- Migrate all icons mdi→lucide app-wide (234 refs); brand/language logos
  (google, whatsapp, mastodon, telegram, language-*) stay on mdi
- generate-icons: emit mdi + lucide subsets; add @iconify-json/lucide
- iconify-offline: register lucide collection
- scripts/icon-map|validate|apply: reusable migration tooling

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:44:56 +02:00
Peter Meier 0b3ddd86e3 feat(termin): Detailseite-Redesign + gedeckeltes Bild
Deploy / verify (push) Successful in 1m20s
Deploy / deploy (push) Successful in 1m37s
- 2-Spalten-Layout (Inhalt auf Lesebreite, sticky Aktions-Card rechts)
- Bild gedeckelt (max-h-440, contained, Hover-Zoom) statt full-width
- Countdown-Badge (Heute/Morgen/in X Tagen/vergangen)
- Aktionen gruppiert: "Zum Kalender" (ICS/Google) + "Teilen" (Link/QR/Bild)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 13:17:27 +02:00
Peter Meier 905c2e4d36 refactor(calendar): EventDateBox-Komponente für Datums-Kachel
Deploy / verify (push) Successful in 1m7s
Deploy / deploy (push) Successful in 1m59s
Einzel- und Mehrtages-Datum in eine dedizierte Komponente ausgelagert.
Beide Varianten gleiche Höhe (h-20); Mehrtag zeigt Wochentag- und Tag-Range
(z. B. "Sa–So" / "27–28") und wächst nur in der Breite. Zentriert gestapelt
(kein Overflow), himmel-Box konsistent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:00:54 +02:00
Peter Meier d9519a867e perf: LCP-Fix, Banner-Crops, SSR-Cache, Bulk-Windkarte
Deploy / verify (push) Successful in 1m3s
Deploy / deploy (push) Successful in 1m47s
- TopBanner: JS-Fade-Gate entfernt (opacity-0 bis Hydration schob LCP
  von ~3s auf ~8.6s) — Bild malt jetzt progressiv über den LQIP
- buildBannerResponsive: Breakpoint-Varianten (1/1, 16/9, 24/9) mit
  media-Attributen, quality 60; CMS braucht explizites h neben ar,
  sonst kein Crop (Mobile bekam 1.92:1 unscharf hochskaliert)
- Layout-Preload: ein Link pro Breakpoint-Variante (WebP)
- Layout-Bootstrap-Batch + Logo-SVG-Fetch TTL-gecacht (liefen pro
  SSR-Request); invalidateCollection versteht CSV-Key-Heads
- WindkarteBlock: 40 Einzel-Fetches -> 1 List-Request mit Filter
- app.html: preconnect cms.pm86.de, api.iconify.design, analytics
- u-url Microformat: echte canonicalUrl statt href="#" (SEO-Audit)
- Icon-Subset: 'cloud' ergänzt (kein Runtime-Fetch an iconify mehr)
- StrommixBlock: nutzloses transition-colors entfernt (CLS-Audit)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:50:01 +02:00
Peter Meier 41597f87b5 feat(windkarte): InfoAccordion, anlagen-Hinweis, Tooltip ohne Icons
Deploy / verify (push) Successful in 1m6s
Deploy / deploy (push) Successful in 1m18s
- Disclaimer aus CMS-Feld (hinweis_text) via InfoAccordion statt Hardcode
- info_text-Feld für Block-Fließtext ergänzt
- anlagen_geplant_hinweis-Feld in WindArea + Panel-Anzeige
- Tooltip-Icons entfernt (square-outline/wind-turbine/map-marker inline SVGs)
- Panel: square-outline → ruler-square für Fläche
- Iconify-Subset neu generiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 12:35:29 +02:00
Peter Meier c225fb8be5 feat: Daten-Disclaimer Accordion oberhalb der Windkarte
Deploy / verify (push) Successful in 1m2s
Deploy / deploy (push) Successful in 1m18s
Erklärt warum Anlagenzahl/Höhe-Schätzwerte entfernt wurden. Default
geschlossen, Chevron-Pfeil als Indikator zum Aufklappen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 08:51:00 +02:00
Peter Meier f90116a923 fix: Windkarten-Tooltip + Anlagenzahl-Disclaimer überarbeitet
Deploy / verify (push) Successful in 1m4s
Deploy / deploy (push) Successful in 1m25s
Windkarte-Tooltip:
- Auf Touch-Geräten (@media hover:none) ausgeblendet, Detail-Panel
  öffnet eh bei Tap
- max-width 360px, min-width 220px, white-space:normal,
  größeres padding/Schrift für bessere Lesbarkeit
- Anlagen-Zahl aus Tooltip entfernt (nicht belastbar)
- Gemeinden ungekürzt anzeigen, Truncate auf 3 entfernt

WindAreaPanel:
- "Anlagen geplant" nach unten verschoben (nach Investor), Zeile mit
  opacity-60 + italic + mute-Farbe — zeigt visuell dass es nur eine
  Schätzung ist
- Disclaimer-Text überarbeitet: Obergrenzen-Schätzung 2-3 Anlagen je
  100 ha (270-Meter-Klasse) statt Faustregel 4-6

StellingnahmeGenerator: gleicher Disclaimer-Text-Wortlaut.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 08:15:06 +02:00
Peter Meier 6d1a084973 refactor: Stellingnahme-Generator UX-Überarbeitung Step 5
Deploy / verify (push) Successful in 1m4s
Deploy / deploy (push) Successful in 1m15s
- Sticky farb-hinterlegter Stepper (Sprung zwischen Schritten klarer)
- Step 5 reorganisiert: Setup oben, "═══ Ergebnis ═══" Trenner,
  Schreibvorlage- und KI-Cards primär sichtbar, Feintuning
  (Vorschau, Argument-Reihenfolge, Einleitung, Abschluss) in
  ein collapsed "Brief im Detail anpassen"-Accordion zusammengelegt
- "Mit Platzhaltern füllen"-Button für Absender-Felder
- KI-Block: nummerierte 1-2-3-4-Anleitung, "In Zwischenablage
  kopieren"-Button + .txt-Download, editierbare Inhalts-Regeln
- KI-Prompt nachgeschärft: konkrete Rechtsgrundlagen, klare Anti-
  KI-Floskel-Liste, expliziter Hinweis "Entwurf-Banner entfernen",
  Verbot von em/en-dashes als Satz-Trenner
- buildText post-processed em/en-dashes zu Kommas
- UI-Texte: em/en-dashes durch Kommas ersetzt
- "Anlagen geplant"-Disclaimer (Schätzung ~4-6 WEA/100ha) in
  Stellingnahme-Card und WindAreaPanel ergänzt
- Reset setzt Step + Scroll zurück, History bleibt erhalten

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:02:48 +02:00
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 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 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 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 9433705709 fix: Header-Adressbuch-Icon auf mdi:import-contacts
Deploy / verify (push) Successful in 56s
Deploy / deploy (push) Successful in 1m10s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:21:35 +02:00
Peter Meier 3c4b847c23 feat: Adressbuch-Icon in Header-Navigation, WhatsApp/YouTube entfernt
Deploy / verify (push) Successful in 1m6s
Deploy / deploy (push) Successful in 1m8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:14:31 +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 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 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 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 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 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 5b4d59175b Redesign SearchableTextBlock UI — stronger visual hierarchy
Deploy / verify (push) Successful in 56s
Deploy / deploy (push) Successful in 1m6s
Green gradient header, white card with shadow, left-border accent on open
accordion items, hover-visible copy button in summary row, lighter dividers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:18:07 +02:00
Peter Meier 0b6174eb53 chore: update iconify subset (map-outline swap)
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 59s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:31:42 +02:00
Peter Meier 39d7d70c8b feat(windkarte): Leaflet-Block für Windvorranggebiete SWT
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 1m18s
Neuer CMS-Block `wind_map` — platzierbar auf jeder Seite per Row-Content.
Fetcht wind_area-Einträge + GeoJSON-Asset client-side, rendert Leaflet-Karte
(CartoDB Light) mit farbcodierten Polygonen nach Planungsstatus. Klick auf
Gebiet öffnet Slide-in-Panel mit Kenndaten, Gemeinden, Stellungnahme-Link
und Puffer-Ringen (200/600/1000 m via Turf). Erweiterbar für 2. Entwurf 2026.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:20:32 +02:00
Peter Meier 23faef550e feat(calendar): image/attachment per event, direct links, copy link, ImageModal component
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 53s
- calendar_item schema: flyer → image (type: image), attachment (assetUrl)
- CalendarItemData: image + attachment fields
- event anchor IDs (#event-{slug}) with onMount hash scroll + auto-open
- share button uses direct calendar anchor URL
- copy link button (mdi:link-variant) copies anchor URL to clipboard
- ImageModal extracted as reusable component
- action buttons always show text (no expand-on-hover)
- onDestroy window guard (SSR fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:02:30 +02:00
Peter Meier 6fb2592eae feat(calendar): add feed subscription, bulk export, share & Google Cal
Deploy / verify (push) Successful in 1m0s
Deploy / deploy (push) Successful in 1m3s
- /api/kalender.ics SSR route: ICS feed of all calendar items (webcal-subscribable, Cache 1h)
- buildICSFeed() / downloadICSFeed() for multi-event ICS export
- googleCalUrl() deep-link pre-fills Google Calendar create form
- Web Share API per event (navigator.share on mobile, clipboard fallback on desktop)
  — "Kopiert\!" toast per event for 2s after clipboard write
- Actions bar (subscribe + bulk export) only rendered after onMount
  — Apple platforms: webcal:// link opens Calendar.app natively
  — All others: direct ICS download via <a download>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 16:50:56 +02:00
Peter Meier 8db68e073c refactor(calendar): stack layout, wider grid, fixed cell height
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 57s
- Calendar widget stays stacked (calendar above events list)
- Grid widens to max-w-2xl with fixed h-10 cells (no aspect-square height growth)
- Gap between cells: 0.5 → 1.5
- Button system extracted to shared btn-* utility classes in app.css
- Replaced Google Calendar link + Google Maps with OSM
- Removed google icon from iconify subset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 09:56:40 +02:00
Peter Meier 892afc2d9f refactor(post-events): read event data from resolved calendarItem
Deploy / verify (push) Successful in 47s
Deploy / deploy (push) Successful in 53s
Replace direct post.isEvent/eventDate/eventLocation reads with
getPostEventInfo() helper that derives info from the resolved
calendarItem reference. Loaders now resolve "calendarItem" and request
that field instead of legacy flat properties.

Also regenerate iconify mdi subset (map-marker-outline, download,
google, calendar-search, refresh).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 16:08:16 +02:00
Peter Meier b633ed2d2b fix(rusty-image): preserve animated GIFs through markdown image processing
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 54s
RustyCMS /api/transform decodes only the first frame of animated formats,
which silently kills GIF animation when markdown HTML images get rewritten
to /api/transform URLs. Skip the transform for `.gif` URLs so the original
asset URL flows through and animation survives.

Also picks up an arrow-right entry in the iconify mdi subset generated
during a UI build.
2026-05-07 13:06:37 +02:00
Peter Meier b7093b703a style(design): consistency sweep + UX upgrades
Deploy / verify (push) Successful in 49s
Deploy / deploy (push) Successful in 1m0s
- Palette: gray/zinc/slate/neutral → stein, red → fire, amber → erde,
  sky → himmel, green → wald
- Footer: replaced raw rgb with stein tokens
- Radius: unified to rounded-xs across cards, buttons, inputs, pagination
- Buttons: .btn-primary/.btn-secondary now font-medium; added secondary
- Card hover: -translate-y-0.5 + shadow + wald-300 border
- Header active link: wald border-bottom + wald-700 text
- Mobile nav: bigger touch targets + wald active accent
- Pagination: prev/next text labels + wald-500 active state with
  separated shape/color classes to avoid Tailwind conflicts
- Lead paragraph: scoped .markdown > p:first-child for top-level
  MarkdownBlock only via [data-block-type="markdown"]
- Section dividers: subtle wald-200 gradient between content rows
- Global focus-visible ring (wald-500)
- Inline hex → palette tokens (Badge amber, Tag custom-color contrast)
- Font weights snapped to design system (300/400/500/600/700)
- transition-all → transition-colors where only color changes
- Removed em-dashes from user-visible templates

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 01:06:22 +02:00
Peter Meier 96cd9e9c57 fix(strommix): SMARD as primary source — accuracy parity with audit
Deploy / verify (push) Successful in 45s
Deploy / deploy (push) Successful in 49s
Audit at 22:15 MESZ flagged the conventional sum (lignite + hard coal
+ gas + nuclear) as 19.1 GW in the widget vs 15.65 GW in reality. The
breakdown showed the gap was Energy-Charts' `Fossil gas` (6.9 GW)
which bundles natural gas + GuD + similar, while SMARD's `Erdgas`
filter 4069 (3.8 GW) tracks just natural gas the way the widget label
implies. SMARD is the canonical German grid feed; switching the
primary source resolves the discrepancy directly.

- New `lib/smard.ts` fetches the 11 relevant filter IDs in parallel:
  conventional (1223/4070/4069/1224), wind (4067/1225), solar (1226),
  hydro (1228), biomass (4066), other-conventional + pumped storage
  (1227/4071, surfaced separately), load (410). Two-step protocol
  (index.json → archive.json) handled inside; output is MW (MWh × 4)
  aligned to one common slot timestamp so every value on screen comes
  from the same point in time.
- Aggregator route uses SMARD for production + load, Energy-Charts
  only for cross-border saldo + day-ahead prices (where SMARD has no
  convenient single-call endpoint). `dataSource: "smard" |
  "energy-charts"` flag travels in the response so the widget can
  attribute the numbers and fall back gracefully when SMARD is down.
- StrommixBlock gets a "Werte prüfen" disclosure that lists every
  component (wind onshore + offshore, solar, the four conventional
  buckets, biomass, hydro, load) with its MW value and the slot
  timestamp + data source. Fact-check users can verify the addition
  themselves now without asking.

Verified live: SMARD @ 21:00 UTC = lignite 8484 + hard coal 3480 +
gas 3811 + nuclear 0 = 15.78 GW conventional. Matches the user's
ground-truth 15.65 GW within sampling noise.
2026-05-04 23:47:52 +02:00
Peter Meier d0dede92fe perf(iconify): tree-shake MDI to used icons (-99.5% JSON, -71% client bundle)
The previous setup loaded the full @iconify-json/mdi collection (7000+
icons, 2.95 MB) just to render the ~30 icons we actually use, producing a
2.99 MB client chunk.

generate-icons.mjs scans src/ for static `icon="mdi:..."` references,
extracts a minimal IconifyJSON containing only those icons (plus an
EXTRA_ICONS list for dynamic CMS-driven names) from
@iconify-json/mdi/icons.json, and writes it to a generated subset file.
iconify-offline.ts now registers that subset instead of the full pack.

  build/client: 5.6 MB -> 1.6 MB (-71%)
  build/server: 5.3 MB -> 2.4 MB (-55%)
  largest client chunk: 2.99 MB -> 127 KB (-96%)
  iconify subset: 2.95 MB -> 15.5 KB (-99.5%)

Hooked into predev/prebuild so the subset stays fresh; missing icons
fall back to the iconify API at runtime.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 16:08:47 +02:00