Commit Graph

294 Commits

Author SHA1 Message Date
Peter Meier 1284be9c42 FilesBlock: size label smaller + no-wrap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:42:32 +02:00
Peter Meier 325f95487f FilesBlock: move size under icon, drop redundant ext label
Icon already communicates file type. Size now sits below the icon
in a fixed-width left column. Ext text removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:41:40 +02:00
Peter Meier 1825b7d9fe FilesBlock: compact single-row layout
Reduce padding, smaller icon, filename + ext/size on one line.
Description hidden on mobile, truncated on desktop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:39:09 +02:00
Peter Meier 4b9214dbaf Fix FilesBlock: external URLs bypass /cms-files proxy
getCmsFileUrl() returned /cms-files?src=... for all URLs including
external ones. resolveCmsSource() rejects foreign hosts (SSRF guard),
so external file links 404'd. External http(s) URLs now returned directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:36:43 +02:00
Peter Meier 44b991f7c1 Improve social media OG/Twitter meta for posts
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 1m2s
- Fix bug: post page was overriding og:image with 150x200 thumbnail
- og:type = "article" for posts (was always "website")
- article:published_time, article:modified_time, article:author, article:tag
- og:image:alt + twitter:image:alt (post headline)
- twitter:label1/data1 with reading time
- Remove thumbnail fallback from socialImage (falls back to site logo instead)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:35:32 +02:00
Peter Meier d7a84aff0d SearchableTextBlock: animated accordion, tag counts, empty state, focus-visible
- Replace <details> with Svelte-controlled accordion + slide transition (180ms)
- Close all items on search/tag filter change
- Live tag counts per filter showing matches for current search query
- Visual empty state with reset button when no results
- focus-visible ring on all interactive elements (was focus: — shows on click too)
- Tag.svelte: remove ring-0, add focus-visible:ring-2
- Card.svelte: add focus-visible ring to linked cards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:31:38 +02:00
Peter Meier fa50617d3c Fix Tooltip overflow-hidden clipping via portal action
Portal the tooltip into document.body so overflow:hidden ancestors
can't clip it. Fixed positioning + getBoundingClientRect() for placement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:25:18 +02:00
Peter Meier cbf0e4aa51 Fix Tooltip clipping in overflow-hidden containers — use fixed positioning
Switches from absolute (clipped by ancestor overflow:hidden) to fixed
with viewport-relative coords from getBoundingClientRect().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:22:27 +02:00
Peter Meier 35a34eb300 Fix SearchableTextBlock h2 color — override global heading CSS with \!text-white
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:21:03 +02:00
Peter Meier 1f430f01a5 Fix SearchableTextBlock header text contrast — white/80 on dark green
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 1m1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:19:08 +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 4fc541acf9 perf: parallel block resolution, immutable asset caching, fewer favicons
Deploy / verify (push) Successful in 57s
Deploy / deploy (push) Successful in 59s
- +page.server.ts: resolveContentImages/Calendar/Deadline/SearchableText
  now run in parallel via Promise.all (after resolvePostOverviewBlocks)
