All Taste components
Animated heroes
Hero — full-bleed brand gradient (motion)
Soft light drifts slowly across the brand gradient while the rings turn into place and breathe, and the chips bob.
slow background drift · rings turn in and breathe · chips pop then bob · copy rises
Fernbill · Recurring billing
Retainers that bill themselves, every month, on the dot.
Set the amount and the day once. Fernbill sends the invoice, collects the card payment and files the receipt.
import type { CSSProperties } from "react";
import { Check, Mail, Repeat } from "lucide-react";
/* Add once to globals.css:
@keyframes drift-a { to { transform: translate(34%, 28%) scale(1.15); } }
@keyframes drift-b { to { transform: translate(-28%, -22%) scale(.9); } }
@keyframes rings-in { from { opacity: 0; scale: .86; rotate: -24deg; } }
@keyframes breathe { to { scale: 1.05; } }
@keyframes chip-pop { from { opacity: 0; scale: .6; } }
@keyframes bob { from { translate: 0 0; } to { translate: 0 -6px; } }
*/
const chips = [
{ icon: Repeat, label: "Monthly · 1st", className: "-left-[8%] top-[14%]" },
{ icon: Check, label: "$1,200 collected", className: "-right-[2%] top-[44%]" },
{ icon: Mail, label: "Receipt sent", className: "bottom-[8%] left-[6%]" },
];
/** Uses the brand ramp (brand-50…950), which stays the same in dark mode — so dark-on-brand text keeps its contrast. */
export function HeroBrandGradientMotion() {
return (
<section
aria-labelledby="hero-title"
className="relative isolate overflow-hidden bg-[radial-gradient(70%_90%_at_100%_100%,var(--tst-brand-500),transparent_70%),linear-gradient(150deg,var(--tst-brand-100),var(--tst-brand-300)_55%,var(--tst-brand-400))] font-sans text-brand-950"
>
{/* Two soft lights drifting on transform only (slow, calm, stops for reduced motion) */}
<div aria-hidden className="pointer-events-none absolute inset-0 -z-10 overflow-hidden">
<span className="absolute -left-[20%] -top-[55%] aspect-square w-[max(62%,420px)] rounded-full bg-[radial-gradient(closest-side,color-mix(in_srgb,var(--tst-brand-50)_70%,transparent),transparent)] animate-[drift-a_24s_ease-in-out_infinite_alternate] motion-reduce:animate-none" />
<span className="absolute -bottom-[60%] -right-[14%] aspect-square w-[max(56%,380px)] rounded-full bg-[radial-gradient(closest-side,color-mix(in_srgb,var(--tst-brand-600)_42%,transparent),transparent)] animate-[drift-b_30s_ease-in-out_infinite_alternate] motion-reduce:animate-none" />
</div>
<div className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-16 sm:px-6 sm:py-24 lg:grid-cols-[1.25fr_0.75fr]">
<div className="flex flex-col items-start gap-5 [&>*]:animate-rise motion-reduce:[&>*]:animate-fade">
<p className="text-[11.5px] font-medium uppercase tracking-[0.12em] text-brand-800">Fernbill · Recurring billing</p>
<h1 id="hero-title" className="text-balance text-4xl font-semibold leading-[1.04] tracking-tight [animation-delay:90ms] sm:text-5xl lg:text-6xl">
Retainers that bill themselves, every month, on the dot.
</h1>
<p className="max-w-lg text-pretty text-base leading-relaxed text-brand-900 [animation-delay:180ms] sm:text-lg">
Set the amount and the day once. Fernbill sends the invoice, collects the card payment and files the receipt.
</p>
<div className="flex flex-wrap gap-3 [animation-delay:270ms]">
<a href="/retainers/new" className="inline-flex h-11 items-center rounded bg-brand-950 px-5 text-[15px] font-medium text-brand-50 transition hover:bg-brand-900 active:scale-[.97]">Set up a retainer</a>
<a href="/retainers" className="inline-flex h-11 items-center rounded border border-brand-950/30 px-5 text-[15px] font-medium transition hover:bg-brand-950/10 active:scale-[.97]">How it works</a>
</div>
</div>
<div aria-hidden className="relative ml-auto hidden aspect-square w-full max-w-[340px] rounded-full border border-brand-950/20 animate-[rings-in_1.1s_cubic-bezier(.2,.7,.1,1)_.3s_both] motion-reduce:animate-fade lg:block">
<div className="absolute inset-[16%] rounded-full border border-brand-950/20 animate-[breathe_7s_ease-in-out_1.4s_infinite_alternate] motion-reduce:animate-none" />
<div className="absolute inset-[34%] rounded-full border border-brand-950/20 bg-brand-50/50 animate-[breathe_9s_ease-in-out_.6s_infinite_alternate-reverse] motion-reduce:animate-none" />
{chips.map(({ icon: Icon, label, className }, i) => (
<span
key={label}
// Pops in, then bobs: each chip has its own start and pace.
style={{ "--in": 850 + i * 150 + "ms", "--pace": 4.6 + i * 0.9 + "s", "--bob": 1.5 + i * 0.4 + "s" } as CSSProperties}
className={"absolute inline-flex items-center gap-2 whitespace-nowrap rounded-full bg-brand-50 px-3 py-2 text-[12.5px] font-medium shadow-lg [animation:chip-pop_.55s_cubic-bezier(.34,1.56,.64,1)_var(--in)_both,bob_var(--pace)_ease-in-out_var(--bob)_infinite_alternate] motion-reduce:animate-fade " + className}
>
<Icon className="size-3.5" /> {label}
</span>
))}
</div>
</div>
</section>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.