feat: unify code-like components with shared CodeWindow, fix blog re-render loop, and stabilize layouts
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m2s
Build & Deploy / 🏗️ Build (push) Failing after 3m44s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s

This commit is contained in:
2026-02-15 17:34:07 +01:00
parent 7c774f65bc
commit c1295546a6
32 changed files with 3293 additions and 1235 deletions

View File

@@ -0,0 +1,181 @@
"use client";
import * as React from "react";
/**
* AbstractCircuit: Premium Canvas Binary Data Flow
*
* - Binary 0/1 characters flow in structured horizontal lanes
* - Vertical cross-traffic for depth
* - Characters "breathe" with sine-wave opacity
* - High performance: uses requestAnimationFrame, minimal allocations per frame
*/
interface Char {
x: number;
y: number;
val: number; // 0 or 1
speed: number; // px per frame
vertical: boolean;
size: number;
baseAlpha: number;
phase: number; // sine-wave phase offset
flipIn: number; // frames until next flip
}
export const AbstractCircuit: React.FC<{
invert?: boolean;
className?: string;
}> = ({ invert = false, className = "" }) => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const stateRef = React.useRef({
chars: [] as Char[],
frame: 0,
dpr: 1,
w: 0,
h: 0,
});
React.useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d", { alpha: true })!;
const s = stateRef.current;
let raf = 0;
// ── Sizing ──
const resize = () => {
s.dpr = Math.min(window.devicePixelRatio || 1, 2);
s.w = canvas.offsetWidth;
s.h = canvas.offsetHeight;
canvas.width = s.w * s.dpr;
canvas.height = s.h * s.dpr;
seed();
};
// ── Seed characters ──
const LANE = 28;
const GAP = 20;
const seed = () => {
const chars: Char[] = [];
const { w, h } = s;
// Horizontal lanes
for (let y = 0; y < h; y += LANE) {
const dir = Math.floor(y / LANE) % 3 === 0 ? -1 : 1;
const spd = (0.15 + Math.random() * 0.6) * dir;
for (let x = 0; x < w + GAP; x += GAP) {
if (Math.random() > 0.45) continue;
chars.push({
x: x + (Math.random() - 0.5) * 6,
y: y + LANE / 2 + (Math.random() - 0.5) * 4,
val: Math.random() > 0.5 ? 1 : 0,
speed: spd,
vertical: false,
size: 9 + Math.floor(Math.random() * 2),
baseAlpha: 0.035 + Math.random() * 0.045,
phase: Math.random() * Math.PI * 2,
flipIn: 120 + Math.floor(Math.random() * 400),
});
}
}
// Vertical lanes (sparser)
for (let x = 0; x < w; x += LANE * 2.5) {
const dir = Math.floor(x / LANE) % 2 === 0 ? 1 : -1;
const spd = (0.1 + Math.random() * 0.4) * dir;
for (let y = 0; y < h + GAP; y += GAP) {
if (Math.random() > 0.3) continue;
chars.push({
x: x + (Math.random() - 0.5) * 4,
y: y + (Math.random() - 0.5) * 6,
val: Math.random() > 0.5 ? 1 : 0,
speed: spd,
vertical: true,
size: 8 + Math.floor(Math.random() * 2),
baseAlpha: 0.025 + Math.random() * 0.035,
phase: Math.random() * Math.PI * 2,
flipIn: 150 + Math.floor(Math.random() * 500),
});
}
}
s.chars = chars;
};
// ── Mouse tracking — use canvas-relative coordinates ──
// ── Render loop ──
const render = () => {
const { w, h, dpr, chars } = s;
s.frame++;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, w, h);
const t = s.frame * 0.02; // global time
// ── Draw characters ──
for (let i = 0, len = chars.length; i < len; i++) {
const c = chars[i];
// Move
if (c.vertical) {
c.y += c.speed;
if (c.y > h + 15) c.y = -15;
else if (c.y < -15) c.y = h + 15;
} else {
c.x += c.speed;
if (c.x > w + 15) c.x = -15;
else if (c.x < -15) c.x = w + 15;
}
// Flip timer
if (--c.flipIn <= 0) {
c.val ^= 1;
c.flipIn = 120 + Math.floor(Math.random() * 400);
}
// Sine-wave "breathing"
const breath = Math.sin(t + c.phase) * 0.015;
let alpha = c.baseAlpha + breath;
// ── Draw ──
const sz = c.size;
ctx.font = `bold ${sz}px "SF Mono", "Fira Code", "Cascadia Code", "Menlo", monospace`;
ctx.fillStyle = invert
? `rgba(255,255,255,${Math.max(alpha, 0)})`
: `rgba(100,116,139,${Math.max(alpha, 0)})`;
ctx.shadowColor = "transparent";
ctx.shadowBlur = 0;
ctx.fillText(c.val ? "1" : "0", c.x, c.y);
}
raf = requestAnimationFrame(render);
};
// ── Event listeners ──
// Listen on the PARENT element (the section) to capture all mouse movement
window.addEventListener("resize", resize);
resize();
raf = requestAnimationFrame(render);
return () => {
cancelAnimationFrame(raf);
window.removeEventListener("resize", resize);
};
}, [invert]);
return (
<div
className={`absolute inset-0 overflow-hidden ${className}`}
aria-hidden="true"
>
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
</div>
);
};

