353 lines
15 KiB
TypeScript
353 lines
15 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useReducedMotion } from "framer-motion";
|
|
import { MaterialIcon } from "@/components/ui/MaterialIcon";
|
|
import { Spotlight } from "@/components/ui/Spotlight";
|
|
import { fadeUp, staggerContainer, staggerSlow, viewport } from "@/lib/animations";
|
|
|
|
// 1. Trust Graphic (Emerald Shield & Checkmark)
|
|
function TrustGraphic({ reduce = false }: { reduce?: boolean }) {
|
|
return (
|
|
<div className="relative flex items-center justify-center p-2">
|
|
<motion.div
|
|
className="absolute h-20 w-20 rounded-full bg-emerald-500/20 blur-xl pointer-events-none"
|
|
animate={reduce ? {} : { scale: [0.9, 1.25, 0.9], opacity: [0.25, 0.5, 0.25] }}
|
|
transition={{ duration: 3.5, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<svg
|
|
width="110"
|
|
height="110"
|
|
viewBox="0 0 110 110"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="relative z-10 w-24 h-24 md:w-28 md:h-28 filter drop-shadow-[0_10px_20px_rgba(0,0,0,0.35)] transition-transform duration-300 group-hover:scale-105"
|
|
>
|
|
<motion.path
|
|
d="M 55 12 L 88 24 C 88 56 72 82 55 96 C 38 82 22 56 22 24 L 55 12 Z"
|
|
fill="url(#trust-grad)"
|
|
stroke="#6EE7B7"
|
|
strokeWidth="2"
|
|
animate={reduce ? {} : { y: [0, -4, 0] }}
|
|
transition={{ duration: 3, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<motion.path
|
|
d="M 40 52 L 50 62 L 72 40"
|
|
stroke="#FFFFFF"
|
|
strokeWidth="6"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
animate={reduce ? {} : { opacity: [0.8, 1, 0.8] }}
|
|
transition={{ duration: 2.5, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<defs>
|
|
<linearGradient id="trust-grad" x1="22" y1="12" x2="88" y2="96" gradientUnits="userSpaceOnUse">
|
|
<stop stopColor="#059669" stopOpacity="0.8" />
|
|
<stop offset="1" stopColor="#34D399" stopOpacity="0.4" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 2. AI Graphic (Purple Sparkles Cluster)
|
|
function AIGraphic({ reduce = false }: { reduce?: boolean }) {
|
|
return (
|
|
<div className="relative flex items-center justify-center p-2">
|
|
<motion.div
|
|
className="absolute h-20 w-20 rounded-full bg-[#7C3AED]/25 blur-xl pointer-events-none"
|
|
animate={reduce ? {} : { scale: [0.9, 1.25, 0.9], opacity: [0.25, 0.5, 0.25] }}
|
|
transition={{ duration: 3.5, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<svg
|
|
width="120"
|
|
height="120"
|
|
viewBox="0 0 120 120"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="relative z-10 w-24 h-24 md:w-28 md:h-28 filter drop-shadow-[0_10px_20px_rgba(0,0,0,0.35)] transition-transform duration-300 group-hover:scale-105"
|
|
>
|
|
<motion.path
|
|
d="M 45 10 C 45 29.33 29.33 45 10 45 C 29.33 45 45 60.67 45 80 C 45 60.67 60.67 45 80 45 C 60.67 45 45 29.33 45 10 Z"
|
|
fill="#818CF8"
|
|
style={{ transformOrigin: "45px 45px" }}
|
|
animate={reduce ? {} : { scale: [1, 1.08, 1], rotate: [0, 4, 0, -4, 0] }}
|
|
transition={{ duration: 3.6, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<motion.path
|
|
d="M 88 15 C 88 23 81 30 73 30 C 81 30 88 37 88 45 C 88 37 95 30 103 30 C 95 30 88 23 88 15 Z"
|
|
fill="#C084FC"
|
|
style={{ transformOrigin: "88px 30px" }}
|
|
animate={reduce ? {} : { scale: [0.85, 1.2, 0.85], y: [0, -4, 0] }}
|
|
transition={{ duration: 2.8, delay: 0.4, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<motion.path
|
|
d="M 82 72 C 82 78.6 76.6 84 70 84 C 76.6 84 82 89.4 82 96 C 82 89.4 87.4 84 94 84 C 87.4 84 82 78.6 82 72 Z"
|
|
fill="#F5D0FE"
|
|
style={{ transformOrigin: "82px 84px" }}
|
|
animate={reduce ? {} : { scale: [1.15, 0.8, 1.15], rotate: [0, -8, 0] }}
|
|
transition={{ duration: 3.0, delay: 1.1, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
</svg>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 3. Merchant First Graphic (Golden Storefront & Canopy)
|
|
function MerchantGraphic({ reduce = false }: { reduce?: boolean }) {
|
|
return (
|
|
<div className="relative flex items-center justify-center p-2">
|
|
<motion.div
|
|
className="absolute h-20 w-20 rounded-full bg-amber-500/20 blur-xl pointer-events-none"
|
|
animate={reduce ? {} : { scale: [0.9, 1.25, 0.9], opacity: [0.25, 0.5, 0.25] }}
|
|
transition={{ duration: 3.5, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<svg
|
|
width="110"
|
|
height="110"
|
|
viewBox="0 0 110 110"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="relative z-10 w-24 h-24 md:w-28 md:h-28 filter drop-shadow-[0_10px_20px_rgba(0,0,0,0.35)] transition-transform duration-300 group-hover:scale-105"
|
|
>
|
|
<motion.path
|
|
d="M 20 40 L 90 40 L 82 22 L 28 22 Z"
|
|
fill="#FBBF24"
|
|
animate={reduce ? {} : { y: [0, -3, 0] }}
|
|
transition={{ duration: 3, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<path d="M 34 22 L 30 40 M 48 22 L 46 40 M 62 22 L 62 40 M 76 22 L 78 40" stroke="#D97706" strokeWidth="2.5" />
|
|
<rect x="25" y="40" width="60" height="46" rx="4" fill="url(#merchant-grad)" stroke="#FDE047" strokeWidth="2" />
|
|
<rect x="43" y="56" width="24" height="30" rx="3" fill="#78350F" />
|
|
<circle cx="55" cy="48" r="4" fill="#FDE047" />
|
|
<defs>
|
|
<linearGradient id="merchant-grad" x1="25" y1="40" x2="85" y2="86" gradientUnits="userSpaceOnUse">
|
|
<stop stopColor="#D97706" stopOpacity="0.8" />
|
|
<stop offset="1" stopColor="#F59E0B" stopOpacity="0.4" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 4. Local Graphic (Sky Pin & Radar Pulse)
|
|
function LocalGraphic({ reduce = false }: { reduce?: boolean }) {
|
|
return (
|
|
<div className="relative flex items-center justify-center p-2">
|
|
<motion.div
|
|
className="absolute h-20 w-20 rounded-full bg-sky-500/20 blur-xl pointer-events-none"
|
|
animate={reduce ? {} : { scale: [0.9, 1.25, 0.9], opacity: [0.25, 0.5, 0.25] }}
|
|
transition={{ duration: 3.5, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<svg
|
|
width="110"
|
|
height="110"
|
|
viewBox="0 0 110 110"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="relative z-10 w-24 h-24 md:w-28 md:h-28 filter drop-shadow-[0_10px_20px_rgba(0,0,0,0.35)] transition-transform duration-300 group-hover:scale-105"
|
|
>
|
|
<motion.path
|
|
d="M 55 18 C 40 18 28 30 28 45 C 28 65 55 88 55 88 C 55 88 82 65 82 45 C 82 30 70 18 55 18 Z"
|
|
fill="url(#local-grad)"
|
|
stroke="#7DD3FC"
|
|
strokeWidth="2"
|
|
animate={reduce ? {} : { y: [0, -6, 0] }}
|
|
transition={{ duration: 2.8, repeat: Infinity, ease: "easeInOut" }}
|
|
/>
|
|
<circle cx="55" cy="43" r="9" fill="#0284C7" />
|
|
<circle cx="55" cy="43" r="4" fill="#FFFFFF" />
|
|
<defs>
|
|
<linearGradient id="local-grad" x1="28" y1="18" x2="82" y2="88" gradientUnits="userSpaceOnUse">
|
|
<stop stopColor="#0284C7" stopOpacity="0.8" />
|
|
<stop offset="1" stopColor="#38BDF8" stopOpacity="0.4" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const CARD_MOTES = [
|
|
{ left: "20%", top: "35%", size: 4, duration: 4.5, delay: 0 },
|
|
{ left: "75%", top: "60%", size: 3, duration: 5.5, delay: 0.8 },
|
|
];
|
|
|
|
const PRINCIPLES = [
|
|
{
|
|
word: "Trust",
|
|
icon: "verified_user",
|
|
line: "Local relationships cannot be replaced.",
|
|
badgeStyle: "bg-emerald-500/15 border-emerald-500/30 text-emerald-400",
|
|
dotColor: "bg-emerald-400",
|
|
spotlightColor: "rgba(52,211,153,0.18)",
|
|
renderGraphic: (reduce: boolean) => <TrustGraphic reduce={reduce} />,
|
|
},
|
|
{
|
|
word: "AI",
|
|
icon: "psychology",
|
|
line: "Removes complexity instead of adding more choices.",
|
|
badgeStyle: "bg-[#7C3AED]/20 border-[#7C3AED]/40 text-[#C084FC]",
|
|
dotColor: "bg-[#C084FC]",
|
|
spotlightColor: "rgba(168,85,247,0.20)",
|
|
renderGraphic: (reduce: boolean) => <AIGraphic reduce={reduce} />,
|
|
},
|
|
{
|
|
word: "Merchant First",
|
|
icon: "storefront",
|
|
line: "Technology should empower neighbourhood businesses.",
|
|
badgeStyle: "bg-amber-500/15 border-amber-500/30 text-amber-400",
|
|
dotColor: "bg-amber-400",
|
|
spotlightColor: "rgba(251,191,36,0.18)",
|
|
renderGraphic: (reduce: boolean) => <MerchantGraphic reduce={reduce} />,
|
|
},
|
|
{
|
|
word: "Local",
|
|
icon: "near_me",
|
|
line: "Every recommendation starts with nearby trusted merchants.",
|
|
badgeStyle: "bg-sky-500/15 border-sky-500/30 text-sky-400",
|
|
dotColor: "bg-sky-400",
|
|
spotlightColor: "rgba(56,189,248,0.18)",
|
|
renderGraphic: (reduce: boolean) => <LocalGraphic reduce={reduce} />,
|
|
},
|
|
];
|
|
|
|
export function CorePrinciples() {
|
|
const reduce = useReducedMotion();
|
|
|
|
return (
|
|
<section
|
|
aria-labelledby="core-principles-title"
|
|
className="w-full bg-white px-2 py-6 sm:px-4 md:py-8"
|
|
>
|
|
<div className="mx-auto max-w-[1840px]">
|
|
<div className="section-card-dark relative px-6 py-10 text-white md:px-12 md:py-12 lg:px-14 lg:py-14">
|
|
{/* Ambient purple wash */}
|
|
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
|
|
<div className="absolute left-[-8%] top-[-10%] h-[420px] w-[420px] rounded-full bg-[#7C3AED]/18 blur-[130px]" />
|
|
<div className="absolute right-[-6%] bottom-[-12%] h-[380px] w-[380px] rounded-full bg-[#A855F7]/12 blur-[120px]" />
|
|
</div>
|
|
|
|
<motion.div
|
|
variants={staggerContainer}
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
viewport={viewport}
|
|
className="relative z-10 max-w-[1100px]"
|
|
>
|
|
<motion.span
|
|
variants={fadeUp}
|
|
className="inline-flex items-center gap-2 rounded-full border border-white/15 px-3.5 py-1 text-xs font-black uppercase tracking-widest text-white/70"
|
|
>
|
|
What We Build On
|
|
</motion.span>
|
|
<motion.h2
|
|
id="core-principles-title"
|
|
variants={fadeUp}
|
|
className="mt-3 text-balance font-display font-black tracking-tight"
|
|
style={{ fontSize: "clamp(32px, 3.8vw, 54px)", lineHeight: 1.06 }}
|
|
>
|
|
Four principles behind{" "}
|
|
<span className="text-[#C084FC]">every decision.</span>
|
|
</motion.h2>
|
|
</motion.div>
|
|
|
|
{/* Editorial principle blocks with left text and right distinct SVG graphic */}
|
|
<motion.div
|
|
variants={staggerSlow}
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
viewport={viewport}
|
|
className="relative z-10 mt-8 grid gap-5 overflow-hidden rounded-3xl md:mt-10 md:grid-cols-2 lg:gap-7"
|
|
>
|
|
{PRINCIPLES.map((principle) => (
|
|
<motion.div
|
|
key={principle.word}
|
|
variants={fadeUp}
|
|
whileHover={reduce ? undefined : { y: -5, scale: 1.008 }}
|
|
transition={{ type: "spring", stiffness: 260, damping: 22 }}
|
|
className="group relative h-full"
|
|
>
|
|
<Spotlight
|
|
className="relative overflow-hidden h-full rounded-3xl border border-white/10 bg-[#150320] p-6 md:p-8 lg:p-9 transition-all duration-300 group-hover:border-white/25 group-hover:shadow-[0_20px_40px_-15px_rgba(0,0,0,0.5)]"
|
|
color={principle.spotlightColor}
|
|
size={460}
|
|
>
|
|
{/* Floating Flying Particle Glow Dots (Motes) - Shown ONLY on Hover */}
|
|
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden z-0 opacity-0 transition-opacity duration-500 group-hover:opacity-100">
|
|
{CARD_MOTES.map((mote, idx) => (
|
|
<motion.span
|
|
key={idx}
|
|
className={`absolute rounded-full ${principle.dotColor} shadow-[0_0_8px_currentColor]`}
|
|
style={{
|
|
left: mote.left,
|
|
top: mote.top,
|
|
width: mote.size,
|
|
height: mote.size,
|
|
}}
|
|
animate={
|
|
reduce
|
|
? {}
|
|
: {
|
|
y: [0, -12, 0],
|
|
x: [0, 6, 0],
|
|
opacity: [0.3, 0.9, 0.3],
|
|
scale: [0.8, 1.2, 0.8],
|
|
}
|
|
}
|
|
transition={{
|
|
duration: mote.duration,
|
|
delay: mote.delay,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Corner Hover Glowing Dot */}
|
|
<div className="absolute top-5 right-5 z-20 flex items-center gap-2 opacity-50 transition-opacity duration-300 group-hover:opacity-100">
|
|
<span className="relative flex h-2.5 w-2.5">
|
|
<span className={`absolute inline-flex h-full w-full animate-ping rounded-full ${principle.dotColor} opacity-75`} />
|
|
<span className={`relative inline-flex h-2.5 w-2.5 rounded-full ${principle.dotColor} shadow-[0_0_8px_currentColor]`} />
|
|
</span>
|
|
</div>
|
|
|
|
{/* Ambient Corner Glow Orb */}
|
|
<div
|
|
aria-hidden="true"
|
|
className={`pointer-events-none absolute -bottom-10 -right-10 h-44 w-44 rounded-full blur-3xl opacity-20 transition-all duration-500 group-hover:scale-150 group-hover:opacity-50 ${principle.dotColor}`}
|
|
/>
|
|
|
|
<div className="relative z-10 flex items-center justify-between gap-6">
|
|
{/* Left Column: Badge + Title + Description */}
|
|
<div className="flex-1 min-w-0 pr-2">
|
|
<span className={`inline-flex h-10 w-10 items-center justify-center rounded-2xl border ${principle.badgeStyle} mb-4 shadow-sm transition-transform duration-300 group-hover:scale-110`}>
|
|
<MaterialIcon icon={principle.icon} size={20} />
|
|
</span>
|
|
|
|
<h3 className="font-display font-black text-white text-xl md:text-2xl lg:text-3xl tracking-tight mb-2">
|
|
{principle.word}
|
|
</h3>
|
|
|
|
<p className="text-sm md:text-base leading-relaxed text-white/75 max-w-[500px]">
|
|
{principle.line}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Right Column: Tailored Animated SVG Graphic */}
|
|
<div className="shrink-0 flex items-center justify-center self-center">
|
|
{principle.renderGraphic(!!reduce)}
|
|
</div>
|
|
</div>
|
|
</Spotlight>
|
|
</motion.div>
|
|
))}
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|