259 lines
No EOL
8.6 KiB
HTML
259 lines
No EOL
8.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Neurameba Dynamic Network</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
background: #0a0a0a;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
}
|
|
#info {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 10px;
|
|
color: #666;
|
|
font-size: 10px;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="canvas"></canvas>
|
|
<div id="info">neurameba · motd.social</div>
|
|
|
|
<script>
|
|
const canvas = document.getElementById('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
function resizeCanvas() {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
}
|
|
|
|
window.addEventListener('resize', resizeCanvas);
|
|
resizeCanvas();
|
|
|
|
// Configuration from parameters
|
|
const config = {
|
|
motion: 0.504,
|
|
density: 0.459,
|
|
complexity: 0.470,
|
|
connectedness: 0.493,
|
|
lifespan: 0.414,
|
|
survivingNodes: 68,
|
|
branchCount: 52,
|
|
loops: 172,
|
|
maxDepth: 15,
|
|
thicknessRatio: 1.25,
|
|
fractalDim: 1.816,
|
|
pulse: {
|
|
avg: 0.54,
|
|
min: 0.30,
|
|
max: 1.45
|
|
},
|
|
tone: {
|
|
anger: 0.00,
|
|
sadness: 0.00,
|
|
curiosity: 0.60,
|
|
dryness: 0.90,
|
|
playfulness: 0.10,
|
|
tension: 0.00
|
|
}
|
|
};
|
|
|
|
// Calculate derived properties
|
|
const nodeCount = Math.floor(config.survivingNodes * (1 + config.connectedness));
|
|
const edgeCount = Math.floor(config.branchCount * (1 + config.connectedness));
|
|
const lineThickness = 0.5 + config.thicknessRatio * 0.7;
|
|
const pulseRange = config.pulse.max - config.pulse.min;
|
|
const energy = config.pulse.avg * 100;
|
|
|
|
// Color palette based on tone
|
|
const hue = 160 + config.tone.curiosity * 100; // Teals
|
|
const saturation = config.tone.dryness > 0.5 ? 0.3 : 0.7;
|
|
const brightness = 0.7 + config.tone.curiosity * 0.2;
|
|
const bgColor = `hsl(200, 10%, 5%)`;
|
|
const nodeColor = `hsl(${hue}, ${saturation * 100}%, ${(brightness - 0.2) * 100}%)`;
|
|
const edgeColor = `hsl(${hue}, ${saturation * 80}%, ${brightness * 80}%)`;
|
|
const pulseColor = `hsl(${hue}, 100%, 80%)`;
|
|
|
|
// Network graph setup
|
|
class Node {
|
|
constructor() {
|
|
this.x = Math.random() * canvas.width;
|
|
this.y = Math.random() * canvas.height;
|
|
this.vx = (Math.random() - 0.5) * 2 * config.motion;
|
|
this.vy = (Math.random() - 0.5) * 2 * config.motion;
|
|
this.size = 2 + Math.random() * 3 * config.density;
|
|
this.connections = [];
|
|
this.life = 0;
|
|
this.maxLife = 100 + Math.random() * 200 * config.lifespan;
|
|
this.depth = Math.floor(Math.random() * config.maxDepth);
|
|
this.pulsePhase = Math.random() * Math.PI * 2;
|
|
}
|
|
|
|
update() {
|
|
this.x += this.vx;
|
|
this.y += this.vy;
|
|
|
|
// Boundary check
|
|
if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
|
|
if (this.y < 0 || this.y > canvas.height) this.vy *= -1;
|
|
|
|
// Life cycle
|
|
this.life++;
|
|
if (this.life > this.maxLife) {
|
|
this.life = 0;
|
|
this.maxLife = 100 + Math.random() * 200 * config.lifespan;
|
|
}
|
|
|
|
// Pulse effect
|
|
this.pulsePhase += 0.01 * config.pulse.avg;
|
|
}
|
|
|
|
connect(other) {
|
|
this.connections.push(other);
|
|
}
|
|
|
|
draw() {
|
|
const pulse = (Math.sin(this.pulsePhase) * 0.5 + 0.5) * config.pulse.avg;
|
|
const alpha = Math.min(1, this.life / this.maxLife);
|
|
const size = this.size * (0.8 + pulse * 0.2);
|
|
const glow = Math.min(1, this.life / (this.maxLife * 0.5)) * 5;
|
|
|
|
// Draw glow
|
|
ctx.shadowColor = pulseColor;
|
|
ctx.shadowBlur = glow;
|
|
ctx.globalAlpha = alpha;
|
|
ctx.fillStyle = nodeColor;
|
|
ctx.beginPath();
|
|
ctx.arc(this.x, this.y, size, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
ctx.shadowBlur = 0;
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
}
|
|
|
|
class Edge {
|
|
constructor(a, b) {
|
|
this.nodes = [a, b];
|
|
this.age = 0;
|
|
this.maxAge = 50 + Math.random() * 200 * config.lifespan;
|
|
this.createPhase = Math.random() * Math.PI * 2;
|
|
}
|
|
|
|
update() {
|
|
this.age++;
|
|
this.createPhase += 0.02;
|
|
}
|
|
|
|
draw() {
|
|
const a = this.nodes[0];
|
|
const b = this.nodes[1];
|
|
|
|
// Color based on age and pulse
|
|
const ageRatio = Math.min(1, this.age / this.maxAge);
|
|
const pulseA = (Math.sin(a.pulsePhase) * 0.5 + 0.5) * config.pulse.avg;
|
|
const pulseB = (Math.sin(b.pulsePhase) * 0.5 + 0.5) * config.pulse.avg;
|
|
const alpha = 0.3 + ageRatio * 0.7;
|
|
const width = lineThickness * (0.5 + ageRatio + (pulseA + pulseB) * 0.2);
|
|
|
|
// Draw edge with thickness variation
|
|
ctx.globalAlpha = alpha;
|
|
ctx.strokeStyle = edgeColor;
|
|
ctx.lineWidth = width;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(a.x, a.y);
|
|
ctx.lineTo(b.x, b.y);
|
|
ctx.stroke();
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
}
|
|
|
|
// Create network
|
|
const nodes = [];
|
|
const edges = [];
|
|
|
|
// Create nodes
|
|
for (let i = 0; i < nodeCount; i++) {
|
|
nodes.push(new Node());
|
|
}
|
|
|
|
// Create edges based on connectedness
|
|
const desiredEdges = edgeCount;
|
|
const possibleEdges = nodeCount * (nodeCount - 1) / 2;
|
|
|
|
// Create initial connections
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
if (Math.random() < config.connectedness * 0.1) {
|
|
nodes[i].connect(nodes[j]);
|
|
nodes[j].connect(nodes[i]);
|
|
edges.push(new Edge(nodes[i], nodes[j]));
|
|
if (edges.length >= desiredEdges) break;
|
|
}
|
|
}
|
|
if (edges.length >= desiredEdges) break;
|
|
}
|
|
|
|
// Add extra connections for loops
|
|
for (let i = 0; i < config.loops * 0.5; i++) {
|
|
if (edges.length < desiredEdges * 2) {
|
|
const a = nodes[Math.floor(Math.random() * nodes.length)];
|
|
const b = a.connections[Math.floor(Math.random() * a.connections.length)];
|
|
if (b) {
|
|
a.connect(b);
|
|
b.connect(a);
|
|
edges.push(new Edge(a, b));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Animation
|
|
function animate() {
|
|
// Clear with slight fade
|
|
ctx.fillStyle = `rgba(10, 10, 10, ${0.05 + config.tone.dryness * 0.1})`;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Update and draw nodes
|
|
nodes.forEach(node => {
|
|
node.update();
|
|
node.draw();
|
|
});
|
|
|
|
// Update and draw edges
|
|
edges.forEach(edge => {
|
|
edge.update();
|
|
edge.draw();
|
|
});
|
|
|
|
// Occasionally add new connections
|
|
if (Math.random() < 0.02 * config.connectedness) {
|
|
const a = nodes[Math.floor(Math.random() * nodes.length)];
|
|
const b = nodes[Math.floor(Math.random() * nodes.length)];
|
|
if (a !== b && !a.connections.includes(b)) {
|
|
a.connect(b);
|
|
b.connect(a);
|
|
edges.push(new Edge(a, b));
|
|
// Remove old edges if we're over capacity
|
|
while (edges.length > desiredEdges * 1.5) {
|
|
edges.shift();
|
|
}
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html> |