/** * Gemeinsame Typen für Content-Blöcke (Rows, Markdown, …). * Wird von SvelteKit-Seiten und Svelte-Komponenten genutzt. * Kalender-Typen basieren auf der OpenAPI-Spec (cms-api.generated). */ import type { BlockLayout } from "./block-layout"; import type { components } from "./cms-api.generated"; /** Aufgelöster Block vom CMS (mit _type, _slug + typ-spezifische Felder). */ export type ResolvedBlock = { _type?: string; _slug?: string; [key: string]: unknown; }; /** Gemeinsames Row-Layout (page, post, footer erben content_layout). */ export interface RowContentLayout { row1Content?: unknown[]; row1JustifyContent?: string; row1AlignItems?: string; row2Content?: unknown[]; row2JustifyContent?: string; row2AlignItems?: string; row3Content?: unknown[]; row3JustifyContent?: string; row3AlignItems?: string; } /** Resolved Markdown-Block vom CMS (_type: "markdown"). resolvedContent = HTML mit transformierten Bildern (von resolveContentImages). */ export interface MarkdownBlockData { _type?: "markdown"; _slug?: string; name?: string; content?: string; /** Gesetzt von resolveContentImages: HTML (Bilder transformiert, in Links eingewickelt). */ resolvedContent?: string; alignment?: "left" | "center" | "right"; layout?: BlockLayout; } /** Headline (_type: "headline"). */ export interface HeadlineBlockData { _type?: "headline"; _slug?: string; tag?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; text?: string; align?: "left" | "center" | "right"; layout?: BlockLayout; } /** HTML (_type: "html"). */ export interface HtmlBlockData { _type?: "html"; _slug?: string; html?: string; layout?: BlockLayout; } /** Iframe (_type: "iframe"). */ export interface IframeBlockData { _type?: "iframe"; _slug?: string; iframe?: string; content?: string; layout?: BlockLayout; } /** Image (_type: "image"). image oder img: Slug, URL-String oder aufgelöstes Objekt (src/description oder file.url). */ export interface ImageBlockData { _type?: "image"; _slug?: string; image?: | string | { _type?: "img"; _slug?: string; src?: string; description?: string; file?: { url?: string }; title?: string; }; /** Alternative zu image (manche CMS-Responses liefern img). */ img?: | string | { _type?: "img"; _slug?: string; src?: string; description?: string; file?: { url?: string }; title?: string; }; /** Nach resolveContentImages: Proxy-URL zum transformierten Bild. */ resolvedImageSrc?: string; caption?: string; aspectRatio?: number; layout?: BlockLayout; } /** Eintrag in einer image_gallery (img mit src/description). */ export interface ImageGalleryImage { _type?: "img"; _slug?: string; src?: string; description?: string; /** Optionaler Hover-Titel (image-Type: alt/caption/title). */ alt?: string; caption?: string; title?: string; file?: { url?: string }; /** Nach resolveContentImages: Proxy-URL zum transformierten Bild (16:9). */ resolvedSrc?: string; } /** Bildergalerie (_type: "image_gallery"). images: Array von img-Objekten (src, description). */ export interface ImageGalleryBlockData { _type?: "image_gallery"; _slug?: string; /** Optionaler Einleitungstext (Markdown). */ description?: string; /** Optional: oberhalb des Galerie-Modals als Header. */ title?: string; /** "standard" = Carousel (Default), "grid" = Thumbnail-Grid mit Modal. */ variant?: "standard" | "grid"; images?: ImageGalleryImage[]; layout?: BlockLayout; } /** Einzelne Datei in einem files-Block (CMS file-Field). */ export interface FilesBlockItem { _type?: "file"; src?: string; title?: string; description?: string; /** Aus Sidecar gemerged (size in Bytes). */ size?: number; mime?: string; filename?: string; } /** Datei-Block (_type: "files"). items = Array von file-Feldern. */ export interface FilesBlockData { _type?: "files"; _slug?: string; headline?: string; description?: string; items?: Array; forceDownload?: boolean; layout?: BlockLayout; } /** YouTube-Video (_type: "youtube_video"). */ export interface YoutubeVideoBlockData { _type?: "youtube_video"; _slug?: string; youtubeId?: string; /** * Playlist-ID (PLxxxxx). Wenn ohne youtubeId gesetzt, lädt die Playlist * von vorne; mit youtubeId startet das Video in der Playlist. * Akzeptiert auch direkte URLs wie * https://www.youtube.com/watch?v=...&list=PL... (wird parsed). */ playlistId?: string; title?: string; description?: string; params?: string; layout?: BlockLayout; } /** Galerie aus YouTube-Video-Referenzen (_type: "youtube_video_gallery"). */ export interface YoutubeVideoGalleryBlockData { _type?: "youtube_video_gallery"; _slug?: string; title?: string; subtitle?: string; description?: string; variant?: "carousel" | "grid"; videos?: YoutubeVideoBlockData[]; layout?: BlockLayout; } /** Zitat (_type: "quote"). */ export interface QuoteBlockData { _type?: "quote"; _slug?: string; quote?: string; author?: string; variant?: "left" | "right" | "center"; layout?: BlockLayout; } /** Zitat-Karussell (_type: "quote_carousel"). Rotiert durch aufgelöste quote-Einträge. */ export interface QuoteCarouselBlockData { _type?: "quote_carousel"; _slug?: string; headline?: string; quotes?: Array< | string | { _slug?: string; quote?: string; author?: string; variant?: "left" | "right" | "center"; } >; autoRotate?: boolean; intervalSeconds?: number; showArrows?: boolean; layout?: BlockLayout; } /** Link-Liste (_type: "link_list"). links können Slugs oder aufgelöste link-Objekte sein. */ export interface LinkListBlockData { _type?: "link_list"; _slug?: string; headline?: string; links?: Array; layout?: BlockLayout; } /** Post-Übersicht (_type: "post_overview"). Nach resolvePostOverviewBlocks: postsResolved gesetzt. */ export interface PostOverviewBlockData { _type?: "post_overview"; _slug?: string; headline?: string; /** Einleitungstext (Markdown). */ text?: string; /** true = alle Posts laden; false = posts (Slugs/Objekte) aus dem Block nutzen. */ allPosts?: boolean; /** Nur wenn allPosts false: feste Post-Liste (Slugs oder aufgelöste Einträge). */ posts?: unknown[]; /** Tag-Slugs, nach denen gefiltert wird (nur bei allPosts true). */ filterByTag?: string[]; /** Tag-Slugs, die ausgeschlossen werden (optional, nach filterByTag angewendet). */ excludeTag?: string[]; /** Max. Anzahl Einträge. */ numberItems?: number; /** "list" | "cards". */ design?: "list" | "cards"; layout?: BlockLayout; /** Nach resolvePostOverviewBlocks: geladene/gefilterte Posts (PostEntry[]). */ postsResolved?: unknown[]; } /** Deadline-Banner (_type: "deadline_banner"). Schmaler Balken unter Hero. */ export interface DeadlineBannerBlockData { _type?: "deadline_banner"; _slug?: string; mode?: "manual" | "auto"; label?: string; text?: string; date?: string; items?: Array< | string | { _slug?: string; title?: string; terminZeit?: string; description?: string; link?: string | { url?: string; linkName?: string; newTab?: boolean; _slug?: string }; } >; link?: string | { url?: string; linkName?: string; newTab?: boolean; _slug?: string }; variant?: "accent" | "urgent" | "info"; showCountdown?: boolean; layout?: BlockLayout; } /** Tag-Referenz (API liefert oft { _slug, _type, name }). */ export type SearchableTextTagRef = string | { _slug?: string; name?: string }; /** Ein Text-Fragment (aus text_fragment), für SearchableTextBlock. */ export interface SearchableTextFragment { _slug?: string; title?: string; text?: string; tags?: SearchableTextTagRef[]; } /** Durchsuchbarer Text (_type: "searchable_text"). textFragments können Slugs oder aufgelöste Objekte sein. */ export interface SearchableTextBlockData { _type?: "searchable_text"; _slug?: string; /** Titel der Suchkomponente. */ title?: string; /** Einleitung (Markdown). */ description?: string; /** Slugs oder aufgelöste text_fragment-Einträge. */ textFragments?: (string | SearchableTextFragment)[]; layout?: BlockLayout; } /** OpnForm embed (_type: "opnform"). */ export interface OpnFormBlockData { _type?: "opnform"; _slug?: string; id?: string; formId?: string; baseUrl?: string; iframeTitle?: string; layout?: BlockLayout; } /** Einzelne Organisation (aufgelöst aus organisation-Collection). */ export interface OrganisationEntry { _type?: "organisation"; _slug?: string; name?: string; logo?: string | { src?: string; description?: string }; /** Nach resolveContentImages: Proxy-URL zum Logo (WebP). */ resolvedLogoSrc?: string; description?: string; link?: string | { url?: string; linkName?: string; newTab?: boolean }; badge?: string | { label?: string; color?: string }; } /** Organisations-Block (_type: "organisations"). */ export interface OrganisationsBlockData { _type?: "organisations"; _slug?: string; headline?: string; description?: string; /** default = Karten-Grid; compact = engere Karten, Logo links; carousel = horizontaler Slider (3 Desktop / 2 Mobile) */ presentation?: "default" | "compact" | "carousel"; addOrganisationTitle?: string; addOrganisationMarkdown?: string; /** Referenz auf link-Collection (aufgelöst: url, linkName, newTab) */ addOrganisationLink?: string | { url?: string; linkName?: string; newTab?: boolean }; organisations?: (string | OrganisationEntry)[]; layout?: BlockLayout; } /** Live-Strommix-Widget (_type: "live_strommix"). Editorial-Konfig + Platzierungs-Instanz. * Fetched live data from /api/strommix (aggregator) and renders status bucket * derived from `(wind_onshore + wind_offshore + solar) / load * 100`. */ export interface LiveStrommixBlockData { _type?: "live_strommix"; _slug?: string; /** `full` = card with argument + breakdown disclosure + history accordion. * `hero` = pct + status + sparkline + trend, no sub-cards or accordion. * `compact` = single horizontal strip for sidebar / homepage placement. * Default: `full`. */ variant?: "full" | "hero" | "compact"; intro_headline?: string; intro_text?: string; threshold_dunkelflaute?: number; threshold_niedrig?: number; threshold_mittel?: number; label_dunkelflaute?: string; label_niedrig?: string; label_mittel?: string; label_hoch?: string; argument_dunkelflaute?: string; argument_niedrig?: string; argument_mittel?: string; argument_hoch?: string; enable_france_comparison?: boolean; disclaimer?: string; /** Day-ahead price (€/MWh) below which `disclaimer` is rendered. * Default 10 — disclaimer about EEG-Vergütung at negative prices is * irrelevant at 130 €/MWh and gets hidden. Set very high (e.g. 9999) * to always show. */ disclaimer_threshold_eur_mwh?: number; /** Reference (resolved: { _slug, _type, ...page-fields } or just slug string). */ explanation_page?: string | { _slug?: string; title?: string }; // --------------------------------------------------------------------- // Stammdaten (BNetzA Marktstammdatenregister, manuell gepflegt). Treiben // den Live-vs-Installiert-Auslastungs-Bereich. Stand quartalsweise // aktualisieren — die Werte bewegen sich um 1–3 GW pro Quartal. // --------------------------------------------------------------------- installed_wind_onshore_gw?: number; installed_wind_offshore_gw?: number; installed_solar_gw?: number; /** Free-form Stand-Marker für die Stammdaten, z. B. "2025-Q4". */ installed_as_of?: string; /** Optional: Hand-gepflegte EinsMan-Entschädigungssumme YTD (Mio €). * BNetzA liefert das nur quartalsweise per PDF. Wenn nicht gesetzt, * zeigt das Widget den Counter ohne Eurosumme. */ einsman_costs_ytd_eur_mio?: number; einsman_costs_as_of?: string; layout?: BlockLayout; } /** Kalender-Item (calendar_item) – aus OpenAPI; terminZeit = ISO date-time. * Lokale Erweiterungen sind im RustyCMS-Schema ergänzt und werden bis zur * OpenAPI-Regen hier inline mitgeführt. */ export type CalendarItemData = components["schemas"]["calendar_item"] & { /** Optionales Endedatum für Mehrtagesveranstaltungen (ISO date-time). */ terminEnde?: string; /** Optionaler Veranstaltungsort (string oder {text, lat, lng}). */ location?: string | { text?: string; lat?: number; lng?: number }; /** Optionale Tags zur Filterung. */ tags?: string[]; /** Optionales Veranstaltungsbild (aufgelöstes image-Feld oder Slug). */ image?: | string | { _type?: "img"; _slug?: string; src?: string; description?: string; file?: { url?: string }; }; /** Optionaler Dateianhang (z.B. PDF-Flyer). */ attachment?: { src?: string; label?: string; }; }; /** Kalender-Block (_type: "calendar"). items nach Resolve: CalendarItemData[]. Basis aus OpenAPI (calendar). */ export type CalendarBlockData = Omit< components["schemas"]["calendar"], "items" > & { _type?: "calendar"; /** Optionaler Titel über dem Kalender. */ title?: string; /** Slugs oder aufgelöste calendar_item-Einträge. */ items?: (string | CalendarItemData)[]; layout?: BlockLayout; }; export interface WindMapBlockData { _type?: "wind_map"; _slug?: string; name?: string; title?: string; subtitle?: string; /** Explizite Auswahl von wind_area-Slugs. Leer/null = alle published areas. */ areas?: (string | { _slug: string;[key: string]: unknown })[]; }