A waitlist join form with an email input, a submit button, and a stacked avatar row showing recent members.
"use client";
import { motion as m, useReducedMotion, animate } from "motion/react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ArrowRight } from "lucide-react";
import { useEffect, useState } from "react";
const easeOut = [0.23, 1, 0.32, 1] as const;
const AVATARS = [1, 2, 3, 4, 5].map((n) => `https://i.pravatar.cc/150?img=${n}`);
const TOTAL = 180;
const NewMember = () => {
const reduce = useReducedMotion();
const [count, setCount] = useState(reduce ? TOTAL : 0);
useEffect(() => {
if (reduce) return;
const controls = animate(0, TOTAL, {
duration: 1.4,
ease: easeOut,
onUpdate: (v) => setCount(Math.round(v)),
});
return () => controls.stop();
}, [reduce]);
return (
<div className="flex h-full w-full items-center justify-center p-6">
<div className="flex w-full max-w-sm flex-col gap-7">
{/* Waitlist input */}
<div className="flex items-center gap-2">
<Input
placeholder="Enter your email"
aria-label="Email address"
className="h-10 bg-muted/40"
/>
<Button className="h-10 shrink-0 gap-1.5 transition-transform duration-150 active:scale-[0.97]">
Join waitlist
<ArrowRight className="size-4" />
</Button>
</div>
{/* Social proof */}
<m.div
initial={reduce ? false : { opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: easeOut }}
className="flex items-center justify-between gap-4"
>
{/* Avatar stack */}
<div className="flex -space-x-3">
{AVATARS.map((src, i) => (
<m.img
src={src}
key={i}
alt=""
draggable={false}
initial={reduce ? false : { opacity: 0, x: -8, scale: 0.8 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
transition={{ duration: 0.4, delay: 0.15 + i * 0.06, ease: easeOut }}
className="relative size-9 rounded-full object-cover ring-2 ring-background outline outline-1 -outline-offset-1 outline-white/10 transition-transform duration-200 hover:z-10 hover:-translate-y-1"
/>
))}
</div>
{/* Counter pill with proud badge */}
<div className="relative flex shrink-0 items-center rounded-full border border-border/60 bg-muted/40 py-2 pl-16 pr-5">
<span
aria-hidden
className="absolute -left-1 top-1/2 flex size-12 -translate-y-1/2 items-center justify-center rounded-full bg-primary text-sm font-semibold tabular-nums text-primary-foreground shadow-md shadow-black/20 ring-4 ring-background"
>
{count}
</span>
<span className="flex items-center gap-1.5 whitespace-nowrap text-sm text-muted-foreground">
People joined
<span className="inline-flex items-center gap-1.5 font-medium text-foreground">
<span className="relative flex size-1.5">
<span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-500/70 motion-reduce:hidden" />
<span className="relative inline-flex size-1.5 rounded-full bg-emerald-500" />
</span>
today
</span>
</span>
</div>
</m.div>
</div>
</div>
);
};
export default NewMember;