forked from emre/www_projectmycelium_com
feat: add architecture section with animated illustrations
- Created HomeArchitecture component showcasing decentralized network principles with SVG animations - Added animated illustrations for mesh networking, no extraction, no control, and no central servers - Enhanced CallToAction with cyan radial glow effect for visual emphasis - Fixed audience gallery image paths to use correct /images/audiences/ directory
This commit is contained in:
247
src/pages/home/animations/NoExtraction.tsx
Normal file
247
src/pages/home/animations/NoExtraction.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
"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 = local data cluster */
|
||||
const Node = ({
|
||||
x,
|
||||
y,
|
||||
r = 10,
|
||||
accent = "#00b8db",
|
||||
pulse = false,
|
||||
delay = 0,
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
r?: number;
|
||||
accent?: string;
|
||||
pulse?: boolean;
|
||||
delay?: number;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<>
|
||||
{/* outer glow */}
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r + 8}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={2}
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.9 }}
|
||||
transition={{ delay, duration: 0.6 }}
|
||||
/>
|
||||
{/* data core */}
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r}
|
||||
fill={accent}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scale: pulse && !prefers ? [1, 1.15, 1] : 1,
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration: pulse && !prefers ? 1.6 : 0.6,
|
||||
repeat: pulse && !prefers ? Infinity : 0,
|
||||
repeatType: "mirror",
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/** A data particle traveling along a given path */
|
||||
const Particle = ({
|
||||
path,
|
||||
delay = 0,
|
||||
accent = "#00b8db",
|
||||
duration = 2,
|
||||
reverse = false,
|
||||
}: {
|
||||
path: string;
|
||||
delay?: number;
|
||||
accent?: string;
|
||||
duration?: number;
|
||||
reverse?: boolean;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<motion.circle
|
||||
r={4}
|
||||
fill={accent}
|
||||
initial={{ offsetDistance: reverse ? "100%" : "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: reverse ? ["100%", "0%"] : ["0%", "100%"],
|
||||
opacity: [0, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration,
|
||||
repeat: !prefers ? Infinity : 0,
|
||||
repeatType: "mirror",
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
style={{
|
||||
offsetPath: `path('${path}')`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function NoExtraction({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
bg = "#0a0a0a",
|
||||
}: Props) {
|
||||
const center = { x: 380, y: 210 };
|
||||
const WBOX = 360;
|
||||
const HBOX = 220;
|
||||
const boxX = center.x - WBOX / 2;
|
||||
const boxY = center.y - HBOX / 2;
|
||||
|
||||
// local nodes within boundary
|
||||
const nodes = [
|
||||
{ x: center.x - 80, y: center.y - 40 },
|
||||
{ x: center.x + 60, y: center.y - 50 },
|
||||
{ x: center.x, y: center.y + 50 },
|
||||
{ x: center.x - 50, y: center.y + 30 },
|
||||
];
|
||||
|
||||
// internal circulation paths
|
||||
const internalPaths = [
|
||||
`M ${center.x - 80} ${center.y - 40} Q ${center.x} ${center.y - 80} ${center.x + 60} ${center.y - 50}`,
|
||||
`M ${center.x - 50} ${center.y + 30} Q ${center.x} ${center.y + 70} ${center.x} ${center.y + 50}`,
|
||||
];
|
||||
|
||||
// escape attempt path
|
||||
const attemptPath = `M ${center.x} ${center.y} Q ${center.x + 200} ${center.y - 50} ${center.x + 130} ${center.y}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="No data extraction 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>
|
||||
|
||||
{/* Cyan glow gradient */}
|
||||
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stopColor={accent} stopOpacity="0.15" />
|
||||
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||
|
||||
{/* Cyan ambient glow field */}
|
||||
<circle
|
||||
cx={center.x}
|
||||
cy={center.y}
|
||||
r={160}
|
||||
fill="url(#glow)"
|
||||
opacity={0.3}
|
||||
/>
|
||||
|
||||
{/* Secure boundary box */}
|
||||
<motion.rect
|
||||
x={boxX}
|
||||
y={boxY}
|
||||
width={WBOX}
|
||||
height={HBOX}
|
||||
rx={16}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={3}
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
/>
|
||||
|
||||
{/* Cyan encryption border */}
|
||||
<motion.rect
|
||||
x={boxX + 4}
|
||||
y={boxY + 4}
|
||||
width={WBOX - 8}
|
||||
height={HBOX - 8}
|
||||
rx={14}
|
||||
stroke={accent}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="10 6"
|
||||
fill="none"
|
||||
animate={{ opacity: [0.4, 0.9, 0.4] }}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Internal data circulation */}
|
||||
{internalPaths.map((p, i) => (
|
||||
<Particle
|
||||
key={`int-${i}`}
|
||||
path={p}
|
||||
delay={i * 0.6}
|
||||
accent={accent}
|
||||
duration={3}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Local data nodes */}
|
||||
{nodes.map((n, i) => (
|
||||
<Node
|
||||
key={i}
|
||||
x={n.x}
|
||||
y={n.y}
|
||||
r={10}
|
||||
accent={accent}
|
||||
pulse={i === 2}
|
||||
delay={i * 0.2}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Data packet attempting to leave */}
|
||||
<Particle path={attemptPath} delay={0.5} accent={accent} duration={2.2} />
|
||||
|
||||
{/* Impact glow at boundary */}
|
||||
<motion.circle
|
||||
cx={center.x + 130}
|
||||
cy={center.y - 10}
|
||||
r={10}
|
||||
fill="#FF4D4D"
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: [0, 1.2, 0.8], opacity: [0, 1, 0] }}
|
||||
transition={{
|
||||
delay: 0.9,
|
||||
duration: 1.4,
|
||||
repeat: Infinity,
|
||||
repeatDelay: 1.5,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user