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>
This commit is contained in:
Peter Meier
2026-05-13 09:20:32 +02:00
parent cdc2a79950
commit 39d7d70c8b
9 changed files with 570 additions and 5 deletions
+126
View File
@@ -0,0 +1,126 @@
<script lang="ts">
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import type { WindArea } from "$lib/windkarte";
let {
area,
onclose,
}: {
area: WindArea | null;
onclose: () => void;
} = $props();
const STATUS_LABEL: Record<string, string> = {
rechtskraeftig: "Rechtskräftig",
entwurf_2: "2. Entwurf 2026",
entwurf_3: "3. Entwurf",
};
const STATUS_CLASS: Record<string, string> = {
rechtskraeftig: "bg-wald-100 text-wald-800",
entwurf_2: "bg-erde-100 text-erde-800",
entwurf_3: "bg-himmel-100 text-himmel-800",
};
function stellungnahmeHref(s: WindArea["stellungnahme"]): string | null {
if (!s) return null;
if (typeof s === "string") return `/post/${s}`;
if (s._slug) return `/post/${s._slug}`;
return null;
}
</script>
{#if area}
<div class="pointer-events-auto flex h-full flex-col overflow-y-auto bg-stein-0 shadow-xl">
<!-- Header -->
<div class="flex items-start justify-between gap-3 border-b border-stein-200 px-5 py-4">
<div class="min-w-0">
<div class="flex items-center gap-2 flex-wrap">
<span class="text-xl font-semibold text-stein-800">{area.name}</span>
{#if area.status}
<span class="rounded-full px-2 py-0.5 text-xs font-medium {STATUS_CLASS[area.status] ?? 'bg-stein-100 text-stein-600'}">
{STATUS_LABEL[area.status] ?? area.status}
</span>
{/if}
</div>
{#if area.bezeichnung}
<p class="mt-0.5 text-sm text-stein-500">{area.bezeichnung}</p>
{/if}
</div>
<button
onclick={onclose}
class="shrink-0 rounded-md p-1.5 text-stein-400 hover:bg-stein-100 hover:text-stein-700 transition-colors"
aria-label="Panel schließen"
>
<Icon icon="mdi:close" class="size-5" />
</button>
</div>
<!-- Body -->
<div class="flex-1 space-y-5 px-5 py-4 text-sm">
<!-- Kenndaten -->
<div class="grid grid-cols-2 gap-3">
{#if area.flaeche_ha != null}
<div class="rounded-lg bg-stein-50 p-3">
<p class="text-xs text-stein-500">Fläche</p>
<p class="mt-0.5 font-semibold text-stein-800">{area.flaeche_ha.toFixed(1)} ha</p>
</div>
{/if}
{#if area.anlagen_geplant != null}
<div class="rounded-lg bg-stein-50 p-3">
<p class="text-xs text-stein-500">Geplante Anlagen</p>
<p class="mt-0.5 font-semibold text-stein-800">{area.anlagen_geplant}</p>
</div>
{/if}
{#if area.hoehe_max_m != null}
<div class="rounded-lg bg-stein-50 p-3">
<p class="text-xs text-stein-500">Max. Nabenhöhe</p>
<p class="mt-0.5 font-semibold text-stein-800">{area.hoehe_max_m} m</p>
</div>
{/if}
{#if area.investor}
<div class="rounded-lg bg-stein-50 p-3 col-span-2">
<p class="text-xs text-stein-500">Investor / Betreiber</p>
<p class="mt-0.5 font-semibold text-stein-800">{area.investor}</p>
</div>
{/if}
</div>
<!-- Gemeinden -->
{#if area.gemeinden && area.gemeinden.length > 0}
<div>
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-stein-500">Betroffene Gemeinden</p>
<div class="flex flex-wrap gap-1.5">
{#each area.gemeinden as g}
<span class="rounded-full bg-himmel-100 px-2.5 py-0.5 text-xs font-medium text-himmel-800">{g}</span>
{/each}
</div>
</div>
{/if}
<!-- Notizen -->
{#if area.notizen}
<div>
<p class="mb-1.5 text-xs font-medium uppercase tracking-wide text-stein-500">Hinweise</p>
<p class="text-stein-700 leading-relaxed">{area.notizen}</p>
</div>
{/if}
<!-- Stellungnahme -->
{#if stellungnahmeHref(area.stellungnahme)}
<a
href={stellungnahmeHref(area.stellungnahme)}
class="flex items-center gap-2 rounded-lg border border-wald-300 bg-wald-50 px-4 py-3 text-sm font-medium text-wald-700 hover:bg-wald-100 transition-colors"
>
<Icon icon="mdi:file-document-edit-outline" class="size-4 shrink-0" />
Zur Stellungnahme-Vorlage
</a>
{:else}
<div class="rounded-lg border border-dashed border-stein-200 px-4 py-3 text-xs text-stein-400">
<Icon icon="mdi:file-document-outline" class="inline size-3.5 mr-1" />
Stellungnahme-Vorlage noch nicht verknüpft
</div>
{/if}
</div>
</div>
{/if}