feat(blocks): SectionGrid/InfoCard/ContactForm + Konsolidierung (Tier 1)
Deploy / verify (push) Successful in 1m31s
Deploy / deploy (push) Successful in 1m33s

Neue Komponenten: SectionGrid, InfoCard (tone/compact), ArrowLink, ContactFormBlock,
SectionGridBlock. CMS-Blöcke section_grid + contact_form (info_card-Collection).
OrganisationsBlock: presentation "grid" (SectionGrid + InfoCard-Tiles).
Kontaktformular-Submit: size md + Send-Icon.

Konsolidierung Tier 1:
- .card-surface + Card-Atom → rounded-lg, .chip → rounded-full, Input-Atome → rounded-md
- tote .btn-*-Klassen aus app.css entfernt (Atom nutzt --color-btn-*-Tokens)
- hardcoded Hex → stein-Tokens (InfoCard, ContactFormBlock)
- redundante rounded-sm auf card-surface-Nutzern entfernt

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-07-12 10:49:54 +02:00
parent 8fd0112dcb
commit f77314dc49
18 changed files with 274 additions and 105 deletions
+59 -25
View File
@@ -5,10 +5,15 @@
*
* `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).
*/
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import RustyImage from "$lib/components/RustyImage.svelte";
import ArrowLink from "$lib/components/ArrowLink.svelte";
type Tone = "default" | "green";
let {
icon = undefined,
@@ -17,6 +22,8 @@
linkLabel = undefined,
linkHref = undefined,
external = false,
tone = "default",
compact = false,
}: {
icon?: string;
title: string;
@@ -24,38 +31,65 @@
linkLabel?: string;
linkHref?: string;
external?: boolean;
tone?: Tone;
compact?: boolean;
} = $props();
// Bild-URL vs Iconify-String unterscheiden.
const isImage = $derived(!!icon && (/^https?:\/\//.test(icon) || icon.startsWith("/")));
const hasLink = $derived(!!linkLabel && !!linkHref);
</script>
<div class="flex h-full flex-col gap-[14px] rounded-lg border border-[#d5d8d6] bg-white p-[22px]">
{#if icon}
<div class="flex size-11 shrink-0 items-center justify-center overflow-hidden rounded-full bg-wald-50 text-wald-500">
{#if isImage}
<RustyImage src={icon} width={44} aspect="1/1" alt="" class="size-full object-cover" />
{:else}
<Icon icon={icon} class="size-[23px]" aria-hidden="true" />
{#snippet iconBadge(sizeClass: string, iconSize: string)}
<div
class="flex shrink-0 items-center justify-center overflow-hidden rounded-full {sizeClass} {isImage
? 'border border-stein-200 bg-white'
: tone === 'green'
? 'bg-white text-wald-600'
: 'bg-wald-50 text-wald-500'}"
>
{#if isImage}
<RustyImage src={icon ?? ""} width={44} aspect="1/1" fit="contain" alt="" class="size-full object-contain p-0.5" />
{:else}
<Icon icon={icon ?? ""} class={iconSize} aria-hidden="true" />
{/if}
</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'}"
>
{#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}
<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}
<p class="line-clamp-3 text-[13px] font-normal leading-[1.45] text-stein-500">{description}</p>
{/if}
{#if hasLink}
<div class="pt-0.5">
<ArrowLink href={linkHref ?? "#"} label={linkLabel ?? ""} {external} size="sm" />
</div>
{/if}
</div>
</div>
{:else}
{#if icon}{@render iconBadge("size-11", "size-[23px]")}{/if}
<div class="flex flex-col gap-1">
<!-- text-*! nötig: app.css setzt h3-Größe unlayered → Cascade-Layers. -->
<h3 class="text-base! font-bold! leading-[1.3]! tracking-[-0.01em] text-stein-800">{title}</h3>
{#if description}
<p class="line-clamp-3 text-sm font-normal leading-[1.45] text-stein-500">{description}</p>
{/if}
</div>
{/if}
<h3 class="text-base font-bold tracking-[-0.01em] text-[#1f2221]">{title}</h3>
{#if description}
<p class="text-sm font-normal leading-[1.45] text-[#636966]">{description}</p>
{/if}
{#if hasLink}
<a
href={linkHref}
target={external ? "_blank" : undefined}
rel={external ? "noopener noreferrer" : undefined}
class="mt-auto inline-flex items-center gap-1.5 text-sm font-semibold text-himmel-500 no-underline hover:text-himmel-600"
>
{linkLabel}
<svg class="size-4" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
{#if hasLink}
<div class="mt-auto pt-0.5">
<ArrowLink href={linkHref ?? "#"} label={linkLabel ?? ""} {external} size="sm" />
</div>
{/if}
{/if}
</div>