View File

@@ -0,0 +1,63 @@
"use client";
import * as React from "react";
interface BinaryStreamProps {
className?: string;
columns?: number;
side?: "left" | "right" | "both";
}
export const BinaryStream: React.FC<BinaryStreamProps> = ({
className = "",
columns = 4,
side = "both",
}) => {
// Generate deterministic binary strings
const generateColumn = (seed: number) => {
const chars: string[] = [];
for (let i = 0; i < 60; i++) {
chars.push(((seed * 137 + i * 31) % 2).toString());
}
return chars.join(" ");
};
const renderColumns = (position: "left" | "right") => (
<div
className={`absolute top-0 ${position === "left" ? "left-0" : "right-0"} flex gap-3 pointer-events-none select-none overflow-hidden`}
style={{ height: "100%", width: `${columns * 16}px` }}
>
{Array.from({ length: columns }).map((_, i) => {
const offset = position === "left" ? i : i + columns;
const duration = 20 + (i % 3) * 8;
const delay = i * 2.5;
return (
<div
key={`${position}-${i}`}
className="binary-column text-[10px] font-mono leading-[1.6] whitespace-pre tracking-widest"
style={{
color: "rgba(203, 213, 225, 0.12)",
writingMode: "vertical-lr",
animation: `binary-scroll ${duration}s linear ${delay}s infinite`,
animationFillMode: "backwards",
}}
>
{generateColumn(offset + 42)}
{"\n"}
{generateColumn(offset + 99)}
</div>
);
})}
</div>
);
return (
<div
className={`absolute inset-0 pointer-events-none overflow-hidden ${className}`}
aria-hidden="true"
>
{(side === "left" || side === "both") && renderColumns("left")}
{(side === "right" || side === "both") && renderColumns("right")}
</div>
);
};

View File

