product feature grid
This commit is contained in:
118
src/components/platform/ConsumerFeatures.tsx
Normal file
118
src/components/platform/ConsumerFeatures.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { fadeUp, staggerContainer, viewport } from "@/lib/animations";
|
||||
import { PhoneMockup } from "./PhoneMockup";
|
||||
import { FeatureGrid } from "./FeatureGrid";
|
||||
|
||||
const getScreenFromCardIndex = (cardIndex: number): number => {
|
||||
switch (cardIndex) {
|
||||
case 0: // Discover Nearby Stores
|
||||
case 4: // Favorites
|
||||
return 0; // Nearby Stores Screen
|
||||
case 1: // Camera Search
|
||||
return 1; // Product Search Screen
|
||||
case 3: // Rewards
|
||||
return 2; // Rewards Screen
|
||||
case 2: // Pickup & Delivery
|
||||
case 5: // Quick Reordering
|
||||
return 3; // Orders Screen
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export function ConsumerFeatures() {
|
||||
const reduce = useReducedMotion();
|
||||
const [activeScreen, setActiveScreen] = useState(0);
|
||||
const [hoveredCardIndex, setHoveredCardIndex] = useState<number | null>(null);
|
||||
|
||||
// Auto-cycling when no card is hovered
|
||||
useEffect(() => {
|
||||
if (reduce || hoveredCardIndex !== null) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setActiveScreen((prev) => (prev + 1) % 4);
|
||||
}, 4500); // Cycle screen every 4.5 seconds
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [hoveredCardIndex, reduce]);
|
||||
|
||||
// Handle card hover updates
|
||||
const handleHoverCard = (index: number | null) => {
|
||||
setHoveredCardIndex(index);
|
||||
if (index !== null) {
|
||||
setActiveScreen(getScreenFromCardIndex(index));
|
||||
}
|
||||
};
|
||||
|
||||
// Determine active card highlighted in grid (reflecting current active screen when not hovering)
|
||||
const activeCardIndex =
|
||||
hoveredCardIndex !== null
|
||||
? hoveredCardIndex
|
||||
: activeScreen === 0
|
||||
? 0
|
||||
: activeScreen === 1
|
||||
? 1
|
||||
: activeScreen === 2
|
||||
? 3
|
||||
: activeScreen === 3
|
||||
? 2
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<section
|
||||
id="consumeros"
|
||||
className="section w-full border-b border-[#6330D6]/10 overflow-hidden bg-[#faf7ff]"
|
||||
>
|
||||
<div className="page-container">
|
||||
{/* Header Block (Mirrors the MerchantOS header styling) */}
|
||||
<motion.div
|
||||
variants={staggerContainer}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewport}
|
||||
className="max-w-[760px]"
|
||||
>
|
||||
<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]"
|
||||
>
|
||||
ConsumerOS
|
||||
</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 }}
|
||||
>
|
||||
How the neighbourhood shops
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
variants={fadeUp}
|
||||
className="text-[#5E5866] mt-4"
|
||||
style={{ fontSize: 18, lineHeight: 1.65 }}
|
||||
>
|
||||
Discovery, search, ordering, and rewards — the app that brings local customers to nearby stores.
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
|
||||
{/* Dynamic Showcase Grid */}
|
||||
<div className="mt-16 grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-16 items-center">
|
||||
{/* LEFT SIDE: Smartphone mockup */}
|
||||
<div className="lg:col-span-5 w-full flex justify-center items-center relative py-6 order-1">
|
||||
<PhoneMockup activeScreen={activeScreen} />
|
||||
</div>
|
||||
|
||||
{/* RIGHT SIDE: Feature list grid */}
|
||||
<div className="lg:col-span-7 w-full order-2">
|
||||
<FeatureGrid
|
||||
activeCardIndex={activeCardIndex}
|
||||
setActiveCardIndex={handleHoverCard}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
70
src/components/platform/FeatureCard.tsx
Normal file
70
src/components/platform/FeatureCard.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
||||
|
||||
type FeatureCardProps = {
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
isActive: boolean;
|
||||
onHoverStart: () => void;
|
||||
onHoverEnd: () => void;
|
||||
};
|
||||
|
||||
export function FeatureCard({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
isActive,
|
||||
onHoverStart,
|
||||
onHoverEnd,
|
||||
}: FeatureCardProps) {
|
||||
// Entrance animations are orchestrated by FeatureGrid, but we manage our interactive transitions here
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={onHoverStart}
|
||||
onMouseLeave={onHoverEnd}
|
||||
onClick={onHoverStart} // Support tap on mobile devices to switch
|
||||
className={`group relative cursor-pointer rounded-2xl p-6 transition-all duration-300 ${
|
||||
isActive
|
||||
? "bg-[#65149A]/[0.02] border-[#65149A] shadow-[0_10px_25px_rgba(99,48,214,0.06)] -translate-y-1"
|
||||
: "bg-white border-[#6330D6]/10 hover:border-[#6330D6]/30 shadow-[0_4px_18px_rgba(22,0,29,0.02)] hover:shadow-[0_12px_28px_rgba(22,0,29,0.06)] hover:-translate-y-1"
|
||||
} border-[1.5px]`}
|
||||
>
|
||||
{/* Accent glow on card hover or active */}
|
||||
<div
|
||||
className={`absolute inset-0 -z-10 rounded-2xl bg-gradient-to-tr from-[#6330D6]/[0.02] to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 ${
|
||||
isActive ? "opacity-100" : ""
|
||||
}`}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
{/* Purple circular icon */}
|
||||
<div
|
||||
className={`flex h-12 w-12 shrink-0 items-center justify-center rounded-full transition-all duration-300 ${
|
||||
isActive
|
||||
? "bg-[#65149A] text-white rotate-12 scale-110"
|
||||
: "bg-[#6330D6]/[0.08] text-[#6330D6] group-hover:bg-[#65149A] group-hover:text-white group-hover:rotate-12 group-hover:scale-110"
|
||||
}`}
|
||||
>
|
||||
<MaterialIcon icon={icon} size={22} />
|
||||
</div>
|
||||
|
||||
{/* Text details */}
|
||||
<div>
|
||||
<h3
|
||||
className={`font-display text-[17px] font-black tracking-tight transition-colors duration-200 ${
|
||||
isActive ? "text-[#65149A]" : "text-[#16001D] group-hover:text-[#65149A]"
|
||||
}`}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-[#5E5866]">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
98
src/components/platform/FeatureGrid.tsx
Normal file
98
src/components/platform/FeatureGrid.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { FeatureCard } from "./FeatureCard";
|
||||
|
||||
type Feature = {
|
||||
icon: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const FEATURES: Feature[] = [
|
||||
{
|
||||
icon: "explore",
|
||||
title: "Discover Nearby Stores",
|
||||
description: "Find trusted local businesses around you.",
|
||||
},
|
||||
{
|
||||
icon: "photo_camera",
|
||||
title: "Camera Search",
|
||||
description: "Scan products and instantly find nearby availability.",
|
||||
},
|
||||
{
|
||||
icon: "local_shipping",
|
||||
title: "Pickup & Delivery",
|
||||
description: "Choose how you want to receive your order.",
|
||||
},
|
||||
{
|
||||
icon: "redeem",
|
||||
title: "Rewards",
|
||||
description: "Earn points every time you support local businesses.",
|
||||
},
|
||||
{
|
||||
icon: "favorite",
|
||||
title: "Favorites",
|
||||
description: "Save your preferred stores and products.",
|
||||
},
|
||||
{
|
||||
icon: "history",
|
||||
title: "Quick Reordering",
|
||||
description: "Buy your regular products again in seconds.",
|
||||
},
|
||||
];
|
||||
|
||||
type FeatureGridProps = {
|
||||
activeCardIndex: number | null;
|
||||
setActiveCardIndex: (index: number | null) => void;
|
||||
};
|
||||
|
||||
// Animation variants for staggered fade-up on scroll
|
||||
const containerVariants = {
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: {
|
||||
staggerChildren: 0.1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 25,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function FeatureGrid({ activeCardIndex, setActiveCardIndex }: FeatureGridProps) {
|
||||
return (
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.15 }}
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-6 lg:gap-8 w-full"
|
||||
>
|
||||
{FEATURES.map((feature, idx) => (
|
||||
<motion.div key={idx} variants={itemVariants}>
|
||||
<FeatureCard
|
||||
icon={feature.icon}
|
||||
title={feature.title}
|
||||
description={feature.description}
|
||||
isActive={activeCardIndex === idx}
|
||||
onHoverStart={() => setActiveCardIndex(idx)}
|
||||
onHoverEnd={() => setActiveCardIndex(null)}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
524
src/components/platform/PhoneMockup.tsx
Normal file
524
src/components/platform/PhoneMockup.tsx
Normal file
@@ -0,0 +1,524 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
||||
|
||||
type PhoneMockupProps = {
|
||||
activeScreen: number;
|
||||
};
|
||||
|
||||
export function PhoneMockup({ activeScreen }: PhoneMockupProps) {
|
||||
// Screens definitions
|
||||
const screens = [
|
||||
// 0: Nearby Stores
|
||||
{
|
||||
id: "nearby-stores",
|
||||
content: (
|
||||
<div className="flex h-full flex-col bg-[#FDFBFF] text-[#16001D]">
|
||||
{/* Header */}
|
||||
<div className="bg-[#65149A] px-4 pt-8 pb-3 text-white">
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-bold opacity-90">
|
||||
<MaterialIcon icon="location_on" size={12} className="text-white shrink-0" />
|
||||
<span className="truncate">R.S. Puram, Coimbatore</span>
|
||||
<MaterialIcon icon="keyboard_arrow_down" size={12} className="text-white shrink-0" />
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
<span className="font-display text-lg font-black tracking-tight">nearle</span>
|
||||
<div className="relative flex h-7 w-7 items-center justify-center rounded-full bg-white/20">
|
||||
<MaterialIcon icon="notifications" size={16} />
|
||||
<span className="absolute top-1.5 right-1.5 h-1.5 w-1.5 rounded-full bg-[#22C55E]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search bar */}
|
||||
<div className="px-4 py-3">
|
||||
<div className="flex items-center gap-2 rounded-full border border-[#6330D6]/15 bg-white px-3.5 py-1.5 shadow-sm">
|
||||
<MaterialIcon icon="search" size={14} className="text-[#6330D6]/60 shrink-0" />
|
||||
<span className="text-[10px] text-[#5E5866]/70 truncate">Search stores & products...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Categories */}
|
||||
<div className="px-4 py-1">
|
||||
<div className="flex gap-2 overflow-x-hidden pb-1">
|
||||
{[
|
||||
{ name: "Sweets", active: true },
|
||||
{ name: "Grocery", active: false },
|
||||
{ name: "Veggies", active: false },
|
||||
{ name: "Bakery", active: false },
|
||||
].map((cat, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className={`rounded-full px-2.5 py-0.5 text-[9px] font-bold transition-colors shrink-0 ${
|
||||
cat.active
|
||||
? "bg-[#65149A] text-white"
|
||||
: "bg-[#6330D6]/5 text-[#6330D6]"
|
||||
}`}
|
||||
>
|
||||
{cat.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stores List */}
|
||||
<div className="flex-1 overflow-y-auto px-4 py-2 space-y-3">
|
||||
{[
|
||||
{
|
||||
name: "Sri Krishna Sweets",
|
||||
rating: "4.8",
|
||||
distance: "300m away",
|
||||
desc: "Mysore Pak, Laddu & Savouries",
|
||||
imgBg: "bg-purple-100",
|
||||
imgText: "🍰",
|
||||
},
|
||||
{
|
||||
name: "Organic Greens Store",
|
||||
rating: "4.9",
|
||||
distance: "600m away",
|
||||
desc: "Fresh Farm Spinach & Organic Veggies",
|
||||
imgBg: "bg-emerald-100",
|
||||
imgText: "🥬",
|
||||
},
|
||||
{
|
||||
name: "A1 Biryani House",
|
||||
rating: "4.7",
|
||||
distance: "850m away",
|
||||
desc: "Local Spiced Biryani & Kebabs",
|
||||
imgBg: "bg-orange-100",
|
||||
imgText: "🍗",
|
||||
},
|
||||
].map((store, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-3 rounded-xl border border-[#6330D6]/5 bg-white p-2.5 shadow-sm transition-transform hover:scale-[1.02]"
|
||||
>
|
||||
<div className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${store.imgBg} text-lg`}>
|
||||
{store.imgText}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="truncate font-display text-[11px] font-black text-[#16001D]">
|
||||
{store.name}
|
||||
</h4>
|
||||
<span className="flex items-center text-[9px] font-bold text-[#E59900] shrink-0">
|
||||
★ {store.rating}
|
||||
</span>
|
||||
</div>
|
||||
<p className="truncate text-[9px] text-[#5E5866]/80">{store.desc}</p>
|
||||
<span className="mt-1 inline-block text-[9px] font-bold text-[#6330D6]/80">
|
||||
{store.distance}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bottom Navigation Bar */}
|
||||
<div className="flex border-t border-[#6330D6]/10 bg-white py-1.5 justify-around items-center shrink-0">
|
||||
<div className="flex flex-col items-center text-[#65149A]">
|
||||
<MaterialIcon icon="explore" size={16} fill />
|
||||
<span className="text-[8px] font-bold mt-0.5">Explore</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="favorite" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Favorites</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="redeem" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Rewards</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="shopping_bag" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Orders</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
// 1: Product Search (Camera scanning)
|
||||
{
|
||||
id: "product-search",
|
||||
content: (
|
||||
<div className="flex h-full flex-col bg-neutral-900 text-white relative overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="absolute top-0 left-0 right-0 z-10 px-4 pt-8 pb-3 bg-gradient-to-b from-black/60 to-transparent flex items-center justify-between">
|
||||
<span className="text-[11px] font-bold tracking-tight">Camera Search</span>
|
||||
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-black/40">
|
||||
<MaterialIcon icon="close" size={14} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scanning frame area */}
|
||||
<div className="flex-1 flex flex-col items-center justify-center p-6 relative">
|
||||
{/* Camera mock elements */}
|
||||
<div className="w-40 h-40 border-2 border-dashed border-white/40 rounded-3xl relative flex items-center justify-center">
|
||||
{/* Corner brackets */}
|
||||
<div className="absolute top-[-2px] left-[-2px] w-6 h-6 border-t-4 border-l-4 border-[#7C3AED] rounded-tl-xl" />
|
||||
<div className="absolute top-[-2px] right-[-2px] w-6 h-6 border-t-4 border-r-4 border-[#7C3AED] rounded-tr-xl" />
|
||||
<div className="absolute bottom-[-2px] left-[-2px] w-6 h-6 border-b-4 border-l-4 border-[#7C3AED] rounded-bl-xl" />
|
||||
<div className="absolute bottom-[-2px] right-[-2px] w-6 h-6 border-b-4 border-r-4 border-[#7C3AED] rounded-br-xl" />
|
||||
|
||||
{/* Scanning laser line animation */}
|
||||
<motion.div
|
||||
animate={{ top: ["10%", "90%", "10%"] }}
|
||||
transition={{ duration: 2.2, repeat: Infinity, ease: "easeInOut" }}
|
||||
className="absolute left-[5%] right-[5%] h-[2px] bg-[#7C3AED] shadow-[0_0_12px_#7C3AED] z-10"
|
||||
/>
|
||||
|
||||
{/* Mock product inside */}
|
||||
<div className="text-4xl filter blur-[0.5px]">🍪</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[9px] text-white/70 mt-6 bg-black/50 px-3 py-1 rounded-full font-medium">
|
||||
Point at any item label
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Bottom sheet showing scanning result */}
|
||||
<motion.div
|
||||
initial={{ y: 50, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="bg-white text-[#16001D] p-3 rounded-t-2xl z-10 border-t border-[#6330D6]/10 shrink-0"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="h-10 w-10 shrink-0 flex items-center justify-center bg-purple-50 rounded-lg text-lg">
|
||||
🍪
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[9px] uppercase font-bold text-[#6330D6] bg-[#6330D6]/10 px-2 py-0.5 rounded-full shrink-0">
|
||||
Match Found
|
||||
</span>
|
||||
<span className="text-[9px] text-[#22C55E] font-bold shrink-0">200m away</span>
|
||||
</div>
|
||||
<h4 className="text-[11px] font-black truncate mt-1 text-[#16001D]">Chocolate Chip Cookies</h4>
|
||||
<p className="text-[9px] text-[#5E5866] mt-0.5 truncate">Available at Sri Krishna Bakery</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex gap-2">
|
||||
<button className="flex-1 bg-[#65149A] text-white text-[10px] font-bold py-1.5 rounded-lg">
|
||||
Buy for ₹80
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
// 2: Rewards
|
||||
{
|
||||
id: "rewards",
|
||||
content: (
|
||||
<div className="flex h-full flex-col bg-[#FAF8FC] text-[#16001D]">
|
||||
{/* Header */}
|
||||
<div className="px-4 pt-8 pb-3 bg-white border-b border-[#6330D6]/5 flex items-center justify-between shrink-0">
|
||||
<span className="font-display text-xs font-black tracking-tight text-[#65149A]">Nearle Club</span>
|
||||
<span className="text-[9px] font-bold text-[#65149A] bg-[#65149A]/5 px-2.5 py-0.5 rounded-full">
|
||||
Gold Tier
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Points Card */}
|
||||
<div className="p-4 shrink-0">
|
||||
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-tr from-[#65149A] to-[#8B5CF6] p-4 text-white shadow-md">
|
||||
{/* Decorative background shape */}
|
||||
<div className="absolute right-[-20px] top-[-20px] h-28 w-28 rounded-full bg-white/10 blur-xl pointer-events-none" />
|
||||
|
||||
<span className="text-[9px] uppercase font-bold tracking-wider opacity-85">Available Points</span>
|
||||
<h2 className="font-display text-2xl font-black mt-1">2,450</h2>
|
||||
|
||||
<div className="mt-3 flex items-center justify-between text-[9px] opacity-90">
|
||||
<span>Next reward at 3,000 pts</span>
|
||||
<span>81%</span>
|
||||
</div>
|
||||
<div className="mt-1 h-1.5 w-full rounded-full bg-white/20 overflow-hidden">
|
||||
<div className="h-full w-[81%] rounded-full bg-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active Offers */}
|
||||
<div className="flex-1 overflow-y-auto px-4 pb-2 space-y-2.5">
|
||||
<h4 className="text-[9px] font-black uppercase tracking-wider text-[#5E5866] mb-1.5">
|
||||
Available Rewards
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{
|
||||
title: "₹50 Off sweets",
|
||||
store: "Sri Krishna Sweets",
|
||||
points: "500 pts",
|
||||
icon: "🍰",
|
||||
},
|
||||
{
|
||||
title: "Free Organic Honey",
|
||||
store: "Organic Greens Store",
|
||||
points: "1200 pts",
|
||||
icon: "🍯",
|
||||
},
|
||||
{
|
||||
title: "₹100 Off order",
|
||||
store: "A1 Biryani House",
|
||||
points: "900 pts",
|
||||
icon: "🍗",
|
||||
},
|
||||
].map((reward, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between rounded-xl border border-dashed border-[#6330D6]/20 bg-white p-2.5"
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className="text-lg shrink-0">{reward.icon}</span>
|
||||
<div className="min-w-0">
|
||||
<h5 className="text-[10px] font-bold text-[#16001D] truncate">{reward.title}</h5>
|
||||
<p className="text-[8px] text-[#5E5866] truncate">{reward.store}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button className="rounded-lg bg-[#6330D6]/10 px-2 py-0.5 text-[8px] font-black text-[#6330D6] shrink-0">
|
||||
{reward.points}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Nav Bar */}
|
||||
<div className="flex border-t border-[#6330D6]/10 bg-white py-1.5 justify-around items-center shrink-0">
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="explore" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Explore</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="favorite" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Favorites</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#65149A]">
|
||||
<MaterialIcon icon="redeem" size={16} fill />
|
||||
<span className="text-[8px] font-bold mt-0.5">Rewards</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="shopping_bag" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Orders</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
// 3: Orders (Pickup/Delivery & Reordering)
|
||||
{
|
||||
id: "orders",
|
||||
content: (
|
||||
<div className="flex h-full flex-col bg-[#FDFBFF] text-[#16001D]">
|
||||
{/* Header */}
|
||||
<div className="px-4 pt-8 pb-3 bg-white border-b border-[#6330D6]/5 flex items-center justify-between shrink-0">
|
||||
<span className="font-display text-xs font-black tracking-tight text-[#16001D]">Track Order</span>
|
||||
<span className="text-[9px] font-bold text-[#22C55E] bg-[#22C55E]/10 px-2.5 py-0.5 rounded-full shrink-0">
|
||||
Out for Delivery
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Map simulation */}
|
||||
<div className="h-28 bg-[#EEF4FF] relative overflow-hidden shrink-0">
|
||||
{/* Roads */}
|
||||
<svg className="absolute inset-0 w-full h-full" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M-10 40 h300 M50 0 v200 M180 0 v200" stroke="white" strokeWidth="6" />
|
||||
<path d="M-10 40 h300 M50 0 v200 M180 0 v200" stroke="#E2E8F0" strokeWidth="1" strokeDasharray="3 3" />
|
||||
|
||||
{/* Route */}
|
||||
<motion.path
|
||||
d="M50 80 L180 80 L180 30"
|
||||
fill="none"
|
||||
stroke="#6330D6"
|
||||
strokeWidth="3.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 4, repeat: Infinity, ease: "linear" }}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* Store Pin */}
|
||||
<div className="absolute left-[38px] top-[68px] flex h-6 w-6 items-center justify-center rounded-full bg-[#65149A] text-white shadow-md">
|
||||
<MaterialIcon icon="store" size={11} className="text-white" />
|
||||
</div>
|
||||
|
||||
{/* Delivery Biker Pin */}
|
||||
<motion.div
|
||||
animate={{
|
||||
left: ["50px", "180px", "180px"],
|
||||
top: ["80px", "80px", "30px"],
|
||||
}}
|
||||
transition={{ duration: 4, repeat: Infinity, ease: "linear" }}
|
||||
className="absolute -translate-x-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full bg-[#22C55E] text-white shadow-md border border-white"
|
||||
>
|
||||
<MaterialIcon icon="local_shipping" size={9} className="text-white" />
|
||||
</motion.div>
|
||||
|
||||
{/* Customer Pin */}
|
||||
<div className="absolute left-[168px] top-[18px] flex h-6 w-6 items-center justify-center rounded-full bg-[#E59900] text-white shadow-md animate-pulse">
|
||||
<MaterialIcon icon="location_on" size={11} className="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Order Details */}
|
||||
<div className="flex-1 p-4 space-y-3 overflow-y-auto">
|
||||
<div className="rounded-xl border border-[#6330D6]/5 bg-white p-3 shadow-sm">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 className="text-[11px] font-black text-[#16001D]">Sri Krishna Sweets</h4>
|
||||
<p className="text-[8px] text-[#5E5866] mt-0.5">Order #88392 • Delivery in 12 mins</p>
|
||||
</div>
|
||||
<span className="text-[11px] font-bold text-[#6330D6] shrink-0">₹240</span>
|
||||
</div>
|
||||
<div className="mt-2.5 pt-2.5 border-t border-[#6330D6]/5 text-[9px] text-[#5E5866] space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<span className="truncate">1x Special Mysore Pak (250g)</span>
|
||||
<span className="shrink-0 ml-1">₹160</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="truncate">1x Fine Laddu (4 pcs)</span>
|
||||
<span className="shrink-0 ml-1">₹80</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Reorder Section */}
|
||||
<div className="rounded-xl border border-[#6330D6]/5 bg-white p-3 shadow-sm">
|
||||
<span className="text-[9px] uppercase font-black text-[#5E5866] tracking-wider block mb-2">
|
||||
Order Again
|
||||
</span>
|
||||
<div className="flex items-center justify-between bg-[#65149A]/5 p-2 rounded-lg gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<h5 className="text-[9px] font-black truncate text-[#65149A]">Last ordered 3 days ago</h5>
|
||||
<p className="text-[8px] text-[#5E5866] truncate">Mysore Pak from Sri Krishna Sweets</p>
|
||||
</div>
|
||||
<button className="bg-[#65149A] hover:bg-[#520f7d] transition-colors text-white text-[9px] font-bold px-2 py-0.5 rounded shrink-0 flex items-center gap-1">
|
||||
<MaterialIcon icon="history" size={10} className="text-white" />
|
||||
<span>Reorder</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Nav Bar */}
|
||||
<div className="flex border-t border-[#6330D6]/10 bg-white py-1.5 justify-around items-center shrink-0">
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="explore" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Explore</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="favorite" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Favorites</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#5E5866]/60">
|
||||
<MaterialIcon icon="redeem" size={16} />
|
||||
<span className="text-[8px] mt-0.5">Rewards</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-[#65149A]">
|
||||
<MaterialIcon icon="shopping_bag" size={16} fill />
|
||||
<span className="text-[8px] font-bold mt-0.5">Orders</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto flex w-full max-w-[280px] sm:max-w-[310px] items-center justify-center">
|
||||
{/* Decorative ambient background glows */}
|
||||
<div className="absolute inset-0 -z-10 bg-gradient-to-tr from-purple-500/20 to-[#6330D6]/20 blur-3xl rounded-full transform scale-110 pointer-events-none" />
|
||||
|
||||
{/* Phone chassis */}
|
||||
<motion.div
|
||||
animate={{
|
||||
y: [-8, 8, -8],
|
||||
rotate: [-0.5, 0.5, -0.5]
|
||||
}}
|
||||
transition={{
|
||||
repeat: Infinity,
|
||||
duration: 6,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className="w-full relative z-10 border-[10px] sm:border-[12px] border-neutral-900 rounded-[38px] sm:rounded-[44px] shadow-[0_24px_50px_rgba(22,0,29,0.15)] bg-neutral-950 aspect-[9/18.8] overflow-hidden select-none"
|
||||
>
|
||||
{/* Dynamic Island */}
|
||||
<div className="absolute top-2.5 sm:top-3.5 left-1/2 -translate-x-1/2 w-20 sm:w-24 h-4 sm:h-5 bg-neutral-900 rounded-full z-30 flex items-center justify-end px-3">
|
||||
<span className="h-1 w-1 sm:h-1.5 sm:w-1.5 rounded-full bg-emerald-500 animate-pulse mr-0.5" />
|
||||
<span className="h-1 w-1 rounded-full bg-blue-600/60" />
|
||||
</div>
|
||||
|
||||
{/* Gloss overlay reflection */}
|
||||
<div className="absolute inset-0 pointer-events-none bg-gradient-to-tr from-transparent via-white/[0.04] to-transparent z-25 rounded-[26px] sm:rounded-[32px]" />
|
||||
|
||||
{/* Screens container */}
|
||||
<div className="w-full h-full rounded-[26px] sm:rounded-[32px] overflow-hidden bg-white relative">
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={activeScreen}
|
||||
initial={{ opacity: 0, scale: 0.96 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 1.04 }}
|
||||
transition={{ duration: 0.45, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="absolute inset-0 w-full h-full"
|
||||
>
|
||||
{screens[activeScreen]?.content}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Small floating decorative location pins and subtle connection dots */}
|
||||
<div className="absolute -left-8 top-1/4 z-20 pointer-events-none hidden sm:block">
|
||||
<motion.div
|
||||
animate={{ y: [-5, 5, -5] }}
|
||||
transition={{ repeat: Infinity, duration: 4.5, ease: "easeInOut" }}
|
||||
className="flex items-center gap-1.5 rounded-full bg-white px-2.5 py-1 shadow-md border border-[#6330D6]/10"
|
||||
>
|
||||
<span className="text-xs">📍</span>
|
||||
<span className="text-[9px] font-black text-[#16001D]">300m away</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="absolute -right-10 bottom-1/3 z-20 pointer-events-none hidden sm:block">
|
||||
<motion.div
|
||||
animate={{ y: [5, -5, 5] }}
|
||||
transition={{ repeat: Infinity, duration: 5, ease: "easeInOut" }}
|
||||
className="flex items-center gap-1.5 rounded-full bg-white px-2.5 py-1 shadow-md border border-[#6330D6]/10"
|
||||
>
|
||||
<span className="text-xs">🎁</span>
|
||||
<span className="text-[9px] font-black text-[#16001D]">+50 pts</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Decorative dot connectors */}
|
||||
<svg className="absolute inset-0 w-full h-full -z-10 pointer-events-none overflow-visible hidden sm:block" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* Left connector */}
|
||||
<motion.path
|
||||
d="M -30 110 Q -5 130 5 100"
|
||||
fill="none"
|
||||
stroke="#6330D6"
|
||||
strokeWidth="1"
|
||||
strokeDasharray="3 3"
|
||||
opacity="0.25"
|
||||
/>
|
||||
<circle cx="-30" cy="110" r="2.5" fill="#6330D6" opacity="0.4" />
|
||||
|
||||
{/* Right connector */}
|
||||
<motion.path
|
||||
d="M 330 220 Q 300 240 280 210"
|
||||
fill="none"
|
||||
stroke="#6330D6"
|
||||
strokeWidth="1"
|
||||
strokeDasharray="3 3"
|
||||
opacity="0.25"
|
||||
/>
|
||||
<circle cx="330" cy="220" r="2.5" fill="#6330D6" opacity="0.4" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -91,11 +91,42 @@ export function ProductFeatureGrid({
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-12 grid items-center gap-10 lg:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)] lg:gap-16">
|
||||
{/* ── Animated device illustration (parallax) ── */}
|
||||
<div className="mt-12 grid items-center gap-10 lg:grid-cols-12 lg:gap-16">
|
||||
{/* ── Compact 2-Column Feature Grid (Left on Desktop) ── */}
|
||||
<motion.div
|
||||
variants={staggerCards}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewport20}
|
||||
className="lg:col-span-7 order-1"
|
||||
>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{features.map((f) => (
|
||||
<motion.div
|
||||
key={f.title}
|
||||
variants={rowVariant}
|
||||
className="group relative flex flex-col gap-3 rounded-2xl border border-[#6330D6]/10 bg-white p-5 shadow-[0_4px_16px_rgba(22,0,29,0.02)] transition-all duration-300 hover:-translate-y-1 hover:border-[#6330D6]/25 hover:shadow-[0_10px_24px_rgba(22,0,29,0.05)]"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="relative z-10 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-[#6330D6]/[0.08] text-[#6330D6] transition-all duration-300 group-hover:scale-110 group-hover:bg-[#6330D6] group-hover:text-white">
|
||||
<MaterialIcon icon={f.icon} size={18} />
|
||||
</span>
|
||||
<h3 className="font-display font-black text-[#16001D] text-[15px] leading-tight transition-colors duration-200 group-hover:text-[#65149A]">
|
||||
{f.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-[13px] leading-relaxed text-[#5E5866]">
|
||||
{f.desc}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* ── Animated device illustration (Right on Desktop) ── */}
|
||||
<motion.div
|
||||
style={{ y: mediaY }}
|
||||
className={`relative mx-auto w-full ${isPhone ? "max-w-[600px]" : "max-w-[440px]"} ${mediaRight ? "lg:order-2" : "lg:order-1"}`}
|
||||
className={`lg:col-span-5 order-2 relative mx-auto w-full ${isPhone ? "max-w-[600px]" : "max-w-[440px]"}`}
|
||||
>
|
||||
{/* ambient glow */}
|
||||
<div
|
||||
@@ -108,40 +139,6 @@ export function ProductFeatureGrid({
|
||||
/>
|
||||
{isPhone ? <ConsumerShowcase /> : <DashboardMockup />}
|
||||
</motion.div>
|
||||
|
||||
{/* ── Editorial feature list (was a 4-col card grid) ── */}
|
||||
<motion.ul
|
||||
variants={staggerCards}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={viewport20}
|
||||
className={`relative ${mediaRight ? "lg:order-1" : "lg:order-2"}`}
|
||||
>
|
||||
{/* connection line threading the icons */}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute left-[23px] top-6 bottom-6 w-px bg-gradient-to-b from-[#6330D6]/25 via-[#6330D6]/12 to-transparent"
|
||||
/>
|
||||
{features.map((f) => (
|
||||
<motion.li
|
||||
key={f.title}
|
||||
variants={rowVariant}
|
||||
className="group relative flex items-start gap-4 rounded-2xl px-3 py-3.5 transition-colors duration-200 hover:bg-[#6330D6]/[0.04]"
|
||||
>
|
||||
<span className="relative z-10 mt-0.5 flex h-[38px] w-[38px] shrink-0 items-center justify-center rounded-xl bg-[#6330D6]/[0.08] text-[#6330D6] transition-transform duration-200 group-hover:scale-110 group-hover:bg-[#6330D6] group-hover:text-white">
|
||||
<MaterialIcon icon={f.icon} size={20} />
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block font-display font-black text-[#16001D] text-[17px] leading-tight">
|
||||
{f.title}
|
||||
</span>
|
||||
<span className="mt-1 block text-[14px] leading-relaxed text-[#5E5866]">
|
||||
{f.desc}
|
||||
</span>
|
||||
</span>
|
||||
</motion.li>
|
||||
))}
|
||||
</motion.ul>
|
||||
</div>
|
||||
|
||||
{footnote && (
|
||||
|
||||
Reference in New Issue
Block a user