update merchantos svg
This commit is contained in:
467
src/components/platform/AnimatedDashboard.tsx
Normal file
467
src/components/platform/AnimatedDashboard.tsx
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { motion, AnimatePresence, useReducedMotion } from "framer-motion";
|
||||||
|
|
||||||
|
type ProductAsset = {
|
||||||
|
emoji: string;
|
||||||
|
name: string;
|
||||||
|
xStart: string; // Tailwind coordinate
|
||||||
|
yStart: string; // Tailwind coordinate
|
||||||
|
floatDelay: number;
|
||||||
|
floatDuration: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define products that float and participate in AI automation
|
||||||
|
const PRODUCT_ASSETS: ProductAsset[] = [
|
||||||
|
{ emoji: "🍎", name: "Apple", xStart: "left-[5%]", yStart: "top-[10%]", floatDelay: 0, floatDuration: 4 },
|
||||||
|
{ emoji: "🥛", name: "Milk", xStart: "right-[5%]", yStart: "top-[15%]", floatDelay: 0.5, floatDuration: 4.5 },
|
||||||
|
{ emoji: "🍞", name: "Bread", xStart: "left-[0%]", yStart: "top-[45%]", floatDelay: 0.8, floatDuration: 4.8 },
|
||||||
|
{ emoji: "🥬", name: "Spinach", xStart: "right-[2%]", yStart: "top-[50%]", floatDelay: 1.2, floatDuration: 5.2 },
|
||||||
|
{ emoji: "🏷️", name: "Price Tag", xStart: "left-[8%]", yStart: "bottom-[12%]", floatDelay: 1.5, floatDuration: 4.2 },
|
||||||
|
{ emoji: "📲", name: "QR Code", xStart: "right-[8%]", yStart: "bottom-[15%]", floatDelay: 2, floatDuration: 4.6 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function AnimatedDashboard() {
|
||||||
|
const reduce = useReducedMotion();
|
||||||
|
|
||||||
|
// Animation states
|
||||||
|
const [animatingIndex, setAnimatingIndex] = useState(0);
|
||||||
|
const [stage, setStage] = useState<"idle" | "flying" | "scanning" | "updating" | "notifying">("idle");
|
||||||
|
const [itemsCount, setItemsCount] = useState(142);
|
||||||
|
const [ordersCount, setOrdersCount] = useState(8);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (reduce) return;
|
||||||
|
|
||||||
|
const cycle = setInterval(() => {
|
||||||
|
// 1. Start Fly-in phase
|
||||||
|
setStage("flying");
|
||||||
|
|
||||||
|
// 2. Scan phase: Product reaches center, trigger laser scan
|
||||||
|
const scanTimer = setTimeout(() => {
|
||||||
|
setStage("scanning");
|
||||||
|
}, 900);
|
||||||
|
|
||||||
|
// 3. Update dashboard values (counter, green success pulse)
|
||||||
|
const updateTimer = setTimeout(() => {
|
||||||
|
setStage("updating");
|
||||||
|
|
||||||
|
// Update specific counters based on what product is processed
|
||||||
|
if (animatingIndex === 0) {
|
||||||
|
setItemsCount((c) => c + 1);
|
||||||
|
} else if (animatingIndex === 1) {
|
||||||
|
setOrdersCount((c) => c + 1);
|
||||||
|
}
|
||||||
|
}, 1400);
|
||||||
|
|
||||||
|
// 4. Micro-notification appears
|
||||||
|
const notifyTimer = setTimeout(() => {
|
||||||
|
setStage("notifying");
|
||||||
|
}, 1800);
|
||||||
|
|
||||||
|
// 5. Clear notification, return to idle
|
||||||
|
const idleTimer = setTimeout(() => {
|
||||||
|
setStage("idle");
|
||||||
|
setAnimatingIndex((prev) => (prev + 1) % 4); // Cycle through the first 4 products
|
||||||
|
}, 5200);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(scanTimer);
|
||||||
|
clearTimeout(updateTimer);
|
||||||
|
clearTimeout(notifyTimer);
|
||||||
|
clearTimeout(idleTimer);
|
||||||
|
};
|
||||||
|
}, 8000); // Complete animation cycle every 8 seconds
|
||||||
|
|
||||||
|
return () => clearInterval(cycle);
|
||||||
|
}, [animatingIndex, reduce]);
|
||||||
|
|
||||||
|
// Floating variants for the idle products surrounding the dashboard
|
||||||
|
const floatVariant = (delay: number, duration: number) => ({
|
||||||
|
animate: {
|
||||||
|
y: [0, -10, 0],
|
||||||
|
rotate: [-3, 3, -3],
|
||||||
|
scale: [1, 1.02, 1],
|
||||||
|
transition: {
|
||||||
|
duration,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
delay,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Curved flight coordinates relative to the absolute dashboard layout
|
||||||
|
// Product index maps to original coordinates:
|
||||||
|
// index 0 (Apple): left-5%, top-10% (x: 5%, y: 10%) -> center (x: 50%, y: 45%)
|
||||||
|
const flightPaths = [
|
||||||
|
{ x: ["5%", "25%", "50%"], y: ["10%", "20%", "45%"] }, // Apple
|
||||||
|
{ x: ["95%", "75%", "50%"], y: ["15%", "25%", "45%"] }, // Milk
|
||||||
|
{ x: ["0%", "20%", "50%"], y: ["45%", "40%", "45%"] }, // Bread
|
||||||
|
{ x: ["98%", "78%", "50%"], y: ["50%", "45%", "45%"] }, // Spinach
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full aspect-[440/336] flex items-center justify-center p-8 sm:p-12 overflow-visible select-none">
|
||||||
|
{/* ── Background Glow ── */}
|
||||||
|
<div className="absolute inset-0 -z-20 bg-gradient-to-tr from-purple-500/10 to-[#6330D6]/10 blur-3xl rounded-full pointer-events-none scale-90" />
|
||||||
|
|
||||||
|
{/* ── 1. Floating Product Assets (Around Dashboard) ── */}
|
||||||
|
{PRODUCT_ASSETS.map((product, idx) => {
|
||||||
|
// Hide specific products on tablet/mobile screens
|
||||||
|
let responsiveClass = "absolute z-20 ";
|
||||||
|
if (idx === 2 || idx === 3) {
|
||||||
|
responsiveClass += "hidden lg:block"; // Only desktop
|
||||||
|
} else if (idx === 0 || idx === 1 || idx === 4 || idx === 5) {
|
||||||
|
responsiveClass += "hidden md:block"; // Desktop & Tablet
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCurrentlyAnimating = stage !== "idle" && animatingIndex === idx;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={product.name}
|
||||||
|
className={`${responsiveClass} ${product.xStart} ${product.yStart}`}
|
||||||
|
variants={floatVariant(product.floatDelay, product.floatDuration)}
|
||||||
|
animate={isCurrentlyAnimating ? "exit" : "animate"}
|
||||||
|
style={{ opacity: isCurrentlyAnimating ? 0.15 : 1 }}
|
||||||
|
>
|
||||||
|
<div className="flex h-11 w-11 items-center justify-center rounded-2xl border border-purple-500/10 bg-white/75 backdrop-blur-md shadow-[0_4px_16px_rgba(99,48,214,0.04)] text-lg">
|
||||||
|
{product.emoji}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* ── 2. Flying Product Scan Asset ── */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{stage === "flying" && animatingIndex < 4 && (
|
||||||
|
<motion.div
|
||||||
|
initial={{
|
||||||
|
x: flightPaths[animatingIndex].x[0],
|
||||||
|
y: flightPaths[animatingIndex].y[0],
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
x: flightPaths[animatingIndex].x,
|
||||||
|
y: flightPaths[animatingIndex].y,
|
||||||
|
opacity: [1, 1, 0.2],
|
||||||
|
scale: [1, 0.7, 0.4],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 0.9,
|
||||||
|
ease: [0.25, 0.1, 0.25, 1],
|
||||||
|
}}
|
||||||
|
className="absolute z-30 pointer-events-none flex h-11 w-11 items-center justify-center rounded-2xl border-2 border-[#7C3AED] bg-white shadow-[0_0_15px_#7C3AED] text-lg"
|
||||||
|
>
|
||||||
|
{PRODUCT_ASSETS[animatingIndex].emoji}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* ── 3. AI Processing Particle Trails ── */}
|
||||||
|
{stage === "flying" && animatingIndex < 4 && (
|
||||||
|
<>
|
||||||
|
{[0, 1, 2].map((particleIdx) => (
|
||||||
|
<motion.div
|
||||||
|
key={particleIdx}
|
||||||
|
initial={{
|
||||||
|
x: flightPaths[animatingIndex].x[0],
|
||||||
|
y: flightPaths[animatingIndex].y[0],
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.8,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
x: flightPaths[animatingIndex].x,
|
||||||
|
y: flightPaths[animatingIndex].y,
|
||||||
|
opacity: [0, 0.7, 0],
|
||||||
|
scale: [0.6, 0.3, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 0.9,
|
||||||
|
delay: 0.08 * (particleIdx + 1),
|
||||||
|
ease: "easeOut",
|
||||||
|
}}
|
||||||
|
className="absolute z-28 w-1.5 h-1.5 rounded-full bg-[#7C3AED] shadow-[0_0_8px_#7C3AED]"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── 4. Slide-in Micro-Notifications ── */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{stage === "notifying" && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: -25, opacity: 0, x: "-50%" }}
|
||||||
|
animate={{ y: 0, opacity: 1, x: "-50%" }}
|
||||||
|
exit={{ y: -20, opacity: 0, x: "-50%" }}
|
||||||
|
transition={{ type: "spring", stiffness: 300, damping: 24 }}
|
||||||
|
className="absolute top-[22%] left-1/2 z-40 bg-neutral-950 text-white rounded-full px-4 py-1.5 shadow-lg border border-purple-500/20 flex items-center gap-2 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<span className="text-emerald-400 text-xs font-bold">✓</span>
|
||||||
|
<span className="text-[10px] sm:text-[11px] font-bold tracking-tight">
|
||||||
|
{animatingIndex === 0 && "Apple Catalog Listing Synced"}
|
||||||
|
{animatingIndex === 1 && "Milk Delivery Stock Updated"}
|
||||||
|
{animatingIndex === 2 && "Bread Pricing Configuration Live"}
|
||||||
|
{animatingIndex === 3 && "Spinach Category Catalog Indexed"}
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* ── 5. Main Dashboard Canvas ── */}
|
||||||
|
<div className="relative w-full h-full flex items-center justify-center">
|
||||||
|
{/* Ambient Success Pulse Border */}
|
||||||
|
<motion.div
|
||||||
|
animate={
|
||||||
|
stage === "updating"
|
||||||
|
? {
|
||||||
|
borderColor: ["rgba(99,48,214,0.1)", "rgba(34,197,94,0.6)", "rgba(99,48,214,0.1)"],
|
||||||
|
boxShadow: [
|
||||||
|
"0 0 0 rgba(34,197,94,0)",
|
||||||
|
"0 0 20px rgba(34,197,94,0.2)",
|
||||||
|
"0 0 0 rgba(34,197,94,0)",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
className="absolute inset-[30px] rounded-[22px] border-[1.5px] border-[#EDE9FE]/0 pointer-events-none z-10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.svg
|
||||||
|
viewBox="0 0 440 336"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="w-full h-auto relative z-0"
|
||||||
|
>
|
||||||
|
{/* Ground shadow */}
|
||||||
|
<ellipse cx="220" cy="312" rx="150" ry="14" fill="#6330D6" opacity="0.08" />
|
||||||
|
|
||||||
|
{/* Main dashboard window */}
|
||||||
|
<g>
|
||||||
|
<rect x="40" y="30" width="360" height="256" rx="22" fill="#FFFFFF" stroke="#EDE9FE" strokeWidth="1.5" />
|
||||||
|
{/* Top window bar chrome */}
|
||||||
|
<circle cx="66" cy="54" r="4" fill="#E6D9F7" />
|
||||||
|
<circle cx="80" cy="54" r="4" fill="#E6D9F7" />
|
||||||
|
<circle cx="94" cy="54" r="4" fill="#E6D9F7" />
|
||||||
|
<rect x="150" y="48" width="140" height="13" rx="6.5" fill="#F3E8F6" />
|
||||||
|
<line x1="40" y1="72" x2="400" y2="72" stroke="#F0E9F8" strokeWidth="1.5" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Sidebar navigation */}
|
||||||
|
<g>
|
||||||
|
<rect x="52" y="82" width="58" height="192" rx="14" fill="#FAF7FD" />
|
||||||
|
<rect x="64" y="98" width="34" height="8" rx="4" fill="#6330D6" />
|
||||||
|
<rect x="64" y="120" width="30" height="7" rx="3.5" fill="#D9CBEF" />
|
||||||
|
<rect x="64" y="138" width="34" height="7" rx="3.5" fill="#D9CBEF" />
|
||||||
|
<rect x="64" y="156" width="26" height="7" rx="3.5" fill="#D9CBEF" />
|
||||||
|
<rect x="64" y="174" width="32" height="7" rx="3.5" fill="#D9CBEF" />
|
||||||
|
|
||||||
|
{/* QR Ordering live badge */}
|
||||||
|
<g>
|
||||||
|
<motion.rect
|
||||||
|
x="64"
|
||||||
|
y="210"
|
||||||
|
width="34"
|
||||||
|
height="12"
|
||||||
|
rx="4"
|
||||||
|
fill="#6330D6"
|
||||||
|
animate={{
|
||||||
|
fill: stage === "updating" && animatingIndex === 3 ? "#22C55E" : "#6330D6",
|
||||||
|
opacity: stage === "updating" && animatingIndex === 3 ? 0.25 : 0.15,
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
/>
|
||||||
|
<motion.text
|
||||||
|
x="68"
|
||||||
|
y="218"
|
||||||
|
fontSize="5"
|
||||||
|
fontWeight="bold"
|
||||||
|
animate={{
|
||||||
|
fill: stage === "updating" && animatingIndex === 3 ? "#16A34A" : "#6330D6",
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
QR ACTIVE
|
||||||
|
</motion.text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Loyalty active badge */}
|
||||||
|
<g>
|
||||||
|
<rect x="64" y="226" width="34" height="12" rx="4" fill="#6330D6" opacity="0.1" />
|
||||||
|
<text x="69" y="234" fill="#6330D6" fontSize="5" fontWeight="bold">LOYALTY</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* ── KPI Chips ── */}
|
||||||
|
{/* KPI 1: Inventory Items */}
|
||||||
|
<g>
|
||||||
|
<rect x="124" y="84" width="80" height="46" rx="12" fill="#F5F1FB" />
|
||||||
|
<text x="136" y="97" fill="#8B7FB5" fontSize="7" fontWeight="bold">ITEMS</text>
|
||||||
|
|
||||||
|
{/* Animate number change */}
|
||||||
|
<text x="136" y="118" fill="#16001D" fontSize="13" fontWeight="900" className="font-display">
|
||||||
|
{itemsCount}
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* Micro pulse indicator */}
|
||||||
|
<motion.circle
|
||||||
|
cx="188"
|
||||||
|
cy="95"
|
||||||
|
r="3.5"
|
||||||
|
fill="#6330D6"
|
||||||
|
initial={{ scale: 0.8 }}
|
||||||
|
animate={stage === "updating" && animatingIndex === 0 ? { scale: [1, 1.8, 1], opacity: [0.8, 0, 0.8] } : {}}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* KPI 2: Sales Counter */}
|
||||||
|
<g>
|
||||||
|
<rect x="212" y="84" width="80" height="46" rx="12" fill="#ECFBF1" />
|
||||||
|
<text x="224" y="97" fill="#008037" fontSize="7" fontWeight="bold">SALES</text>
|
||||||
|
<text x="224" y="118" fill="#007239" fontSize="13" fontWeight="900" className="font-display">
|
||||||
|
₹18,240
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* KPI 3: Orders Counter */}
|
||||||
|
<g>
|
||||||
|
<rect x="300" y="84" width="80" height="46" rx="12" fill="#FFF7E8" />
|
||||||
|
<text x="312" y="97" fill="#C28C00" fontSize="7" fontWeight="bold">ORDERS</text>
|
||||||
|
|
||||||
|
{/* Animate orders increase */}
|
||||||
|
<text x="312" y="118" fill="#D97706" fontSize="13" fontWeight="900" className="font-display">
|
||||||
|
{ordersCount}
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<motion.circle
|
||||||
|
cx="364"
|
||||||
|
cy="95"
|
||||||
|
r="3"
|
||||||
|
fill="#D97706"
|
||||||
|
initial={{ scale: 0.8 }}
|
||||||
|
animate={stage === "updating" && animatingIndex === 1 ? { scale: [1, 1.8, 1], opacity: [0.8, 0, 0.8] } : {}}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* ── Chart Panel (Middle Area) ── */}
|
||||||
|
<g>
|
||||||
|
<rect x="124" y="142" width="168" height="120" rx="14" fill="#FCFAFF" stroke="#F0E9F8" strokeWidth="1.2" />
|
||||||
|
<rect x="138" y="156" width="44" height="8" rx="4" fill="#C9B8EC" />
|
||||||
|
|
||||||
|
{/* Gridlines */}
|
||||||
|
<line x1="138" y1="196" x2="278" y2="196" stroke="#EFE7FB" strokeWidth="1" />
|
||||||
|
<line x1="138" y1="222" x2="278" y2="222" stroke="#EFE7FB" strokeWidth="1" />
|
||||||
|
|
||||||
|
{/* Dynamic line graph */}
|
||||||
|
<motion.path
|
||||||
|
d="M140 236 L168 218 L196 224 L224 196 L252 204 L278 176"
|
||||||
|
fill="none"
|
||||||
|
stroke="#6330D6"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
animate={
|
||||||
|
stage === "updating" && animatingIndex === 2
|
||||||
|
? {
|
||||||
|
stroke: ["#6330D6", "#10B981", "#6330D6"],
|
||||||
|
strokeWidth: [2.5, 3.5, 2.5],
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Pulse node */}
|
||||||
|
<motion.circle
|
||||||
|
cx="278"
|
||||||
|
cy="176"
|
||||||
|
r="4"
|
||||||
|
fill="#6330D6"
|
||||||
|
stroke="#fff"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
animate={stage === "updating" && animatingIndex === 2 ? { scale: [1, 1.6, 1] } : {}}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* ── Mini Bar Chart ── */}
|
||||||
|
<g>
|
||||||
|
<rect x="300" y="142" width="80" height="120" rx="14" fill="#FCFAFF" stroke="#F0E9F8" strokeWidth="1.2" />
|
||||||
|
|
||||||
|
{[
|
||||||
|
{ x: 314, h: 34, c: "#DDD0F4" },
|
||||||
|
{ x: 332, h: 52, c: "#B99BEC" },
|
||||||
|
{ x: 350, h: 44, c: "#9B78E4" },
|
||||||
|
{ x: 368, h: 66, c: "#6330D6" },
|
||||||
|
].map((b, bIdx) => {
|
||||||
|
// If index 2 product (price/pricing) updates, scale the heights briefly
|
||||||
|
const isTargetBar = bIdx === 2 || bIdx === 3;
|
||||||
|
const multiplier = stage === "updating" && animatingIndex === 2 && isTargetBar ? 1.25 : 1;
|
||||||
|
const barHeight = b.h * multiplier;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.rect
|
||||||
|
key={b.x}
|
||||||
|
x={b.x}
|
||||||
|
y={250 - barHeight}
|
||||||
|
width="11"
|
||||||
|
height={barHeight}
|
||||||
|
rx="4"
|
||||||
|
fill={b.c}
|
||||||
|
transition={{ type: "spring", stiffness: 100, damping: 12 }}
|
||||||
|
style={{ transformBox: "fill-box", transformOrigin: "bottom" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* ── Floating order card top right (floating) ── */}
|
||||||
|
<motion.g
|
||||||
|
animate={{ y: [0, -6, 0] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 3.6, ease: "easeInOut" }}
|
||||||
|
>
|
||||||
|
<rect x="316" y="24" width="104" height="42" rx="13" fill="#16001D" />
|
||||||
|
<circle cx="336" cy="45" r="10" fill="#7C3AED" />
|
||||||
|
<path d="M332 45 l3 3 l6 -6.5" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none" />
|
||||||
|
<rect x="352" y="38" width="52" height="6" rx="3" fill="#ffffff" opacity="0.9" />
|
||||||
|
<rect x="352" y="49" width="34" height="5" rx="2.5" fill="#C9A2FF" />
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
{/* ── Floating live stock status bottom left (floating) ── */}
|
||||||
|
<motion.g
|
||||||
|
animate={{ y: [0, 6, 0] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 4.2, ease: "easeInOut" }}
|
||||||
|
>
|
||||||
|
<rect x="22" y="196" width="96" height="38" rx="12" fill="#FFFFFF" stroke="#EDE9FE" strokeWidth="1.5" />
|
||||||
|
<circle cx="42" cy="215" r="6" fill="#22C55E" />
|
||||||
|
<rect x="56" y="208" width="48" height="6" rx="3" fill="#16001D" opacity="0.75" />
|
||||||
|
<rect x="56" y="219" width="30" height="5" rx="2.5" fill="#B9A6E0" />
|
||||||
|
</motion.g>
|
||||||
|
|
||||||
|
{/* ── Purple AI Laser Scanning Beam ── */}
|
||||||
|
{stage === "scanning" && (
|
||||||
|
<motion.line
|
||||||
|
x1="45"
|
||||||
|
y1="34"
|
||||||
|
x2="395"
|
||||||
|
y2="34"
|
||||||
|
stroke="#7C3AED"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
initial={{ y: 0, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
y: [0, 240, 0],
|
||||||
|
opacity: [0, 0.85, 0.85, 0],
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.95, ease: "easeInOut" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</motion.svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { motion, useReducedMotion, useScroll, useTransform } from "framer-motion";
|
import { motion, useReducedMotion, useScroll, useTransform } from "framer-motion";
|
||||||
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
||||||
import { DashboardMockup } from "@/components/platform/DeviceShowcase";
|
import { AnimatedDashboard } from "@/components/platform/AnimatedDashboard";
|
||||||
import { ConsumerShowcase } from "@/components/platform/ConsumerShowcase";
|
|
||||||
import { fadeUp, staggerContainer, staggerCards, viewport, viewport20 } from "@/lib/animations";
|
import { fadeUp, staggerContainer, staggerCards, viewport, viewport20 } from "@/lib/animations";
|
||||||
|
|
||||||
export type ProductFeature = {
|
export type ProductFeature = {
|
||||||
@@ -137,7 +136,7 @@ export function ProductFeatureGrid({
|
|||||||
"radial-gradient(circle at 50% 40%, rgba(99,48,214,0.16), transparent 62%)",
|
"radial-gradient(circle at 50% 40%, rgba(99,48,214,0.16), transparent 62%)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isPhone ? <ConsumerShowcase /> : <DashboardMockup />}
|
<AnimatedDashboard />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user