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>
- 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>
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>
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>
Renders a single CMS entry as a block in isolation — no header, footer, or
preview banner. Lets us share a deep link to a single widget (e.g. the live
strommix gauge) for embedding or quick previews without exposing the rest of
the page.
- New BlockRenderer.svelte: switch over `_type` shared with ContentRows.
- Generic `getEntryBySlug(collection, slug, options)` in cms.ts so any
collection (live_strommix, deadline_banner, image, ...) is fetchable.
- Route group `(embed)/+layout@.svelte` resets the layout chain so no
Header/Footer/TopBanner inherit. `x-robots-tag: noindex`.
- Optional `?preview=<token>` for drafts. Cache: short s-maxage for live,
no-store for preview.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>