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:
2025-11-12 15:47:58 +01:00
parent 0d9f357881
commit eba1bb7047
19 changed files with 1440 additions and 51 deletions

View File

@@ -0,0 +1,238 @@
"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;
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 (
<>
<motion.circle
cx={x}
cy={y}
r={r + 8}
stroke="#1F2937"
strokeWidth={2}
fill="none"
initial={{ opacity: 0 }}
animate={{ opacity: 0.8 }}
transition={{ delay, duration: 0.6 }}
/>
<motion.circle
cx={x}
cy={y}
r={r}
fill={accent}
initial={{ opacity: 0, scale: 0.8 }}
animate={{
opacity: 1,
scale: pulse && !prefers ? [1, 1.1, 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],
}}
/>
</>
);
};
const Packet = ({
path,
delay = 0,
accent = "#00b8db",
duration = 2.4,
}: {
path: string;
delay?: number;
accent?: string;
duration?: number;
}) => {
const prefers = useReducedMotion();
return (
<motion.circle
r={4}
fill={accent}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{
offsetDistance: ["0%", "100%"],
opacity: [0, 1, 0],
}}
transition={{
delay,
duration,
repeat: !prefers ? Infinity : 0,
repeatType: "loop",
ease: "linear",
}}
style={{ offsetPath: `path('${path}')` }}
/>
);
};
export default function NoCentral({
className,
accent = "#00b8db",
bg = "#0a0a0a",
}: Props) {
const center = { x: 380, y: 210 };
const nodes = [
{ x: 160, y: 100 },
{ x: 270, y: 70 },
{ x: 500, y: 90 },
{ x: 620, y: 150 },
{ x: 220, y: 300 },
{ x: 360, y: 340 },
{ x: 530, y: 290 },
];
const links = [
[nodes[0], nodes[1]],
[nodes[1], nodes[2]],
[nodes[2], nodes[3]],
[nodes[0], nodes[4]],
[nodes[4], nodes[5]],
[nodes[5], nodes[6]],
[nodes[1], nodes[5]],
[nodes[3], nodes[6]],
];
return (
<div
className={clsx("relative overflow-hidden", className)}
aria-hidden="true"
role="img"
aria-label="Distributed network 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>
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor={accent} stopOpacity="0.12" />
<stop offset="100%" stopColor={accent} stopOpacity="0" />
</radialGradient>
</defs>
<rect width={W} height={H} fill="url(#grid-dark)" />
{/* Cyan glow field */}
<circle cx={center.x} cy={center.y} r={200} fill="url(#glow)" opacity={0.4} />
{/* Static connection lines */}
{links.map(([a, b], i) => (
<motion.path
key={`line-${i}`}
d={`M ${a.x} ${a.y} L ${b.x} ${b.y}`}
stroke="#1F2937"
strokeWidth={2}
strokeLinecap="round"
fill="none"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 0.4 }}
transition={{
delay: 0.05 * i,
duration: 0.8,
ease: [0.22, 1, 0.36, 1],
}}
/>
))}
{/* Cyan animated signal lines */}
{links.slice(0, 5).map(([a, b], i) => (
<motion.path
key={`signal-${i}`}
d={`M ${a.x} ${a.y} L ${b.x} ${b.y}`}
stroke={accent}
strokeWidth={2.5}
strokeDasharray="10 10"
strokeLinecap="round"
fill="none"
animate={{ opacity: [0.3, 0.8, 0.3] }}
transition={{
duration: 2,
repeat: Infinity,
delay: i * 0.2,
ease: "easeInOut",
}}
/>
))}
{/* Circulating packets on random links */}
{links.map(([a, b], i) => (
<Packet
key={`pkt-${i}`}
path={`M ${a.x} ${a.y} L ${b.x} ${b.y}`}
delay={i * 0.4}
accent={accent}
duration={3}
/>
))}
{/* Pulsing nodes */}
{nodes.map((n, i) => (
<Node
key={`node-${i}`}
x={n.x}
y={n.y}
r={10}
accent={accent}
pulse={i % 2 === 0}
delay={i * 0.1}
/>
))}
{/* Faded red “no central” mark at middle */}
<motion.circle
cx={center.x}
cy={center.y}
r={18}
fill="none"
stroke="#FF4D4D"
strokeWidth={3}
strokeDasharray="6 4"
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 0.7 }}
transition={{ delay: 1, duration: 0.8 }}
/>
<motion.path
d={`M ${center.x - 10} ${center.y - 10} L ${center.x + 10} ${center.y + 10} M ${center.x - 10} ${center.y + 10} L ${center.x + 10} ${center.y - 10}`}
stroke="#FF4D4D"
strokeWidth={3}
strokeLinecap="round"
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 1.2, duration: 0.6 }}
/>
</svg>
</div>
);
}