@@ -0,0 +1,317 @@
"use client";
import * as React from "react";
import { useEffect, useRef, useCallback } from "react";
interface Node {
x: number;
y: number;
connections: number[];
pulsePhase: number;
pulseSpeed: number;
size: number;
}
interface Trace {
from: number;
to: number;
progress: number;
speed: number;
active: boolean;
delay: number;
}
interface CircuitBoardProps {
className?: string;
density?: "low" | "medium" | "high";
animate?: boolean;
}
export const CircuitBoard: React.FC<CircuitBoardProps> = ({
className = "",
density = "medium",
animate = true,
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const nodesRef = useRef<Node[]>([]);
const tracesRef = useRef<Trace[]>([]);
const animationFrameRef = useRef<number>(0);
const timeRef = useRef<number>(0);
const dimensionsRef = useRef<{ width: number; height: number }>({
width: 0,
height: 0,
});
const densityMap = { low: 12, medium: 20, high: 30 };
const initCircuit = useCallback(
(width: number, height: number) => {
const nodeCount = densityMap[density];
const nodes: Node[] = [];
const traces: Trace[] = [];
const gridCols = Math.ceil(Math.sqrt(nodeCount * (width / height)));
const gridRows = Math.ceil(nodeCount / gridCols);
const cellW = width / gridCols;
const cellH = height / gridRows;
// Create nodes on a jittered grid
for (let row = 0; row < gridRows; row++) {
for (let col = 0; col < gridCols; col++) {
if (nodes.length >= nodeCount) break;
nodes.push({
x: cellW * (col + 0.3 + Math.random() * 0.4),
y: cellH * (row + 0.3 + Math.random() * 0.4),
connections: [],
pulsePhase: Math.random() * Math.PI * 2,
pulseSpeed: 0.5 + Math.random() * 1.5,
size: 1.5 + Math.random() * 2,
});
}
}
// Connect nearby nodes with orthogonal traces (PCB style)
for (let i = 0; i < nodes.length; i++) {
const maxConnections = 2 + Math.floor(Math.random() * 2);
const distances: { idx: number; dist: number }[] = [];
for (let j = 0; j < nodes.length; j++) {
if (i === j) continue;
const dx = nodes[j].x - nodes[i].x;
const dy = nodes[j].y - nodes[i].y;
distances.push({ idx: j, dist: Math.sqrt(dx * dx + dy * dy) });
}
distances.sort((a, b) => a.dist - b.dist);
let connected = 0;
for (const d of distances) {
if (connected >= maxConnections) break;
if (d.dist > Math.max(cellW, cellH) * 2) break;
// Avoid duplicate traces
const exists = traces.some(
(t) =>
(t.from === i && t.to === d.idx) ||
(t.from === d.idx && t.to === i),
);
if (exists) continue;
nodes[i].connections.push(d.idx);
nodes[d.idx].connections.push(i);
traces.push({
from: i,
to: d.idx,
progress: 0,
speed: 0.002 + Math.random() * 0.004,
active: Math.random() > 0.6,
delay: Math.random() * 3000,
});
connected++;
}
}
nodesRef.current = nodes;
tracesRef.current = traces;
},
[density],
);
const drawTrace = useCallback(
(
ctx: CanvasRenderingContext2D,
x1: number,
y1: number,
x2: number,
y2: number,
alpha: number,
) => {
// Draw orthogonal PCB-style trace (L-shaped)
const midX = Math.random() > 0.5 ? x2 : x1;
const midY = Math.random() > 0.5 ? y1 : y2;
ctx.beginPath();
ctx.moveTo(x1, y1);
if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) {
ctx.lineTo(x2, y1);
ctx.lineTo(x2, y2);
} else {
ctx.lineTo(x1, y2);
ctx.lineTo(x2, y2);
}
ctx.strokeStyle = `rgba(203, 213, 225, ${alpha})`;
ctx.lineWidth = 0.5;
ctx.stroke();
},
[],
);
const animateFrame = useCallback(() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext("2d");
if (!canvas || !ctx) return;
const { width, height } = dimensionsRef.current;
const nodes = nodesRef.current;
const traces = tracesRef.current;
timeRef.current += 16;
const time = timeRef.current;
ctx.clearRect(0, 0, width, height);
// Draw static traces
traces.forEach((trace) => {
const from = nodes[trace.from];
const to = nodes[trace.to];
if (!from || !to) return;
// Static trace line
ctx.beginPath();
ctx.moveTo(from.x, from.y);
if (Math.abs(to.x - from.x) > Math.abs(to.y - from.y)) {
ctx.lineTo(to.x, from.y);
ctx.lineTo(to.x, to.y);
} else {
ctx.lineTo(from.x, to.y);
ctx.lineTo(to.x, to.y);
}
ctx.strokeStyle = "rgba(226, 232, 240, 0.4)";
ctx.lineWidth = 0.5;
ctx.stroke();
// Animated data packet traveling along trace
if (animate && trace.active && time > trace.delay) {
trace.progress += trace.speed;
if (trace.progress > 1) {
trace.progress = 0;
trace.active = Math.random() > 0.3;
trace.delay = time + Math.random() * 5000;
}
const p = trace.progress;
let px: number, py: number;
if (Math.abs(to.x - from.x) > Math.abs(to.y - from.y)) {
if (p < 0.5) {
px = from.x + (to.x - from.x) * (p * 2);
py = from.y;
} else {
px = to.x;
py = from.y + (to.y - from.y) * ((p - 0.5) * 2);
}
} else {
if (p < 0.5) {
px = from.x;
py = from.y + (to.y - from.y) * (p * 2);
} else {
px = from.x + (to.x - from.x) * ((p - 0.5) * 2);
py = to.y;
}
}
// Data packet glow
const gradient = ctx.createRadialGradient(px, py, 0, px, py, 8);
gradient.addColorStop(0, "rgba(148, 163, 184, 0.6)");
gradient.addColorStop(1, "rgba(148, 163, 184, 0)");
ctx.fillStyle = gradient;
ctx.fillRect(px - 8, py - 8, 16, 16);
// Data packet dot
ctx.beginPath();
ctx.arc(px, py, 1.5, 0, Math.PI * 2);
ctx.fillStyle = "rgba(148, 163, 184, 0.8)";
ctx.fill();
}
});
// Draw nodes
nodes.forEach((node) => {
const pulse = animate
? 0.3 + Math.sin(time * 0.001 * node.pulseSpeed + node.pulsePhase) * 0.2
: 0.4;
// Node glow
const gradient = ctx.createRadialGradient(
node.x,
node.y,
0,
node.x,
node.y,
node.size * 4,
);
gradient.addColorStop(0, `rgba(191, 203, 219, ${pulse * 0.3})`);
gradient.addColorStop(1, "rgba(191, 203, 219, 0)");
ctx.fillStyle = gradient;
ctx.fillRect(
node.x - node.size * 4,
node.y - node.size * 4,
node.size * 8,
node.size * 8,
);
// Node dot
ctx.beginPath();
ctx.arc(node.x, node.y, node.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(203, 213, 225, ${pulse + 0.2})`;
ctx.fill();
});
// Draw subtle binary text near some nodes
if (animate) {
ctx.font = "9px ui-monospace, monospace";
nodes.forEach((node, i) => {
if (i % 4 !== 0) return; // Only every 4th node
const binaryAlpha =
0.06 + Math.sin(time * 0.0008 + node.pulsePhase) * 0.04;
ctx.fillStyle = `rgba(148, 163, 184, ${binaryAlpha})`;
const binary = ((time * 0.01 + i * 137) % 256)
.toString(2)
.padStart(8, "0");
ctx.fillText(binary, node.x + node.size * 3, node.y + 3);
});
}
animationFrameRef.current = requestAnimationFrame(animateFrame);
}, [animate]);
const handleResize = useCallback(() => {
const container = containerRef.current;
const canvas = canvasRef.current;
if (!container || !canvas) return;
const rect = container.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
const ctx = canvas.getContext("2d");
if (ctx) ctx.scale(dpr, dpr);
dimensionsRef.current = { width: rect.width, height: rect.height };
initCircuit(rect.width, rect.height);
}, [initCircuit]);
useEffect(() => {
handleResize();
window.addEventListener("resize", handleResize);
animationFrameRef.current = requestAnimationFrame(animateFrame);
return () => {
window.removeEventListener("resize", handleResize);
cancelAnimationFrame(animationFrameRef.current);
};
}, [handleResize, animateFrame]);
return (
<div
ref={containerRef}
className={`absolute inset-0 pointer-events-none ${className}`}
aria-hidden="true"
>
<canvas ref={canvasRef} className="w-full h-full" />
</div>
);
};

View File

@@ -0,0 +1,195 @@
"use client";
import * as React from "react";
import { useEffect, useState, useRef } from "react";
import { motion, useInView } from "framer-motion";
interface CodeSnippetProps {
className?: string;
variant?: "code" | "git" | "terminal";
}
const codeLines = [
{ indent: 0, text: "async deploy(config) {", color: "text-slate-500" },
{ indent: 1, text: "const build = await compile({", color: "text-slate-400" },
{ indent: 2, text: 'target: "production",', color: "text-slate-300" },
{ indent: 2, text: "optimize: true,", color: "text-slate-300" },
{ indent: 2, text: 'performance: "maximum"', color: "text-slate-300" },
{ indent: 1, text: "});", color: "text-slate-400" },
{ indent: 1, text: "", color: "" },
{ indent: 1, text: "await pipeline.run([", color: "text-slate-400" },
{ indent: 2, text: "lint, test, build, stage", color: "text-slate-300" },
{ indent: 1, text: "]);", color: "text-slate-400" },
{ indent: 1, text: "", color: "" },
{ indent: 1, text: 'return { status: "live" };', color: "text-slate-400" },
{ indent: 0, text: "}", color: "text-slate-500" },
];
const gitBranches = [
{
type: "commit",
branch: "main",
label: "v2.1.0 Production",
active: false,
},
{
type: "branch",
branch: "feature",
label: "feature/redesign",
active: true,
},
{ type: "commit", branch: "feature", label: "Neues Layout", active: true },
{
type: "commit",
branch: "feature",
label: "Performance-Optimierung",
active: true,
},
{ type: "merge", branch: "main", label: "Merge → Production", active: false },
{ type: "commit", branch: "main", label: "v2.2.0 Live", active: false },
];
const terminalLines = [
{ prompt: true, text: "npm run build", delay: 0 },
{ prompt: false, text: "✓ Compiled successfully", delay: 0.3 },
{ prompt: false, text: "✓ Lighthouse: 98/100", delay: 0.6 },
{ prompt: false, text: "✓ Bundle: 42kb gzipped", delay: 0.9 },
{ prompt: true, text: "git push origin main", delay: 1.5 },
{ prompt: false, text: "→ Deploying to production...", delay: 1.8 },
{ prompt: false, text: "✓ Live in 12s", delay: 2.4 },
];
import { CodeWindow } from "./CodeWindow";
export const CodeSnippet: React.FC<CodeSnippetProps> = ({
className = "",
variant = "code",
}) => {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-10%" });
const [visibleLineIndex, setVisibleLineIndex] = useState(-1);
const [displayText, setDisplayText] = useState<string[]>([]);
useEffect(() => {
if (!isInView) return;
const lines =
variant === "code"
? codeLines
: variant === "git"
? gitBranches.map((b) => ({ text: b.label }))
: terminalLines;
const animate = async () => {
for (let i = 0; i < lines.length; i++) {
setVisibleLineIndex(i);
const line = lines[i];
const text = "text" in line ? line.text : (line as any).label || "";
for (let j = 0; j <= text.length; j++) {
setDisplayText((prev) => {
const next = [...prev];
next[i] = text.slice(0, j);
return next;
});
const speed = "prompt" in line && line.prompt ? 40 : 25;
await new Promise((r) => setTimeout(r, speed));
}
const pause = "delay" in line ? (line as any).delay * 1000 : 150;
await new Promise((r) => setTimeout(r, pause));
}
};
animate();
}, [isInView, variant]);
const title =
variant === "code"
? "deploy.ts"
: variant === "git"
? "git log"
: "terminal";
return (
<CodeWindow title={title} className={className} minHeight="380px">
<div ref={ref}>
{variant === "code" &&
codeLines.map((line, i) => (
<motion.div
key={i}
initial={{ opacity: 0 }}
animate={i <= visibleLineIndex ? { opacity: 1 } : { opacity: 0 }}
className={`${line.color} whitespace-pre flex items-center h-6`}
style={{ paddingLeft: `${line.indent * 20}px` }}
>
<span>{displayText[i] || ""}</span>
{i === visibleLineIndex && (
<motion.span
animate={{ opacity: [1, 0] }}
transition={{ repeat: Infinity, duration: 0.6 }}
className="inline-block w-1.5 h-4 bg-slate-300 ml-1 shrink-0"
/>
)}
</motion.div>
))}
{variant === "git" && (
<div className="relative">
<div className="absolute left-[7px] top-3 bottom-3 w-px bg-slate-200" />
{gitBranches.map((item, i) => (
<motion.div
key={i}
initial={{ opacity: 0, x: -5 }}
animate={i <= visibleLineIndex ? { opacity: 1, x: 0 } : {}}
className="flex items-center gap-4 py-1.5 relative h-8"
>
<div
className={`w-4 h-4 rounded-full border-2 z-10 shrink-0 ${item.type === "merge" ? "border-slate-400 bg-slate-100" : item.active ? "border-slate-300 bg-white" : "border-slate-200 bg-slate-50"}`}
/>
{item.type === "branch" && (
<span className="text-[10px] px-2 py-0.5 rounded-full bg-slate-100 border border-slate-200 text-slate-400 font-bold shrink-0">
{item.branch}
</span>
)}
<span
className={`text-xs ${item.active ? "text-slate-500" : "text-slate-300"} truncate`}
>
{displayText[i] || ""}
</span>
</motion.div>
))}
</div>
)}
{variant === "terminal" &&
terminalLines.map((line, i) => (
<motion.div
key={i}
initial={{ opacity: 0 }}
animate={i <= visibleLineIndex ? { opacity: 1 } : {}}
className="flex items-start gap-2 py-0.5 min-h-[1.5rem]"
>
{line.prompt && (
<span className="text-slate-300 select-none shrink-0"></span>
)}
<span
className={
line.prompt ? "text-slate-500 font-medium" : "text-slate-300"
}
>
{displayText[i] || ""}
</span>
{i === visibleLineIndex && (
<motion.span
animate={{ opacity: [1, 0] }}
transition={{ repeat: Infinity, duration: 0.6 }}
className="inline-block w-1.5 h-4 bg-slate-300 ml-0.5 shrink-0"
/>
)}
</motion.div>
))}
</div>
</CodeWindow>
);
};

View File

@@ -0,0 +1,59 @@
"use client";
import * as React from "react";
import { cn } from "../../utils/cn";
interface CodeWindowProps {
title: string;
children: React.ReactNode;
className?: string;
actions?: React.ReactNode;
fixedHeight?: boolean;
minHeight?: string;
}
/**
* CodeWindow: A shared, stable browser-frame chassis for code, terminal, and diagrams.
* - Enforces dimension stability to prevent layout shifts.
* - Standardizes the "Systems, not Brochures" aesthetic.
*/
export const CodeWindow: React.FC<CodeWindowProps> = ({
title,
children,
className = "",
actions,
fixedHeight = false,
minHeight = "380px",
}) => {
return (
<div
className={cn(
"relative rounded-xl border border-slate-100 bg-slate-50/50 backdrop-blur-sm overflow-hidden w-full max-w-[600px] mx-auto flex-shrink-0 flex flex-col",
fixedHeight && "h-[400px]",
className,
)}
style={!fixedHeight && minHeight ? { minHeight } : {}}
>
{/* Window chrome */}
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100 bg-white/50 shrink-0">
<div className="flex items-center gap-1.5">
<div className="w-2 h-2 rounded-full bg-slate-200" />
<div className="w-2 h-2 rounded-full bg-slate-200" />
<div className="w-2 h-2 rounded-full bg-slate-200" />
<span className="ml-3 text-[9px] font-mono text-slate-300 uppercase tracking-widest select-none">
{title}
</span>
</div>
{actions && <div className="flex items-center gap-2">{actions}</div>}
</div>
{/* Content area */}
<div className="flex-1 overflow-x-auto overflow-y-auto p-4 md:p-6 font-mono text-xs md:text-sm leading-relaxed relative">
{children}
</div>
{/* Bottom gradient fade for aesthetics/depth */}
<div className="absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-slate-50/80 to-transparent pointer-events-none" />
</div>
);
};

View File

@@ -0,0 +1,120 @@
"use client";
import * as React from "react";
interface DataFlowProps {
className?: string;
lines?: number;
speed?: "slow" | "normal" | "fast";
}
export const DataFlow: React.FC<DataFlowProps> = ({
className = "",
lines = 3,
speed = "normal",
}) => {
const speedMap = { slow: "8s", normal: "5s", fast: "3s" };
const duration = speedMap[speed];
return (
<div
className={`relative w-full overflow-hidden ${className}`}
aria-hidden="true"
>
<svg
viewBox="0 0 1200 40"
className="w-full h-8 md:h-10"
preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg"
>
{Array.from({ length: lines }).map((_, i) => {
const y = 8 + (i * 24) / lines;
const delay = i * 0.8;
return (
<g key={i}>
{/* Static trace line */}
<line
x1="0"
y1={y}
x2="1200"
y2={y}
stroke="rgba(226, 232, 240, 0.3)"
strokeWidth="0.5"
/>
{/* Animated data packet */}
<circle r="2" fill="rgba(148, 163, 184, 0.6)">
<animateMotion
dur={duration}
repeatCount="indefinite"
begin={`${delay}s`}
path={`M-20,${y} L1220,${y}`}
/>
</circle>
{/* Trailing glow */}
<circle r="6" fill="rgba(148, 163, 184, 0.08)">
<animateMotion
dur={duration}
repeatCount="indefinite"
begin={`${delay}s`}
path={`M-20,${y} L1220,${y}`}
/>
</circle>
{/* Secondary packet (opposite direction, slower) */}
<rect
width="12"
height="1"
rx="0.5"
fill="rgba(203, 213, 225, 0.3)"
>
<animateMotion
dur={`${parseFloat(duration) * 1.4}s`}
repeatCount="indefinite"
begin={`${delay + 2}s`}
path={`M1220,${y + 2} L-20,${y + 2}`}
/>
</rect>
</g>
);
})}
{/* Junction nodes */}
{[200, 500, 800, 1050].map((x, i) => (
<g key={`node-${i}`}>
<circle cx={x} cy="20" r="2" fill="rgba(203, 213, 225, 0.4)">
<animate
attributeName="r"
values="1.5;2.5;1.5"
dur="3s"
begin={`${i * 0.7}s`}
repeatCount="indefinite"
/>
</circle>
<circle
cx={x}
cy="20"
r="6"
fill="none"
stroke="rgba(203, 213, 225, 0.15)"
strokeWidth="0.5"
>
<animate
attributeName="r"
values="4;8;4"
dur="3s"
begin={`${i * 0.7}s`}
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0.3;0;0.3"
dur="3s"
begin={`${i * 0.7}s`}
repeatCount="indefinite"
/>
</circle>
</g>
))}
</svg>
</div>
);
};

View File

@@ -0,0 +1,82 @@
"use client";
import * as React from "react";
interface GradientMeshProps {
className?: string;
variant?: "subtle" | "metallic" | "warm";
animate?: boolean;
}
export const GradientMesh: React.FC<GradientMeshProps> = ({
className = "",
variant = "subtle",
animate = true,
}) => {
const gradients = {
subtle: {
bg: "transparent",
blob1: "rgba(226, 232, 240, 0.6)",
blob2: "rgba(241, 245, 249, 0.7)",
blob3: "rgba(203, 213, 225, 0.35)",
},
metallic: {
bg: "transparent",
blob1: "rgba(186, 206, 235, 0.4)",
blob2: "rgba(214, 224, 240, 0.5)",
blob3: "rgba(170, 190, 220, 0.25)",
},
warm: {
bg: "transparent",
blob1: "rgba(241, 245, 249, 0.6)",
blob2: "rgba(248, 250, 252, 0.7)",
blob3: "rgba(226, 232, 240, 0.4)",
},
};
const colors = gradients[variant];
return (
<div
className={`absolute inset-0 pointer-events-none overflow-hidden ${className}`}
aria-hidden="true"
>
{/* Large Blob 1 */}
<div
className={`absolute rounded-full ${animate ? "animate-gradient-blob-1" : ""}`}
style={{
width: "900px",
height: "900px",
background: `radial-gradient(circle, ${colors.blob1} 0%, transparent 65%)`,
top: "-20%",
left: "-10%",
filter: "blur(80px)",
}}
/>
{/* Large Blob 2 */}
<div
className={`absolute rounded-full ${animate ? "animate-gradient-blob-2" : ""}`}
style={{
width: "800px",
height: "800px",
background: `radial-gradient(circle, ${colors.blob2} 0%, transparent 65%)`,
bottom: "-25%",
right: "-10%",
filter: "blur(70px)",
}}
/>
{/* Accent Blob 3 */}
<div
className={`absolute rounded-full ${animate ? "animate-gradient-blob-3" : ""}`}
style={{
width: "600px",
height: "600px",
background: `radial-gradient(circle, ${colors.blob3} 0%, transparent 65%)`,
top: "30%",
left: "25%",
filter: "blur(60px)",
}}
/>
</div>
);
};

View File

@@ -0,0 +1,6 @@
export { CircuitBoard } from "./CircuitBoard";
export { DataFlow } from "./DataFlow";
export { BinaryStream } from "./BinaryStream";
export { GradientMesh } from "./GradientMesh";
export { CodeSnippet } from "./CodeSnippet";
export { AbstractCircuit } from "./AbstractCircuit";