import React, { useState, useEffect, useRef } from "react"; import Link from "next/link"; const asciiStyle: React.CSSProperties = { fontFamily: "var(--font-mono)", fontSize: "0.72rem", lineHeight: 1.4, color: "var(--colorPrimary)", opacity: 0.7, whiteSpace: "pre", margin: 0, }; const randomRow = () => Math.floor(Math.random() * 3); const randomPath = () => [randomRow(), randomRow(), randomRow()]; const NetworkAnimation = () => { // Packets traverse 5 stages: gw_e(0) → M1(1) → M2(2) → M3(3) → gw_ex(4) // stage -1 = not yet mounted (SSR-safe, renders all ○) const [packets, setPackets] = useState([ { path: randomPath(), stage: -1 }, { path: randomPath(), stage: -1 }, ]); useEffect(() => { // kick off with staggered positions setPackets([ { path: randomPath(), stage: 0 }, { path: randomPath(), stage: 3 }, ]); const id = setInterval(() => { setPackets((prev) => prev.map((p) => { const next = (p.stage + 1) % 5; return { stage: next, path: next === 0 ? randomPath() : p.path }; }) ); }, 300); return () => clearInterval(id); }, []); const gwNode = (stage: number) => { const active = packets.some((p) => p.stage === stage); return ( {active ? "\u25CF" : "\u25CB"} ); }; const mixNode = (col: number, row: number) => { const active = packets.some( (p) => p.stage === col + 1 && p.path[col] === row ); const filled = active; return ( {filled ? "\u25CF" : "\u25CB"} ); }; return (
{"gw_e M1 M2 M3 gw_ex\n"}
{" "}
{mixNode(0, 0)}
{" \u2500\u2500 "}
{mixNode(1, 0)}
{" \u2500\u2500 "}
{mixNode(2, 0)}
{"\n"}
{" \\ / \\ /\n"}
{" "}
{gwNode(0)}
{" \u2500\u2500 "}
{mixNode(0, 1)}
{" \u2500\u2500 "}
{mixNode(1, 1)}
{" \u2500\u2500 "}
{mixNode(2, 1)}
{" \u2500\u2500 "}
{gwNode(4)}
{"\n"}
{" / \\ / \\\n"}
{" "}
{mixNode(0, 2)}
{" \u2500\u2500 "}
{mixNode(1, 2)}
{" \u2500\u2500 "}
{mixNode(2, 2)}
);
};
const TypewriterAnimation = () => {
const text =
"let client = MixnetClient::connect_new().await?;\n" +
"\n" +
"client.send(msg).await;";
const [charCount, setCharCount] = useState(0);
const [showCursor, setShowCursor] = useState(true);
useEffect(() => {
let cancelled = false;
const run = () => {
setCharCount(0);
let i = 0;
const type = () => {
if (cancelled) return;
if (i <= text.length) {
setCharCount(i);
i++;
setTimeout(type, 40);
} else {
setTimeout(() => {
if (!cancelled) run();
}, 2000);
}
};
type();
};
run();
return () => {
cancelled = true;
};
}, []);
useEffect(() => {
const id = setInterval(() => setShowCursor((v) => !v), 530);
return () => clearInterval(id);
}, []);
return (
{text.slice(0, charCount)}
{showCursor ? "\u258C" : " "}
{text.slice(charCount)}
);
};
const OperatorsAnimation = () => {
const totalBars = 10;
const [tick, setTick] = useState(0);
const mixRef = useRef(0);
const [mixCount, setMixCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setTick((t) => t + 1);
mixRef.current += Math.floor(Math.random() * 8) + 5;
setMixCount(mixRef.current);
}, 80);
return () => clearInterval(id);
}, []);
const mixFilled = Math.min(tick % 12, totalBars);
const bar = (f: number) =>
"\u25A0".repeat(f) + "\u25A1".repeat(totalBars - f);
const fmt = (n: number) => n.toLocaleString("en");
return (
{"> nym-node run\n\n"}
{" mixing: "}
{bar(mixFilled)}
{" "}
{fmt(mixCount)}
{" pkts"}
);
};
const ApiAnimation = () => {
const lines = [
"GET /v1/mixnodes/active",
"",
'{ "count": 498,',
' "nodes": [ ... ] }',
];
const [visibleLines, setVisibleLines] = useState(0);
useEffect(() => {
let cancelled = false;
const run = () => {
setVisibleLines(0);
setTimeout(() => {
if (cancelled) return;
setVisibleLines(1);
setTimeout(() => {
if (cancelled) return;
let i = 2;
const reveal = () => {
if (cancelled) return;
if (i <= lines.length) {
setVisibleLines(i);
i++;
setTimeout(reveal, 300);
} else {
setTimeout(() => {
if (!cancelled) run();
}, 2000);
}
};
reveal();
}, 800);
}, 100);
};
run();
return () => {
cancelled = true;
};
}, []);
return (
{lines.slice(0, visibleLines).map((line, i) => (
{i > 0 && "\n"}
{line}
))}
{lines.slice(visibleLines).map((line, i) => (
{visibleLines > 0 || i > 0 ? "\n" : ""}
{line}
))}
);
};
const sections = [
{
title: "Network",
description:
"Architecture, cryptographic systems, and how the Mixnet protects your traffic.",
href: "/network",
animation: "network" as const,
},
{
title: "Developers",
description: "SDKs, tutorials, and integration guides for building on Nym.",
href: "/developers",
animation: "typewriter" as const,
},
{
title: "Operators",
description:
"Set up and maintain mix nodes, gateways, and network infrastructure.",
href: "/operators/introduction",
animation: "progress" as const,
},
{
title: "APIs",
description: "Interactive specs for querying Nym infrastructure.",
href: "/apis/introduction",
animation: "api" as const,
},
];
const AnimationBlock = ({ type }: { type: string }) => {
switch (type) {
case "network":
return {s.description}
Rust and TypeScript libraries for Mixnet integration.
{sdk.description}