Skip to content
All Taste components

Animated heroes

Hero with floating cards

An opening section whose headline arrives word by word while product cards drift beside it.

word-by-word headline · rising buttons · 3 floating cards · stacks on narrow widths

NewAuto-match for bank feeds

Closeyourbooksbeforethecoffeegetscold.

Northwind matches every payment to its invoice the moment it lands, so month-end is a review, not a rescue.

14-day trial · No card needed

import { ArrowRight, Check } from "lucide-react";

/* Add once to globals.css (the Taste preset already has rise, fade, pop, float, grow-*):
@keyframes word-in { from { opacity: 0; transform: translateY(.45em); filter: blur(8px); } }
@keyframes card-in { from { opacity: 0; transform: translateY(28px) scale(.94); } }
*/

const hero = {
  pill: { tag: "New", label: "Auto-match for bank feeds", href: "/changelog" },
  title: "Close your books before the coffee gets cold.",
  lead: "Northwind matches every payment to its invoice the moment it lands, so month-end is a review, not a rescue.",
  note: "14-day trial · No card needed",
};
const cashBars = [38, 52, 44, 63, 57, 72, 66, 88];

export function HeroFloatingCards() {
  const words = hero.title.split(" ");
  return (
    <section aria-labelledby="hero-title" className="overflow-hidden bg-bg font-sans">
      <div className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-20 sm:px-6 lg:grid-cols-[1.05fr_1fr]">
        <div className="flex flex-col items-start gap-5">
          <a href={hero.pill.href} className="inline-flex items-center gap-2 rounded-full border border-border bg-bg py-0.5 pl-0.5 pr-3 text-[13px] text-fg-muted animate-fade motion-reduce:animate-none">
            <span className="rounded-full bg-primary-soft px-2 py-0.5 text-xs font-medium text-primary-soft-fg">{hero.pill.tag}</span>
            {hero.pill.label}
            <ArrowRight className="size-3.5" aria-hidden />
          </a>
          <h1 id="hero-title" className="text-balance text-4xl font-medium leading-[1.05] tracking-tight text-fg sm:text-5xl">
            {words.map((w, i) => (
              <span
                key={i}
                className="mr-[.24em] inline-block animate-[word-in_.8s_cubic-bezier(.2,.7,.1,1)_both] motion-reduce:animate-none"
                style={{ animationDelay: 120 + i * 65 + "ms" }}
              >
                {w}
              </span>
            ))}
          </h1>
          <p className="max-w-lg text-pretty text-base leading-relaxed text-fg-muted animate-rise [animation-delay:520ms] motion-reduce:animate-none">{hero.lead}</p>
          <div className="flex flex-wrap gap-3">
            <a href="/signup" className="inline-flex h-11 items-center gap-1.5 rounded bg-primary px-5 font-medium text-primary-fg transition hover:bg-primary-hover active:scale-[.97] animate-pop [animation-delay:720ms] motion-reduce:animate-none">
              Start free <ArrowRight className="size-4" aria-hidden />
            </a>
            <a href="/demo" className="inline-flex h-11 items-center rounded border border-border-strong bg-bg px-5 font-medium text-fg transition hover:bg-surface animate-pop [animation-delay:800ms] motion-reduce:animate-none">
              Book a demo
            </a>
          </div>
          <p className="text-[13px] text-fg-subtle animate-fade [animation-delay:900ms] motion-reduce:animate-none">{hero.note}</p>
        </div>

        {/* Product cards: each one springs in, then floats on its own phase */}
        <div aria-hidden className="relative h-[300px]">
          <div className="absolute inset-y-[6%] inset-x-0 rounded-full bg-[radial-gradient(closest-side,color-mix(in_srgb,var(--tst-primary)_22%,transparent),transparent)]" />
          <FloatingCard className="left-0 top-0 w-[min(250px,78%)]" index={0} tilt="-rotate-2">
            <div className="flex items-center gap-2.5">
              <span className="grid size-8 place-items-center rounded bg-success-soft text-success-fg"><Check className="size-4" /></span>
              <div>
                <div className="text-lg font-medium tabular-nums tracking-tight text-fg">+₹48,200</div>
                <div className="text-xs text-fg-muted">Kestrel Studio · just now</div>
              </div>
            </div>
          </FloatingCard>
          <FloatingCard className="right-0 top-[30%] w-[min(232px,72%)]" index={1} tilt="rotate-[1.5deg]">
            <div className="flex justify-between text-xs text-fg-subtle"><span>Cash in, 30 days</span><span className="text-success-fg">+18%</span></div>
            <div className="mt-1 text-lg font-medium tabular-nums text-fg">₹6,42,800</div>
            <div className="mt-2 flex h-14 items-end gap-1">
              {cashBars.map((h, i) => (
                <span
                  key={i}
                  className={"flex-1 origin-bottom rounded-t-sm animate-grow-y motion-reduce:animate-none " + (i === cashBars.length - 1 ? "bg-primary" : "bg-primary-soft")}
                  style={{ height: h + "%", animationDelay: 900 + i * 55 + "ms" }}
                />
              ))}
            </div>
          </FloatingCard>
          <FloatingCard className="bottom-0 left-[6%] w-[min(226px,70%)]" index={2} tilt="-rotate-1">
            <p className="text-sm text-fg-muted">3 reminders sent</p>
            <div className="mt-2 h-1.5 overflow-hidden rounded-full bg-surface-2">
              <span className="block h-full w-[72%] origin-left rounded-full bg-primary animate-grow-x [animation-delay:1100ms] motion-reduce:animate-none" />
            </div>
          </FloatingCard>
        </div>
      </div>
    </section>
  );
}

function FloatingCard({ className, index, tilt, children }: { className: string; index: number; tilt: string; children: React.ReactNode }) {
  return (
    <div
      className={"absolute animate-[card-in_.9s_cubic-bezier(.34,1.56,.64,1)_both] motion-reduce:animate-none " + className}
      style={{ animationDelay: 520 + index * 150 + "ms" }}
    >
      <div className={tilt}>
        <div
          className="rounded-lg border border-border bg-bg p-3.5 shadow-[0_18px_40px_-26px_rgb(0_0_0/.35)] animate-float motion-reduce:animate-none"
          style={{ animationDelay: index * -1.9 + "s", animationDuration: "6s" }}
        >
          {children}
        </div>
      </div>
    </div>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.