- +page.server.ts: banner widths 5→3 [640,1024,1536]
- hooks.server.ts: /_app/immutable/** gets Cache-Control: max-age=31536000
- +layout.svelte: favicon tags 18→4 (ico, 32px png, 192px, apple-180px)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:00:07 +02:00
Peter Meier 19f3948f48 feat(carousel): 2 cols tablet, 3 cols desktop; equal-height cards
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m4s
- PostOverviewBlock carousel: md:2 cols, lg:3 cols via snap-scroll
- PostCard: add class prop for external styling
- Carousel cards: h-full for equal height

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:54:36 +02:00
Peter Meier 41dfbb2930 feat: add carousel design to PostOverview; extract shared Carousel component
Deploy / verify (push) Successful in 57s
Deploy / deploy (push) Successful in 1m5s
- New Carousel.svelte: index-fade, arrows, dots, optional auto-rotate
- QuoteCarouselBlock refactored to use Carousel
- PostOverviewBlock: carousel design variant added
- CMS schema: "carousel" added to post_overview design enum

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:27:31 +02:00
Peter Meier a1dcedd855 fix: WindkarteBlock col-span + data-block attrs
Deploy / verify (push) Successful in 52s
Deploy / deploy (push) Successful in 1m24s
Block fehlte layoutClasses (col-span) und data-block/type/slug attrs.
Layout-Feld zu WindMapBlockData ergänzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:14:56 +02:00
Peter Meier 3cb728d677 fix: add wind_map block to ContentRows
Deploy / verify (push) Successful in 1m4s
Deploy / deploy (push) Successful in 1m40s
ContentRows.svelte fehlte wind_map — Block wurde als "Unbekannter Block" gerendert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:59:06 +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 b4581e4d4f feat(windkarte): tap-to-activate overlay for mobile scroll fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:31:23 +02:00
Peter Meier ad72c44d98 fix(windkarte): 2km/5km-Ringe selektion-basiert statt permanent
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m1s
Ringe erscheinen nur bei ausgewähltem Gebiet (wie 200m/600m/1km),
Toggle steuert Sichtbarkeit. Permanente Allgebiets-Berechnung entfernt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:24:14 +02:00
Peter Meier f9b7057ba4 fix(windkarte): maxZoom bei Selektion von 14 auf 12 reduziert
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 1m1s
2026-05-13 15:21:11 +02:00
Peter Meier cd67ff8dc8 feat(windkarte): permanente 2km/5km-Ringe togglebar in Legende
Deploy / verify (push) Successful in 55s
Deploy / deploy (push) Successful in 1m4s
Zeigt 2km (indigo) und 5km (lila) Pufferringe für alle Gebiete gleichzeitig,
unabhängig von Selektion. Default versteckt, Klick auf Legendenitem ein-/ausblenden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:19:44 +02:00
Peter Meier 0f6a58fed4 feat(windkarte): Legend-Toggle — Status ein-/ausblendbar per Klick
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m1s
Klick auf Legendenitem blendet den Status aus (opacity 0, kein Klick,
kein Hover). Panel schließt sich wenn der Status des gewählten Gebiets
ausgeblendet wird. Visuell: opacity + line-through auf versteckten Items.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:09:14 +02:00
Peter Meier 7d4f52ba02 feat(windkarte): neuer Status entwurf_2_voraussichtlich (orange)
Deploy / verify (push) Successful in 56s
Deploy / deploy (push) Successful in 1m3s
Farbe #d4752a. Legende, Typ-Union, Übersetzungen ergänzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:17:15 +02:00
Peter Meier ca742345f3 feat(windkarte): geometry eingebettet in wind_area CMS-Einträge
Deploy / verify (push) Successful in 49s
Deploy / deploy (push) Successful in 1m8s
GeoJSON-Geometrie direkt in CMS-Einträgen gespeichert — kein separates
GeoJSON-File mehr. WindkarteMap baut FeatureCollection aus areas[].geometry.
WindArea-Typ bekommt geometry-Feld. geojsonUrl-Prop entfernt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:34:59 +02:00
Peter Meier ee56a457d3 feat(windkarte): alle UI-Strings auf t(T.windkarte_*) umgestellt
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 59s
Keine hardcodierten Texte mehr — alle Strings über CMS translation_bundle
(app.json5). Keys in translations.ts registriert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:09:09 +02:00
Peter Meier 91a09fd3c4 fix(windkarte): Interne "nicht verknüpft"-Meldung aus Panel entfernt
Deploy / verify (push) Successful in 50s
Deploy / deploy (push) Successful in 59s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:03:59 +02:00
Peter Meier fece9c7cfc fix(windkarte): Mobile — Map höher, Panel kürzer (52% statt 72%)
Deploy / verify (push) Successful in 52s
Deploy / deploy (push) Successful in 59s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:28:07 +02:00
Peter Meier 0cbf48f1fb fix(windkarte): Panel z-index über Leaflet-Controls (1001 > 1000)
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 1m0s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:26:23 +02:00
Peter Meier 0e28a7e334 feat(windkarte): Karten-UX Verbesserungen
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m7s
- Polygon-Labels ab Zoom 13 mit zoom-proportionaler Schriftgröße
- Hover-Highlight, Deselect per Hintergrundklick
- Mobile Bottom-Sheet / Desktop Side-Panel (CSS-only)
- Share-Link via URL-Hash (#W-10), auto-select on load
- Share-Button + OSM-Link im Panel
- Reset-Button auf Karte
- Buffer-Spinner während Turf-Berechnung
- Leaflet-Attribution nach links (kollidierte mit Panel)
- fitBounds mit asymmetrischem Padding + maxZoom 14

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:24:56 +02:00
Peter Meier 6fef86fc6b fix(markdown): use _slug instead of removed name field for data attribute
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:41:25 +02:00
Peter Meier c66899fa9b feat(windkarte): slugPrefix enforcement, name→gebiets_nr, name field removed
Deploy / verify (push) Successful in 48s
Deploy / deploy (push) Successful in 59s
- RustyCMS: SchemaDefinition.slug_prefix + auto-prepend in create_entry
- wind_area/wind_map schemas: slugPrefix config, name field removed
- wind_area entries: name→gebiets_nr (faktische Gebietsnummer, GeoJSON-Join-Key)
- WindkarteMap/WindAreaPanel: Join + Anzeige auf gebiets_nr

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:35:28 +02:00
Peter Meier 067e847524 feat(windkarte): wind_map referenziert wind_area explizit
Deploy / verify (push) Successful in 49s
Deploy / deploy (push) Successful in 1m1s
areas-Feld im Schema + Block-Logik: referenzierte Slugs werden einzeln
gefetcht. Leer = alle published areas als Fallback. Ermöglicht verschiedene
Karten-Blöcke mit unterschiedlichen Gebiet-Subsets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:25:00 +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 cdc2a79950 refactor(strommix): route EC live snapshot through CMS, keep direct fallback
Deploy / verify (push) Successful in 52s
Deploy / deploy (push) Successful in 54s
Energy-Charts /public_power now fetched via strommix_history_de CMS collection
(which uses ${time:day_start|iso}/${time:now|iso} in its source URL). When the
CMS returns null (old binary without env_subst support), falls back to a direct
EC call so charts keep working during the transition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 08:17:41 +02:00
Peter Meier 81e419aa4d fix(strommix): show sparkline without history backend
Deploy / verify (push) Successful in 55s
Deploy / deploy (push) Successful in 57s
The strommix_history_de collection source URL 404s (Energy-Charts needs
start/end params), so the CMS history backend stays empty and todayHourly
was always null — making the Wind & Solar sparkline never appear.

Extend fetchEnergyChartsPublicPower to start at midnight (was: -2h) and
return the raw series alongside the snapshot values. Build todayHourly
from it when the history backend has no items. No extra API call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:58:34 +02:00
Peter Meier 10fb2a3ef6 feat(calendar): markdown descriptions, grid & header polish
Deploy / verify (push) Successful in 49s
Deploy / deploy (push) Successful in 50s
- description field rendered as Markdown (marked.parse + prose)
- month label left/larger, nav arrows grouped right
- weekday headers colored (himmel/stein)
- day cells weekend/weekday coloring
- remove pl-[4.75rem] indent from accordion body

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:53:51 +02:00
Peter Meier 7ea23531e9 feat(calendar): grid color polish, month header layout, direct event links
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 53s
- month label left + larger, prev/next arrows grouped right
- weekday headers colored (himmel/stein by weekday/weekend)
- day cells colored by weekday/weekend + event state
- event anchor IDs, onDestroy SSR fix, ImageModal component
- copy link button, action buttons always show text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 20:43:21 +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 6fc2c0a345 feat(calendar): hover tooltip, light theme grid, UX polish
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 55s
- Hover tooltip on event cells (pointer:fine only, position:fixed bypasses overflow-hidden)
  — shows title + right-aligned time, max 3 events, "+N weitere"
- Grid background changed to himmel-100 (light), all text tokens updated to dark
- Today cell: wald-200 green background + wald-400 outline
- Empty day cells: subtle bg-himmel-50/40 tint
- Event badge larger on desktop (sm:min-w-4.5 sm:h-4.5 sm:text-[10px])
- Weekday headers: font-semibold text-himmel-800
- Calendar takes full width inside widget (removed max-w-2xl cap)
- Web Share API per event (navigator.share / clipboard fallback, Kopiert\! toast)
- Subscribe bar: webcal:// on Apple, direct ICS download on other platforms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 17:23:41 +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 7fa52bb629 fix(calendar): remove hard max-w cap on location text
Deploy / verify (push) Successful in 52s
Deploy / deploy (push) Successful in 56s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:08:17 +02:00
Peter Meier 4dd17b7ac4 fix(docker): add healthcheck and stop grace period
Deploy / verify (push) Successful in 57s
Deploy / deploy (push) Successful in 56s
Watchtower waits for container to be healthy before stopping the old
one. Grace period allows in-flight requests to drain on shutdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 14:06:46 +02:00
Peter Meier ec9930762d fix(TopBanner): align text-only page title with content container
Deploy / verify (push) Successful in 1m1s
Deploy / deploy (push) Successful in 58s
Replace `container mx-auto px-6` with `container-custom` so the
headline/subheadline block (no image case) matches the main content
padding and max-width.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 13:11:10 +02:00
Peter Meier ef163fb336 feat(layout): add global announcement banner above nav
Deploy / verify (push) Successful in 1m12s
Deploy / deploy (push) Successful in 1m6s
Adds AnnouncementBanner component fed by a new top_banner CMS entry
(site-announcement). Extended top_banner schema and TS types with
optional link + linkLabel fields. Rendered inline-markdown text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 21:56:42 +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 b491e3767a Revert "fix(calendar): stack layout, cap block at 1000px, restore events scroll"
Deploy / verify (push) Successful in 51s
Deploy / deploy (push) Successful in 51s
This reverts commit bf6ca475db.
2026-05-08 08:18:05 +02:00
Peter Meier bf6ca475db fix(calendar): stack layout, cap block at 1000px, restore events scroll
Deploy / verify (push) Successful in 53s
Deploy / deploy (push) Successful in 53s
The 2-col grid forced the events list to share the calendar's square
height which cropped event details. Stack calendar over events instead,
cap the whole block at 1000px (centered), constrain the calendar grid to
480px so day cells don't grow oversized, and re-add a 600px max-height
with internal scroll on the events list so the page stays compact and
sticky day-group headers regain their scroll context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 08:16:57 +02:00
Peter Meier ce4c4570d6 feat(header): keep internal social-nav links in same tab
Deploy / verify (push) Successful in 55s
Deploy / deploy (push) Successful in 57s
Social-link entries with internal URLs (starting with /) used to open in a
new tab via target=_blank, which is wrong for internal navigation like the
new /kalender entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 07:53:43 +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 fc6c59bc10 fix(deadline-banner): count calendar days, not 24h windows
Deploy / verify (push) Successful in 49s
Deploy / deploy (push) Successful in 53s
daysLeft used Math.ceil(ms/24h) which counted partial 24h windows:
30h before a midnight event tomorrow showed "Noch 2 Tage" instead
of "Morgen". Compare midnight-to-midnight day-keys instead, same
approach as CalendarBlock relativeDays().

Also adds data-block="DeadlineBanner" identifier.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:45:22 +02:00