forked from emre/www_projectmycelium_com
refactor: redesign pods page with bento grid layout and improved benefits sections
- Increased CloudHeroNew vertical padding on large screens (lg:py-16 to lg:py-24) - Reduced spacing between description paragraphs in CloudHeroNew (mt-4 to mt-2) - Created PodsBento component with animated bento grid showcasing pod benefits - Added animations for data control, connectivity, security, and resilience features - Refactored PodsDesign from accordion layout to centered intro with 4-column grid - Create
This commit is contained in:
175
src/pages/pods/animations/NoControl.tsx
Normal file
175
src/pages/pods/animations/NoControl.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
"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;
|
||||
|
||||
/** Node component */
|
||||
const Node = ({
|
||||
x,
|
||||
y,
|
||||
r = 14,
|
||||
accent = "#00b8db",
|
||||
pulse = false,
|
||||
faded = false,
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
r?: number;
|
||||
accent?: string;
|
||||
pulse?: boolean;
|
||||
faded?: boolean;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<>
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r + 10}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={2}
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: faded ? 0.3 : 0.9 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
/>
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r}
|
||||
fill={accent}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{
|
||||
opacity: faded ? 0.3 : 1,
|
||||
scale: pulse && !prefers ? [1, 1.12, 1] : 1,
|
||||
}}
|
||||
transition={{
|
||||
duration: pulse && !prefers ? 1.8 : 0.6,
|
||||
repeat: pulse && !prefers ? Infinity : 0,
|
||||
repeatType: "mirror",
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/** Moving packet along a path */
|
||||
const Packet = ({
|
||||
path,
|
||||
delay = 0,
|
||||
accent = "#00b8db",
|
||||
}: {
|
||||
path: string;
|
||||
delay?: number;
|
||||
accent?: string;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<motion.circle
|
||||
r={5}
|
||||
fill={accent}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration: 1.6,
|
||||
repeat: !prefers ? Infinity : 0,
|
||||
repeatType: "loop",
|
||||
ease: "linear",
|
||||
}}
|
||||
style={{
|
||||
offsetPath: `path('${path}')`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function NoControl({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
bg = "#0a0a0a",
|
||||
}: Props) {
|
||||
const center = { x: 380, y: 210 };
|
||||
const outer = [
|
||||
{ x: 160, y: 120 },
|
||||
{ x: 600, y: 120 },
|
||||
{ x: 160, y: 300 },
|
||||
{ x: 600, y: 300 },
|
||||
];
|
||||
|
||||
const link = (a: any, b: any) => `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="No single point of control illustration"
|
||||
style={{ background: bg }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* Background grid */}
|
||||
<defs>
|
||||
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#0f0f0f" strokeWidth="1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||
|
||||
{/* Connections between outer nodes (decentralized ring) */}
|
||||
<motion.path
|
||||
d={`M ${outer[0].x} ${outer[0].y}
|
||||
L ${outer[1].x} ${outer[1].y}
|
||||
L ${outer[3].x} ${outer[3].y}
|
||||
L ${outer[2].x} ${outer[2].y} Z`}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0.3 }}
|
||||
animate={{ pathLength: 1, opacity: 0.6 }}
|
||||
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
|
||||
{/* Cyan pulsing signals around the ring */}
|
||||
{outer.map((o, i) => {
|
||||
const next = outer[(i + 1) % outer.length];
|
||||
const p = link(o, next);
|
||||
return <Packet key={`p-${i}`} path={p} delay={i * 0.4} accent={accent} />;
|
||||
})}
|
||||
|
||||
{/* Faded center node (represents the “former” single point) */}
|
||||
<Node x={center.x} y={center.y} r={18} accent={accent} faded pulse={false} />
|
||||
|
||||
{/* Outer sovereign nodes */}
|
||||
{outer.map((n, i) => (
|
||||
<Node key={i} x={n.x} y={n.y} r={14} accent={accent} pulse />
|
||||
))}
|
||||
|
||||
{/* Cross mark over center node (control denied) */}
|
||||
<motion.path
|
||||
d={`M ${center.x - 12} ${center.y - 12} L ${center.x + 12} ${center.y + 12} M ${center.x - 12} ${center.y + 12} L ${center.x + 12} ${center.y - 12}`}
|
||||
stroke="#FF4D4D"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.8, duration: 0.6 }}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user