feat(blocks): SectionGrid/InfoCard/ContactForm + Konsolidierung (Tier 1)
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user