57f4b226f9
Footer: - Newsletter 2-spaltig (Titel/Desc links, Formular rechts), Desc als Translation-Key newsletter_inline_desc - Link-Spalten-Überschriften als kleine Label-Kappen, Links ohne Unterstrich - Bottom-Bar: Logo + Site-Name (data.siteName) links, Copyright/Lizenz rechts - Site-Name-Default "Windwiderstand Thüringen" (constants.ts) OrganisationCard: - Neue Komponente auf InfoCard-Basis; InfoCard um href/media/badge/footer-Slots erweitert - OrganisationsBlock: 4 divergierende Kartenpfade → 1 OrganisationCard - Story + CTA-Button (mitmachen) size md + Icon Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
166 lines
5.8 KiB
Svelte
166 lines
5.8 KiB
Svelte
<script lang="ts">
|
|
import { marked } from "$lib/markdown-safe";
|
|
import Icon from "@iconify/svelte";
|
|
import "$lib/iconify-offline";
|
|
import { getBlockLayoutClasses } from "$lib/block-layout";
|
|
import type { OrganisationsBlockData, OrganisationEntry } from "$lib/block-types";
|
|
import Card from "$lib/components/Card.svelte";
|
|
import Button from "$lib/ui/Button.svelte";
|
|
import SectionHeader from "$lib/components/SectionHeader.svelte";
|
|
import SectionGrid from "$lib/components/SectionGrid.svelte";
|
|
import OrganisationCard from "$lib/components/OrganisationCard.svelte";
|
|
import IconButton from "$lib/components/IconButton.svelte";
|
|
|
|
let { block }: { block: OrganisationsBlockData } = $props();
|
|
const actionRef = $derived(
|
|
block.action && typeof block.action === "object" ? block.action : null,
|
|
);
|
|
|
|
const layoutClasses = $derived(
|
|
block.layout ? getBlockLayoutClasses(block.layout) : "col-span-12 mb-4",
|
|
);
|
|
const compact = $derived(block.presentation === "compact");
|
|
const carousel = $derived(block.presentation === "carousel");
|
|
// "grid" = SectionGrid + InfoCard-Tiles (Logo im Kreis, Name, Beschreibung).
|
|
const grid = $derived(block.presentation === "grid");
|
|
const orgs = $derived(block.organisations ?? []);
|
|
const ctaHtml = $derived(
|
|
block.addOrganisationMarkdown
|
|
? (marked.parse(block.addOrganisationMarkdown) as string)
|
|
: ""
|
|
);
|
|
|
|
const addOrganisationCtaLink = $derived.by(() => {
|
|
const raw = block.addOrganisationLink;
|
|
if (!raw || typeof raw !== "object") return undefined;
|
|
const url = typeof raw.url === "string" ? raw.url.trim() : "";
|
|
if (!url) return undefined;
|
|
const linkName =
|
|
typeof raw.linkName === "string" && raw.linkName.trim() !== ""
|
|
? raw.linkName.trim()
|
|
: url;
|
|
return { url, linkName, newTab: Boolean(raw.newTab) };
|
|
});
|
|
|
|
const showAddOrganisationCard = $derived(
|
|
Boolean(
|
|
block.addOrganisationTitle?.trim() ||
|
|
block.addOrganisationMarkdown?.trim() ||
|
|
addOrganisationCtaLink
|
|
)
|
|
);
|
|
|
|
let scroller: HTMLDivElement | undefined = $state();
|
|
function scrollByPage(dir: 1 | -1) {
|
|
if (!scroller) return;
|
|
scroller.scrollBy({ left: scroller.clientWidth * dir * 0.9, behavior: "smooth" });
|
|
}
|
|
</script>
|
|
|
|
|
|
{#snippet ctaCard()}
|
|
<Card
|
|
variant="callout"
|
|
href={addOrganisationCtaLink?.url}
|
|
target={addOrganisationCtaLink?.newTab ? "_blank" : undefined}
|
|
rel={addOrganisationCtaLink?.newTab ? "noopener noreferrer" : undefined}
|
|
class="h-full w-full max-w-full md:flex-row md:items-center md:justify-between md:gap-4"
|
|
>
|
|
<div class="flex flex-col gap-1.5 min-w-0">
|
|
{#if block.addOrganisationTitle}
|
|
<h5 class="text-xl font-semibold text-wald-800 leading-snug">{block.addOrganisationTitle}</h5>
|
|
{/if}
|
|
{#if ctaHtml}
|
|
<div class="markdown text-xs text-wald-700">{@html ctaHtml}</div>
|
|
{/if}
|
|
</div>
|
|
{#if addOrganisationCtaLink}
|
|
<div class="self-start md:self-center md:shrink-0">
|
|
<Button variant="primary" size="md">
|
|
<Icon icon="lucide:users" class="size-4 shrink-0" aria-hidden="true" />
|
|
{addOrganisationCtaLink.linkName}
|
|
</Button>
|
|
</div>
|
|
{/if}
|
|
</Card>
|
|
{/snippet}
|
|
|
|
<div class={layoutClasses} data-block="Organisations" data-block-type="organisations" data-block-slug={block._slug}>
|
|
{#if block.headline && !grid}
|
|
{#if compact}
|
|
<h3 class="mb-2 text-lg font-semibold text-stein-900">{block.headline}</h3>
|
|
{:else}
|
|
<div class="mb-4">
|
|
<SectionHeader
|
|
number={block.number}
|
|
kicker={block.kicker}
|
|
title={block.headline}
|
|
tag="h3"
|
|
actionLabel={actionRef?.linkName}
|
|
actionHref={actionRef?.url}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
|
|
{#if grid}
|
|
<SectionGrid
|
|
number={block.number}
|
|
kicker={block.kicker}
|
|
title={block.headline}
|
|
subtitle={block.description}
|
|
actionLabel={actionRef?.linkName}
|
|
actionHref={actionRef?.url}
|
|
columns={3}
|
|
>
|
|
{#each orgs as org, orgIndex}
|
|
{#if typeof org === "object" && org !== null}
|
|
<OrganisationCard org={org as OrganisationEntry} index={orgIndex} />
|
|
{/if}
|
|
{/each}
|
|
</SectionGrid>
|
|
{:else if carousel}
|
|
<div class="relative">
|
|
<div
|
|
bind:this={scroller}
|
|
class="flex gap-4 overflow-x-auto scroll-smooth snap-x snap-mandatory pt-1 pb-2 [scrollbar-width:thin]"
|
|
>
|
|
{#each orgs as org, orgIndex}
|
|
{#if typeof org === "object" && org !== null}
|
|
<div
|
|
class="shrink-0 snap-start w-[calc((100%-0.5rem)/1.5)] sm:w-[calc((100%-1rem)/2)] lg:w-[calc((100%-2rem)/3)]"
|
|
>
|
|
<OrganisationCard org={org as OrganisationEntry} index={orgIndex} />
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
{#if orgs.length > 2}
|
|
<IconButton icon="lucide:chevron-left" label="Vorherige Organisationen" variant="surface" size="sm" onclick={() => scrollByPage(-1)} class="hidden md:flex absolute -left-3 top-1/2 -translate-y-1/2 -translate-x-1/2" />
|
|
<IconButton icon="lucide:chevron-right" label="Weitere Organisationen" variant="surface" size="sm" onclick={() => scrollByPage(1)} class="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 translate-x-1/2" />
|
|
{/if}
|
|
</div>
|
|
{#if showAddOrganisationCard}
|
|
<div class="mt-2 min-w-0">
|
|
{@render ctaCard()}
|
|
</div>
|
|
{/if}
|
|
{:else}
|
|
<div
|
|
class={compact
|
|
? "grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3"
|
|
: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"}
|
|
>
|
|
{#each orgs as org, orgIndex}
|
|
{#if typeof org === "object" && org !== null}
|
|
<OrganisationCard org={org as OrganisationEntry} {compact} index={orgIndex} />
|
|
{/if}
|
|
{/each}
|
|
|
|
{#if showAddOrganisationCard}
|
|
{@render ctaCard()}
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|