diff --git a/.env.backup b/.env.backup new file mode 100644 index 0000000..0ead10a --- /dev/null +++ b/.env.backup @@ -0,0 +1,4 @@ +PUBLIC_CMS_URL=http://localhost:3000 + +PUBLIC_FORMBRICKS_APP_URL=https://forms.pm86.de +PUBLIC_FORMBRICKS_ENVIRONMENT_ID=cmnlgrjle0008mr01k018oaiz diff --git a/.env.example b/.env.example index 0fcef07..1d87ffd 100644 --- a/.env.example +++ b/.env.example @@ -13,4 +13,8 @@ PUBLIC_CMS_URL=http://localhost:3000 # PUBLIC_UMAMI_SCRIPT_URL=https://eu.umami.is/script.js # PUBLIC_UMAMI_WEBSITE_ID=your-website-id +# Formbricks Website/App Surveys (beide setzen → SDK lädt, vgl. https://formbricks.com/docs/xm-and-surveys/surveys/website-app-surveys/framework-guides#html ) +# PUBLIC_FORMBRICKS_APP_URL=https://forms.pm86.de +# PUBLIC_FORMBRICKS_ENVIRONMENT_ID= + # Firebase: Projekt in .firebaserc anpassen oder: firebase use diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index bd6c014..6b9b614 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -18,6 +18,14 @@ on: description: "PUBLIC_SITE_URL" required: false default: "https://windwiderstand.de" + public_formbricks_app_url: + description: "PUBLIC_FORMBRICKS_APP_URL (leer = kein Formbricks)" + required: false + default: "https://forms.pm86.de" + public_formbricks_environment_id: + description: "PUBLIC_FORMBRICKS_ENVIRONMENT_ID" + required: false + default: "cmnlgrjle0008mr01k018oaiz" jobs: deploy: @@ -41,6 +49,8 @@ jobs: env: PUBLIC_CMS_URL: ${{ inputs.public_cms_url }} PUBLIC_SITE_URL: ${{ inputs.public_site_url }} + PUBLIC_FORMBRICKS_APP_URL: ${{ inputs.public_formbricks_app_url || 'https://forms.pm86.de' }} + PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ inputs.public_formbricks_environment_id || 'cmnlgrjle0008mr01k018oaiz' }} run: yarn build - name: Deploy to Firebase diff --git a/package.json b/package.json index 0217eef..b20c06f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,9 @@ "astro": "^6.1.1", "json5": "^2.2.3", "marked": "^17.0.2", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-photo-album": "^3.5.1", "svelte": "^5.51.2", "tailwindcss": "^4.1.18" }, @@ -35,6 +38,8 @@ "@storybook/svelte": "10", "@storybook/svelte-vite": "10", "@types/node": "^22.10.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", "astro-icon": "^1.1.5", "firebase": "^11.0.1", "openapi-typescript": "^7.4.0", diff --git a/public/favicons/android-icon-144x144.png b/public/favicons/android-icon-144x144.png new file mode 100644 index 0000000..e8c8b34 Binary files /dev/null and b/public/favicons/android-icon-144x144.png differ diff --git a/public/favicons/android-icon-192x192.png b/public/favicons/android-icon-192x192.png new file mode 100644 index 0000000..5a8f829 Binary files /dev/null and b/public/favicons/android-icon-192x192.png differ diff --git a/public/favicons/android-icon-36x36.png b/public/favicons/android-icon-36x36.png new file mode 100644 index 0000000..d43bc5d Binary files /dev/null and b/public/favicons/android-icon-36x36.png differ diff --git a/public/favicons/android-icon-48x48.png b/public/favicons/android-icon-48x48.png new file mode 100644 index 0000000..09c3614 Binary files /dev/null and b/public/favicons/android-icon-48x48.png differ diff --git a/public/favicons/android-icon-72x72.png b/public/favicons/android-icon-72x72.png new file mode 100644 index 0000000..160cc4b Binary files /dev/null and b/public/favicons/android-icon-72x72.png differ diff --git a/public/favicons/android-icon-96x96.png b/public/favicons/android-icon-96x96.png new file mode 100644 index 0000000..f68e007 Binary files /dev/null and b/public/favicons/android-icon-96x96.png differ diff --git a/public/favicons/apple-icon-114x114.png b/public/favicons/apple-icon-114x114.png new file mode 100644 index 0000000..a5b46d8 Binary files /dev/null and b/public/favicons/apple-icon-114x114.png differ diff --git a/public/favicons/apple-icon-120x120.png b/public/favicons/apple-icon-120x120.png new file mode 100644 index 0000000..c3e30c2 Binary files /dev/null and b/public/favicons/apple-icon-120x120.png differ diff --git a/public/favicons/apple-icon-144x144.png b/public/favicons/apple-icon-144x144.png new file mode 100644 index 0000000..fb74aa1 Binary files /dev/null and b/public/favicons/apple-icon-144x144.png differ diff --git a/public/favicons/apple-icon-152x152.png b/public/favicons/apple-icon-152x152.png new file mode 100644 index 0000000..7d09c1d Binary files /dev/null and b/public/favicons/apple-icon-152x152.png differ diff --git a/public/favicons/apple-icon-180x180.png b/public/favicons/apple-icon-180x180.png new file mode 100644 index 0000000..5738ab6 Binary files /dev/null and b/public/favicons/apple-icon-180x180.png differ diff --git a/public/favicons/apple-icon-57x57.png b/public/favicons/apple-icon-57x57.png new file mode 100644 index 0000000..7bab301 Binary files /dev/null and b/public/favicons/apple-icon-57x57.png differ diff --git a/public/favicons/apple-icon-60x60.png b/public/favicons/apple-icon-60x60.png new file mode 100644 index 0000000..0372483 Binary files /dev/null and b/public/favicons/apple-icon-60x60.png differ diff --git a/public/favicons/apple-icon-72x72.png b/public/favicons/apple-icon-72x72.png new file mode 100644 index 0000000..24270d2 Binary files /dev/null and b/public/favicons/apple-icon-72x72.png differ diff --git a/public/favicons/apple-icon-76x76.png b/public/favicons/apple-icon-76x76.png new file mode 100644 index 0000000..b5e9092 Binary files /dev/null and b/public/favicons/apple-icon-76x76.png differ diff --git a/public/favicons/apple-icon-precomposed.png b/public/favicons/apple-icon-precomposed.png new file mode 100644 index 0000000..ff96e2b Binary files /dev/null and b/public/favicons/apple-icon-precomposed.png differ diff --git a/public/favicons/apple-icon.png b/public/favicons/apple-icon.png new file mode 100644 index 0000000..ff96e2b Binary files /dev/null and b/public/favicons/apple-icon.png differ diff --git a/public/favicons/browserconfig.xml b/public/favicons/browserconfig.xml new file mode 100644 index 0000000..890faf3 --- /dev/null +++ b/public/favicons/browserconfig.xml @@ -0,0 +1,11 @@ + + + + + + + + #ffffff + + + diff --git a/public/favicons/favicon-16x16.png b/public/favicons/favicon-16x16.png new file mode 100644 index 0000000..515bd88 Binary files /dev/null and b/public/favicons/favicon-16x16.png differ diff --git a/public/favicons/favicon-32x32.png b/public/favicons/favicon-32x32.png new file mode 100644 index 0000000..0aecd45 Binary files /dev/null and b/public/favicons/favicon-32x32.png differ diff --git a/public/favicons/favicon-96x96.png b/public/favicons/favicon-96x96.png new file mode 100644 index 0000000..4af9923 Binary files /dev/null and b/public/favicons/favicon-96x96.png differ diff --git a/public/favicons/favicon.ico b/public/favicons/favicon.ico new file mode 100644 index 0000000..07e927c Binary files /dev/null and b/public/favicons/favicon.ico differ diff --git a/public/favicons/manifest.json b/public/favicons/manifest.json new file mode 100644 index 0000000..60bed5f --- /dev/null +++ b/public/favicons/manifest.json @@ -0,0 +1,46 @@ +{ + "name": "Windwiderstand", + "short_name": "Windwiderstand", + "start_url": "/", + "display": "browser", + "background_color": "#ffffff", + "theme_color": "#ffffff", + "icons": [ + { + "src": "/favicons/android-icon-36x36.png", + "sizes": "36x36", + "type": "image/png", + "density": "0.75" + }, + { + "src": "/favicons/android-icon-48x48.png", + "sizes": "48x48", + "type": "image/png", + "density": "1.0" + }, + { + "src": "/favicons/android-icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "density": "1.5" + }, + { + "src": "/favicons/android-icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "density": "2.0" + }, + { + "src": "/favicons/android-icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "density": "3.0" + }, + { + "src": "/favicons/android-icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "density": "4.0" + } + ] +} diff --git a/public/favicons/ms-icon-144x144.png b/public/favicons/ms-icon-144x144.png new file mode 100644 index 0000000..fb74aa1 Binary files /dev/null and b/public/favicons/ms-icon-144x144.png differ diff --git a/public/favicons/ms-icon-150x150.png b/public/favicons/ms-icon-150x150.png new file mode 100644 index 0000000..4900f79 Binary files /dev/null and b/public/favicons/ms-icon-150x150.png differ diff --git a/public/favicons/ms-icon-310x310.png b/public/favicons/ms-icon-310x310.png new file mode 100644 index 0000000..0f811f1 Binary files /dev/null and b/public/favicons/ms-icon-310x310.png differ diff --git a/public/favicons/ms-icon-70x70.png b/public/favicons/ms-icon-70x70.png new file mode 100644 index 0000000..45bcb06 Binary files /dev/null and b/public/favicons/ms-icon-70x70.png differ diff --git a/public/images/raw/1007c45a502710bb477b0d9a.pdf b/public/images/raw/1007c45a502710bb477b0d9a.pdf new file mode 100644 index 0000000..d98d6d7 Binary files /dev/null and b/public/images/raw/1007c45a502710bb477b0d9a.pdf differ diff --git a/public/images/raw/14094c206aa4f2b80400d4f0.pdf b/public/images/raw/14094c206aa4f2b80400d4f0.pdf new file mode 100644 index 0000000..6ff73c3 Binary files /dev/null and b/public/images/raw/14094c206aa4f2b80400d4f0.pdf differ diff --git a/public/images/raw/2ce9a7e4098d63bd1649f43c.pdf b/public/images/raw/2ce9a7e4098d63bd1649f43c.pdf new file mode 100644 index 0000000..548a80b Binary files /dev/null and b/public/images/raw/2ce9a7e4098d63bd1649f43c.pdf differ diff --git a/public/images/raw/385cd463da0c64e17c3def81.pdf b/public/images/raw/385cd463da0c64e17c3def81.pdf new file mode 100644 index 0000000..7678a7a Binary files /dev/null and b/public/images/raw/385cd463da0c64e17c3def81.pdf differ diff --git a/public/images/raw/49f803861ecf8ba0bcde8b54.pdf b/public/images/raw/49f803861ecf8ba0bcde8b54.pdf new file mode 100644 index 0000000..4369a80 Binary files /dev/null and b/public/images/raw/49f803861ecf8ba0bcde8b54.pdf differ diff --git a/public/images/raw/6482c116101ea4e33fdba3ca.pdf b/public/images/raw/6482c116101ea4e33fdba3ca.pdf new file mode 100644 index 0000000..e1adea8 Binary files /dev/null and b/public/images/raw/6482c116101ea4e33fdba3ca.pdf differ diff --git a/public/images/raw/679e7ab6db93a8d07df8d755.pdf b/public/images/raw/679e7ab6db93a8d07df8d755.pdf new file mode 100644 index 0000000..21bf691 Binary files /dev/null and b/public/images/raw/679e7ab6db93a8d07df8d755.pdf differ diff --git a/public/images/raw/744ddfaee72282bbee801a88.pdf b/public/images/raw/744ddfaee72282bbee801a88.pdf new file mode 100644 index 0000000..aad3c07 Binary files /dev/null and b/public/images/raw/744ddfaee72282bbee801a88.pdf differ diff --git a/public/images/raw/96540fe512af2059983fb811.pdf b/public/images/raw/96540fe512af2059983fb811.pdf new file mode 100644 index 0000000..d79a703 Binary files /dev/null and b/public/images/raw/96540fe512af2059983fb811.pdf differ diff --git a/public/images/raw/9be87ed6d70705eed322b097.docx b/public/images/raw/9be87ed6d70705eed322b097.docx new file mode 100644 index 0000000..449db22 Binary files /dev/null and b/public/images/raw/9be87ed6d70705eed322b097.docx differ diff --git a/public/images/raw/a2a018015f56214cd976fd53.pdf b/public/images/raw/a2a018015f56214cd976fd53.pdf new file mode 100644 index 0000000..70e41a2 Binary files /dev/null and b/public/images/raw/a2a018015f56214cd976fd53.pdf differ diff --git a/public/images/raw/a5b3a73995ed0a50a6c30423.pdf b/public/images/raw/a5b3a73995ed0a50a6c30423.pdf new file mode 100644 index 0000000..989bbac Binary files /dev/null and b/public/images/raw/a5b3a73995ed0a50a6c30423.pdf differ diff --git a/public/images/raw/b374798a40facbea4dcfb2e0.pdf b/public/images/raw/b374798a40facbea4dcfb2e0.pdf new file mode 100644 index 0000000..6ef87d0 Binary files /dev/null and b/public/images/raw/b374798a40facbea4dcfb2e0.pdf differ diff --git a/public/images/raw/b45a37021e3ea97f6e2df68d.pdf b/public/images/raw/b45a37021e3ea97f6e2df68d.pdf new file mode 100644 index 0000000..c7a02de Binary files /dev/null and b/public/images/raw/b45a37021e3ea97f6e2df68d.pdf differ diff --git a/public/images/raw/bf2280f5f8d1ba42a1b4d573.pdf b/public/images/raw/bf2280f5f8d1ba42a1b4d573.pdf new file mode 100644 index 0000000..506dfcd Binary files /dev/null and b/public/images/raw/bf2280f5f8d1ba42a1b4d573.pdf differ diff --git a/public/images/raw/f467041451db8af440e4d246.pdf b/public/images/raw/f467041451db8af440e4d246.pdf new file mode 100644 index 0000000..d3ded83 Binary files /dev/null and b/public/images/raw/f467041451db8af440e4d246.pdf differ diff --git a/public/images/raw/f4c2025a07625104d148b8be.pdf b/public/images/raw/f4c2025a07625104d148b8be.pdf new file mode 100644 index 0000000..8761519 Binary files /dev/null and b/public/images/raw/f4c2025a07625104d148b8be.pdf differ diff --git a/src/components/Badge.svelte b/src/components/Badge.svelte new file mode 100644 index 0000000..1399bd7 --- /dev/null +++ b/src/components/Badge.svelte @@ -0,0 +1,22 @@ + + + + {@render children?.()} + diff --git a/src/components/Card.svelte b/src/components/Card.svelte new file mode 100644 index 0000000..4a67927 --- /dev/null +++ b/src/components/Card.svelte @@ -0,0 +1,22 @@ + + +{#if href} + + {@render children?.()} + +{:else} +
+ {@render children?.()} +
+{/if} diff --git a/src/components/ContentRows.svelte b/src/components/ContentRows.svelte index 5a52016..1dc2021 100644 --- a/src/components/ContentRows.svelte +++ b/src/components/ContentRows.svelte @@ -11,6 +11,8 @@ import PostOverviewBlock from "./blocks/PostOverviewBlock.svelte"; import SearchableTextBlock from "./blocks/SearchableTextBlock.svelte"; import CalendarBlock from "./blocks/CalendarBlock.svelte"; + import OrganisationsBlock from "./blocks/OrganisationsBlock.svelte"; + import OpnFormBlock from "./blocks/OpnFormBlock.svelte"; import { isBlockLayoutBreakout, getSpaceBottomClass } from "../lib/block-layout"; import type { BlockLayout } from "../lib/block-layout"; import type { Translations } from "../lib/translations"; @@ -29,6 +31,8 @@ PostOverviewBlockData, SearchableTextBlockData, CalendarBlockData, + OrganisationsBlockData, + OpnFormBlockData, } from "../lib/block-types"; let { layout, class: className = "", translations = {} }: { layout: RowContentLayout; class?: string; translations?: Translations | null } = $props(); @@ -116,6 +120,10 @@ {:else if blockType(item) === "calendar"} + {:else if blockType(item) === "organisations"} + + {:else if blockType(item) === "opnform"} + {:else}
Unbekannter Block: {blockType(item)} diff --git a/src/components/Header.svelte b/src/components/Header.svelte index 9ac643d..e81704a 100644 --- a/src/components/Header.svelte +++ b/src/components/Header.svelte @@ -7,9 +7,20 @@ import { t as tStatic, T } from "../lib/translations"; import type { Translations } from "../lib/translations"; - interface NavLink { - href: string; - label: string; + type NavChild = { href: string; label: string }; + /** Flacher Link oder Navgroup (nur Label + Unterlinks, optional eigener Parent-href). */ + type NavItem = + | NavChild + | { label: string; href?: string; children: NavChild[] }; + + function isNavGroup( + item: NavItem, + ): item is { label: string; href?: string; children: NavChild[] } { + return ( + "children" in item && + Array.isArray(item.children) && + item.children.length > 0 + ); } interface SocialLink { @@ -22,11 +33,14 @@ links = [], socialLinks = [], logoUrl = null, + logoSvgHtml = null, translations = {}, }: { - links?: NavLink[]; + links?: NavItem[]; socialLinks?: SocialLink[]; logoUrl?: string | null; + /** Inline-SVG-Markup (z. B. aus Layout per fetch); hat Vorrang vor logoUrl. */ + logoSvgHtml?: string | null; translations?: Translations | null; } = $props(); @@ -89,15 +103,20 @@
- {link.label} - + {#each links as item} + {#if isNavGroup(item)} +
+ + {item.label} + + +
+ {:else} + + {item.label} + + {/if} {/each} -
|
+
|
{#each socialLinks as social}
+ diff --git a/src/components/OrganisationCard.svelte b/src/components/OrganisationCard.svelte new file mode 100644 index 0000000..b77e080 --- /dev/null +++ b/src/components/OrganisationCard.svelte @@ -0,0 +1,40 @@ + + +
+
+
{org.name ?? ""}
+ {#if badge?.label} + + {badge.label} + + {/if} +
+ {#if org.description} +

{org.description}

+ {/if} + {#if link?.url} + + {link.linkName ?? link.url} + + + {/if} +
diff --git a/src/components/blocks/ImageGalleryBlock.svelte b/src/components/blocks/ImageGalleryBlock.svelte index e0c10c9..69603f8 100644 --- a/src/components/blocks/ImageGalleryBlock.svelte +++ b/src/components/blocks/ImageGalleryBlock.svelte @@ -1,12 +1,14 @@
@@ -122,74 +161,9 @@ {/if} {:else} -
-
- {#key currentIndex} - {@const current = withUrl[currentIndex]} -
- {current.img.description - {#if current.img.description} -
- {current.img.description} -
- {/if} -
- {/key} -
- - - - -
- {#each withUrl as _, i} - - {/each} -
-
+
{/if}
diff --git a/src/components/blocks/OpnFormBlock.svelte b/src/components/blocks/OpnFormBlock.svelte new file mode 100644 index 0000000..e80c4ec --- /dev/null +++ b/src/components/blocks/OpnFormBlock.svelte @@ -0,0 +1,81 @@ + + +
+ {#if iframeSrc} + + {:else} +

OpnForm: formId fehlt.

+ {/if} +
diff --git a/src/components/blocks/OrganisationsBlock.svelte b/src/components/blocks/OrganisationsBlock.svelte new file mode 100644 index 0000000..a941c6a --- /dev/null +++ b/src/components/blocks/OrganisationsBlock.svelte @@ -0,0 +1,200 @@ + + +
+ {#if block.headline} +

{block.headline}

+ {/if} +
+ {#each orgs as org, orgIndex} + {#if typeof org === "object" && org !== null} + {@const o = org as OrganisationEntry} + {@const linkUrl = + typeof o.link === "object" && + o.link !== null && + typeof o.link.url === "string" && + o.link.url.trim() !== "" + ? o.link.url.trim() + : undefined} + {@const logoSrc = organisationLogoSrc(o)} + {#if compact} + +
+ {#if logoSrc} + {organisationLogoAlt(o)} + {:else} + {@const fishMaskId = `org-logo-fallback-mask-c-${orgIndex}`} + + {/if} +
+
+
+
{o.name ?? ""}
+ {#if o.badge && typeof o.badge === "object" && o.badge.label} + {o.badge.label} + {/if} +
+ {#if o.description} +

{o.description}

+ {/if} +
+
+ {:else} + +
+ {#if logoSrc} + {organisationLogoAlt(o)} + {:else} + {@const fishMaskId = `org-logo-fallback-mask-${orgIndex}`} + + {/if} +
+ {#if o.badge && typeof o.badge === "object" && o.badge.label} +
+ {o.badge.label} +
+ {/if} +
{o.name ?? ""}
+ {#if o.description} +

{o.description}

+ {/if} +
+ {/if} + {/if} + {/each} + + {#if block.addOrganisationTitle || block.addOrganisationMarkdown} + + {#if block.addOrganisationTitle} +
+ {block.addOrganisationTitle} +
+ {/if} + {#if ctaHtml} +
+ {@html ctaHtml} +
+ {/if} +
+ {/if} +
+
diff --git a/src/env.d.ts b/src/env.d.ts index beaa143..9e58753 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -4,6 +4,10 @@ interface ImportMetaEnv { readonly PUBLIC_CMS_URL: string; readonly PUBLIC_UMAMI_SCRIPT_URL?: string; readonly PUBLIC_UMAMI_WEBSITE_ID?: string; + /** Formbricks-Instanz (ohne trailing slash), z. B. https://forms.pm86.de */ + readonly PUBLIC_FORMBRICKS_APP_URL?: string; + /** Formbricks Environment ID (Setup Checklist) */ + readonly PUBLIC_FORMBRICKS_ENVIRONMENT_ID?: string; } interface ImportMeta { diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 80e0259..fe2eca4 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -81,12 +81,12 @@ const resolvedBreadcrumbItems = : (item.label ?? ""), })) ?? []; -// Header: Navigation laden und Links bauen -interface NavLink { - href: string; - label: string; -} -let headerLinks: NavLink[] = []; +// Header: Navigation (flache Links + navgroup-Untermenüs, vgl. angelverein-vachdorf.de Layout.astro) +type HeaderNavChild = { href: string; label: string }; +type HeaderNavItem = + | HeaderNavChild + | { label: string; href?: string; children: HeaderNavChild[] }; +let headerNavItems: HeaderNavItem[] = []; /** Führenden Slash ignorieren (CMS liefert z. B. "slug": "/about"). */ function normalizeSlug(s: string | undefined): string { return (s ?? "").replace(/^\//, "").trim(); @@ -96,6 +96,49 @@ function getSlugFromEntry(entry: NavLinkEntry): string { const e = entry as { _slug?: string; slug?: string }; return e.slug ?? e._slug ?? ""; } +function normalizeNavHref(url: string): string { + return url.startsWith("/") || url.startsWith("http") + ? url + : `/${url.replace(/^\//, "")}`; +} +/** CMS-Kanon-Slug ohne führenden Slash (z. B. bi/vachdorf) → URL-Pfad. */ +function pageSlugToPath(canonicalSlug: string): string { + const s = (canonicalSlug ?? "").trim(); + if (!s || s === "home") return "/"; + return ( + "/" + + s.split("/") + .filter(Boolean) + .map((seg) => encodeURIComponent(seg)) + .join("/") + ); +} +function resolveNavEntryToChild( + entry: NavLinkEntry, + slugToLabel: Map, + slugToPageHref: Map, + slugToPostHref: Map, +): HeaderNavChild | null { + const asObj = + typeof entry === "object" && entry !== null + ? (entry as { url?: string; linkName?: string; name?: string }) + : null; + if (asObj?.url && String(asObj.url).trim() !== "") { + const href = normalizeNavHref(String(asObj.url).trim()); + const label = asObj.linkName ?? asObj.name ?? asObj.url ?? ""; + return { href, label }; + } + const raw = getSlugFromEntry(entry); + const slug = normalizeSlug(raw) || "home"; + const label = slugToLabel.get(slug) ?? slug; + const postHref = slugToPostHref.get(slug); + const pageSlug = slugToPageHref.get(slug) ?? slug; + const hrefSlug = normalizeSlug(pageSlug) || "home"; + const href = + postHref ?? + (hrefSlug === "home" ? "/" : pageSlugToPath(hrefSlug)); + return { href, label }; +} try { const nav = await getNavigationByKey(NavigationKeys.header, { locale: "de", @@ -138,29 +181,51 @@ try { } } for (const entry of rawLinks) { - const asObj = + const asRecord = typeof entry === "object" && entry !== null - ? (entry as { url?: string; linkName?: string; name?: string }) + ? (entry as Record) : null; - if (asObj?.url) { - const href = - asObj.url.startsWith("/") || asObj.url.startsWith("http") - ? asObj.url - : `/${asObj.url.replace(/^\//, "")}`; - const label = asObj.linkName ?? asObj.name ?? asObj.url ?? ""; - headerLinks.push({ href, label }); + const rawLinksInner = asRecord?.links; + const isNavGroup = + asRecord && + typeof asRecord.label === "string" && + Array.isArray(rawLinksInner) && + rawLinksInner.length > 0 && + typeof rawLinksInner[0] === "object" && + rawLinksInner[0] !== null; + + if (isNavGroup) { + const children: HeaderNavChild[] = []; + for (const sub of rawLinksInner as unknown[]) { + const child = resolveNavEntryToChild( + sub as NavLinkEntry, + slugToLabel, + slugToPageHref, + slugToPostHref, + ); + if (child) children.push(child); + } + if (children.length === 0) continue; + const urlRaw = asRecord.url; + const parentHref = + typeof urlRaw === "string" && urlRaw.trim() !== "" + ? normalizeNavHref(urlRaw.trim()) + : undefined; + headerNavItems.push({ + label: asRecord.label as string, + ...(parentHref ? { href: parentHref } : {}), + children, + }); continue; } - const raw = getSlugFromEntry(entry); - const slug = normalizeSlug(raw) || "home"; - const label = slugToLabel.get(slug) ?? slug; - const postHref = slugToPostHref.get(slug); - const pageSlug = slugToPageHref.get(slug) ?? slug; - const hrefSlug = normalizeSlug(pageSlug) || "home"; - const href = - postHref ?? - (hrefSlug === "home" ? "/" : `/${encodeURIComponent(hrefSlug)}`); - headerLinks.push({ href, label }); + + const flat = resolveNavEntryToChild( + entry, + slugToLabel, + slugToPageHref, + slugToPostHref, + ); + if (flat) headerNavItems.push(flat); } } catch { // CMS nicht erreichbar @@ -215,6 +280,8 @@ try { // Logo und SEO-Templates aus page_config (wie windwiderstand). let logoUrl: string | null = null; +/** Bei SVG: serverseitig geladener Markup für Inline-Render (currentColor statt img-Tag). */ +let logoSvgHtml: string | null = null; let pageConfig: Awaited> | null = null; try { // Zuerst page-config-default (häufiger Slug), dann default – vermeidet doppelten 404 @@ -244,11 +311,26 @@ try { typeof logoFileUrl === "string" && logoFileUrl.startsWith("//") ? `https:${logoFileUrl}` : logoFileUrl; - logoUrl = await ensureTransformedImage(u, { - height: 56, - fit: "contain", - format: "webp", - }); + const isSvg = u.split("?")[0].toLowerCase().endsWith(".svg"); + if (isSvg) { + // SVGs können nicht transformiert werden — direkt als URL nutzen + logoUrl = u.startsWith("/") ? `${import.meta.env.PUBLIC_CMS_URL}${u}` : u; + try { + const res = await fetch(logoUrl); + if (res.ok) { + const text = (await res.text()).trim(); + if (text.includes(" - + - + + + + + + + + + + + + + + + + + + - {seoTitle} {seoDescription && } @@ -441,6 +601,42 @@ const cmsFromCache = getCmsFromCache(); /> ) } + { + import.meta.env.PUBLIC_FORMBRICKS_APP_URL && + import.meta.env.PUBLIC_FORMBRICKS_ENVIRONMENT_ID && ( + + ) + }