forked from emre/www_projectmycelium_com
feat: add Pods capabilities and how-it-works sections
- Created PodsCapabilities component showcasing four key features (communicate, organization, manage, storage) with icon-based layout - Added PodsHow section with CloudPods animation demonstrating pod-to-pod communication concept - Implemented animated CloudPods visualization with inter-cluster data flow and pulsing effects
This commit is contained in:
195
src/pages/pods/animations/CloudPods.tsx
Normal file
195
src/pages/pods/animations/CloudPods.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string;
|
||||
bg?: string;
|
||||
};
|
||||
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
|
||||
export default function CloudPods({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
bg = "#0b0b0b",
|
||||
}: Props) {
|
||||
const prefers = useReducedMotion();
|
||||
|
||||
// Pods: 2 clusters (top-right & bottom-left)
|
||||
const clusterA = [
|
||||
{ x: 180, y: 300 },
|
||||
{ x: 240, y: 330 },
|
||||
{ x: 300, y: 290 },
|
||||
];
|
||||
|
||||
const clusterB = [
|
||||
{ x: 480, y: 100 },
|
||||
{ x: 540, y: 130 },
|
||||
{ x: 600, y: 90 },
|
||||
];
|
||||
|
||||
// Combine all pods for rendering
|
||||
const pods = [...clusterA, ...clusterB];
|
||||
|
||||
// Inter-cluster communication paths (cross connections)
|
||||
const links = [
|
||||
[0, 3],
|
||||
[1, 4],
|
||||
[2, 5],
|
||||
[0, 4],
|
||||
[1, 5],
|
||||
];
|
||||
|
||||
const drawLink = (i: number, j: number) => {
|
||||
const a = pods[i];
|
||||
const b = pods[j];
|
||||
return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx("relative overflow-hidden", className)}>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" style={{ background: bg }}>
|
||||
<defs>
|
||||
{/* Subtle cyan glow */}
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="b" />
|
||||
<feMerge>
|
||||
<feMergeNode in="b" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
|
||||
{/* Cyan gradient for pods */}
|
||||
<radialGradient id="podGradient" cx="50%" cy="50%" r="60%">
|
||||
<stop offset="0%" stopColor={accent} stopOpacity="0.9" />
|
||||
<stop offset="100%" stopColor={accent} stopOpacity="0.05" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
{/* ✅ Static base links between clusters */}
|
||||
{links.map(([i, j], idx) => (
|
||||
<motion.path
|
||||
key={idx}
|
||||
d={drawLink(i, j)}
|
||||
stroke="#1f2937"
|
||||
strokeWidth={1.8}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
opacity="0.5"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 1.2, delay: idx * 0.05 }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* ✅ Cyan data signals moving across clusters */}
|
||||
{!prefers &&
|
||||
links.map(([i, j], idx) => (
|
||||
<motion.circle
|
||||
key={`pulse-${idx}`}
|
||||
r={4}
|
||||
fill={accent}
|
||||
style={{ offsetPath: `path('${drawLink(i, j)}')` }}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0.2, 1, 0.2],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2.6,
|
||||
delay: idx * 0.25,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
}}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* ✅ Cloud Pods */}
|
||||
{pods.map((p, i) => (
|
||||
<g key={i}>
|
||||
<motion.ellipse
|
||||
cx={p.x}
|
||||
cy={p.y}
|
||||
rx={34}
|
||||
ry={22}
|
||||
fill="url(#podGradient)"
|
||||
stroke="#1f2937"
|
||||
strokeWidth={1.6}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.05 * i }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
|
||||
{/* Soft inner cyan pulse */}
|
||||
{!prefers && (
|
||||
<motion.ellipse
|
||||
cx={p.x}
|
||||
cy={p.y}
|
||||
rx={18}
|
||||
ry={12}
|
||||
fill={accent}
|
||||
opacity={0.08}
|
||||
animate={{ opacity: [0.05, 0.3, 0.05], scale: [1, 1.1, 1] }}
|
||||
transition={{
|
||||
duration: 2.4,
|
||||
delay: i * 0.15,
|
||||
repeat: Infinity,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
))}
|
||||
|
||||
{/* ✅ Cluster halos */}
|
||||
{!prefers && (
|
||||
<>
|
||||
<motion.circle
|
||||
cx="250"
|
||||
cy="310"
|
||||
r="100"
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={1.2}
|
||||
opacity={0.1}
|
||||
animate={{
|
||||
scale: [1, 1.1, 1.2],
|
||||
opacity: [0.05, 0.2, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3.8,
|
||||
repeat: Infinity,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
<motion.circle
|
||||
cx="560"
|
||||
cy="110"
|
||||
r="100"
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={1.2}
|
||||
opacity={0.1}
|
||||
animate={{
|
||||
scale: [1, 1.1, 1.2],
|
||||
opacity: [0.05, 0.2, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 3.8,
|
||||
delay: 1.2,
|
||||
repeat: Infinity,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user