refactor: rename and update pod animations with improved visuals and consistency

- Renamed NoCentral to DataControl with enhanced data ownership visualization
- Renamed NoExtraction to Connectivity with full mesh P2P topology
- Renamed NoSinglePoint to Security with improved styling
- Renamed NoControl to Resilience with consistent styling
- Added lg:-mt-12 margin to Security and Resilience animations for alignment
- Updated DataControl to show explicit permission gates and bi-directional replication
This commit is contained in:
2025-11-17 15:12:55 +01:00
parent 97da7ba907
commit 1a34508699
9 changed files with 909 additions and 893 deletions

View File

@@ -1,10 +1,10 @@
"use client"; "use client";
import { Eyebrow, H3, P } from "@/components/Texts"; import { Eyebrow, H3, P } from "@/components/Texts";
import NoExtraction from "./animations/NoExtraction"; import DataControl from "./animations/DataControl";
import NoControl from "./animations/NoControl"; import Connectivity from "./animations/Connectivity";
import NoCentral from "./animations/NoCentral"; import Security from "./animations/Security";
import NoSinglePoint from "./animations/NoSinglePoint"; import Resilience from "./animations/Resilience";
const deterministicCards = [ const deterministicCards = [
{ {
@@ -26,7 +26,7 @@ const deterministicCards = [
title: "Your Data Lives on Your Pods", title: "Your Data Lives on Your Pods",
description: description:
"Full control of where your data is stored and how its shared.", "Full control of where your data is stored and how its shared.",
animation: <NoCentral className="lg:-mt-12" />, animation: <DataControl className="lg:-mt-12" />,
colSpan: "lg:col-span-3", colSpan: "lg:col-span-3",
rowSpan: "lg:row-span-1", rowSpan: "lg:row-span-1",
rounded: "lg:rounded-tr-4xl max-lg:rounded-t-4xl", rounded: "lg:rounded-tr-4xl max-lg:rounded-t-4xl",
@@ -40,7 +40,7 @@ const deterministicCards = [
title: "Direct Pod-to-Pod Networking", title: "Direct Pod-to-Pod Networking",
description: description:
"Direct connections between Pods for faster, private communication.", "Direct connections between Pods for faster, private communication.",
animation: <NoExtraction className="lg:-mt-12" />, animation: <Connectivity className="lg:-mt-12" />,
colSpan: "lg:col-span-2", colSpan: "lg:col-span-2",
rowSpan: "lg:row-span-1", rowSpan: "lg:row-span-1",
rounded: "lg:rounded-bl-4xl max-lg:rounded-b-4xl", rounded: "lg:rounded-bl-4xl max-lg:rounded-b-4xl",
@@ -54,7 +54,7 @@ const deterministicCards = [
title: "No One Can Spy or Shut You Down", title: "No One Can Spy or Shut You Down",
description: description:
"Independence from corporate servers or cloud outages.", "Independence from corporate servers or cloud outages.",
animation: <NoSinglePoint />, animation: <Security className="lg:-mt-12" />,
colSpan: "lg:col-span-2", colSpan: "lg:col-span-2",
rowSpan: "lg:row-span-1", rowSpan: "lg:row-span-1",
rounded: "", rounded: "",
@@ -67,7 +67,7 @@ const deterministicCards = [
title: "Resilient Even if Nodes Disconnect", title: "Resilient Even if Nodes Disconnect",
description: description:
"Continuous availability even if one node disconnects.", "Continuous availability even if one node disconnects.",
animation: <NoControl />, animation: <Resilience className="lg:-mt-12" />,
colSpan: "lg:col-span-2", colSpan: "lg:col-span-2",
rowSpan: "lg:row-span-1", rowSpan: "lg:row-span-1",
rounded: "lg:rounded-br-4xl max-lg:rounded-b-4xl", rounded: "lg:rounded-br-4xl max-lg:rounded-b-4xl",

View File

@@ -0,0 +1,176 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import clsx from "clsx";
type Props = {
className?: string;
accent?: string;
bg?: string;
gridStroke?: string;
};
const W = 720; // 4:3 width
const H = 540; // 4:3 height
export default function Connectivity({
className,
accent = "#00b8db",
bg = "#0b0b0b",
gridStroke = "#2b2a2a",
}: Props) {
const prefers = useReducedMotion();
// Hex-like P2P topology — all peers connect *directly*
const pods = [
{ x: 250, y: 160 },
{ x: 470, y: 160 },
{ x: 580, y: 290 },
{ x: 470, y: 420 },
{ x: 250, y: 420 },
{ x: 140, y: 290 },
];
// Every pod connects to every other pod = full direct mesh
const links: [number, number][] = [];
for (let i = 0; i < pods.length; i++) {
for (let j = i + 1; j < pods.length; j++) links.push([i, j]);
}
const line = (i: number, j: number) => {
const a = pods[i], 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 }}>
{/* ========= GRID + FILTERS ========= */}
<defs>
<pattern id="conn-grid" width="28" height="28" patternUnits="userSpaceOnUse">
<path
d="M 28 0 L 0 0 0 28"
fill="none"
stroke={gridStroke}
strokeWidth="1"
opacity="0.35"
/>
</pattern>
<filter id="conn-glow">
<feGaussianBlur stdDeviation="3" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
{/* Cyan soft gradient for pods */}
<radialGradient id="conn-pod" cx="50%" cy="50%" r="60%">
<stop offset="0%" stopColor={accent} stopOpacity="0.9" />
<stop offset="100%" stopColor={accent} stopOpacity="0.06" />
</radialGradient>
</defs>
<rect width={W} height={H} fill="url(#conn-grid)" />
{/* ========= BASE CONNECTION LINES (Full Mesh) ========= */}
{links.map(([i, j], idx) => (
<motion.path
key={`base-${idx}`}
d={line(i, j)}
stroke="#1f1f1f"
strokeWidth={3}
strokeLinecap="round"
fill="none"
initial={{ pathLength: 0, opacity: 0.2 }}
animate={{ pathLength: 1, opacity: 0.5 }}
transition={{ duration: 0.6, delay: idx * 0.03 }}
/>
))}
{/* ========= ACCENT DASH (Active P2P links) ========= */}
{links.map(([i, j], idx) => (
<motion.path
key={`dash-${idx}`}
d={line(i, j)}
stroke={accent}
strokeWidth={2}
fill="none"
strokeDasharray="12 10"
initial={{ pathLength: 0, opacity: 0.9 }}
animate={{ pathLength: 1, opacity: [0.9, 0.6, 0.9] }}
transition={{
duration: 1,
delay: idx * 0.04,
repeat: Infinity,
repeatType: "reverse",
}}
filter="url(#conn-glow)"
/>
))}
{/* ========= MOVING SIGNALS (Direct P2P) ========= */}
{!prefers &&
links.map(([i, j], idx) => (
<motion.circle
key={`pulse-${idx}`}
r={5}
fill={accent}
style={{ offsetPath: `path('${line(i, j)}')` }}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{
offsetDistance: ["0%", "100%"],
opacity: [0.2, 1, 0.2],
}}
transition={{
duration: 2.3,
delay: idx * 0.2,
repeat: Infinity,
ease: "linear",
}}
filter="url(#conn-glow)"
/>
))}
{/* ========= CLOUD PODS ========= */}
{pods.map((p, i) => (
<g key={`pod-${i}`}>
{/* Outer soft cyan aura */}
{!prefers && (
<motion.ellipse
cx={p.x}
cy={p.y}
rx={42}
ry={28}
fill="none"
stroke={accent}
strokeWidth={2}
opacity={0.15}
animate={{ scale: [1, 1.1, 1] , opacity: [0.1, 0.25, 0.1] }}
transition={{ duration: 2.3, delay: i * 0.15, repeat: Infinity }}
filter="url(#conn-glow)"
/>
)}
{/* Pod body */}
<ellipse
cx={p.x}
cy={p.y}
rx={36}
ry={24}
fill="url(#conn-pod)"
stroke="#1f1f1f"
strokeWidth={2}
filter="url(#conn-glow)"
/>
{/* Inner dark core */}
<ellipse cx={p.x} cy={p.y} rx={18} ry={12} fill="#0f0f0f" stroke="#292929" strokeWidth={1.5} />
</g>
))}
</svg>
</div>
);
}

View File

@@ -0,0 +1,239 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import clsx from "clsx";
type Props = {
className?: string;
accent?: string; // cyan
bg?: string; // solid dark
gridStroke?: string; // grid color
};
const W = 720; // 4:3
const H = 540; // 4:3
export default function DataControl({
className,
accent = "#00b8db",
bg = "#0b0b0b",
gridStroke = "#2b2a2a",
}: Props) {
const prefers = useReducedMotion();
// Central "Your Pod"
const center = { x: W / 2, y: H / 2 + 10 };
// Personal pods (owned) — left-bottom cluster
const owned = [
{ x: 200, y: 380 },
{ x: 260, y: 320 },
{ x: 140, y: 320 },
];
// External peers (share targets) — top-right cluster
const peers = [
{ x: 520, y: 180 },
{ x: 600, y: 140 },
{ x: 580, y: 240 },
];
// helper
const line = (a: {x:number;y:number}, b:{x:number;y:number}) => `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
// "Gates" sit near the center to symbolize explicit permission before data leaves your pod
const gates = peers.map((p, i) => {
// point 30% of the way from center to peer
const gx = center.x + (p.x - center.x) * 0.28;
const gy = center.y + (p.y - center.y) * 0.28;
return { x: gx, y: gy };
});
// Paths for outbound shares (center -> gate -> peer)
const sharePaths = peers.map((p, i) =>
`M ${center.x} ${center.y} L ${gates[i].x} ${gates[i].y} L ${p.x} ${p.y}`
);
// Paths for local replication (center <-> owned pods)
const localPaths = owned.map((o) => line(center, o));
return (
<div className={clsx("relative overflow-hidden", className)} aria-hidden="true" role="img" style={{ background: bg }}>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
{/* GRID + GLOW */}
<defs>
<pattern id="dc-grid" width="28" height="28" patternUnits="userSpaceOnUse">
<path d="M 28 0 L 0 0 0 28" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.35" />
</pattern>
<filter id="dc-glow">
<feGaussianBlur stdDeviation="3" result="b"/>
<feMerge>
<feMergeNode in="b"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<radialGradient id="pod-cyan" cx="50%" cy="50%" r="60%">
<stop offset="0%" stopColor={accent} stopOpacity="0.9"/>
<stop offset="100%" stopColor={accent} stopOpacity="0.06"/>
</radialGradient>
</defs>
<rect width={W} height={H} fill="url(#dc-grid)" />
{/* ====== BASELINES ====== */}
{/* Local control links (owned replicas) */}
{localPaths.map((d, i) => (
<motion.path
key={`local-${i}`}
d={d}
stroke="#1e1e1e"
strokeWidth={3}
strokeLinecap="round"
fill="none"
initial={{ pathLength: 0, opacity: 0.3 }}
animate={{ pathLength: 1, opacity: 0.6 }}
transition={{ duration: 0.7, delay: 0.05 * i, ease: [0.22,1,0.36,1] }}
/>
))}
{/* Outbound paths (to peers) */}
{sharePaths.map((d, i) => (
<motion.path
key={`share-base-${i}`}
d={d}
stroke="#2a2a2a"
strokeWidth={3}
strokeLinecap="round"
fill="none"
initial={{ pathLength: 0, opacity: 0.25 }}
animate={{ pathLength: 1, opacity: 0.5 }}
transition={{ duration: 0.8, delay: 0.1 * i, ease: [0.22,1,0.36,1] }}
/>
))}
{/* Cyan dash overlays to show active/allowed routes */}
{localPaths.map((d, i) => (
<motion.path
key={`local-accent-${i}`}
d={d}
stroke={accent}
strokeWidth={2}
strokeDasharray="10 8"
fill="none"
initial={{ pathLength: 0, opacity: 0.9 }}
animate={{ pathLength: 1, opacity: 0.9 }}
transition={{ duration: 0.7, delay: 0.15 * i }}
filter="url(#dc-glow)"
/>
))}
{sharePaths.map((d, i) => (
<motion.path
key={`share-accent-${i}`}
d={d}
stroke={accent}
strokeWidth={2}
strokeDasharray="10 8"
fill="none"
initial={{ pathLength: 0, opacity: 0.85 }}
animate={{ pathLength: 1, opacity: [0.85, 0.6, 0.85] }}
transition={{ duration: 0.9, delay: 0.2 * i, repeat: Infinity, repeatType: "reverse" }}
filter="url(#dc-glow)"
/>
))}
{/* ====== MOVING DATA (Packets) ====== */}
{!prefers && (
<>
{/* Local replication packets (bi-directional) */}
{localPaths.map((d, i) => (
<motion.circle
key={`localpkt-${i}`}
r={5}
fill={accent}
style={{ offsetPath: `path('${d}')` }}
initial={{ offsetDistance: "0%", opacity: 0.2 }}
animate={{ offsetDistance: ["0%", "100%"], opacity: [0.2, 1, 0.2] }}
transition={{ duration: 2.2, delay: i * 0.2, repeat: Infinity, ease: "linear" }}
filter="url(#dc-glow)"
/>
))}
{/* Outbound share packets: center -> gate -> peer */}
{sharePaths.map((d, i) => (
<motion.circle
key={`sharepkt-${i}`}
r={5}
fill={accent}
style={{ offsetPath: `path('${d}')` }}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{ offsetDistance: ["0%", "100%"], opacity: [0, 1, 0] }}
transition={{ duration: 2.8, delay: 0.4 * i, repeat: Infinity, ease: "linear" }}
filter="url(#dc-glow)"
/>
))}
</>
)}
{/* ====== GATES (permission points) ====== */}
{gates.map((g, i) => (
<g key={`gate-${i}`}>
<circle cx={g.x} cy={g.y} r={12} fill="#0f0f0f" stroke="#232323" strokeWidth={2} />
{!prefers && (
<motion.circle
cx={g.x} cy={g.y} r={8} fill="none" stroke={accent} strokeWidth={2}
initial={{ opacity: 0 }}
animate={{ opacity: [0, 1, 0] }}
transition={{ duration: 1.6, delay: 0.4 * i, repeat: Infinity, ease: [0.22,1,0.36,1] }}
filter="url(#dc-glow)"
/>
)}
{/* tiny "lock" glyph */}
<path d={`M ${g.x-3} ${g.y+2} h 6 v 4 h -6 Z`} fill="#1d1d1d" stroke="#2b2b2b" strokeWidth={1}/>
<path d={`M ${g.x-2} ${g.y+2} v -3 a 3 3 0 0 1 6 0 v 3`} fill="none" stroke="#2b2b2b" strokeWidth={1}/>
</g>
))}
{/* ====== PODS ====== */}
{/* Center (Your Pod) */}
<g>
<ellipse cx={center.x} cy={center.y} rx={52} ry={34} fill="url(#pod-cyan)" stroke="#1f1f1f" strokeWidth={2} filter="url(#dc-glow)" />
<ellipse cx={center.x} cy={center.y} rx={26} ry={18} fill="#0f0f0f" stroke="#232323" strokeWidth={2} />
{!prefers && (
<motion.ellipse
cx={center.x} cy={center.y} rx={38} ry={26}
fill="none" stroke={accent} strokeWidth={2} opacity={0.5}
animate={{ scale: [1, 1.06, 1], opacity: [0.35, 0.7, 0.35] }}
transition={{ duration: 2.2, repeat: Infinity, ease: [0.22,1,0.36,1] }}
filter="url(#dc-glow)"
/>
)}
</g>
{/* Owned pods (left-bottom cluster) */}
{owned.map((p, i) => (
<g key={`owned-${i}`}>
<ellipse cx={p.x} cy={p.y} rx={34} ry={22} fill="#0f0f0f" stroke="#232323" strokeWidth={2} />
<ellipse cx={p.x} cy={p.y} rx={16} ry={11} fill="#121212" stroke="#272727" strokeWidth={1.5} />
</g>
))}
{/* External peers (top-right cluster) */}
{peers.map((p, i) => (
<g key={`peer-${i}`}>
<ellipse cx={p.x} cy={p.y} rx={32} ry={21} fill="#0f0f0f" stroke="#232323" strokeWidth={2} />
{/* subtle cyan rim = marked as “shared” endpoints */}
<motion.ellipse
cx={p.x} cy={p.y} rx={27} ry={18}
fill="none" stroke={accent} strokeWidth={1.8} opacity={0.35}
animate={!prefers ? { opacity: [0.2, 0.5, 0.2] } : { opacity: 0.35 }}
transition={{ duration: 2.4, delay: i * 0.2, repeat: prefers ? 0 : Infinity }}
filter="url(#dc-glow)"
/>
</g>
))}
</svg>
</div>
);
}

View File

@@ -1,238 +0,0 @@
"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>
);
}

View File

@@ -1,175 +0,0 @@
"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>
);
}

View File

@@ -1,247 +0,0 @@
"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>
);
}

View File

@@ -1,225 +0,0 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import clsx from "clsx";
type Props = {
className?: string;
accent?: string; // cyan
bg?: string; // solid dark background
gridStroke?: string;
};
const W = 720; // 4:3
const H = 540; // 4:3
export default function NoSinglePoint({
className,
accent = "#00b8db",
bg = "#0b0b0b",
gridStroke = "#2b2a2a",
}: Props) {
const prefers = useReducedMotion();
// Nodes (left source, right dest, top hub, bottom hub, plus two relays)
const nodes = {
left: { x: 120, y: H / 2 },
right: { x: W - 120, y: H / 2 },
top: { x: W / 2, y: 160 },
bot: { x: W / 2, y: H - 160 },
tl: { x: 240, y: 200 },
br: { x: W - 240, y: H - 200 },
};
// Redundant paths from left → right
const upperPath = `M ${nodes.left.x} ${nodes.left.y}
L ${nodes.tl.x} ${nodes.tl.y}
L ${nodes.top.x} ${nodes.top.y}
L ${nodes.right.x} ${nodes.right.y}`;
const lowerPath = `M ${nodes.left.x} ${nodes.left.y}
L ${nodes.bot.x} ${nodes.bot.y}
L ${nodes.br.x} ${nodes.br.y}
L ${nodes.right.x} ${nodes.right.y}`;
return (
<div
className={clsx("relative overflow-hidden", className)}
aria-hidden="true"
role="img"
style={{ background: bg }}
>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
{/* Subtle dark grid */}
<defs>
<pattern id="grid-dark-4x3" width="28" height="28" patternUnits="userSpaceOnUse">
<path d="M 28 0 L 0 0 0 28" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.35" />
</pattern>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<rect width={W} height={H} fill="url(#grid-dark-4x3)" />
{/* Base links (all potential connectivity) */}
{[
`M ${nodes.left.x} ${nodes.left.y} L ${nodes.tl.x} ${nodes.tl.y}`,
`M ${nodes.tl.x} ${nodes.tl.y} L ${nodes.top.x} ${nodes.top.y}`,
`M ${nodes.top.x} ${nodes.top.y} L ${nodes.right.x} ${nodes.right.y}`,
`M ${nodes.left.x} ${nodes.left.y} L ${nodes.bot.x} ${nodes.bot.y}`,
`M ${nodes.bot.x} ${nodes.bot.y} L ${nodes.br.x} ${nodes.br.y}`,
`M ${nodes.br.x} ${nodes.br.y} L ${nodes.right.x} ${nodes.right.y}`,
// cross edges (mesh redundancy)
`M ${nodes.tl.x} ${nodes.tl.y} L ${nodes.bot.x} ${nodes.bot.y}`,
`M ${nodes.top.x} ${nodes.top.y} L ${nodes.br.x} ${nodes.br.y}`,
].map((d, i) => (
<motion.path
key={`base-${i}`}
d={d}
stroke="#1e1e1e"
strokeWidth={3}
strokeLinecap="round"
fill="none"
initial={{ pathLength: 0, opacity: 0.2 }}
animate={{ pathLength: 1, opacity: 0.5 }}
transition={{ duration: 0.8, delay: i * 0.05, ease: [0.22, 1, 0.36, 1] }}
/>
))}
{/* Highlight the two redundant main routes */}
<motion.path
d={upperPath}
fill="none"
stroke="#3a3a3a"
strokeWidth={4}
strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.6 }}
animate={{ pathLength: 1, opacity: 0.6 }}
transition={{ duration: 0.9 }}
/>
<motion.path
d={lowerPath}
fill="none"
stroke="#3a3a3a"
strokeWidth={4}
strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.6 }}
animate={{ pathLength: 1, opacity: 0.6 }}
transition={{ duration: 0.9, delay: 0.1 }}
/>
{/* Cyan accent dash showing “preferred/active” path(s) */}
<motion.path
d={upperPath}
fill="none"
stroke={accent}
strokeWidth={2}
strokeDasharray="10 8"
initial={{ pathLength: 0, opacity: 0.8 }}
animate={{ pathLength: 1, opacity: [0.8, 0.2, 0.8] }} // will fade as "blocked"
transition={{ duration: 1.1, repeat: Infinity, repeatType: "reverse" }}
filter="url(#glow)"
/>
<motion.path
d={lowerPath}
fill="none"
stroke={accent}
strokeWidth={2}
strokeDasharray="10 8"
initial={{ pathLength: 0, opacity: 1 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 1 }}
filter="url(#glow)"
/>
{/* Moving packets: one tries upper (gets dimmed at top hub), one uses lower and continues */}
{!prefers && (
<>
{/* Upper path packet (dims near top hub to suggest block/censor but NOT stopping overall flow) */}
<motion.circle
r={5}
fill={accent}
style={{ offsetPath: `path('${upperPath}')` }}
initial={{ offsetDistance: "0%", opacity: 0.9 }}
animate={{
offsetDistance: ["0%", "100%"],
opacity: [0.9, 0.4, 0.9], // subtle dimming cycle
}}
transition={{ duration: 3.0, repeat: Infinity, ease: "linear" }}
filter="url(#glow)"
/>
{/* Lower path packet (stable flow) */}
<motion.circle
r={6}
fill={accent}
style={{ offsetPath: `path('${lowerPath}')` }}
initial={{ offsetDistance: "0%", opacity: 1 }}
animate={{ offsetDistance: ["0%", "100%"] }}
transition={{ duration: 2.4, repeat: Infinity, ease: "linear" }}
filter="url(#glow)"
/>
</>
)}
{/* Nodes */}
{Object.values(nodes).map((n, i) => (
<g key={`node-${i}`}>
<circle cx={n.x} cy={n.y} r={20} fill="#0f0f0f" stroke="#1f1f1f" strokeWidth={2} />
<motion.circle
cx={n.x}
cy={n.y}
r={8}
fill={i === 2 ? "#0f0f0f" : accent} // top hub inner is dark (to hint “blocked” moment)
stroke="#222"
strokeWidth={2}
animate={
!prefers
? i === 2 // top node (potential choke/attack point) pulses differently
? { opacity: [0.5, 0.25, 0.5], scale: [1, 0.95, 1] }
: { opacity: [0.9, 1, 0.9], scale: [1, 1.06, 1] }
: { opacity: 1 }
}
transition={{
duration: 2.2,
repeat: prefers ? 0 : Infinity,
ease: [0.22, 1, 0.36, 1],
delay: i * 0.05,
}}
filter="url(#glow)"
/>
</g>
))}
{/* A subtle “block” overlay on the top hub (appears/disappears) */}
{!prefers && (
<motion.g
initial={{ opacity: 0 }}
animate={{ opacity: [0, 0.7, 0] }}
transition={{ duration: 3.2, repeat: Infinity, ease: "easeInOut", delay: 0.8 }}
>
<circle
cx={nodes.top.x}
cy={nodes.top.y}
r={18}
fill="none"
stroke="#8b8b8b"
strokeWidth={2}
/>
<path
d={`M ${nodes.top.x - 10} ${nodes.top.y - 10} L ${nodes.top.x + 10} ${nodes.top.y + 10}
M ${nodes.top.x + 10} ${nodes.top.y - 10} L ${nodes.top.x - 10} ${nodes.top.y + 10}`}
stroke="#8b8b8b"
strokeWidth={2}
strokeLinecap="round"
/>
</motion.g>
)}
</svg>
</div>
);
}

View File

@@ -0,0 +1,255 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import clsx from "clsx";
type Props = {
className?: string;
accent?: string; // cyan
danger?: string; // failing node color
bg?: string; // dark background
gridStroke?: string; // grid
};
const W = 720;
const H = 540;
export default function Resilience({
className,
accent = "#00b8db",
danger = "#ff4d4d",
bg = "#0b0b0b",
gridStroke = "#2b2a2a",
}: Props) {
const prefers = useReducedMotion();
// Primary healthy nodes
const nodes = [
{ x: 220, y: 180 },
{ x: 500, y: 180 },
{ x: 580, y: 330 },
{ x: 360, y: 420 },
{ x: 160, y: 330 },
];
// One failing node
const failing = { x: 360, y: 100 };
// Full mesh except failing node
const links: [number, number][] = [];
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
links.push([i, j]);
}
}
// Paths for fallback reroute (detour link when failing node disappears)
const reroute = `M ${nodes[0].x} ${nodes[0].y}
L ${nodes[3].x} ${nodes[3].y}
L ${nodes[2].x} ${nodes[2].y}`;
const line = (i: number, j: number) =>
`M ${nodes[i].x} ${nodes[i].y} L ${nodes[j].x} ${nodes[j].y}`;
const failRay = `M ${failing.x} ${failing.y} L ${nodes[1].x} ${nodes[1].y}`;
return (
<div className={clsx("relative overflow-hidden", className)}>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" style={{ background: bg }}>
{/* ===== GRID + FILTERS ===== */}
<defs>
<pattern id="res-grid" width="28" height="28" patternUnits="userSpaceOnUse">
<path
d="M 28 0 L 0 0 0 28"
stroke={gridStroke}
strokeWidth="1"
opacity="0.35"
fill="none"
/>
</pattern>
<filter id="res-glow">
<feGaussianBlur stdDeviation="3" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<radialGradient id="res-pod" cx="50%" cy="50%" r="60%">
<stop offset="0%" stopColor={accent} stopOpacity="0.9" />
<stop offset="100%" stopColor={accent} stopOpacity="0.06" />
</radialGradient>
</defs>
<rect width={W} height={H} fill="url(#res-grid)" />
{/* ===== BASE LINKS (Healthy Mesh) ===== */}
{links.map(([i, j], idx) => (
<motion.path
key={`mesh-${idx}`}
d={line(i, j)}
fill="none"
stroke="#1f1f1f"
strokeWidth={3}
strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.25 }}
animate={{ pathLength: 1, opacity: 0.55 }}
transition={{ duration: 0.6, delay: idx * 0.04 }}
/>
))}
{/* ===== FAILING NODE LINK (Fading Out) ===== */}
<motion.path
d={failRay}
fill="none"
stroke={danger}
strokeWidth={2}
strokeDasharray="10 8"
initial={{ pathLength: 0, opacity: 0.8 }}
animate={{ pathLength: 1, opacity: [0.8, 0.15, 0.8] }}
transition={{ duration: 1.5, repeat: Infinity, repeatType: "reverse" }}
filter="url(#res-glow)"
/>
{/* ===== ACTIVE REROUTE (Compensating Path) ===== */}
<motion.path
d={reroute}
fill="none"
stroke={accent}
strokeWidth={2}
strokeDasharray="12 10"
initial={{ opacity: 0 }}
animate={{
opacity: [0, 1, 1, 0],
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "loop",
ease: "easeInOut",
}}
filter="url(#res-glow)"
/>
{/* ===== MOVING PACKETS ON HEALTHY LINKS ===== */}
{!prefers &&
links.map(([i, j], idx) => (
<motion.circle
key={`pkt-${idx}`}
r={5}
fill={accent}
style={{ offsetPath: `path('${line(i, j)}')` }}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{ offsetDistance: ["0%", "100%"], opacity: [0.15, 1, 0.15] }}
transition={{
duration: 2.4,
delay: idx * 0.15,
repeat: Infinity,
ease: "linear",
}}
filter="url(#res-glow)"
/>
))}
{/* ===== PACKETS ON REROUTE WHEN FAILURE OCCURS ===== */}
{!prefers && (
<motion.circle
r={6}
fill={accent}
style={{ offsetPath: `path('${reroute}')` }}
initial={{ offsetDistance: "0%", opacity: 0 }}
animate={{
offsetDistance: ["0%", "100%"],
opacity: [0.2, 1, 0.2],
}}
transition={{
duration: 2.8,
repeat: Infinity,
ease: "linear",
}}
filter="url(#res-glow)"
/>
)}
{/* ===== HEALTHY NODES ===== */}
{nodes.map((n, i) => (
<g key={`node-${i}`}>
{/* Aura */}
{!prefers && (
<motion.ellipse
cx={n.x}
cy={n.y}
rx={40}
ry={26}
fill="none"
stroke={accent}
strokeWidth={2}
opacity={0.18}
animate={{
scale: [1, 1.07, 1],
opacity: [0.1, 0.25, 0.1],
}}
transition={{
duration: 2.2,
delay: i * 0.1,
repeat: Infinity,
}}
filter="url(#res-glow)"
/>
)}
{/* Pod body */}
<ellipse
cx={n.x}
cy={n.y}
rx={34}
ry={22}
fill="url(#res-pod)"
stroke="#1f1f1f"
strokeWidth={2}
/>
{/* Core */}
<ellipse
cx={n.x}
cy={n.y}
rx={16}
ry={11}
fill="#0f0f0f"
stroke="#272727"
strokeWidth={1.5}
/>
</g>
))}
{/* ===== FAILING NODE (Flickering / Disconnecting) ===== */}
<g>
<ellipse
cx={failing.x}
cy={failing.y}
rx={30}
ry={20}
fill="#1a1a1a"
stroke="#3a3a3a"
strokeWidth={2}
/>
{!prefers && (
<motion.ellipse
cx={failing.x}
cy={failing.y}
rx={18}
ry={12}
fill={danger}
opacity={0.5}
animate={{ opacity: [0.5, 0.1, 0.5] }}
transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
/>
)}
</g>
</svg>
</div>
);
}

View File

@@ -0,0 +1,231 @@
"use client";
import { motion, useReducedMotion } from "framer-motion";
import clsx from "clsx";
type Props = {
className?: string;
accent?: string; // cyan
danger?: string; // hostile color
bg?: string; // dark background
gridStroke?: string; // subtle grid
};
const W = 720;
const H = 540;
export default function Security({
className,
accent = "#00b8db",
danger = "#ff4d4d",
bg = "#0b0b0b",
gridStroke = "#2b2a2a",
}: Props) {
const prefers = useReducedMotion();
// Central protected pod
const center = { x: W / 2, y: H / 2 };
// External hostile/corporate nodes
const hostile = [
{ x: 160, y: 120 },
{ x: 560, y: 120 },
{ x: 120, y: 300 },
{ x: 600, y: 380 },
{ x: 350, y: 80 },
];
// Helper function for attack rays
const attackRay = (h: { x: number; y: number }) =>
`M ${h.x} ${h.y} L ${center.x} ${center.y}`;
return (
<div
className={clsx("relative overflow-hidden", className)}
aria-hidden="true"
role="img"
style={{ background: bg }}
>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
{/* ====== GRID + FILTERS ====== */}
<defs>
<pattern id="sec-grid" width="28" height="28" patternUnits="userSpaceOnUse">
<path
d="M 28 0 L 0 0 0 28"
stroke={gridStroke}
strokeWidth="1"
opacity="0.35"
fill="none"
/>
</pattern>
<filter id="sec-glow">
<feGaussianBlur stdDeviation="3" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<radialGradient id="sec-core" cx="50%" cy="50%" r="60%">
<stop offset="0%" stopColor={accent} stopOpacity="0.9" />
<stop offset="100%" stopColor={accent} stopOpacity="0.06" />
</radialGradient>
</defs>
<rect width={W} height={H} fill="url(#sec-grid)" />
{/* ====== ATTACK LINES (blocked) ====== */}
{hostile.map((h, i) => (
<motion.path
key={`atk-${i}`}
d={attackRay(h)}
stroke={danger}
strokeWidth={2}
strokeDasharray="10 8"
strokeLinecap="round"
fill="none"
initial={{ pathLength: 0, opacity: 0.7 }}
animate={{
pathLength: 1,
opacity: [0.7, 0.2, 0.7], // attempt but weakened
}}
transition={{
duration: 1.5,
delay: i * 0.25,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut",
}}
filter="url(#sec-glow)"
/>
))}
{/* ====== BLOCKING SHIELD ====== */}
{/* Outer shield */}
<motion.circle
cx={center.x}
cy={center.y}
r={90}
fill="none"
stroke={accent}
strokeWidth={2}
opacity={0.25}
animate={!prefers ? { scale: [1, 1.05, 1], opacity: [0.2, 0.4, 0.2] } : {}}
transition={{ duration: 2.6, repeat: Infinity, ease: [0.22, 1, 0.36, 1] }}
filter="url(#sec-glow)"
/>
{/* Impact pulses when attacks hit shield */}
{!prefers &&
hostile.map((h, i) => {
const midX = (h.x + center.x) / 2;
const midY = (h.y + center.y) / 2;
return (
<motion.circle
key={`impact-${i}`}
cx={midX}
cy={midY}
r={10}
fill="none"
stroke={danger}
strokeWidth={2}
initial={{ scale: 0.8, opacity: 0 }}
animate={{
scale: [0.8, 1.4, 1.8],
opacity: [0, 0.4, 0],
}}
transition={{
delay: i * 0.25 + 0.6,
duration: 1.2,
repeat: Infinity,
ease: "easeOut",
}}
filter="url(#sec-glow)"
/>
);
})}
{/* ====== EXTERNAL HOSTILE NODES (flickering / unstable) ====== */}
{hostile.map((h, i) => (
<g key={`hnode-${i}`}>
<circle
cx={h.x}
cy={h.y}
r={18}
fill="#1a1a1a"
stroke="#3a3a3a"
strokeWidth={2}
/>
{!prefers && (
<motion.circle
cx={h.x}
cy={h.y}
r={10}
fill={danger}
opacity={0.3}
animate={{ opacity: [0.3, 0.1, 0.3] }}
transition={{
duration: 1.8,
delay: i * 0.3,
repeat: Infinity,
ease: "easeInOut",
}}
/>
)}
</g>
))}
{/* ====== CENTRAL SECURE POD (always stable) ====== */}
<g>
{/* Glowing pod aura */}
{!prefers && (
<motion.circle
cx={center.x}
cy={center.y}
r={60}
fill="none"
stroke={accent}
strokeWidth={2}
opacity={0.2}
animate={{ scale: [1, 1.08, 1], opacity: [0.15, 0.35, 0.15] }}
transition={{ duration: 2.4, repeat: Infinity, ease: "easeInOut" }}
filter="url(#sec-glow)"
/>
)}
{/* Pod body */}
<ellipse
cx={center.x}
cy={center.y}
rx={48}
ry={30}
fill="url(#sec-core)"
stroke="#1f1f1f"
strokeWidth={2}
filter="url(#sec-glow)"
/>
{/* Inner dark core */}
<ellipse cx={center.x} cy={center.y} rx={22} ry={14} fill="#0f0f0f" stroke="#292929" strokeWidth={2} />
</g>
{/* Inner stable heartbeat */}
{!prefers && (
<motion.circle
cx={center.x}
cy={center.y}
r={20}
fill="none"
stroke={accent}
strokeWidth={2}
animate={{ scale: [1, 1.15, 1], opacity: [0.35, 1, 0.35] }}
transition={{ duration: 2.2, repeat: Infinity, ease: "easeInOut" }}
filter="url(#sec-glow)"
/>
)}
</svg>
</div>
);
}