197 lines
6.9 KiB
TypeScript
197 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useReducedMotion } from "framer-motion";
|
|
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
|
import { fadeUp, staggerContainer, viewport } from "@/lib/animations";
|
|
|
|
const EASE = [0.22, 1, 0.36, 1] as [number, number, number, number];
|
|
|
|
const TRADITIONAL = [
|
|
{ icon: "edit_note", label: "Manual Catalog" },
|
|
{ icon: "store", label: "Offline Store" },
|
|
{ icon: "directions_walk", label: "Walk-in Customers" },
|
|
{ icon: "receipt_long", label: "Manual Billing" },
|
|
];
|
|
|
|
const NEARLE = [
|
|
{ icon: "auto_awesome", label: "ProductLLM" },
|
|
{ icon: "storefront", label: "Digital Storefront" },
|
|
{ icon: "shopping_bag", label: "Omnichannel Ordering" },
|
|
{ icon: "travel_explore", label: "AI Powered Discovery" },
|
|
];
|
|
|
|
const nodeVariant = {
|
|
hidden: { opacity: 0, y: 20 },
|
|
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: EASE } },
|
|
};
|
|
|
|
const connectorVariant = {
|
|
hidden: { scaleY: 0 },
|
|
visible: { scaleY: 1, transition: { duration: 0.5, ease: EASE } },
|
|
};
|
|
|
|
function FlowColumn({
|
|
eyebrow,
|
|
items,
|
|
tone,
|
|
reduce,
|
|
}: {
|
|
eyebrow: string;
|
|
items: { icon: string; label: string }[];
|
|
tone: "muted" | "brand";
|
|
reduce: boolean | null;
|
|
}) {
|
|
const isBrand = tone === "brand";
|
|
|
|
return (
|
|
<motion.div
|
|
variants={{ hidden: {}, visible: { transition: { staggerChildren: 0.12 } } }}
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
viewport={{ once: true, amount: 0.2 }}
|
|
className={`relative flex flex-col items-center rounded-[32px] border p-8 md:p-10 ${
|
|
isBrand
|
|
? "border-white/10 bg-gradient-to-br from-[#1A0322] via-[#2B0040] to-[#0A000D] shadow-[0_24px_60px_rgba(20,0,30,0.32)]"
|
|
: "border-[#6330D6]/10 bg-[#FAF7FD] shadow-[0_14px_40px_rgba(22,0,29,0.05)]"
|
|
}`}
|
|
>
|
|
<motion.p
|
|
variants={nodeVariant}
|
|
className={`text-xs font-black uppercase tracking-widest ${
|
|
isBrand ? "text-[#C9A2FF]" : "text-[#5E5866]"
|
|
}`}
|
|
>
|
|
{eyebrow}
|
|
</motion.p>
|
|
|
|
<div className="mt-8 flex w-full max-w-[300px] flex-col items-center">
|
|
{items.map((item, i) => (
|
|
<div key={item.label} className="flex w-full flex-col items-center">
|
|
<motion.div
|
|
variants={nodeVariant}
|
|
whileHover={reduce ? undefined : { y: -4, scale: 1.02 }}
|
|
transition={{ duration: 0.25, ease: EASE }}
|
|
className={`flex w-full items-center gap-4 rounded-2xl border px-5 py-4 ${
|
|
isBrand
|
|
? "border-white/10 bg-white/[0.06] hover:border-[#C9A2FF]/40 hover:bg-white/[0.1]"
|
|
: "border-[#6330D6]/10 bg-white shadow-[0_8px_22px_rgba(22,0,29,0.04)]"
|
|
} transition-colors duration-300`}
|
|
>
|
|
<div
|
|
className={`flex h-11 w-11 shrink-0 items-center justify-center rounded-xl ${
|
|
isBrand ? "bg-white/10 text-[#C9A2FF]" : "bg-[#6330D6]/[0.08] text-[#6330D6]"
|
|
}`}
|
|
>
|
|
<MaterialIcon icon={item.icon} size={22} />
|
|
</div>
|
|
<p
|
|
className={`font-display font-black leading-tight ${
|
|
isBrand ? "text-white" : "text-[#16001D]"
|
|
} text-[15px] md:text-base`}
|
|
>
|
|
{item.label}
|
|
</p>
|
|
</motion.div>
|
|
|
|
{/* Animated connector + travelling pulse */}
|
|
{i < items.length - 1 && (
|
|
<div className="relative my-1.5 h-8 w-0.5">
|
|
<motion.span
|
|
variants={connectorVariant}
|
|
className={`absolute inset-0 origin-top rounded-full ${
|
|
isBrand ? "bg-[#C9A2FF]/40" : "bg-[#6330D6]/25"
|
|
}`}
|
|
/>
|
|
{!reduce && (
|
|
<motion.span
|
|
aria-hidden="true"
|
|
className={`absolute left-1/2 top-0 h-2 w-2 -translate-x-1/2 rounded-full ${
|
|
isBrand
|
|
? "bg-[#C9A2FF] shadow-[0_0_10px_2px_rgba(201,162,255,0.6)]"
|
|
: "bg-[#7C3AED] shadow-[0_0_10px_2px_rgba(124,58,237,0.5)]"
|
|
}`}
|
|
animate={{ y: [0, 26], opacity: [0, 1, 0] }}
|
|
transition={{
|
|
repeat: Infinity,
|
|
duration: 1.8,
|
|
ease: "easeInOut",
|
|
delay: i * 0.3,
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
export function BeforeAfterFlow() {
|
|
const reduce = useReducedMotion();
|
|
|
|
return (
|
|
<section className="section section-soft w-full border-b border-[#6330D6]/10 overflow-hidden">
|
|
<div className="page-container">
|
|
<motion.div
|
|
variants={staggerContainer}
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
viewport={viewport}
|
|
className="text-center max-w-[720px] mx-auto"
|
|
>
|
|
<motion.span
|
|
variants={fadeUp}
|
|
className="inline-flex items-center rounded-full bg-[#6330D6]/[0.08] px-3.5 py-1 text-xs font-black uppercase tracking-widest text-[#6330D6]"
|
|
>
|
|
Before vs After
|
|
</motion.span>
|
|
<motion.h2
|
|
variants={fadeUp}
|
|
className="font-display font-black text-[#16001D] mt-4 tracking-tight"
|
|
style={{ fontSize: "clamp(30px, 3vw, 48px)", lineHeight: 1.1 }}
|
|
>
|
|
The same store, reimagined
|
|
</motion.h2>
|
|
</motion.div>
|
|
|
|
<div className="relative mt-14 grid gap-6 md:grid-cols-2 md:items-start md:gap-8">
|
|
<FlowColumn
|
|
eyebrow="Traditional Commerce"
|
|
items={TRADITIONAL}
|
|
tone="muted"
|
|
reduce={reduce}
|
|
/>
|
|
|
|
{/* Center VS badge (desktop) */}
|
|
<motion.div
|
|
aria-hidden="true"
|
|
initial={{ opacity: 0, scale: 0.6 }}
|
|
whileInView={{ opacity: 1, scale: 1 }}
|
|
viewport={{ once: true, amount: 0.5 }}
|
|
transition={{ duration: 0.5, delay: 0.25, ease: EASE }}
|
|
className="pointer-events-none absolute left-1/2 top-1/2 z-20 hidden h-14 w-14 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-[#6330D6]/15 bg-white text-xs font-black uppercase tracking-widest text-[#6330D6] shadow-[0_10px_30px_rgba(99,48,214,0.22)] md:flex"
|
|
>
|
|
{!reduce && (
|
|
<motion.span
|
|
className="absolute inset-0 rounded-full border-2 border-[#7C3AED]"
|
|
animate={{ scale: [1, 1.45], opacity: [0.5, 0] }}
|
|
transition={{ repeat: Infinity, duration: 2.2, ease: "easeOut" }}
|
|
/>
|
|
)}
|
|
VS
|
|
</motion.div>
|
|
|
|
<FlowColumn
|
|
eyebrow="Nearle AI Commerce"
|
|
items={NEARLE}
|
|
tone="brand"
|
|
reduce={reduce}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|