Zum Inhalt springen
UI Kit
Accent

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.

5 VariantsEine von 2 Components in Security und Trust

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

Siegel mit BedingungenNeu in 1.2

Ein großes gezeichnetes 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 SeiteNeu in 1.2

Eine gezeichnete Waage kippt beim Scrollen und legt das Gewicht auf die Seite des Anbieters. Darunter zeigen zwei Karten, was Kunden tragen und was das Unternehmen trägt.

So läuft eine RückgabeNeu in 1.2

Die Tage einer Rückgabe auf einer Spur, die sich beim Scrollen füllt. Auf dem Telefon nach unten, auf breiten Bildschirmen quer, der letzte Schritt ist das Geld zurück.

Versprechen mit KleingedrucktemNeu in 1.2

Mehrere Garantien in einem Haarlinienraster, jede mit einer Zahl, die hochzählt, einem kurzen Versprechen und dem Kleingedruckten, das sich an Ort und Stelle öffnet.

Neben dem KaufknopfNeu in 1.2

Eine kompakte Karte mit einem Ring, der sich um die Tage schließt, und drei kurzen Versprechen, gemacht für direkt unter Preis und Kaufknopf.

Details

Was Garantie mitbringt, aus Registry und Quellcode gelesen.

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

Props

Die Schnittstelle GuaranteeProps aus components/ui/guarantee.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • variantoptional
    GuaranteeVariant

    sealbalancerefund-stepspromisesinline

  • eyebrowoptional
    React.ReactNode
  • titleoptional
    React.ReactNode
  • descriptionoptional
    React.ReactNode
  • daysoptional
    number

    seal, inline: number of days

  • rimoptional
    string

    seal: text on the rim

  • pointsoptional
    GuaranteePoint[]

    seal, inline: what is promised, short

  • fineoptional
    string

    seal: 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

  • promisesoptional
    GuaranteePromise[]

    promises

  • ctaoptional
    { label: string; href: string }
  • noteoptional
    string

    A 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
daysEnglisch daysDeutsch Tage
moneyBackEnglisch Money backDeutsch Geld zurück
fineEnglisch The small printDeutsch Das Kleingedruckte
hideEnglisch Hide the small printDeutsch Kleingedrucktes ausblenden
guaranteeEnglisch GuaranteeDeutsch Garantie
yoursEnglisch You carryDeutsch Du trägst
oursEnglisch We carryDeutsch Wir tragen
nothingEnglisch Nothing but a few minutesDeutsch 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
components/ui/guarantee.tsx
'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.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert