Geld-zurück-Garantie
Garantien, die Käufern das Risiko abnehmen: ein großes Siegel mit den Tagen, eine Waage, die beim Scrollen kippt, die Schritte einer Erstattung, mehrere Versprechen mit Kleingedrucktem und eine schmale Zeile neben dem Kaufknopf.
Das Risiko von den Käufern nehmen
Eine Garantie beantwortet den letzten Zweifel vor dem Kauf, was ist, wenn es für mich nicht passt. Diese fünf Sections zeigen das Versprechen klar und halten die Bedingungen ehrlich und nah, die Garantie schafft so Vertrauen, statt neue Fragen aufzuwerfen.
„Siegel mit Bedingungen“ zeichnet ein großes Siegel, dessen Rand sich mit dem Scrollen dreht und dessen Tage hochzählen, daneben das Versprechen und was es umfasst, die Bedingungen hinter einem Aufklapper. „Risiko auf unserer Seite“ lässt eine gezeichnete Waage beim Scrollen kippen und zeigt, was Kunden tragen und was das Unternehmen trägt. „So läuft eine Rückgabe“ legt die Tage einer Rückgabe auf eine Spur, die sich beim Scrollen füllt, am Telefon nach unten, auf breiten Bildschirmen quer, der letzte Schritt ist das Geld zurück. „Versprechen mit Kleingedrucktem“ setzt mehrere Garantien in ein Haarlinienraster, jede mit Zahl, kurzem Versprechen und Kleingedrucktem zum Aufklappen. „Neben dem Kaufknopf“ ist eine kompakte Karte mit einem Ring um die Tage und drei kurzen Versprechen.
Formulier die Garantie als einen Satz, den Kunden weitererzählen können. Halte das Kleingedruckte kurz und lesbar, versteckte Bedingungen kosten mehr Vertrauen, als sie sparen. Wiederhol die kompakte Fassung neben dem Preis, dort entsteht der Zweifel.
Variants
Details
Was Garantie mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- Security und Trust
- Bereich
- Marketing
- Variants
- 5
- Version
- 1.0.0 · seit 1.2.0
- Datei
- components/ui/guarantee.tsx
- Bringt mit
- utilskitmotionbuttoniconnumber-tickersection
- npm-Pakete
- Keine
- Sprachen
- Englisch und Deutsch, 8 eigene Texte
- Studio
- Im Code anpassen
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle GuaranteeProps aus components/ui/guarantee.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.
variantoptionalGuaranteeVariantsealbalancerefund-stepspromisesinline
eyebrowoptionalReact.ReactNodetitleoptionalReact.ReactNodedescriptionoptionalReact.ReactNodedaysoptionalnumberseal, inline: number of days
rimoptionalstringseal: text on the rim
pointsoptionalGuaranteePoint[]seal, inline: what is promised, short
fineoptionalstringseal: the conditions in full
riskoptional{ yoursLabel: string; yours: string[]; oursLabel: string; ours: string[] }balance: what each side carries
stepsoptional{ day: string; title: string; text?: string; icon?: IconName }[]refund-steps: the days of a return
promisesoptionalGuaranteePromise[]promises
ctaoptional{ label: string; href: string }noteoptionalstringA short line under the action
Eingebaute Texte
Diese Texte schreibt Garantie selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.
Alle 8 Texte anzeigen
| Schlüssel | Englisch | Deutsch |
|---|---|---|
| days | Englisch days | Deutsch Tage |
| moneyBack | Englisch Money back | Deutsch Geld zurück |
| fine | Englisch The small print | Deutsch Das Kleingedruckte |
| hide | Englisch Hide the small print | Deutsch Kleingedrucktes ausblenden |
| guarantee | Englisch Guarantee | Deutsch Garantie |
| yours | Englisch You carry | Deutsch Du trägst |
| ours | Englisch We carry | Deutsch Wir tragen |
| nothing | Englisch Nothing but a few minutes | Deutsch Nichts außer ein paar Minuten |
Barrierefreiheit
Aus dem Quellcode der Component abgeleitet.
- Setzt aria-controls und aria-expanded, damit Screenreader Zweck und Zustand ansagen.
- Zeigt bei Bedienung mit der Tastatur einen sichtbaren Fokusring.
- Kommt ohne Client-State aus. Der Server schreibt sie vollständig, sie ist auch ohne JavaScript lesbar.
Bewegung
- Erscheint beim ersten Sichtkontakt mit einem kurzen Auftritt (kit-rise, useInView).
- Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 6 Stellen im Code schalten Bewegung dann ab.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 410 Zeilen
'use client';
import { useId, useState } from 'react';
import { cn, round, useInView, useTexts } from '@/lib/kit';
import { Magnetic, useScrollProgress } from '@/components/ui/motion';
import { Button } from '@/components/ui/button';
import { Icon, type IconName } from '@/components/ui/icon';
import { NumberTicker } from '@/components/ui/number-ticker';
import { Eyebrow, Section } from '@/components/ui/section';
/*
* A guarantee that takes the risk off the buyer, said plainly and with the
* conditions in view. Five layouts:
*
* seal a large drawn seal with the days, the promise and its conditions
* balance a scale that tips while scrolling: what you carry, what we carry
* refund-steps how a return works, day by day on a track that fills
* promises several guarantees with large figures and their small print
* inline a compact row for next to a buy button
*/
export type GuaranteeVariant = 'seal' | 'balance' | 'refund-steps' | 'promises' | 'inline';
export interface GuaranteePoint {
icon?: IconName;
title: string;
text?: string;
}
export interface GuaranteePromise {
/** A figure that counts up, e.g. 30, or a text like 24/7 */
value: number | string;
/** After the figure, e.g. days or % */
unit?: string;
title: string;
text?: string;
/** Small print, opened on request */
fine?: string;
icon?: IconName;
}
export interface GuaranteeProps {
variant?: GuaranteeVariant;
eyebrow?: React.ReactNode;
title?: React.ReactNode;
description?: React.ReactNode;
/** seal, inline: number of days */
days?: number;
/** seal: text on the rim */
rim?: string;
/** seal, inline: what is promised, short */
points?: GuaranteePoint[];
/** seal: the conditions in full */
fine?: string;
/** balance: what each side carries */
risk?: { yoursLabel: string; yours: string[]; oursLabel: string; ours: string[] };
/** refund-steps: the days of a return */
steps?: { day: string; title: string; text?: string; icon?: IconName }[];
/** promises */
promises?: GuaranteePromise[];
cta?: { label: string; href: string };
/** A short line under the action */
note?: string;
labels?: Partial<Texts>;
className?: string;
}
const TEXTS = {
en: { days: 'days', moneyBack: 'Money back', fine: 'The small print', hide: 'Hide the small print', guarantee: 'Guarantee', yours: 'You carry', ours: 'We carry', nothing: 'Nothing but a few minutes' },
de: { days: 'Tage', moneyBack: 'Geld zurück', fine: 'Das Kleingedruckte', hide: 'Kleingedrucktes ausblenden', guarantee: 'Garantie', yours: 'Du trägst', ours: 'Wir tragen', nothing: 'Nichts außer ein paar Minuten' },
};
type Texts = (typeof TEXTS)['en'];
type ViewProps = GuaranteeProps & { t: Texts };
const delay = (ms: number) => ({ '--kit-delay': `${ms}ms` }) as React.CSSProperties;
function Head({ eyebrow, title, description, center, className }: Pick<GuaranteeProps, 'eyebrow' | 'title' | 'description'> & { center?: boolean; className?: string }) {
if (!title) return null;
return (
<div className={cn('max-w-[46rem]', center && 'mx-auto max-w-[42rem] text-center', className)}>
{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
<h2 className="kit-rise text-balance text-h2 font-semibold text-foreground">{title}</h2>
{description && (
<p className={cn('kit-rise mt-5 max-w-[36rem] text-pretty text-lead text-muted-foreground', center && 'mx-auto')} style={delay(80)}>
{description}
</p>
)}
</div>
);
}
function Action({ cta, note, center, className }: Pick<GuaranteeProps, 'cta' | 'note'> & { center?: boolean; className?: string }) {
if (!cta && !note) return null;
return (
<div className={cn('flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-5', center && 'items-center sm:justify-center', className)}>
{cta && (
<Magnetic className="max-sm:flex max-sm:w-full">
<Button href={cta.href} size="lg" icon="arrow-right" className="max-sm:w-full">
{cta.label}
</Button>
</Magnetic>
)}
{note && (
<p className="flex items-center gap-1.5 text-[13.5px] text-muted-foreground">
<Icon name="shield" size={14} className="shrink-0 text-success" />
{note}
</p>
)}
</div>
);
}
/** The conditions behind a disclosure, readable without JavaScript */
function Fine({ text, t, className }: { text: string; t: Texts; className?: string }) {
return (
<details className={cn('group/fine', className)}>
<summary className="inline-flex min-h-11 cursor-pointer list-none items-center gap-1.5 rounded-md text-[13.5px] font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-primary sm:min-h-8 [&::-webkit-details-marker]:hidden">
<Icon name="chevron-right" size={15} className="transition-transform duration-300 group-open/fine:rotate-90" />
{t.fine}
</summary>
<p className="mt-2 max-w-[38rem] text-pretty text-[13.5px] leading-relaxed text-muted-foreground">{text}</p>
</details>
);
}
/** A big round seal with the days in the middle and text on the turning rim */
function DaySeal({ days, rim, t }: { days: number; rim: string; t: Texts }) {
const id = useId().replace(/:/g, '');
const scroll = useScrollProgress<HTMLDivElement>({ reach: false });
const words = rim.length > 28 ? `${rim} · ` : `${rim} · ${rim} · `;
return (
<div ref={scroll} className="relative mx-auto aspect-square w-full max-w-[16.25rem] lg:max-w-[21rem]">
<div aria-hidden className="absolute inset-[8%] rounded-full bg-[radial-gradient(closest-side,var(--glow),transparent)] blur-xl" />
<svg viewBox="0 0 200 200" aria-hidden data-clip-ok className="absolute inset-0 size-full">
<defs>
<path id={`${id}-r`} d="M100 100 m-78 0 a78 78 0 1 1 156 0 a78 78 0 1 1 -156 0" />
</defs>
<circle cx="100" cy="100" r="96" className="fill-card stroke-border" strokeWidth="1" />
<circle cx="100" cy="100" r="88" fill="none" className="stroke-[color-mix(in_oklab,var(--primary)_45%,var(--border))]" strokeWidth="0.8" strokeDasharray="1.5 3.5" />
<circle cx="100" cy="100" r="64" className="fill-[color-mix(in_oklab,var(--primary)_10%,var(--card))] stroke-[color-mix(in_oklab,var(--primary)_40%,var(--border))]" strokeWidth="1" />
{/* the rim turns with the scroll, a quarter turn over the pass */}
<g className="origin-center motion-reduce:![transform:none]" style={{ transform: 'rotate(calc(var(--kit-progress, 0.5) * 90deg - 45deg))', transformBox: 'view-box' }}>
<text fontSize="10.5" fontWeight="600" letterSpacing="2.6" className="fill-foreground uppercase">
<textPath href={`#${id}-r`} textLength="484" lengthAdjust="spacing">
{words}
</textPath>
</text>
</g>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center text-center">
<span className="text-[clamp(3.5rem,2.8rem+3vw,5.5rem)] font-semibold leading-none tracking-[-0.05em] text-foreground tabular-nums">
<NumberTicker value={days} duration={1200} />
</span>
<span className="mt-1 text-[12px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">{t.days}</span>
</div>
</div>
);
}
/* ------------------------------------------------------------------ */
/* seal */
/* ------------------------------------------------------------------ */
function Seal(p: ViewProps) {
const { t } = p;
return (
<Section className={p.className}>
<div className="grid grid-cols-[minmax(0,1fr)] items-center gap-12 lg:grid-cols-[minmax(0,22rem)_minmax(0,1fr)] lg:gap-20">
<div className="kit-rise lg:order-1">
<DaySeal days={p.days ?? 30} rim={p.rim ?? `${t.moneyBack} · ${t.guarantee}`} t={t} />
</div>
<div className="lg:order-2">
<Head {...p} />
{p.points?.length ? (
<ul className="mt-9 grid gap-5 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2">
{p.points.map((x, i) => (
<li key={i} className="kit-rise flex gap-3.5" style={delay(140 + i * 70)}>
<span className="grid size-9 shrink-0 place-items-center rounded-full bg-[color-mix(in_oklab,var(--success)_14%,var(--card))] text-success-foreground">
<Icon name={x.icon ?? 'check'} size={17} strokeWidth={1.9} />
</span>
<span>
<span className="block text-[15px] font-semibold text-foreground">{x.title}</span>
{x.text && <span className="mt-0.5 block text-pretty text-[14px] leading-relaxed text-muted-foreground">{x.text}</span>}
</span>
</li>
))}
</ul>
) : null}
<Action cta={p.cta} note={p.note} className="mt-10" />
{p.fine && <Fine text={p.fine} t={t} className="mt-6" />}
</div>
</div>
</Section>
);
}
/* ------------------------------------------------------------------ */
/* balance */
/* ------------------------------------------------------------------ */
function Balance(p: ViewProps) {
const { t } = p;
const ref = useScrollProgress<HTMLDivElement>({ start: 0.1, end: 0.55 });
const r = p.risk ?? { yoursLabel: t.yours, yours: [], oursLabel: t.ours, ours: [] };
// tilt from level (0) to the side that carries more (our side goes down)
const tilt = 'calc(var(--kit-progress, 1) * 9deg)';
return (
<Section background="surface" className={p.className}>
<Head {...p} center />
<div ref={ref} className="relative mx-auto mt-14 max-w-[60rem]">
{/* the scale */}
<svg viewBox="0 0 600 150" aria-hidden className="mx-auto block w-full max-w-[40rem] overflow-visible max-sm:hidden">
<path d="M300 146 300 40" className="stroke-border-strong" strokeWidth="3" strokeLinecap="round" />
<path d="M262 146h76" className="stroke-border-strong" strokeWidth="3" strokeLinecap="round" />
<circle cx="300" cy="36" r="7" className="fill-card stroke-border-strong" strokeWidth="2" />
<g className="motion-reduce:![transform:none]" style={{ transform: `rotate(${tilt})`, transformOrigin: '300px 36px' }}>
<path d="M90 36H510" className="stroke-foreground" strokeWidth="3" strokeLinecap="round" />
{/* the pans hang straight, they turn back by the same angle */}
<g style={{ transform: `rotate(calc(${tilt} * -1))`, transformOrigin: '90px 36px' }} className="motion-reduce:![transform:none]">
<path d="M90 36 60 96M90 36l30 60" className="stroke-muted-foreground" strokeWidth="1.5" />
<path d="M50 96h80a40 12 0 0 1-80 0Z" className="fill-card stroke-border-strong" strokeWidth="1.5" />
</g>
<g style={{ transform: `rotate(calc(${tilt} * -1))`, transformOrigin: '510px 36px' }} className="motion-reduce:![transform:none]">
<path d="M510 36 480 96M510 36l30 60" className="stroke-muted-foreground" strokeWidth="1.5" />
<path d="M470 96h80a40 12 0 0 1-80 0Z" className="fill-[color-mix(in_oklab,var(--primary)_22%,var(--card))] stroke-primary" strokeWidth="1.5" />
<rect x="492" y="78" width="36" height="18" rx="4" className="fill-primary" />
</g>
</g>
</svg>
<div className="grid grid-cols-[minmax(0,1fr)] gap-4 sm:mt-2 sm:grid-cols-2 sm:gap-6">
<div className="kit-rise rounded-2xl border border-border bg-card p-6 shadow-[var(--highlight),var(--shadow-sm)] sm:p-7" style={delay(120)}>
<p className="flex items-center gap-2 text-[13px] font-medium uppercase tracking-[0.1em] text-muted-foreground">
<Icon name="user" size={15} />
{r.yoursLabel}
</p>
<ul className="mt-5 grid gap-3">
{(r.yours.length ? r.yours : [t.nothing]).map((x, i) => (
<li key={i} className="flex gap-3 text-pretty text-[15px] text-foreground">
<Icon name="minus" size={16} className="mt-1 shrink-0 text-subtle-foreground" />
{x}
</li>
))}
</ul>
</div>
<div className="kit-rise rounded-2xl border border-[color-mix(in_oklab,var(--primary)_45%,var(--border))] bg-[color-mix(in_oklab,var(--primary)_6%,var(--card))] p-6 shadow-[var(--highlight),0_0_0_4px_var(--glow),var(--shadow-md)] sm:p-7" style={delay(200)}>
<p className="flex items-center gap-2 text-[13px] font-medium uppercase tracking-[0.1em] text-[color-mix(in_oklab,var(--primary)_75%,var(--foreground))]">
<Icon name="shield" size={15} />
{r.oursLabel}
</p>
<ul className="mt-5 grid gap-3">
{r.ours.map((x, i) => (
<li key={i} className="flex gap-3 text-pretty text-[15px] text-foreground">
<Icon name="check" size={16} strokeWidth={2} className="mt-1 shrink-0 text-primary" />
{x}
</li>
))}
</ul>
</div>
</div>
</div>
<Action cta={p.cta} note={p.note} center className="mt-12" />
</Section>
);
}
/* ------------------------------------------------------------------ */
/* refund-steps */
/* ------------------------------------------------------------------ */
function RefundSteps(p: ViewProps) {
const steps = p.steps ?? [];
const ref = useScrollProgress<HTMLDivElement>({ start: 0.15, end: 0.6 });
return (
<Section className={p.className}>
<Head {...p} />
<div ref={ref} className="relative mt-14" style={{ ['--n' as string]: steps.length }}>
{/* the track across on wider screens, from the first icon to the last */}
<span aria-hidden className="absolute left-[calc(50%/var(--n))] right-[calc(50%/var(--n))] top-[21px] h-0.5 rounded-full bg-track max-md:hidden" />
<span aria-hidden className="absolute left-[calc(50%/var(--n))] right-[calc(50%/var(--n))] top-[21px] h-0.5 origin-left rounded-full bg-primary [transform:scaleX(var(--kit-progress,1))] max-md:hidden" />
<ol className="relative grid grid-cols-[minmax(0,1fr)] gap-8 md:grid-cols-[repeat(var(--n),minmax(0,1fr))] md:gap-6">
{steps.map((s, i) => (
<li key={i} className="relative grid grid-cols-[44px_minmax(0,1fr)] gap-4 md:flex md:flex-col md:items-center md:text-center">
{/* on phones a segment runs down to the next step, it ends at the last icon */}
{i < steps.length - 1 && (
<span aria-hidden className="absolute -bottom-8 left-[21px] top-11 w-0.5 bg-track md:hidden">
<span className="block size-full origin-top bg-primary" style={{ transform: `scaleY(clamp(0, calc(var(--kit-progress, 1) * ${Math.max(1, steps.length - 1)} - ${i}), 1))` }} />
</span>
)}
<span className="relative z-[1] grid size-11 place-items-center rounded-full border-2 border-primary bg-background text-primary shadow-[0_0_0_6px_var(--background)]">
<Icon name={s.icon ?? (i === steps.length - 1 ? 'check' : 'circle')} size={18} strokeWidth={1.9} />
</span>
<div className="kit-rise pt-1 md:mt-4 md:pt-0" style={delay(i * 100)}>
<p className="text-[12.5px] font-semibold uppercase tracking-[0.12em] text-[color-mix(in_oklab,var(--primary)_75%,var(--muted-foreground))]">{s.day}</p>
<p className="mt-1.5 text-balance text-[17px] font-semibold leading-snug text-foreground">{s.title}</p>
{s.text && <p className="mt-1.5 text-pretty text-[14.5px] leading-relaxed text-muted-foreground md:mx-auto md:max-w-[16rem]">{s.text}</p>}
</div>
</li>
))}
</ol>
</div>
<Action cta={p.cta} note={p.note} className="mt-14" />
</Section>
);
}
/* ------------------------------------------------------------------ */
/* promises */
/* ------------------------------------------------------------------ */
function Promises(p: ViewProps) {
const { t } = p;
const list = p.promises ?? [];
const [open, setOpen] = useState<number | null>(null);
const id = useId();
return (
<Section className={p.className}>
<Head {...p} center />
<ul className={cn('mt-14 grid grid-cols-[minmax(0,1fr)] gap-px overflow-hidden rounded-2xl border border-border bg-border shadow-[var(--highlight),var(--shadow-sm)]', list.length >= 3 ? 'md:grid-cols-3' : 'md:grid-cols-2', list.length === 4 && 'md:grid-cols-2 lg:grid-cols-4')}>
{list.map((x, i) => (
<li key={i} className="flex flex-col bg-card p-6 sm:p-7">
<div className="kit-rise flex h-full flex-col" style={delay(i * 90)}>
<Icon name={x.icon ?? 'shield'} size={20} className="text-primary" />
<p className={cn('mt-6 flex items-baseline font-semibold tracking-[-0.04em] text-foreground', x.unit?.startsWith('%') ? 'gap-0.5' : 'gap-1.5')}>
<span className="text-[clamp(2.75rem,2.3rem+1.6vw,3.5rem)] leading-none tabular-nums">{typeof x.value === 'number' ? <NumberTicker value={x.value} delay={i * 120} /> : x.value}</span>
{x.unit && <span className="text-[18px] tracking-[-0.01em] text-muted-foreground">{x.unit}</span>}
</p>
<p className="mt-4 text-[16px] font-semibold text-foreground">{x.title}</p>
{x.text && <p className="mt-1.5 text-pretty text-[14.5px] leading-relaxed text-muted-foreground">{x.text}</p>}
{x.fine && (
<div className="mt-auto pt-5">
<button
type="button"
aria-expanded={open === i}
aria-controls={`${id}-${i}`}
onClick={() => setOpen(open === i ? null : i)}
className="inline-flex min-h-11 cursor-pointer items-center gap-1.5 rounded-md text-[13px] font-medium text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-primary sm:min-h-8"
>
<Icon name="plus" size={14} className={cn('transition-transform duration-300', open === i && 'rotate-45')} />
{open === i ? t.hide : t.fine}
</button>
<div id={`${id}-${i}`} className={cn('grid transition-[grid-template-rows] duration-500 ease-[var(--ease-kit)] motion-reduce:transition-none', open === i ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]')}>
<p className={cn('overflow-hidden text-pretty text-[13px] leading-relaxed text-muted-foreground', open !== i && 'invisible')}>
<span className="block pt-2">{x.fine}</span>
</p>
</div>
</div>
)}
</div>
</li>
))}
</ul>
<Action cta={p.cta} note={p.note} center className="mt-12" />
</Section>
);
}
/* ------------------------------------------------------------------ */
/* inline */
/* ------------------------------------------------------------------ */
function Inline(p: ViewProps) {
const { t } = p;
const [ref, inView] = useInView<HTMLDivElement>();
const days = p.days ?? 30;
const C = 2 * Math.PI * 20;
return (
<div ref={ref} className={cn('flex flex-col gap-4 rounded-2xl border border-border bg-card p-4 shadow-[var(--highlight),var(--shadow-sm)] sm:flex-row sm:items-center sm:gap-5 sm:p-5', p.className)}>
<div className="flex items-center gap-4">
<span className="relative grid size-14 shrink-0 place-items-center">
<svg viewBox="0 0 48 48" aria-hidden className="absolute inset-0 size-full -rotate-90">
<circle cx="24" cy="24" r="20" fill="none" className="stroke-track" strokeWidth="3" />
<circle cx="24" cy="24" r="20" fill="none" className="stroke-success transition-[stroke-dashoffset] duration-[1400ms] ease-[var(--ease-kit)] motion-reduce:transition-none" strokeWidth="3" strokeLinecap="round" strokeDasharray={round(C)} strokeDashoffset={inView ? 0 : round(C)} />
</svg>
<span className="relative text-[16px] font-semibold tabular-nums text-foreground">{days}</span>
</span>
<span className="min-w-0">
<span className="block text-[15px] font-semibold text-foreground">{p.title ?? `${days} ${t.days} ${t.moneyBack}`}</span>
{p.description && <span className="mt-0.5 block text-pretty text-[13.5px] text-muted-foreground">{p.description}</span>}
</span>
</div>
{p.points?.length ? (
<ul className="grid gap-2 border-t border-border pt-4 text-[13.5px] text-muted-foreground sm:ml-auto sm:border-l sm:border-t-0 sm:pl-5 sm:pt-0">
{p.points.map((x, i) => (
<li key={i} className="flex items-center gap-2">
<Icon name={x.icon ?? 'check'} size={15} className="shrink-0 text-success-foreground" />
{x.title}
</li>
))}
</ul>
) : null}
</div>
);
}
/* ------------------------------------------------------------------ */
const VIEWS: Record<GuaranteeVariant, (p: ViewProps) => React.ReactNode> = {
seal: Seal,
balance: Balance,
'refund-steps': RefundSteps,
promises: Promises,
inline: Inline,
};
export function Guarantee({ variant = 'seal', labels, ...rest }: GuaranteeProps) {
const t = useTexts(TEXTS, labels);
const View = VIEWS[variant] ?? Seal;
return <View {...rest} variant={variant} t={t} />;
}Häufige Fragen
Wie installiere ich Garantie?
Mit aiw add @ai-website/guarantee. Der Befehl legt die Datei components/ui/guarantee.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind motion, button, icon, number-ticker und section. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Garantie?
5 Varianten, Siegel mit Bedingungen, Risiko auf unserer Seite, So läuft eine Rückgabe, Versprechen mit Kleingedrucktem und Neben dem Kaufknopf. Du wählst sie mit der Prop variant, zum Beispiel variant="balance".
Kann ich die Texte von Garantie übersetzen?
Ja. Garantie bringt 8 eigene Texte auf Deutsch und Englisch mit, etwa „Tage“, „Geld zurück“ und „Das Kleingedruckte“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.
Kann ein KI-Assistent Garantie einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Garantie mit aiw_components und installieren sie mit aiw_add @ai-website/guarantee. Nenn im Prompt die Variante, etwa variant="seal", dann schreibt der Assistent deine Inhalte direkt in die Props.