Playful team workspace and CRM landing page with an interactive leads table, pastel bento cards, autonomous sprint checklist, and 3D momentum conveyor.
Set your access key from API keys as NEXTUI_TOKEN in .env.local and add the @nextui namespace below to components.json once. Then every item installs by name. Each install counts as a copy on a free account.
$pnpm dlx shadcn@latest add @nextui/task-hub
1{2"registries": {3"@nextui": {4"url": "https://nextui.dev/r/{name}.json",5"headers": { "Authorization": "Bearer ${NEXTUI_TOKEN}" }6}7}8}
"use client";
import {
createContext,
useContext,
useEffect,
useMemo,
useRef,
useState,
type ComponentType,
type CSSProperties,
type ReactNode,
type RefObject,
} from "react";
import {
AnimatePresence,
animate,
motion,
useAnimationFrame,
useInView,
useReducedMotion,
} from "motion/react";
import {
ArrowDown,
ArrowRight,
ArrowUp,
ArrowUpDown,
Calendar,
Check,
ChevronDown,
ChevronLeft,
ChevronRight,
CircleCheck,
CircleDollarSign,
CircleMinus,
CircleX,
Clock3,
Flame,
FolderKanban,
LayoutGrid,
ListChecks,
Loader,
Menu,
MessageCircle,
MessagesSquare,
Minus,
Phone,
Play,
Search,
Sparkles,
Star,
Target,
UserPlus,
Users,
Wand2,
X,
Zap,
} from "lucide-react";
import { cn } from "@/lib/utils";
/* -------------------------------------------------------------------------- */
/* TOKENS & CONTEXT */
/* -------------------------------------------------------------------------- */
const ScrollerContext = createContext<RefObject<HTMLDivElement | null>>({ current: null });
const useScroller = () => useContext(ScrollerContext);
const SANS: CSSProperties = {
fontFamily: '"Plus Jakarta Sans", "Inter", system-ui, -apple-system, sans-serif',
};
const EASE = [0.22, 1, 0.36, 1] as const;
const SPRING = { type: "spring", stiffness: 280, damping: 22 } as const;
/* -------------------------------------------------------------------------- */
/* FLOATING DOODLES & CURSOR CALLOUTS */
/* -------------------------------------------------------------------------- */
function FloatingSticker({
children,
className,
delay = 0,
duration = 4.6,
distance = 8,
}: {
children: ReactNode;
className?: string;
delay?: number;
duration?: number;
distance?: number;
}) {
const reduce = useReducedMotion();
return (
<motion.div
aria-hidden
className={cn("pointer-events-none absolute select-none", className)}
animate={
reduce
? undefined
: {
y: [0, -distance, 0],
rotate: [-3, 4, -3],
}
}
transition={{
duration,
repeat: Infinity,
ease: "easeInOut",
delay,
}}
>
{children}
</motion.div>
);
}
function CursorBadge({
name,
bg,
textColor = "#191A23",
pointerSide = "right",
}: {
name: string;
bg: string;
textColor?: string;
pointerSide?: "left" | "right";
}) {
return (
<div className="relative inline-flex items-center">
{pointerSide === "left" && (
<svg
width="14"
height="14"
viewBox="0 0 16 16"
className="-mr-1 -mt-5 shrink-0"
fill="#191A23"
>
<path d="M2 2L14 6.5L8.5 9L6.5 14.5L2 2Z" />
</svg>
)}
<span
className="rounded-full px-3.5 py-1 text-[11px] font-extrabold shadow-sm"
style={{ backgroundColor: bg, color: textColor }}
>
{name}
</span>
{pointerSide === "right" && (
<svg
width="14"
height="14"
viewBox="0 0 16 16"
className="-ml-1 -mt-5 shrink-0 -scale-x-100"
fill="#191A23"
>
<path d="M2 2L14 6.5L8.5 9L6.5 14.5L2 2Z" />
</svg>
)}
</div>
);
}
function AvatarBubble({
emoji,
bg,
name,
size = "md",
}: {
emoji: string;
bg: string;
name?: string;
size?: "xs" | "sm" | "md" | "lg";
}) {
const cls =
size === "xs"
? "size-6 text-[11px]"
: size === "sm"
? "size-7 text-xs"
: size === "lg"
? "size-10 text-base"
: "size-8 text-sm";
return (
<span
title={name}
className={cn(
"inline-flex shrink-0 items-center justify-center rounded-full border-2 border-white shadow-2xs select-none",
cls
)}
style={{ backgroundColor: bg }}
>
{emoji}
</span>
);
}
/* -------------------------------------------------------------------------- */
/* HEADER */
/* -------------------------------------------------------------------------- */
const NAV_ITEMS = [
{ label: "Home", id: "top" },
{ label: "Workspace", id: "workspace" },
{ label: "Why Us", id: "different" },
{ label: "Automation", id: "automation" },
{ label: "Workflow", id: "features" },
] as const;
function Header({ onNavigate }: { onNavigate: (id: string) => void }) {
const [mobileOpen, setMobileOpen] = useState(false);
const headerRef = useRef<HTMLElement>(null);
useEffect(() => {
if (!mobileOpen) return;
const handlePointerDown = (e: PointerEvent) => {
if (!headerRef.current?.contains(e.target as Node)) {
setMobileOpen(false);
}
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setMobileOpen(false);
};
document.addEventListener("pointerdown", handlePointerDown);
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("pointerdown", handlePointerDown);
document.removeEventListener("keydown", handleKeyDown);
};
}, [mobileOpen]);
const handleSelect = (id: string) => {
setMobileOpen(false);
onNavigate(id);
};
return (
<header ref={headerRef} className="sticky top-0 z-40 mx-auto max-w-6xl px-4 pt-4 sm:px-8">
<div className="relative flex items-center justify-between rounded-2xl border border-black/[0.05] bg-white/85 px-4 py-3 shadow-[0_8px_30px_-12px_rgba(0,0,0,0.06)] backdrop-blur-md sm:px-5">
<button
type="button"
onClick={() => handleSelect("top")}
className="group flex cursor-pointer items-center gap-2.5 text-left"
>
<motion.div
whileHover={{ rotate: 90, scale: 1.08 }}
transition={SPRING}
className="grid size-8 place-items-center rounded-xl bg-[#6842FF] text-white shadow-[0_6px_16px_-4px_rgba(104,66,255,0.55)]"
>
<LayoutGrid className="size-4" />
</motion.div>
<span className="text-base font-extrabold tracking-tight text-[#191A23]">
Task<span className="text-[#6842FF]">Hub</span>
</span>
</button>
<nav className="hidden items-center gap-7 text-[13px] font-semibold text-[#6E7180] md:flex">
{NAV_ITEMS.map((item) => (
<button
key={item.label}
type="button"
onClick={() => handleSelect(item.id)}
className="cursor-pointer transition-colors hover:text-[#191A23]"
>
{item.label}
</button>
))}
</nav>
<div className="flex items-center gap-2 sm:gap-3">
<button
type="button"
onClick={() => handleSelect("cta")}
className="hidden sm:inline-flex cursor-pointer items-center justify-center px-3 py-1.5 text-xs font-bold text-[#191A23] transition-colors hover:text-[#6842FF]"
>
Login
</button>
<button
type="button"
onClick={() => handleSelect("cta")}
className="hidden sm:inline-flex cursor-pointer items-center gap-1.5 rounded-full bg-[#191A23] px-5 py-2.5 text-xs font-bold text-white shadow-xs transition-all hover:bg-[#6842FF] active:scale-95"
>
<span>Start Your Free Trial</span>
</button>
{/* Mobile Hamburger Menu Button */}
<button
type="button"
aria-label={mobileOpen ? "Close navigation menu" : "Open navigation menu"}
aria-expanded={mobileOpen}
onClick={() => setMobileOpen((prev) => !prev)}
className={cn(
"inline-flex size-9 cursor-pointer items-center justify-center rounded-xl border border-black/[0.06] transition-colors md:hidden",
mobileOpen
? "bg-[#6842FF] text-white"
: "bg-[#FAF8FF] text-[#191A23] hover:bg-[#F3F0FF] hover:text-[#6842FF]"
)}
>
<AnimatePresence mode="wait" initial={false}>
<motion.span
key={mobileOpen ? "close" : "open"}
initial={{ opacity: 0, rotate: -90, scale: 0.8 }}
animate={{ opacity: 1, rotate: 0, scale: 1 }}
exit={{ opacity: 0, rotate: 90, scale: 0.8 }}
transition={{ duration: 0.16 }}
>
{mobileOpen ? <X className="size-4" /> : <Menu className="size-4" />}
</motion.span>
</AnimatePresence>
</button>
</div>
</div>
{/* Mobile Dropdown Navigation Drawer */}
<AnimatePresence>
{mobileOpen && (
<motion.div
initial={{ opacity: 0, y: -10, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -8, scale: 0.98 }}
transition={{ duration: 0.22, ease: EASE }}
className="mt-2 overflow-hidden rounded-2xl border border-black/[0.06] bg-white/95 p-3 shadow-[0_20px_40px_-15px_rgba(25,26,35,0.12)] backdrop-blur-md md:hidden"
>
<nav className="flex flex-col gap-1">
{NAV_ITEMS.map((item) => (
<button
key={item.label}
type="button"
onClick={() => handleSelect(item.id)}
className="flex w-full cursor-pointer items-center justify-between rounded-xl px-3.5 py-2.5 text-left text-xs font-extrabold text-[#191A23] transition-colors hover:bg-[#FAF8FF] hover:text-[#6842FF]"
>
<span>{item.label}</span>
<ArrowRight className="size-3.5 text-[#6E7180]" />
</button>
))}
</nav>
<div className="mt-3 flex flex-col gap-2 border-t border-black/[0.05] pt-3 sm:hidden">
<button
type="button"
onClick={() => handleSelect("cta")}
className="w-full cursor-pointer rounded-xl bg-[#FAF8FF] py-2.5 text-center text-xs font-bold text-[#191A23] transition-colors hover:bg-[#F3F0FF] hover:text-[#6842FF]"
>
Login
</button>
<button
type="button"
onClick={() => handleSelect("cta")}
className="w-full cursor-pointer rounded-full bg-[#6842FF] py-2.5 text-center text-xs font-bold text-white shadow-xs transition-all hover:bg-[#FF5484] active:scale-95"
>
Start Your Free Trial
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</header>
);
}
/* -------------------------------------------------------------------------- */
/* LEADS TABLE COMPONENT (FROM /blocks/leads-table) */
/* -------------------------------------------------------------------------- */
type LeadStatus =
| "Won"
| "Call Booked"
| "Qualified"
| "In Contact"
| "New Lead"
| "No Show"
| "Unqualified - No Money"
| "Unqualified - Ghosted"
| "Lost - Financials";
type Tone = "amber" | "blue" | "violet" | "rose" | "green" | "pink" | "gray" | "teal";
type Lead = {
id: number;
name: string;
email: string;
tone: Tone;
messages?: number;
status: LeadStatus;
value?: number | null;
assignee: string;
role: "C" | "S";
interacted: number;
payment: string;
added: number;
};
type SortKey = "value" | "interacted" | "added";
type Sort = { key: SortKey; dir: "asc" | "desc" } | null;
const DOT: Record<Tone, string> = {
amber: "bg-[#FFB81C]",
blue: "bg-blue-500",
violet: "bg-[#6842FF]",
rose: "bg-[#FF5484]",
green: "bg-lime-500",
pink: "bg-pink-500",
gray: "bg-zinc-400",
teal: "bg-emerald-500",
};
const STATUS: Record<LeadStatus, { icon: ComponentType<{ className?: string }>; className: string }> = {
Won: { icon: CircleCheck, className: "text-emerald-500" },
"Call Booked": { icon: Phone, className: "text-blue-500" },
Qualified: { icon: Star, className: "fill-[#FFB81C] text-[#FFB81C]" },
"In Contact": { icon: MessagesSquare, className: "text-[#6842FF]" },
"New Lead": { icon: UserPlus, className: "text-[#FF5484]" },
"No Show": { icon: CircleMinus, className: "text-zinc-400" },
"Unqualified - No Money": { icon: CircleX, className: "text-[#FF5484]" },
"Unqualified - Ghosted": { icon: CircleX, className: "text-[#FF5484]" },
"Lost - Financials": { icon: CircleX, className: "text-[#FF5484]" },
};
const TEAM: Record<string, { tone: Tone; role: "C" | "S" }> = {
"Minh T.": { tone: "amber", role: "C" },
"Rafael O.": { tone: "rose", role: "C" },
"Lena K.": { tone: "violet", role: "S" },
"Theo B.": { tone: "green", role: "C" },
"Hana S.": { tone: "gray", role: "S" },
};
const SEED: Array<[string, Tone, LeadStatus, number | null | undefined, keyof typeof TEAM, number, string, number, number?]> = [
["Camille Laurent", "amber", "Won", 2500, "Minh T.", 2880, "Full Payment", 30, 3],
["Bao Tran", "blue", "Call Booked", null, "Rafael O.", 180, "N/A", 5],
["Aisha Rahman", "violet", "Unqualified - No Money", undefined, "Lena K.", 10080, "N/A", 14],
["Felix Wagner", "amber", "Won", 5000, "Theo B.", 5760, "$1K Deposit", 6],
["Marta Novak", "rose", "No Show", null, "Hana S.", 15, "N/A", 1, 5],
["Kai Nakamura", "blue", "Won", 3000, "Minh T.", 10080, "Installments: 2/6", 21],
["Rosa Delgado", "blue", "Qualified", null, "Lena K.", 1440, "N/A", 7],
["Arjun Mehta", "violet", "Lost - Financials", undefined, "Theo B.", 8640, "N/A", 30],
["Sienna Brooks", "pink", "New Lead", null, "Hana S.", 30, "N/A", 0],
["Tobias Lind", "green", "Call Booked", null, "Rafael O.", 2880, "N/A", 10, 1],
["Leila Haddad", "amber", "Won", 1500, "Theo B.", 7200, "Full Payment", 60],
["Nico Ferraro", "pink", "Unqualified - Ghosted", undefined, "Lena K.", 20160, "N/A", 14],
["June Park", "teal", "In Contact", null, "Hana S.", 60, "N/A", 4],
["Ezra Cohen", "amber", "Qualified", null, "Lena K.", 8640, "N/A", 21, 2],
["Freya Olsen", "violet", "Won", 4200, "Minh T.", 4320, "Installments: 1/4", 18],
["Mateus Costa", "blue", "In Contact", null, "Rafael O.", 420, "N/A", 3, 4],
["Thanh Le", "rose", "New Lead", null, "Hana S.", 5, "N/A", 0],
["Ingrid Holm", "green", "Won", 7800, "Theo B.", 2880, "Full Payment", 45],
["Rami Aziz", "pink", "Call Booked", null, "Minh T.", 1080, "N/A", 2],
["Chiara Bianchi", "gray", "No Show", null, "Rafael O.", 4320, "N/A", 9],
["Oscar Nyberg", "teal", "Qualified", null, "Lena K.", 240, "N/A", 6, 1],
["Amara Obi", "amber", "Lost - Financials", undefined, "Theo B.", 30240, "N/A", 40],
["Jonas Weber", "violet", "Won", 2200, "Minh T.", 1440, "$500 Deposit", 12],
["Vy Pham", "blue", "Unqualified - No Money", undefined, "Hana S.", 12960, "N/A", 25],
["Sami Kaya", "rose", "In Contact", null, "Rafael O.", 90, "N/A", 2, 2],
["Elif Demir", "green", "Call Booked", null, "Theo B.", 720, "N/A", 8],
["Hugo Martin", "pink", "Won", 6400, "Minh T.", 20160, "Installments: 4/6", 75],
["Nadia Petrova", "teal", "New Lead", null, "Lena K.", 45, "N/A", 1],
["Luca Moretti", "amber", "Qualified", null, "Rafael O.", 2160, "N/A", 11],
["Zara Khan", "gray", "Unqualified - Ghosted", undefined, "Hana S.", 25920, "N/A", 33],
["Ivo Horvat", "violet", "Won", 3600, "Theo B.", 600, "Full Payment", 16, 6],
["Mei Lin", "blue", "No Show", null, "Minh T.", 2880, "N/A", 4],
];
const FIRST = ["Alba", "Bilal", "Clara", "Dario", "Esme", "Farid", "Greta", "Hiro", "Ilse", "Jamal", "Kira", "Lars", "Mila", "Nils", "Odette", "Pablo", "Quinn", "Rhea", "Soren", "Talia", "Umar", "Vera", "Wen", "Ximena", "Yara", "Zane", "Anh", "Bruno", "Celine", "Dmitri", "Emre", "Flora", "Goran", "Hoa", "Ivan", "Jade", "Kofi", "Lucia", "Malik", "Noor"];
const LAST = ["Andersen", "Bakker", "Castillo", "Duarte", "Eriksen", "Fontaine", "Grimaldi", "Hoang", "Ivanova", "Jansen", "Kaur", "Lindqvist", "Mendes", "Nakagawa", "Osei", "Peralta", "Quach", "Rahimi", "Sato", "Toivonen", "Ueda", "Varga", "Wojcik", "Yilmaz", "Zamora", "Dinh", "Fuchs", "Navarro", "Okoro", "Romano"];
const TONES: Tone[] = ["amber", "blue", "violet", "rose", "green", "pink", "gray", "teal"];
const STATUS_POOL: LeadStatus[] = ["New Lead", "New Lead", "New Lead", "Call Booked", "Call Booked", "Call Booked", "In Contact", "In Contact", "Qualified", "Qualified", "Won", "Won", "Won", "No Show", "No Show", "Unqualified - No Money", "Unqualified - Ghosted", "Lost - Financials"];
const MINUTES = [2, 5, 12, 15, 30, 45, 60, 90, 120, 180, 300, 480, 720, 1440, 2160, 2880, 4320, 5760, 7200, 8640, 10080, 14400, 20160, 30240];
const PAYMENT_POOL = ["Full Payment", "Full Payment", "$1K Deposit", "$500 Deposit", "$2K Deposit", "Installments: 1/3", "Installments: 2/6", "Installments: 3/4", "Installments: 5/6", "Installments: 1/12"];
function rng(seed: number) {
return () => {
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function generate(count: number, seed: number): typeof SEED {
const rand = rng(seed);
const pick = <T,>(list: readonly T[]) => list[Math.floor(rand() * list.length)];
const team = Object.keys(TEAM) as Array<keyof typeof TEAM>;
return Array.from({ length: count }, () => {
const status = pick(STATUS_POOL);
const interacted = pick(MINUTES);
const added = Math.ceil(interacted / 1440) + Math.floor(rand() * 75);
const won = status === "Won";
const open = !won && !status.startsWith("Unqualified") && !status.startsWith("Lost");
const value = won ? Math.round((800 + rand() * 11_200) / 250) * 250 : open ? null : undefined;
const messages = rand() < 0.22 ? 1 + Math.floor(rand() * 9) : undefined;
return [`${pick(FIRST)} ${pick(LAST)}`, pick(TONES), status, value, pick(team), interacted, won ? pick(PAYMENT_POOL) : "N/A", added, messages];
});
}
const LEADS: Lead[] = [...SEED, ...generate(118, 7)].map(([name, tone, status, value, assignee, interacted, payment, added, messages], i) => ({
id: i + 1,
name,
email: `${name.toLowerCase().replace(/\s+/g, ".")}@mail.com`,
tone,
messages,
status,
value,
assignee,
role: TEAM[assignee].role,
interacted,
payment,
added,
}));
const RANGES = [
{ label: "Last 24 hours", minutes: 1440 },
{ label: "Last 7 days", minutes: 10080 },
{ label: "Last 30 days", minutes: 43200 },
{ label: "All time", minutes: Infinity },
];
const PAYMENTS = ["All Payments", "Full Payment", "Deposit", "Installments", "N/A"];
const DEFAULT_RANGE = "Last 30 days";
const COLS = ["5%", "19%", "17%", "12%", "14%", "11%", "12%", "10%"];
function ago(minutes: number) {
const unit = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"} ago`;
if (minutes < 60) return unit(minutes, "min");
if (minutes < 1440) return unit(Math.round(minutes / 60), "hour");
if (minutes < 10080) return unit(Math.round(minutes / 1440), "day");
if (minutes < 43200) return unit(Math.round(minutes / 10080), "week");
return unit(Math.round(minutes / 43200), "month");
}
function addedAgo(days: number) {
return days === 0 ? "Today" : ago(days * 1440);
}
function sortValue(lead: Lead, key: SortKey) {
if (key === "value") return typeof lead.value === "number" ? lead.value : null;
return lead[key];
}
function HeroLeadsTable({ leads = LEADS }: { leads?: Lead[] }) {
const reduce = useReducedMotion();
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref, { once: true, amount: 0.15 });
const shown = inView || !!reduce;
const [range, setRange] = useState(DEFAULT_RANGE);
const [user, setUser] = useState("All Users");
const [status, setStatus] = useState("Any Status");
const [payment, setPayment] = useState("All Payments");
const [query, setQuery] = useState("");
const [sort, setSort] = useState<Sort>(null);
const [sorted, setSorted] = useState(false);
const [pageSize, setPageSize] = useState(10);
const [page, setPage] = useState(1);
const [selected, setSelected] = useState<Set<number>>(new Set());
const filtered = useMemo(() => {
const max = RANGES.find((r) => r.label === range)?.minutes ?? Infinity;
const q = query.trim().toLowerCase();
const rows = leads.filter(
(l) =>
l.interacted <= max &&
(user === "All Users" || l.assignee === user) &&
(status === "Any Status" || l.status === status) &&
(payment === "All Payments" || l.payment.startsWith(payment) || (payment === "Deposit" && l.payment.endsWith("Deposit"))) &&
(!q || l.name.toLowerCase().includes(q) || l.email.includes(q)),
);
if (!sort) return rows;
const sign = sort.dir === "asc" ? 1 : -1;
return [...rows].sort((a, b) => {
const x = sortValue(a, sort.key);
const y = sortValue(b, sort.key);
if (x === null || y === null) return x === y ? 0 : x === null ? 1 : -1;
return (x - y) * sign;
});
}, [leads, range, user, status, payment, query, sort]);
const pages = Math.max(1, Math.ceil(filtered.length / pageSize));
const current = Math.min(page, pages);
const rows = filtered.slice((current - 1) * pageSize, current * pageSize);
const viewKey = `${range}|${user}|${status}|${payment}|${query}|${pageSize}|${current}`;
const [firstKey] = useState(viewKey);
const intro = viewKey === firstKey && !sorted;
useEffect(() => {
if (reduce || pages <= 1) return;
const id = setInterval(() => {
setPage((p) => (p >= pages ? 1 : p + 1));
}, 30000);
return () => clearInterval(id);
}, [reduce, pages]);
const resetPage = <T,>(set: (v: T) => void) => (v: T) => {
set(v);
setPage(1);
};
const toggleSort = (key: SortKey) => {
setSorted(true);
setSort((s) => (s?.key !== key ? { key, dir: "desc" } : s.dir === "desc" ? { key, dir: "asc" } : null));
};
const allOnPage = rows.length > 0 && rows.every((r) => selected.has(r.id));
const someOnPage = rows.some((r) => selected.has(r.id));
const toggleAll = () =>
setSelected((s) => {
const next = new Set(s);
rows.forEach((r) => (allOnPage ? next.delete(r.id) : next.add(r.id)));
return next;
});
const toggleOne = (id: number) =>
setSelected((s) => {
const next = new Set(s);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
const filtersOn = user !== "All Users" || status !== "Any Status" || payment !== "All Payments" || query !== "" || range !== DEFAULT_RANGE;
const clear = () => {
setRange(DEFAULT_RANGE);
setUser("All Users");
setStatus("Any Status");
setPayment("All Payments");
setQuery("");
setPage(1);
};
const enter = (delay: number) =>
reduce
? {}
: {
initial: { opacity: 0, y: 10, filter: "blur(6px)" },
animate: shown ? { opacity: 1, y: 0, filter: "blur(0px)" } : undefined,
transition: { duration: 0.6, ease: EASE, delay },
};
return (
<div ref={ref} className="w-full text-[#191A23]">
{/* Toolbar */}
<motion.div {...enter(0)} className="relative z-20 mb-4 flex flex-wrap items-center gap-2">
<Dropdown icon={Calendar} value={range} options={RANGES.map((r) => r.label)} onChange={resetPage(setRange)} />
<span aria-hidden className="mx-1 hidden h-5 w-px bg-black/[0.08] sm:block" />
<Dropdown
icon={Users}
value={user}
options={["All Users", ...Object.keys(TEAM)]}
onChange={resetPage(setUser)}
render={(o) => (o === "All Users" ? o : <Person name={o} />)}
/>
<Dropdown
icon={Loader}
value={status}
options={["Any Status", ...(Object.keys(STATUS) as LeadStatus[])]}
onChange={resetPage(setStatus)}
render={(o) => (o === "Any Status" ? o : <StatusLabel status={o as LeadStatus} />)}
/>
<Dropdown icon={CircleDollarSign} value={payment} options={PAYMENTS} onChange={resetPage(setPayment)} />
<AnimatePresence>
{filtersOn && (
<motion.button
type="button"
onClick={clear}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="inline-flex h-8 cursor-pointer items-center gap-1 rounded-lg px-2.5 text-xs font-semibold text-[#6E7180] transition-colors hover:bg-[#FFF0F5] hover:text-[#FF5484]"
>
<X className="size-3.5" /> Reset
</motion.button>
)}
</AnimatePresence>
<label className="relative ml-auto w-full sm:w-64">
<span className="sr-only">Search leads</span>
<Search className="pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-[#6E7180]" />
<input
value={query}
onChange={(e) => resetPage(setQuery)(e.target.value)}
placeholder="Search leads by name, email..."
className="h-8 w-full rounded-lg border border-black/[0.08] bg-[#FAF8FF]/60 pr-3 pl-8 text-xs text-[#191A23] outline-none transition-all placeholder:text-[#6E7180] focus:border-[#6842FF]/40 focus:bg-white focus:ring-2 focus:ring-[#6842FF]/15"
/>
</label>
</motion.div>
{/* Table */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 24, scale: 0.985, clipPath: "inset(0 0 100% 0 round 16px)" }}
animate={shown ? { opacity: 1, y: 0, scale: 1, clipPath: "inset(0 0 0% 0 round 16px)" } : undefined}
transition={{ duration: 0.9, ease: EASE, delay: 0.1 }}
className="overflow-hidden rounded-2xl border border-black/[0.07] bg-white shadow-[0_1px_2px_rgba(0,0,0,0.03),0_8px_24px_-12px_rgba(104,66,255,0.08)]"
>
<div className="overflow-hidden">
<table className="w-full table-fixed border-collapse text-[13px]">
<colgroup>
{COLS.map((w, i) => (
<col key={i} style={{ width: w }} />
))}
</colgroup>
<thead>
<tr className="bg-[#FAF8FF] text-left text-xs font-semibold text-[#6E7180]">
<Th className="pl-4">
<Checkbox checked={allOnPage} mixed={!allOnPage && someOnPage} onChange={toggleAll} label="Select all rows on this page" />
</Th>
<Th>Lead name</Th>
<Th>Status</Th>
<SortTh label="Deal Value" k="value" sort={sort} onSort={toggleSort} />
<Th>Assigned To</Th>
<SortTh label="Interacted" k="interacted" sort={sort} onSort={toggleSort} />
<Th>Payment Status</Th>
<SortTh label="Date Added" k="added" sort={sort} onSort={toggleSort} />
</tr>
</thead>
<tbody key={viewKey}>
{rows.map((lead, i) => (
<motion.tr
key={lead.id}
layout={reduce ? false : "position"}
initial={reduce ? false : { opacity: 0, x: -14, filter: "blur(4px)" }}
animate={shown ? { opacity: 1, x: 0, filter: "blur(0px)" } : undefined}
transition={{
duration: 0.5,
ease: EASE,
delay: intro ? 0.35 + i * 0.04 : i * 0.025,
layout: { duration: 0.45, ease: EASE },
}}
data-selected={selected.has(lead.id) || undefined}
className="group border-t border-black/[0.06] transition-colors hover:bg-[#FAF8FF]/80 data-[selected]:bg-[#F3F0FF]/70"
>
<Td className="pl-4">
<Checkbox checked={selected.has(lead.id)} onChange={() => toggleOne(lead.id)} label={`Select ${lead.name}`} />
</Td>
<Td>
<span className="flex items-center gap-2.5">
<span className={cn("size-4 shrink-0 rounded-full", DOT[lead.tone])} />
<span className="font-semibold text-[#191A23]">{lead.name}</span>
{lead.messages ? (
<span className="inline-flex items-center gap-0.5 rounded-full bg-[#FF5484] px-1.5 py-px text-[10px] font-bold text-white">
<MessageCircle className="size-2.5 fill-current" />
{lead.messages}
</span>
) : null}
</span>
</Td>
<Td>
<StatusLabel status={lead.status} />
</Td>
<Td>
<DealValue value={lead.value} shown={shown} count={intro && !reduce} />
</Td>
<Td>
<span className="flex items-center justify-between gap-3 pr-2">
<Person name={lead.assignee} />
<span className="font-mono text-[11px] font-semibold text-[#6E7180]">[{lead.role}]</span>
</span>
</Td>
<Td className="text-[#6E7180]">{ago(lead.interacted)}</Td>
<Td className={cn(lead.payment === "N/A" ? "text-[#6E7180]" : "font-medium text-[#191A23]")}>{lead.payment}</Td>
<Td className="text-[#6E7180]">{addedAgo(lead.added)}</Td>
</motion.tr>
))}
{rows.length === 0 && (
<tr className="border-t border-black/[0.06]">
<td colSpan={8} className="py-14 text-center">
<p className="text-sm font-bold text-[#191A23]">No leads match these filters</p>
<button
type="button"
onClick={clear}
className="mt-2 cursor-pointer text-xs font-semibold text-[#6842FF] underline underline-offset-4 hover:text-[#FF5484]"
>
Clear filters
</button>
</td>
</tr>
)}
</tbody>
</table>
</div>
</motion.div>
</div>
);
}
function Th({ children, className }: { children?: ReactNode; className?: string }) {
return <th className={cn("h-10 border-r border-black/[0.06] px-3 font-semibold last:border-r-0", className)}>{children}</th>;
}
function Td({ children, className }: { children?: ReactNode; className?: string }) {
return <td className={cn("h-11 truncate border-r border-black/[0.06] px-3 whitespace-nowrap last:border-r-0", className)}>{children}</td>;
}
function SortTh({ label, k, sort, onSort }: { label: string; k: SortKey; sort: Sort; onSort: (k: SortKey) => void }) {
const active = sort?.key === k ? sort.dir : null;
const Icon = active === "asc" ? ArrowUp : active === "desc" ? ArrowDown : ArrowUpDown;
return (
<th aria-sort={active === "asc" ? "ascending" : active === "desc" ? "descending" : "none"} className="h-10 border-r border-black/[0.06] p-0 font-semibold last:border-r-0">
<button
type="button"
onClick={() => onSort(k)}
className={cn(
"flex h-10 w-full cursor-pointer items-center justify-between gap-2 px-3 text-left transition-colors hover:bg-[#F3F0FF]/60 hover:text-[#191A23]",
active && "text-[#6842FF]",
)}
>
{label}
<AnimatePresence mode="wait" initial={false}>
<motion.span
key={active ?? "none"}
initial={{ opacity: 0, y: active === "asc" ? 4 : -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: active === "asc" ? -4 : 4 }}
transition={{ duration: 0.15 }}
>
<Icon className={cn("size-3.5", !active && "opacity-60")} />
</motion.span>
</AnimatePresence>
</button>
</th>
);
}
function StatusLabel({ status }: { status: LeadStatus }) {
const { icon: Icon, className } = STATUS[status];
return (
<span className="flex items-center gap-2 font-medium text-[#191A23]">
<Icon className={cn("size-4 shrink-0", className)} />
{status}
</span>
);
}
function Person({ name }: { name: string }) {
return (
<span className="flex items-center gap-2 font-medium text-[#191A23]">
<span className={cn("size-4 shrink-0 rounded-full", DOT[TEAM[name]?.tone ?? "gray"])} />
{name}
</span>
);
}
function DealValue({ value, shown, count }: { value: Lead["value"]; shown: boolean; count: boolean }) {
const [n, setN] = useState(count && typeof value === "number" ? 0 : (value ?? 0));
useEffect(() => {
if (!count || !shown || typeof value !== "number") return;
const controls = animate(0, value, { duration: 1.1, ease: EASE, delay: 0.45, onUpdate: setN });
return () => controls.stop();
}, [value, shown, count]);
if (value === undefined) return <span className="text-[#6E7180]">N/A</span>;
if (value === null)
return (
<span className="flex items-center justify-between text-[#6E7180]">
Pending... <Minus className="size-3" />
</span>
);
return (
<span className="flex items-center justify-between font-semibold text-[#191A23] tabular-nums">
<span className="text-[#6E7180]">$</span>
{n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</span>
);
}
function Checkbox({ checked, mixed, onChange, label }: { checked: boolean; mixed?: boolean; onChange: () => void; label: string }) {
return (
<button
type="button"
role="checkbox"
aria-checked={mixed ? "mixed" : checked}
aria-label={label}
onClick={onChange}
className={cn(
"grid size-4 cursor-pointer place-items-center rounded-[4px] border border-black/[0.15] bg-white transition-colors focus-visible:ring-2 focus-visible:ring-[#6842FF]/30 focus-visible:outline-none",
(checked || mixed) && "border-[#6842FF] bg-[#6842FF] text-white",
)}
>
<AnimatePresence initial={false}>
{(checked || mixed) && (
<motion.span initial={{ scale: 0.4, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} exit={{ scale: 0.4, opacity: 0 }} transition={{ duration: 0.15 }}>
{mixed ? <Minus className="size-3" strokeWidth={3} /> : <Check className="size-3" strokeWidth={3} />}
</motion.span>
)}
</AnimatePresence>
</button>
);
}
function Dropdown({
icon: Icon,
value,
options,
onChange,
render = (o) => o,
up,
}: {
icon?: ComponentType<{ className?: string }>;
value: string;
options: string[];
onChange: (v: string) => void;
render?: (o: string) => ReactNode;
up?: boolean;
}) {
const [open, setOpen] = useState(false);
const [active, setActive] = useState(0);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const close = (e: PointerEvent) => !ref.current?.contains(e.target as Node) && setOpen(false);
document.addEventListener("pointerdown", close);
return () => document.removeEventListener("pointerdown", close);
}, [open]);
const show = () => {
setActive(Math.max(0, options.indexOf(value)));
setOpen(true);
};
const pick = (o: string) => {
onChange(o);
setOpen(false);
};
return (
<div
ref={ref}
className="relative"
onKeyDown={(e) => {
if (e.key === "Escape") setOpen(false);
if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
e.preventDefault();
show();
} else if (open && e.key === "ArrowDown") {
e.preventDefault();
setActive((a) => (a + 1) % options.length);
} else if (open && e.key === "ArrowUp") {
e.preventDefault();
setActive((a) => (a - 1 + options.length) % options.length);
} else if (open && e.key === "Enter") {
e.preventDefault();
pick(options[active]);
}
}}
>
<button
type="button"
aria-haspopup="listbox"
aria-expanded={open}
onClick={() => (open ? setOpen(false) : show())}
className={cn(
"inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-lg border border-black/[0.08] bg-white px-2.5 text-xs font-semibold text-[#191A23] shadow-2xs transition-colors hover:bg-[#FAF8FF]",
open && "border-[#6842FF]/30 bg-[#FAF8FF]",
)}
>
{Icon && <Icon className="size-3.5 text-[#6842FF]" />}
{value}
<motion.span animate={{ rotate: open ? 180 : 0 }} transition={{ duration: 0.2 }}>
<ChevronDown className="size-3.5 text-[#6E7180]" />
</motion.span>
</button>
<AnimatePresence>
{open && (
<motion.ul
role="listbox"
initial={{ opacity: 0, y: up ? 6 : -6, scale: 0.97 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: up ? 4 : -4, scale: 0.98 }}
transition={{ duration: 0.16, ease: EASE }}
className={cn(
"absolute left-0 z-30 min-w-full overflow-hidden rounded-xl border border-black/[0.08] bg-white p-1 text-xs text-[#191A23] shadow-xl",
up ? "bottom-full mb-1 origin-bottom" : "top-full mt-1 origin-top",
)}
>
{options.map((o, i) => (
<li key={o} role="option" aria-selected={o === value}>
<button
type="button"
onMouseEnter={() => setActive(i)}
onClick={() => pick(o)}
className={cn(
"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2.5 py-1.5 text-left font-medium whitespace-nowrap",
i === active && "bg-[#F3F0FF] text-[#6842FF]",
)}
>
<span className="flex-1">{render(o)}</span>
<Check className={cn("size-3.5 text-[#6842FF]", o === value ? "opacity-100" : "opacity-0")} />
</button>
</li>
))}
</motion.ul>
)}
</AnimatePresence>
</div>
);
}
/* -------------------------------------------------------------------------- */
/* HERO SECTION + LEADS TABLE WORKSPACE & FLOATING SIDE CARDS */
/* -------------------------------------------------------------------------- */
const HERO_WORDS = [
{ text: "living motion.", bg: "#6842FF", shadow: "rgba(104,66,255,0.38)", angle: -1.8 },
{ text: "remote teams.", bg: "#FF5484", shadow: "rgba(255,84,132,0.38)", angle: 1.5 },
{ text: "fast startups.", bg: "#FFB81C", shadow: "rgba(255,184,28,0.42)", angle: -1.2 },
];
function HeroSection({ onNavigate }: { onNavigate: (id: string) => void }) {
const reduce = useReducedMotion();
const [wordIdx, setWordIdx] = useState(0);
const [sprintPct, setSprintPct] = useState(56);
useEffect(() => {
if (reduce) return;
const id = setInterval(() => {
setWordIdx((prev) => (prev + 1) % HERO_WORDS.length);
}, 3400);
return () => clearInterval(id);
}, [reduce]);
const activeWord = HERO_WORDS[wordIdx];
const triggerQuickAction = (_msg: string, _newGauge: number, newSprint: number) => {
setSprintPct(newSprint);
};
return (
<section
data-section="top"
className="relative overflow-hidden pt-10 pb-16 sm:pt-14"
>
{/* Subtle Pastel Radial Backdrop */}
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-8 mx-auto h-[540px] max-w-5xl rounded-full bg-gradient-to-b from-[#FFF0F5]/80 via-[#F3F0FF]/60 to-transparent blur-3xl"
/>
{/* Playful Floating Stickers & Cursor Callouts in Signature Palette */}
<FloatingSticker className="left-[8%] top-12 hidden lg:block" delay={0.1} distance={9}>
<div className="flex items-center gap-2 rounded-2xl border border-[#FF5484]/15 bg-white px-3.5 py-2 shadow-[0_12px_28px_-8px_rgba(255,84,132,0.18)] -rotate-6">
<span className="flex size-6 items-center justify-center rounded-lg bg-[#FFF0F5] text-xs">
🎨
</span>
<span className="text-[11px] font-bold text-[#191A23]">Zero-code shaders</span>
</div>
</FloatingSticker>
<FloatingSticker className="right-[8%] top-14 hidden lg:block" delay={0.6} distance={8}>
<div className="flex items-center gap-2 rounded-2xl border border-[#6842FF]/15 bg-white px-3.5 py-2 shadow-[0_12px_28px_-8px_rgba(104,66,255,0.18)] rotate-6">
<span className="size-2 rounded-full bg-[#FF5484] animate-ping" />
<span className="text-[11px] font-bold text-[#191A23]">60 FPS Spring Engine</span>
</div>
</FloatingSticker>
<FloatingSticker className="left-[13%] top-48 hidden lg:block" delay={0.3} distance={7}>
<CursorBadge name="Maya" bg="#FF5484" textColor="#FFFFFF" pointerSide="right" />
</FloatingSticker>
<FloatingSticker className="right-[14%] top-52 hidden lg:block" delay={0.8} distance={8}>
<CursorBadge name="Leo" bg="#6842FF" textColor="#FFFFFF" pointerSide="left" />
</FloatingSticker>
<div className="relative mx-auto max-w-6xl px-4 text-center sm:px-8">
{/* Top Signature Pink Pill Badge */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: EASE }}
className="inline-flex items-center gap-2.5 rounded-full border border-[#FF5484]/15 bg-[#FFF0F5] px-4 py-1.5 text-xs font-bold text-[#FF5484]"
>
<Wand2 className="size-3.5" />
<span>TaskHub 2.0 · Team & Motion Workspace</span>
</motion.div>
{/* Main Kinetic Headline */}
<motion.h1
initial={reduce ? false : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.06, ease: EASE }}
className="mt-6 text-4xl font-extrabold leading-[1.14] tracking-[-0.03em] text-[#191A23] sm:text-6xl md:text-[64px]"
>
Turn wild sparks
<br />
<span>into </span>
<span className="relative inline-block">
<AnimatePresence mode="wait">
<motion.button
key={activeWord.text}
type="button"
onClick={() => setWordIdx((prev) => (prev + 1) % HERO_WORDS.length)}
initial={reduce ? false : { opacity: 0, y: 18, rotate: -6, scale: 0.92 }}
animate={{ opacity: 1, y: 0, rotate: activeWord.angle, scale: 1 }}
exit={{ opacity: 0, y: -16, rotate: 6, scale: 0.92 }}
transition={SPRING}
style={{
backgroundColor: activeWord.bg,
boxShadow: `0 16px 34px -10px ${activeWord.shadow}`,
}}
className="inline-block cursor-pointer rounded-2xl px-4 py-1 text-white"
title="Click to switch focus"
>
{activeWord.text}
</motion.button>
</AnimatePresence>
</span>
</motion.h1>
{/* Subtitle */}
<motion.p
initial={reduce ? false : { opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.14, ease: EASE }}
className="mx-auto mt-6 max-w-lg text-xs leading-relaxed text-[#6E7180] sm:text-sm"
>
Sketch vector worlds, choreograph tactile spring physics, and jam with your crew
on a single sunlit canvas—without touching a complex timeline.
</motion.p>
{/* Dual CTA Buttons in Signature Palette */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.22, ease: EASE }}
className="mt-8 flex flex-wrap items-center justify-center gap-5"
>
<button
type="button"
onClick={() => onNavigate("workspace")}
className="group inline-flex cursor-pointer items-center gap-2.5 rounded-full bg-[#191A23] px-7 py-3.5 text-xs font-bold text-white shadow-md transition-all hover:scale-105 hover:bg-[#6842FF] active:scale-95"
>
<span>Explore Workspace</span>
<Sparkles className="size-3.5 transition-transform group-hover:rotate-45" />
</button>
<button
type="button"
onClick={() => {
setWordIdx((prev) => (prev + 1) % HERO_WORDS.length);
triggerQuickAction("Remixed workspace theme & metrics!", 92, 74);
}}
className="group relative flex cursor-pointer items-center gap-3 text-xs font-bold text-[#191A23]"
>
<span className="relative flex size-10 items-center justify-center">
<span className="absolute inset-0 scale-150 rounded-full bg-[#FF5484]/25 blur-md transition-transform group-hover:scale-175" />
<span className="relative flex size-8 items-center justify-center rounded-full bg-white text-[#FF5484] shadow-sm ring-1 ring-[#FF5484]/20 transition-transform group-hover:scale-110">
<Play className="size-3.5 fill-current ml-0.5" />
</span>
</span>
<span>Watch Live Demo</span>
</button>
</motion.div>
{/* ==================================================================== */}
{/* INTERACTIVE HERO WORKSPACE DASHBOARD + FLOATING SIDE CARDS */}
{/* ==================================================================== */}
<div id="workspace" data-section="workspace" className="relative mt-12 sm:mt-16">
{/* Hand-drawn Callout Annotations above the Workspace Frame (appear after table finishes unfolding) */}
<div
aria-hidden
className="pointer-events-none mx-auto mb-3 hidden max-w-4xl items-end justify-between px-6 md:flex"
>
<motion.div
initial={reduce ? false : { opacity: 0, y: 10, scale: 0.92 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 1.25, ease: EASE }}
className="flex items-center gap-2 -rotate-6"
>
<span className="text-xs font-extrabold leading-tight text-[#191A23]">
Quick Action
<br />
Buttons
</span>
<svg width="48" height="28" viewBox="0 0 48 28" fill="none">
<motion.path
d="M2 6C18 2 34 8 42 24M42 24L35 21M42 24L45 17"
stroke="#191A23"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
initial={reduce ? false : { pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 0.55, delay: 1.35, ease: EASE }}
/>
</svg>
</motion.div>
<motion.div
initial={reduce ? false : { opacity: 0, y: 10, scale: 0.92 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 1.4, ease: EASE }}
className="flex items-center gap-2 rotate-6"
>
<svg width="48" height="28" viewBox="0 0 48 28" fill="none">
<motion.path
d="M46 22C32 6 18 4 6 16M6 16L13 16M6 16L7 9"
stroke="#191A23"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
initial={reduce ? false : { pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 0.55, delay: 1.5, ease: EASE }}
/>
</svg>
<span className="text-xs font-extrabold leading-tight text-[#191A23]">
Clean and
<br />
Minimal Layout
</span>
</motion.div>
</div>
{/* FLOATING LEFT PEEK CARD */}
<motion.div
animate={reduce ? undefined : { y: [0, -8, 0] }}
transition={{ duration: 5, repeat: Infinity, ease: "easeInOut" }}
className="pointer-events-none absolute -left-10 bottom-10 z-20 hidden w-60 rounded-3xl border border-[#FF5484]/15 bg-white/95 p-5 text-left shadow-[0_24px_48px_-12px_rgba(255,84,132,0.16)] backdrop-blur-md xl:block"
>
<div className="flex items-start justify-between">
<div>
<div className="text-xs font-extrabold text-[#191A23]">Refresh the web design</div>
<p className="mt-1 text-[10px] leading-relaxed text-[#6E7180]">
Reflect the latest motion tokens and spring curves.
</p>
</div>
<span className="text-xl font-extrabold text-[#FF5484]">{sprintPct}%</span>
</div>
<div className="mt-3 h-2 w-full overflow-hidden rounded-full bg-[#FFF0F5]">
<motion.div
animate={{ width: `${sprintPct}%` }}
className="h-full rounded-full bg-[#FF5484]"
/>
</div>
<div className="mt-4 flex items-center justify-between">
<div className="flex gap-1.5">
<span className="rounded-full bg-[#FFF0F5] px-2.5 py-0.5 text-[9px] font-extrabold text-[#FF5484]">
UI/UX
</span>
<span className="rounded-full bg-[#FFF9EB] px-2.5 py-0.5 text-[9px] font-extrabold text-[#D97706]">
Motion
</span>
</div>
<div className="flex -space-x-1.5">
<AvatarBubble emoji="👩🏻" bg="#FBCFE8" size="xs" />
<AvatarBubble emoji="🧑🏽" bg="#FDE68A" size="xs" />
<AvatarBubble emoji="🧔🏼" bg="#DDD6FE" size="xs" />
</div>
</div>
</motion.div>
{/* FLOATING RIGHT PEEK CARD */}
<motion.div
animate={reduce ? undefined : { y: [0, 8, 0] }}
transition={{ duration: 5.5, repeat: Infinity, ease: "easeInOut", delay: 0.5 }}
className="pointer-events-none absolute -right-10 bottom-14 z-20 hidden w-60 rounded-3xl border border-[#6842FF]/15 bg-white/95 p-5 text-left shadow-[0_24px_48px_-12px_rgba(104,66,255,0.16)] backdrop-blur-md xl:block"
>
<div className="text-xs font-extrabold text-[#191A23]">Brand Motion System</div>
<div className="text-[10px] text-[#6E7180]">Studio: TaskHub Lab</div>
<div className="mt-3 flex items-center justify-between">
<div>
<div className="text-[10px] font-bold text-[#6E7180]">Team Members</div>
<div className="mt-1 flex items-center -space-x-1.5">
<AvatarBubble emoji="🧑🏻" bg="#DDD6FE" size="xs" />
<AvatarBubble emoji="👩🏽" bg="#FDE68A" size="xs" />
<AvatarBubble emoji="🧔🏻" bg="#FBCFE8" size="xs" />
<span className="inline-flex size-6 items-center justify-center rounded-full border-2 border-white bg-[#6842FF] text-[10px] font-bold text-white">
+
</span>
</div>
</div>
<div className="text-right">
<div className="text-xl font-extrabold text-[#6842FF]">84%</div>
<div className="mt-1 h-1.5 w-14 overflow-hidden rounded-full bg-[#F3F0FF]">
<div className="h-full w-[84%] rounded-full bg-[#6842FF]" />
</div>
</div>
</div>
</motion.div>
{/* MAIN WORKSPACE APPLICATION SHELL (LEADS TABLE COMPONENT) */}
<motion.div
initial={reduce ? false : { opacity: 0, y: 28 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.85, delay: 0.25, ease: EASE }}
className="mx-auto max-w-5xl rounded-[32px] border border-[#6842FF]/10 bg-white p-4 sm:p-7 text-left shadow-[0_32px_80px_-24px_rgba(104,66,255,0.14)]"
>
<HeroLeadsTable />
</motion.div>
</div>
{/* ==================================================================== */}
{/* TRUSTED BY WORLD'S INNOVATIVE TEAMS STRIP */}
{/* ==================================================================== */}
<div className="mt-16 pt-4">
<p className="text-xs font-bold text-[#76798A] sm:text-sm">
Trusted by creative teams & fast-moving studios worldwide
</p>
<div className="mt-7 flex flex-wrap items-center justify-center gap-8 sm:gap-14">
{[
{ name: "Netdot", color: "#6842FF", glyph: "N" },
{ name: "Sparkweb", color: "#FF5484", glyph: "S" },
{ name: "Webgear", color: "#FFB81C", glyph: "W" },
{ name: "Digitech", color: "#6842FF", glyph: "D" },
{ name: "Codelink", color: "#FF5484", glyph: "C" },
].map((brand) => (
<div
key={brand.name}
className="flex items-center gap-2.5 opacity-85 transition-opacity hover:opacity-100"
>
<span
className="grid size-6 place-items-center rounded-lg text-xs font-black text-white"
style={{ backgroundColor: brand.color }}
>
{brand.glyph}
</span>
<span className="text-sm font-extrabold tracking-tight text-[#191A23] sm:text-base">
{brand.name}
</span>
</div>
))}
</div>
</div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* SECTION 2: "WHAT MAKES US DIFFERENT" (FROM SAMPLE IMAGE 3) */
/* -------------------------------------------------------------------------- */
const DATES = [
{ label: "Thu, November 5, 2026", pct: 56 },
{ label: "Fri, November 6, 2026", pct: 72 },
{ label: "Mon, November 9, 2026", pct: 89 },
];
const COLLAB_MEMBERS = [
{ name: "Darrell Steward", role: "Design Lead", emoji: "🧑🏻", bg: "#E0E7FF", tilted: false },
{ name: "Maya Lin", role: "Motion Architect", emoji: "👩🏽", bg: "#FCE7F3", tilted: true },
{ name: "Liam Vance", role: "Product Engineer", emoji: "🧔🏼", bg: "#FEF3C7", tilted: false },
];
function WhatMakesUsDifferentSection() {
const [dateIdx, setDateIdx] = useState(0);
const [todayTasks, setTodayTasks] = useState(15);
const [inProgressTasks, setInProgressTasks] = useState(23);
const [teamPct, setTeamPct] = useState(84);
const [pingedMember, setPingedMember] = useState<string | null>(null);
const activeDate = DATES[dateIdx];
const handleAddPriorityTask = () => {
setTodayTasks((t) => t + 1);
setInProgressTasks((p) => p + 1);
setTeamPct((pct) => (pct >= 98 ? 84 : pct + 2));
};
const handlePing = (name: string, action: string) => {
setPingedMember(`${action} ${name}...`);
setTimeout(() => setPingedMember(null), 1800);
};
return (
<section
id="different"
data-section="different"
className="relative mx-auto max-w-6xl px-4 py-20 sm:px-8 sm:py-24"
>
{/* Section Heading with Tilted Signature Pink/Amber Sticker */}
<div className="text-center">
<div className="relative inline-block">
<motion.span
whileHover={{ rotate: -6, scale: 1.06 }}
transition={SPRING}
className="absolute -top-6 left-[52%] -translate-x-1/2 -rotate-[12deg] rounded-full bg-[#FFB81C] px-3.5 py-1 text-[11px] font-extrabold text-white shadow-xs"
>
Why choose us
</motion.span>
<h2 className="text-3xl font-extrabold tracking-tight text-[#191A23] sm:text-5xl">
What makes us different
</h2>
</div>
<p className="mx-auto mt-4 max-w-md text-xs leading-relaxed text-[#6E7180] sm:text-sm">
Simplify project planning, streamline collaboration, and boost productivity
all with TaskHub task & motion management.
</p>
</div>
{/* 3 Signature Pastel Bento Cards (Pink / Amber / Purple) */}
<div className="mt-14 grid gap-6 lg:grid-cols-3">
{/* CARD 1: Activity tracking & insights (Soft Rose Pink Tint) */}
<motion.div
whileHover={{ y: -5 }}
transition={SPRING}
className="relative flex flex-col justify-between overflow-hidden rounded-[28px] bg-[#FFF0F5]/85 p-7"
style={{
backgroundImage: "radial-gradient(rgba(255,84,132,0.12) 1px, transparent 1px)",
backgroundSize: "14px 14px",
}}
>
<div>
<div className="grid size-12 place-items-center rounded-2xl bg-[#FF5484] text-white shadow-[0_10px_20px_-6px_rgba(255,84,132,0.45)]">
<Clock3 className="size-5" />
</div>
<h3 className="mt-6 text-lg font-extrabold text-[#191A23]">
Activity tracking & insights
</h3>
<p className="mt-2 text-xs leading-relaxed text-[#6E7180]">
Built to work seamlessly across devices, ensuring your creative crew stays
connected and informed.
</p>
</div>
<div className="mt-7 space-y-3.5">
{/* Crisp White Inner Card */}
<div className="rounded-2xl bg-white p-5 shadow-[0_14px_30px_-12px_rgba(255,84,132,0.12)]">
<div className="flex items-start justify-between gap-2">
<div>
<div className="text-xs font-extrabold text-[#191A23]">User Research</div>
<p className="mt-1 text-[10px] leading-relaxed text-[#6E7180]">
Ensure all marketing strategies and promotional materials...
</p>
</div>
<div className="text-right">
<span className="text-2xl font-extrabold text-[#FF5484] tabular-nums">
{activeDate.pct}%
</span>
<div className="mt-1.5 h-1.5 w-14 overflow-hidden rounded-full bg-[#FFF0F5]">
<motion.div
animate={{ width: `${activeDate.pct}%` }}
className="h-full rounded-full bg-[#FF5484]"
/>
</div>
</div>
</div>
<div className="mt-5 flex items-center justify-between">
<span className="rounded-full bg-[#FFF0F5] px-3 py-1 text-[10px] font-extrabold text-[#FF5484]">
Web UI
</span>
<div className="flex -space-x-1.5">
<AvatarBubble emoji="👩🏻" bg="#FBCFE8" size="xs" />
<AvatarBubble emoji="🧔🏽" bg="#FDE68A" size="xs" />
<AvatarBubble emoji="🧑🏼" bg="#DDD6FE" size="xs" />
</div>
</div>
</div>
{/* Tilted Interactive Date Switcher Pill */}
<motion.div
whileHover={{ rotate: 0, scale: 1.02 }}
className="flex -rotate-[2deg] items-center justify-between rounded-2xl bg-white px-4 py-2.5 shadow-[0_12px_24px_-10px_rgba(255,84,132,0.14)]"
>
<span className="text-[11px] font-bold text-[#6E7180]">
{activeDate.label}
</span>
<div className="flex items-center gap-1.5">
<button
type="button"
aria-label="Previous day"
onClick={() => setDateIdx((i) => (i - 1 + DATES.length) % DATES.length)}
className="grid size-6 cursor-pointer place-items-center rounded-lg text-[#6E7180] hover:bg-[#FFF0F5] hover:text-[#FF5484]"
>
<ChevronLeft className="size-3.5" />
</button>
<button
type="button"
aria-label="Next day"
onClick={() => setDateIdx((i) => (i + 1) % DATES.length)}
className="grid size-6 cursor-pointer place-items-center rounded-lg text-[#6E7180] hover:bg-[#FFF0F5] hover:text-[#FF5484]"
>
<ChevronRight className="size-3.5" />
</button>
<span className="grid size-7 place-items-center rounded-lg bg-[#FF5484] text-white">
<Calendar className="size-3.5" />
</span>
</div>
</motion.div>
</div>
</motion.div>
{/* CARD 2: Task prioritization (Soft Sunny Amber Tint) */}
<motion.div
whileHover={{ y: -5 }}
transition={SPRING}
className="relative flex flex-col justify-between overflow-hidden rounded-[28px] bg-[#FFF9EB] p-7"
style={{
backgroundImage: "radial-gradient(rgba(255,184,28,0.16) 1px, transparent 1px)",
backgroundSize: "14px 14px",
}}
>
<div>
<div className="grid size-12 place-items-center rounded-2xl bg-[#FFB81C] text-white shadow-[0_10px_20px_-6px_rgba(255,184,28,0.5)]">
<FolderKanban className="size-5" />
</div>
<h3 className="mt-6 text-lg font-extrabold text-[#191A23]">
Task prioritization
</h3>
<p className="mt-2 text-xs leading-relaxed text-[#6E7180]">
Organize your tasks by priority levels to make sure your team focuses on
what matters most.
</p>
</div>
{/* Layered White Card with Sub-boxes */}
<div className="relative mt-7">
<div className="mx-3 h-3 rounded-t-2xl bg-white/60" />
<div className="rounded-2xl bg-white p-5 shadow-[0_14px_30px_-12px_rgba(255,184,28,0.14)]">
<div className="flex items-center justify-between">
<div>
<div className="text-xs font-extrabold text-[#191A23]">Team Members</div>
<div className="mt-2 flex items-center -space-x-1.5">
<AvatarBubble emoji="🧑🏻" bg="#DDD6FE" size="xs" />
<AvatarBubble emoji="👩🏽" bg="#FDE68A" size="xs" />
<AvatarBubble emoji="🧔🏻" bg="#FBCFE8" size="xs" />
<AvatarBubble emoji="👩🏼" bg="#F3E8FF" size="xs" />
<button
type="button"
onClick={handleAddPriorityTask}
title="Click to assign priority task"
className="inline-flex size-6 cursor-pointer items-center justify-center rounded-full border-2 border-white bg-[#FFB81C] text-xs font-bold text-white transition-transform hover:scale-110 active:scale-95"
>
+
</button>
</div>
</div>
<div className="text-right">
<span className="text-2xl font-extrabold text-[#191A23] tabular-nums">
{teamPct}%
</span>
<div className="mt-1.5 h-1.5 w-16 overflow-hidden rounded-full bg-[#FFF9EB]">
<motion.div
animate={{ width: `${teamPct}%` }}
className="h-full rounded-full bg-[#FFB81C]"
/>
</div>
</div>
</div>
<div className="mt-5 grid grid-cols-2 gap-3">
<div className="rounded-xl bg-[#FFF9EB]/70 p-3">
<div className="flex items-center gap-1.5 text-[10px] font-semibold text-[#6E7180]">
<Zap className="size-3 text-[#FFB81C]" />
<span>Task Today</span>
</div>
<div className="mt-2 flex items-baseline gap-1">
<span className="text-xl font-extrabold text-[#191A23] tabular-nums">
{todayTasks}
</span>
<span className="text-[10px] font-semibold text-[#6E7180]">Tasks</span>
</div>
</div>
<div className="rounded-xl bg-[#F3F0FF]/70 p-3">
<div className="flex items-center gap-1.5 text-[10px] font-semibold text-[#6E7180]">
<Sparkles className="size-3 text-[#6842FF]" />
<span>In Progress</span>
</div>
<div className="mt-2 flex items-baseline gap-1">
<span className="text-xl font-extrabold text-[#191A23] tabular-nums">
{inProgressTasks}
</span>
<span className="text-[10px] font-semibold text-[#6E7180]">Tasks</span>
</div>
</div>
</div>
</div>
</div>
</motion.div>
{/* CARD 3: Team collaboration (Soft Iris Purple Tint) */}
<motion.div
whileHover={{ y: -5 }}
transition={SPRING}
className="relative flex flex-col justify-between overflow-hidden rounded-[28px] bg-[#F3F0FF]/90 p-7"
style={{
backgroundImage: "radial-gradient(rgba(104,66,255,0.12) 1px, transparent 1px)",
backgroundSize: "14px 14px",
}}
>
<div>
<div className="grid size-12 place-items-center rounded-2xl bg-[#6842FF] text-white shadow-[0_10px_20px_-6px_rgba(104,66,255,0.45)]">
<MessageCircle className="size-5" />
</div>
<div className="mt-6 flex items-center justify-between">
<h3 className="text-lg font-extrabold text-[#191A23]">
Team collaboration
</h3>
{pingedMember && (
<span className="rounded-full bg-[#6842FF]/15 px-2.5 py-0.5 text-[10px] font-bold text-[#6842FF]">
{pingedMember}
</span>
)}
</div>
<p className="mt-2 text-xs leading-relaxed text-[#6E7180]">
Collaborate effortlessly with your team, no matter where they are in
real-time.
</p>
</div>
{/* 3 Stacked & Tilted Teammate Contact Pills */}
<div className="mt-7 space-y-3">
{COLLAB_MEMBERS.map((m) => (
<motion.div
key={m.name}
whileHover={{ rotate: 0, scale: 1.03 }}
transition={SPRING}
className={cn(
"flex items-center justify-between rounded-2xl bg-white px-4 py-3 shadow-[0_12px_26px_-10px_rgba(104,66,255,0.12)]",
m.tilted && "rotate-[3.5deg] shadow-[0_18px_34px_-10px_rgba(104,66,255,0.2)]"
)}
>
<div className="flex items-center gap-3">
<AvatarBubble emoji={m.emoji} bg={m.bg} size="sm" />
<div>
<div className="text-xs font-extrabold text-[#191A23]">{m.name}</div>
<div className="text-[10px] text-[#6E7180]">{m.role}</div>
</div>
</div>
<div className="flex items-center gap-1.5">
<button
type="button"
aria-label={`Message ${m.name}`}
onClick={() => handlePing(m.name.split(" ")[0], "Messaged")}
className="grid size-7 cursor-pointer place-items-center rounded-full bg-[#FFF0F5] text-[#FF5484] transition-transform hover:scale-110 active:scale-95"
>
<MessageCircle className="size-3.5" />
</button>
<button
type="button"
aria-label={`Call ${m.name}`}
onClick={() => handlePing(m.name.split(" ")[0], "Calling")}
className="grid size-7 cursor-pointer place-items-center rounded-full bg-[#F3F0FF] text-[#6842FF] transition-transform hover:scale-110 active:scale-95"
>
<Phone className="size-3.5" />
</button>
</div>
</motion.div>
))}
</div>
</motion.div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* SECTION 3: CUSTOMIZED NEXTUI.DEV COMPONENTS (CHECKLIST & CONVEYOR) */
/* -------------------------------------------------------------------------- */
const AGENT_STEPS = [
{ label: "Sync design tokens with Figma & Tailwind" },
{ label: "Generate spring physics for hero cards" },
{ label: "Optimize SVG keyframes for 60 FPS mobile" },
{ label: "Deploy live preview link to team channel" },
];
const CONVEYOR_ROWS = [
{ value: "6.5 hrs", label: "Deep flow", icon: Clock3, colors: "from-cyan-400 to-blue-500" },
{ value: "28", label: "Tasks shipped", icon: ListChecks, colors: "from-amber-400 to-orange-500" },
{ value: "14 days", label: "Sprint streak", icon: Flame, colors: "from-pink-500 to-rose-500" },
{ value: "+5", label: "Motion kits", icon: FolderKanban, colors: "from-emerald-400 to-teal-500" },
{ value: "96%", label: "Velocity score", icon: Target, colors: "from-violet-500 to-purple-600" },
];
function NextUIShowcaseSection() {
const reduce = useReducedMotion();
// 1. State for Customized AgentTaskChecklist (from registry/components/task-checklist.tsx)
const [activeStep, setActiveStep] = useState(1);
const [stepPct, setStepPct] = useState(67);
useEffect(() => {
if (reduce) return;
const id = setInterval(() => {
setStepPct((prev) => {
if (prev < 100) return prev + 33 > 100 ? 100 : prev + 33;
setActiveStep((s) => (s + 1) % (AGENT_STEPS.length + 1));
return 34;
});
}, 950);
return () => clearInterval(id);
}, [reduce]);
// 2. Refs for Customized AnimatedMetricsCard conveyor (from registry/components/animated-metrics-card.tsx)
const pillRefs = useRef<(HTMLLIElement | null)[]>([]);
const H = 76;
const GAP = 12;
const VIEW = 320;
useAnimationFrame((time) => {
if (reduce) return;
const offset = (time / 1000) * 38;
const step = H + GAP;
const loop = CONVEYOR_ROWS.length * step;
const focus = VIEW * 0.5;
pillRefs.current.forEach((el, i) => {
if (!el) return;
const top = ((((i * step - offset) % loop) + loop) % loop) - step;
const dist = Math.min(Math.abs(top + H / 2 - focus) / (VIEW / 2), 1);
const scale = 1 - 0.32 * dist ** 1.4;
const opacity = 1 - 0.8 * dist ** 2.6;
el.style.transform = `translateY(${top}px) scale(${scale})`;
el.style.opacity = String(opacity);
});
});
return (
<section
id="automation"
data-section="automation"
className="relative mx-auto max-w-6xl px-4 py-12 sm:px-8 sm:py-20"
>
<div className="grid items-stretch gap-8 lg:grid-cols-12">
{/* LEFT (6 cols): Customized NextUI AgentTaskChecklist Card */}
<div className="flex flex-col justify-between rounded-[32px] border border-black/[0.06] bg-[#FAF8FF] p-7 sm:p-9 lg:col-span-6">
<div>
<div className="inline-flex items-center gap-2 rounded-full bg-[#6842FF]/10 px-3.5 py-1 text-xs font-bold text-[#6842FF]">
<Wand2 className="size-3.5" />
<span>Autonomous Sprint Co-Pilot</span>
</div>
<h3 className="mt-4 text-2xl font-extrabold tracking-tight text-[#191A23] sm:text-3xl">
Hands-free task execution.
</h3>
<p className="mt-2 text-xs leading-relaxed text-[#6E7180] sm:text-sm">
Watch TaskHub break down your release checklist and tick off each step
in real time—click any row to jump directly to that stage.
</p>
</div>
{/* Interactive Collapsible Ring Checklist Component */}
<div className="mt-7 rounded-2xl bg-white p-2 shadow-[0_18px_40px_-15px_rgba(104,66,255,0.14)] ring-1 ring-black/[0.05]">
<div className="flex items-center justify-between border-b border-black/[0.05] px-3.5 py-3">
<div className="flex items-center gap-2.5">
<span className="grid size-6 place-items-center rounded-full bg-[#6842FF] text-white">
<Sparkles className="size-3.5" />
</span>
<span className="text-xs font-extrabold text-[#191A23]">
Q4 Product Launch Pipeline
</span>
</div>
<span className="rounded-full bg-[#F4F1FF] px-2.5 py-0.5 text-[11px] font-extrabold text-[#6842FF] tabular-nums">
{Math.min(activeStep, AGENT_STEPS.length)}/{AGENT_STEPS.length} done
</span>
</div>
<ol className="divide-y divide-black/[0.04] px-2 py-1">
{AGENT_STEPS.map((st, idx) => {
const isDone = idx < activeStep;
const isCurrent = idx === activeStep;
return (
<li
key={st.label}
onClick={() => {
setActiveStep(idx);
setStepPct(67);
}}
className="flex cursor-pointer items-center justify-between gap-3 rounded-xl px-2.5 py-3 transition-colors hover:bg-slate-50"
>
<div className="flex items-center gap-3">
{isDone ? (
<motion.span
initial={{ scale: 0.6 }}
animate={{ scale: 1 }}
className="grid size-5 place-items-center rounded-full bg-emerald-500 text-white"
>
<Check className="size-3 stroke-[3]" />
</motion.span>
) : isCurrent ? (
<span className="relative grid size-5 place-items-center">
<span className="absolute inset-0 animate-ping rounded-full bg-[#6842FF]/30" />
<span className="size-3 rounded-full border-2 border-[#6842FF] bg-white" />
</span>
) : (
<span className="size-5 rounded-full border-2 border-slate-200" />
)}
<span
className={cn(
"text-xs font-bold transition-all",
isDone
? "text-[#6E7180] line-through"
: isCurrent
? "text-[#191A23]"
: "text-[#6E7180]"
)}
>
{st.label}
</span>
</div>
<span
className={cn(
"text-[11px] font-extrabold tabular-nums",
isDone
? "text-emerald-600"
: isCurrent
? "text-[#6842FF]"
: "text-slate-300"
)}
>
{isDone ? "100%" : isCurrent ? `${stepPct}%` : "Queued"}
</span>
</li>
);
})}
</ol>
</div>
</div>
{/* RIGHT (6 cols): Customized NextUI AnimatedMetricsCard Conveyor */}
<div className="grid grid-cols-1 items-center gap-6 overflow-hidden rounded-[32px] border border-black/[0.06] bg-[#FFF9EE] p-7 sm:grid-cols-[1fr_0.95fr] sm:p-9 lg:col-span-6">
<div>
<div className="inline-flex items-center gap-2 rounded-full bg-[#FFDA66]/50 px-3.5 py-1 text-xs font-bold text-[#191A23]">
<span className="size-2 rounded-full bg-emerald-500" />
<span>Live Team Momentum</span>
</div>
<h3 className="mt-4 text-2xl font-extrabold leading-tight tracking-tight text-[#191A23] sm:text-3xl">
Small steps.
<br />
Real momentum.
</h3>
<p className="mt-3 text-xs leading-relaxed text-[#6E7180] sm:text-sm">
Make space for focused creative work, finish what matters, and watch your
team's weekly velocity add up automatically.
</p>
</div>
{/* 3D Vertical Conveyor of Colorful Gradient Pills */}
<ul
aria-label="Weekly progress metrics"
className="relative mx-auto w-full max-w-[270px] overflow-hidden"
style={{
height: VIEW,
maskImage: "linear-gradient(to bottom, transparent, #000 16%, #000 84%, transparent)",
WebkitMaskImage:
"linear-gradient(to bottom, transparent, #000 16%, #000 84%, transparent)",
}}
>
{CONVEYOR_ROWS.map((row, i) => {
const Icon = row.icon;
return (
<li
key={row.label}
ref={(el) => {
pillRefs.current[i] = el;
}}
className={cn(
"absolute inset-x-0 top-0 flex w-full items-center gap-3 rounded-full bg-gradient-to-r p-2 pr-5 text-white shadow-lg shadow-black/10 will-change-transform",
row.colors
)}
style={{ height: H }}
>
<span className="grid aspect-square h-full shrink-0 place-items-center rounded-full bg-white text-[#191A23] shadow-2xs">
<Icon className="size-5" />
</span>
<div className="min-w-0 flex-1">
<div className="truncate text-lg font-extrabold leading-none">
{row.value}
</div>
<div className="mt-1 truncate text-xs font-medium text-white/90">
{row.label}
</div>
</div>
</li>
);
})}
</ul>
</div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* SECTION 4: "PROJECTS THAT WORK." 3-COLUMN FEATURE SWITCHER */
/* -------------------------------------------------------------------------- */
const FEATURE_TABS = [
{
id: "dashboard",
title: "Dashboard",
sub: "Everything in one calm place",
ringColor: "#FF5484",
dotColor: "#FF5484",
cardBg: "#FFF5F7",
cardAccent: "#FF5484",
headerTitle: "Team Overview",
activeRow: 0,
rows: [
{ title: "Q4 Design System Launch", tag: "On Track", width: "w-36" },
{ title: "Mobile App Onboarding", tag: "In Review", width: "w-28" },
{ title: "Customer Feedback Loop", tag: "Synced", width: "w-32" },
],
},
{
id: "project",
title: "Project",
sub: "Clear overview, full efficiency",
ringColor: "#FFB81C",
dotColor: "#FFB81C",
cardBg: "#FFF9EE",
cardAccent: "#F59E0B",
headerTitle: "Manage Project",
activeRow: 1,
rows: [
{ title: "Brand Guidelines Refresh", tag: "Done", width: "w-32" },
{ title: "Interactive Prototype Sprint", tag: "Active", width: "w-40" },
{ title: "Stakeholder Demo Deck", tag: "Next", width: "w-28" },
],
},
{
id: "task",
title: "Task",
sub: "Where focused work gets done",
ringColor: "#6842FF",
dotColor: "#6842FF",
cardBg: "#F4F1FF",
cardAccent: "#6842FF",
headerTitle: "Sprint Tasks",
activeRow: 2,
rows: [
{ title: "Ship new navigation header", tag: "Merged", width: "w-36" },
{ title: "Polish card spring animations", tag: "Testing", width: "w-32" },
{ title: "Finalize mobile breakpoints", tag: "Priority", width: "w-40" },
],
},
{
id: "agenda",
title: "Agenda",
sub: "Your own personal board",
ringColor: "#10B981",
dotColor: "#10B981",
cardBg: "#ECFDF5",
cardAccent: "#10B981",
headerTitle: "Daily Agenda",
activeRow: 0,
rows: [
{ title: "10:00 AM · Design Crit Sync", tag: "Live", width: "w-36" },
{ title: "01:30 PM · Roadmap Planning", tag: "Upcoming", width: "w-32" },
{ title: "04:00 PM · Async Wrap-up", tag: "Scheduled", width: "w-28" },
],
},
];
function FeaturesSection() {
const reduce = useReducedMotion();
const [activeIdx, setActiveIdx] = useState(1);
const [paused, setPaused] = useState(false);
const [selectedTaskRow, setSelectedTaskRow] = useState<number | null>(null);
useEffect(() => {
if (reduce || paused) return;
const id = setInterval(() => {
setActiveIdx((prev) => (prev + 1) % FEATURE_TABS.length);
setSelectedTaskRow(null);
}, 4200);
return () => clearInterval(id);
}, [reduce, paused]);
const current = FEATURE_TABS[activeIdx];
const highlightedRow = selectedTaskRow ?? current.activeRow;
return (
<section
id="features"
data-section="features"
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}
className="relative mx-auto max-w-6xl px-4 py-16 sm:px-8 sm:py-24"
>
<div className="grid items-center gap-12 lg:grid-cols-12">
<div className="lg:col-span-4">
<div className="inline-flex items-center rounded-full bg-[#FFF0F5] px-4 py-1.5 text-xs font-bold text-[#FF5484]">
Management with TaskHub
</div>
<h2 className="mt-5 text-3xl font-extrabold leading-[1.15] tracking-tight text-[#191A23] sm:text-4xl">
Projects That
<br />
Work.
</h2>
<p className="mt-5 text-xs leading-relaxed text-[#6E7180] sm:text-sm">
Whether you're managing your next big launch or organizing daily sprints,
everyone knows who's doing what, when.
</p>
</div>
<div className="lg:col-span-5">
<AnimatePresence mode="wait">
<motion.div
key={current.id}
initial={{ opacity: 0, y: 14, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -12, scale: 0.98 }}
transition={{ duration: 0.4, ease: EASE }}
className="relative rounded-[32px] p-7 sm:p-9 shadow-[0_24px_60px_-24px_rgba(25,26,35,0.08)]"
style={{ backgroundColor: current.cardBg }}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<span
className="grid size-7 place-items-center rounded-lg text-white"
style={{ backgroundColor: current.cardAccent }}
>
<Sparkles className="size-3.5 fill-current" />
</span>
<span
className="text-base font-extrabold tracking-tight"
style={{ color: current.cardAccent }}
>
{current.headerTitle}
</span>
</div>
<div className="flex -space-x-1.5">
<AvatarBubble emoji="🧑🏻" bg="#FFE4B5" size="sm" />
<AvatarBubble emoji="👩🏽" bg="#FFD1DC" size="sm" />
<AvatarBubble emoji="🧔🏼" bg="#E0E7FF" size="sm" />
</div>
</div>
<div className="mt-7 space-y-3.5">
{current.rows.map((row, rIdx) => {
const isHighlighted = highlightedRow === rIdx;
return (
<motion.div
key={row.title}
onClick={() => setSelectedTaskRow(rIdx)}
layout
transition={SPRING}
className={cn(
"flex cursor-pointer items-center gap-4 rounded-2xl p-3.5 transition-colors",
isHighlighted
? "bg-white shadow-sm ring-2"
: "bg-white/55 hover:bg-white/80"
)}
style={{
borderColor: isHighlighted ? current.cardAccent : "transparent",
boxShadow: isHighlighted
? `0 10px 25px -10px ${current.cardAccent}40`
: undefined,
}}
>
<div
className="flex size-7 shrink-0 items-center justify-center rounded-lg transition-colors"
style={{
backgroundColor: isHighlighted ? `${current.cardAccent}22` : "#FFFFFF",
}}
>
{isHighlighted && (
<Check
className="size-4 stroke-[3]"
style={{ color: current.cardAccent }}
/>
)}
</div>
<div className="flex-1 space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-[#191A23]">
{row.title}
</span>
<span
className="rounded-full px-2 py-0.5 text-[10px] font-bold"
style={{
backgroundColor: isHighlighted
? `${current.cardAccent}18`
: "rgba(0,0,0,0.04)",
color: isHighlighted ? current.cardAccent : "#6E7180",
}}
>
{row.tag}
</span>
</div>
<div className="flex gap-2">
<div
className={cn("h-2 rounded-full", row.width)}
style={{
backgroundColor: isHighlighted
? `${current.cardAccent}40`
: "rgba(25,26,35,0.08)",
}}
/>
<div className="h-2 w-10 rounded-full bg-black/[0.05]" />
</div>
</div>
</motion.div>
);
})}
</div>
</motion.div>
</AnimatePresence>
</div>
<div className="space-y-6 lg:col-span-3 lg:pl-4">
{FEATURE_TABS.map((tab, idx) => {
const isActive = activeIdx === idx;
return (
<button
key={tab.id}
type="button"
onClick={() => {
setActiveIdx(idx);
setSelectedTaskRow(null);
}}
className="group block w-full cursor-pointer text-left"
>
<div className="flex items-center gap-3.5">
<motion.span
animate={{ scale: isActive ? 1.18 : 1 }}
transition={SPRING}
className="size-4 shrink-0 rounded-full border-[2.5px] bg-white"
style={{ borderColor: tab.ringColor }}
/>
<span
className={cn(
"text-base font-extrabold transition-colors",
isActive
? "text-[#191A23]"
: "text-[#191A23]/70 group-hover:text-[#191A23]"
)}
>
{tab.title}
</span>
</div>
<div className="mt-1.5 flex items-center gap-2 pl-7 text-xs text-[#6E7180]">
<span
className="size-1.5 rounded-full"
style={{ backgroundColor: tab.dotColor }}
/>
<span>{tab.sub}</span>
</div>
</button>
);
})}
</div>
</div>
</section>
);
}
/* -------------------------------------------------------------------------- */
/* SECTION 5: WARM GRID CTA BANNER & MINIMAL FOOTER */
/* -------------------------------------------------------------------------- */
function CtaAndFooterSection({ onNavigate }: { onNavigate: (id: string) => void }) {
const [joined, setJoined] = useState(false);
const [email, setEmail] = useState("");
return (
<footer id="cta" data-section="cta" className="mx-auto max-w-6xl px-4 pt-8 pb-20 sm:px-8">
<div
className="relative overflow-hidden rounded-[36px] bg-[#FAF8FF] px-6 py-14 text-center sm:px-14 sm:py-16"
style={{
backgroundImage:
"linear-gradient(to right, rgba(104,66,255,0.04) 1px, transparent 1px), linear-gradient(to bottom, rgba(104,66,255,0.04) 1px, transparent 1px)",
backgroundSize: "36px 36px",
}}
>
<div className="inline-flex items-center gap-2 rounded-full bg-[#FFF0F5] px-4 py-1.5 text-xs font-bold text-[#FF5484]">
<span className="rounded-full bg-[#FF5484] px-2 py-0.5 text-[10px] font-extrabold text-white">
New
</span>
<span>The Essential Creative Powerhouse</span>
</div>
<h3 className="mx-auto mt-5 max-w-xl text-3xl font-extrabold tracking-tight text-[#191A23] sm:text-5xl">
Your go-to motion powerhouse for{" "}
<span className="inline-block -rotate-1 rounded-xl bg-[#FFB81C] px-3 py-0.5 text-white shadow-xs">
remote teams
</span>
</h3>
<p className="mx-auto mt-4 max-w-md text-xs leading-relaxed text-[#6E7180] sm:text-sm">
Free forever for small crews. Set up your shared workspace in under a minute.
</p>
<form
onSubmit={(e) => {
e.preventDefault();
setJoined(true);
}}
className="mx-auto mt-8 flex max-w-md flex-col gap-3 sm:flex-row"
>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="flex-1 rounded-full border border-[#6842FF]/15 bg-white px-6 py-3.5 text-xs font-medium text-[#191A23] shadow-2xs outline-none focus:border-[#6842FF]"
/>
<button
type="submit"
className="cursor-pointer rounded-full bg-[#6842FF] px-8 py-3.5 text-xs font-extrabold text-white shadow-[0_12px_24px_-8px_rgba(104,66,255,0.45)] transition-all hover:scale-105 hover:bg-[#FF5484] active:scale-95"
>
{joined ? "Invite Sent ✓" : "Get Started"}
</button>
</form>
</div>
<div className="mt-12 flex flex-col items-center justify-between gap-4 text-xs text-[#6E7180] sm:flex-row">
<button
type="button"
onClick={() => onNavigate("top")}
className="flex cursor-pointer items-center gap-2 font-extrabold text-[#191A23]"
>
<span className="grid size-7 place-items-center rounded-lg bg-[#6842FF] text-white">
<Sparkles className="size-3.5 fill-current" />
</span>
<span>TaskHub</span>
</button>
<p>© 2026 TaskHub Studio. Light, joyful teamwork & motion for modern crews.</p>
</div>
</footer>
);
}
/* -------------------------------------------------------------------------- */
/* MAIN COMPONENT */
/* -------------------------------------------------------------------------- */
export function TaskHub({ className }: { className?: string }) {
const scroller = useRef<HTMLDivElement>(null);
const reduce = useReducedMotion();
const goTo = (id: string) => {
const root = scroller.current;
if (!root) return;
if (id === "top") {
root.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
return;
}
const section = root.querySelector<HTMLElement>(`[data-section="${id}"]`);
if (!section) return;
const top =
root.scrollTop +
section.getBoundingClientRect().top -
root.getBoundingClientRect().top -
32;
root.scrollTo({ top, behavior: reduce ? "auto" : "smooth" });
};
return (
<ScrollerContext value={scroller}>
<div
className={cn(
"relative h-svh overflow-hidden bg-white text-[#191A23] antialiased selection:bg-[#6842FF] selection:text-white",
className
)}
style={SANS}
>
<div
ref={scroller}
className="relative h-full overflow-y-auto overflow-x-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
<Header onNavigate={goTo} />
<main>
<HeroSection onNavigate={goTo} />
<WhatMakesUsDifferentSection />
<NextUIShowcaseSection />
<FeaturesSection />
</main>
<CtaAndFooterSection onNavigate={goTo} />
</div>
</div>
</ScrollerContext>
);
}
export const CyberNeoGenesis = TaskHub;
export default function TaskHubPreview() {
return <TaskHub />;
}