feat(footer,org): Footer-Redesign + OrganisationCard auf InfoCard-Basis
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>
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* InfoCard — Icon/Bild + Titel + Beschreibung + optionaler Link.
|
||||
* Wiederverwendbare Kachel für Sektions-Grids (siehe SectionGrid).
|
||||
* Wiederverwendbare Kachel für Sektions-Grids (siehe SectionGrid) UND als
|
||||
* Basis für spezialisierte Karten (OrganisationCard) via Snippet-Slots.
|
||||
*
|
||||
* `icon` ist entweder ein Iconify-String ("mdi:email") ODER eine Bild-URL
|
||||
* (https://… oder /…) — wird automatisch erkannt.
|
||||
* `tone`: default (weiß) oder green (grün getönt, z.B. Aside-Boxen).
|
||||
* `compact`: kleinere Maße + Icon inline neben dem Titel (spart Höhe).
|
||||
*
|
||||
* Erweiterungspunkte (halten die Basis konsistent, ohne Besonderheiten zu verlieren):
|
||||
* - `href` → ganze Karte wird Link (Tile-Hover wie Card).
|
||||
* - `media` → ersetzt das Standard-Icon-Badge (z.B. quadratisches Logo + Fallback).
|
||||
* - `badge` → absolut oben-rechts overlay.
|
||||
* - `footer` → am unteren Rand (z.B. Social-Chips).
|
||||
*/
|
||||
import type { Snippet } from "svelte";
|
||||
import Icon from "@iconify/svelte";
|
||||
import "$lib/iconify-offline";
|
||||
import RustyImage from "$lib/components/RustyImage.svelte";
|
||||
@@ -24,6 +32,12 @@
|
||||
external = false,
|
||||
tone = "default",
|
||||
compact = false,
|
||||
href = undefined,
|
||||
target = undefined,
|
||||
rel = undefined,
|
||||
media = undefined,
|
||||
badge = undefined,
|
||||
footer = undefined,
|
||||
}: {
|
||||
icon?: string;
|
||||
title: string;
|
||||
@@ -33,10 +47,25 @@
|
||||
external?: boolean;
|
||||
tone?: Tone;
|
||||
compact?: boolean;
|
||||
href?: string;
|
||||
target?: string;
|
||||
rel?: string;
|
||||
media?: Snippet;
|
||||
badge?: Snippet;
|
||||
footer?: Snippet;
|
||||
} = $props();
|
||||
|
||||
const isImage = $derived(!!icon && (/^https?:\/\//.test(icon) || icon.startsWith("/")));
|
||||
const hasLink = $derived(!!linkLabel && !!linkHref);
|
||||
|
||||
const shell = $derived(
|
||||
`relative flex h-full flex-col rounded-lg border ${compact ? "gap-2 p-4" : "gap-[14px] p-[22px]"} ${
|
||||
tone === "green" ? "border-wald-100 bg-wald-50" : "border-stein-200 bg-white"
|
||||
}`,
|
||||
);
|
||||
/* Klickbare Karte: Tile-Hover identisch zu Card.svelte (variant=tile). */
|
||||
const linkCls =
|
||||
"transition-[transform,box-shadow,border-color] duration-200 no-underline text-inherit hover:-translate-y-0.5 hover:shadow-md hover:border-wald-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wald-500 focus-visible:ring-offset-2";
|
||||
</script>
|
||||
|
||||
{#snippet iconBadge(sizeClass: string, iconSize: string)}
|
||||
@@ -55,16 +84,18 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div
|
||||
class="flex h-full flex-col rounded-lg border {compact ? 'gap-2 p-4' : 'gap-[14px] p-[22px]'} {tone ===
|
||||
'green'
|
||||
? 'border-wald-100 bg-wald-50'
|
||||
: 'border-stein-200 bg-white'}"
|
||||
>
|
||||
{#snippet content()}
|
||||
{#if badge}
|
||||
<div class="absolute right-2 top-2 z-10">{@render badge()}</div>
|
||||
{/if}
|
||||
{#if compact}
|
||||
<!-- Kompakt: Icon hängt links, Text-Spalte (Titel/Desc/Link) rechts bündig. -->
|
||||
<div class="flex gap-2.5">
|
||||
{#if icon}<div class="pt-px">{@render iconBadge("size-5", "size-3")}</div>{/if}
|
||||
{#if media}
|
||||
<div class="pt-px">{@render media()}</div>
|
||||
{:else if icon}
|
||||
<div class="pt-px">{@render iconBadge("size-5", "size-3")}</div>
|
||||
{/if}
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-2">
|
||||
<h3 class="text-sm! font-bold! leading-[1.3]! tracking-[-0.01em] text-stein-800">{title}</h3>
|
||||
{#if description}
|
||||
@@ -75,10 +106,17 @@
|
||||
<ArrowLink href={linkHref ?? "#"} label={linkLabel ?? ""} {external} size="sm" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if footer}
|
||||
<div class="pt-0.5">{@render footer()}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if icon}{@render iconBadge("size-11", "size-[23px]")}{/if}
|
||||
{#if media}
|
||||
{@render media()}
|
||||
{:else if icon}
|
||||
{@render iconBadge("size-11", "size-[23px]")}
|
||||
{/if}
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-base! font-bold! leading-[1.3]! tracking-[-0.01em] text-stein-800">{title}</h3>
|
||||
{#if description}
|
||||
@@ -90,5 +128,18 @@
|
||||
<ArrowLink href={linkHref ?? "#"} label={linkLabel ?? ""} {external} size="sm" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if footer}
|
||||
<div class="mt-auto pt-1">{@render footer()}</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if href}
|
||||
<a {href} {target} {rel} class="{shell} {linkCls}">
|
||||
{@render content()}
|
||||
</a>
|
||||
{:else}
|
||||
<div class={shell}>
|
||||
{@render content()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user