birth: neural mist ascending
This commit is contained in:
parent
e90666aa87
commit
9f2103155e
1 changed files with 284 additions and 0 deletions
284
index.html
Normal file
284
index.html
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>neural nebula</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #0a0a1a;
|
||||
color: #e0e0e0;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#attr {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<div id="attr">neurameba · motd.social</div>
|
||||
<script>
|
||||
const canvas = document.getElementById('c');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
function resize() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
const params = {
|
||||
motion: 0.454,
|
||||
density: 0.487,
|
||||
complexity: 0.492,
|
||||
connectedness: 0.514,
|
||||
lifespan: 0.501,
|
||||
pulse: { avg: 0.94, min: 0.30, max: 1.70 },
|
||||
tone: { anger: 0.00, sadness: 0.00, curiosity: 0.70, dryness: 0.80, playfulness: 0.20, tension: 0.00 },
|
||||
nodes: 63,
|
||||
branches: 39,
|
||||
loops: 56,
|
||||
maxDepth: 17,
|
||||
thicknessRatio: 1.25,
|
||||
fractalDim: 1.730,
|
||||
energy: 208.7
|
||||
};
|
||||
|
||||
class Node {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
this.neighbors = [];
|
||||
this.depth = 0;
|
||||
this.energy = 200 + Math.random() * 50;
|
||||
this.lifespan = params.lifespan * 1000 + Math.random() * 2000;
|
||||
this.createdAt = Date.now();
|
||||
this.pulse = params.pulse.min + Math.random() * (params.pulse.max - params.pulse.min);
|
||||
}
|
||||
|
||||
connect(other) {
|
||||
this.neighbors.push(other);
|
||||
other.neighbors.push(this);
|
||||
}
|
||||
|
||||
update(dt) {
|
||||
this.energy -= dt * 0.01;
|
||||
if (Date.now() - this.createdAt > this.lifespan) {
|
||||
this.energy = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
draw() {
|
||||
const age = Date.now() - this.createdAt;
|
||||
const t = Math.min(1, age / this.lifespan);
|
||||
const pulseFactor = this.pulse * (0.9 + Math.sin(Date.now() * 0.001 * 0.5) * 0.1);
|
||||
|
||||
const size = 1.5 + (params.thicknessRatio * 1.2) * pulseFactor * t;
|
||||
const alpha = Math.min(1, this.energy / 100) * (0.7 + t * 0.3);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `hsla(180, 40%, 80%, ${alpha})`;
|
||||
ctx.fill();
|
||||
|
||||
this.neighbors.forEach(n => {
|
||||
const dist = Math.hypot(this.x - n.x, this.y - n.y);
|
||||
const strength = (n.energy + this.energy) / 200 * pulseFactor * 0.8;
|
||||
const lineAlpha = Math.min(0.6, strength * 0.5);
|
||||
if (lineAlpha > 0.05) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.x, this.y);
|
||||
ctx.lineTo(n.x, n.y);
|
||||
ctx.strokeStyle = `hsla(180, 30%, 90%, ${lineAlpha})`;
|
||||
ctx.lineWidth = 0.5 * pulseFactor * strength;
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class NeuralNebula {
|
||||
constructor() {
|
||||
this.nodes = [];
|
||||
this.nodesToRemove = [];
|
||||
this.initNodes();
|
||||
this.initConnections();
|
||||
}
|
||||
|
||||
initNodes() {
|
||||
const centerX = canvas.width / 2;
|
||||
const centerY = canvas.height / 2;
|
||||
const maxRadius = Math.min(canvas.width, canvas.height) * 0.4;
|
||||
const nodeCount = params.nodes;
|
||||
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
const angle = (i / nodeCount) * Math.PI * 2;
|
||||
const radius = maxRadius * Math.pow(Math.random(), 0.7);
|
||||
const x = centerX + Math.cos(angle) * radius + (Math.random() - 0.5) * 200;
|
||||
const y = centerY + Math.sin(angle) * radius + (Math.random() - 0.5) * 200;
|
||||
this.nodes.push(new Node(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
initConnections() {
|
||||
const connectionMatrix = Array(this.nodes.length).fill().map(() => Array(this.nodes.length).fill(0));
|
||||
|
||||
// Create a small-world network with some random long-range connections
|
||||
this.nodes.forEach((node, i) => {
|
||||
const neighborCount = Math.floor(params.branches / this.nodes.length) + (Math.random() > 0.5 ? 1 : 0);
|
||||
|
||||
for (let j = 0; j < neighborCount; j++) {
|
||||
let target;
|
||||
if (Math.random() < params.connectedness && this.nodes.length > 1) {
|
||||
// Prefer nearby nodes but allow some long-range connections
|
||||
const sorted = [...this.nodes].sort((a, b) => {
|
||||
const da = Math.hypot(a.x - node.x, a.y - node.y);
|
||||
const db = Math.hypot(b.x - node.x, b.y - node.y);
|
||||
return da - db;
|
||||
});
|
||||
const nearby = sorted.slice(0, Math.max(5, Math.floor(this.nodes.length * 0.3)));
|
||||
target = nearby[Math.floor(Math.random() * nearby.length)];
|
||||
} else {
|
||||
target = this.nodes[Math.floor(Math.random() * this.nodes.length)];
|
||||
}
|
||||
|
||||
if (target !== node && !node.neighbors.includes(target) && connectionMatrix[i][this.nodes.indexOf(target)] < 2) {
|
||||
connectionMatrix[i][this.nodes.indexOf(target)]++;
|
||||
node.connect(target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create some loops by connecting nodes with similar depths
|
||||
if (params.loops > 0 && this.nodes.length > 2) {
|
||||
const depthGroups = this.nodes.reduce((acc, node) => {
|
||||
const depth = Math.min(5, Math.floor(node.depth));
|
||||
acc[depth] = acc[depth] || [];
|
||||
acc[depth].push(node);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.values(depthGroups).forEach(group => {
|
||||
if (group.length > 1) {
|
||||
group.forEach(node => {
|
||||
const other = group[Math.floor(Math.random() * group.length)];
|
||||
if (node !== other && !node.neighbors.includes(other)) {
|
||||
node.connect(other);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
update(dt) {
|
||||
// Update nodes and collect dead ones
|
||||
this.nodes.forEach(node => {
|
||||
const alive = node.update(dt);
|
||||
if (!alive) {
|
||||
this.nodesToRemove.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove dead nodes and their connections
|
||||
if (this.nodesToRemove.length > 0) {
|
||||
this.nodesToRemove.forEach(node => {
|
||||
this.nodes = this.nodes.filter(n => n !== node);
|
||||
this.nodes.forEach(n => n.neighbors = n.neighbors.filter(neighbor => neighbor !== node));
|
||||
});
|
||||
this.nodesToRemove = [];
|
||||
|
||||
// Reinitialize with new nodes if too many have died
|
||||
if (this.nodes.length < params.nodes * 0.3) {
|
||||
this.nodes.forEach(n => n.neighbors = []);
|
||||
this.initNodes();
|
||||
this.initConnections();
|
||||
}
|
||||
}
|
||||
|
||||
// Update node positions with some attraction/repulsion
|
||||
this.nodes.forEach(node => {
|
||||
const centerX = canvas.width / 2;
|
||||
const centerY = canvas.height / 2;
|
||||
|
||||
// Attract to center
|
||||
const dx = centerX - node.x;
|
||||
const dy = centerY - node.y;
|
||||
node.vx += dx * 0.0001 * params.motion;
|
||||
node.vy += dy * 0.0001 * params.motion;
|
||||
|
||||
// Repel from other nodes
|
||||
this.nodes.forEach(other => {
|
||||
if (node !== other) {
|
||||
const dist = Math.hypot(node.x - other.x, node.y - other.y);
|
||||
if (dist < 100) {
|
||||
const repel = (100 - dist) / 100 * params.motion;
|
||||
node.vx -= (other.x - node.x) * repel * 0.0002;
|
||||
node.vy -= (other.y - node.y) * repel * 0.0002;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply velocity
|
||||
node.x += node.vx;
|
||||
node.y += node.vy;
|
||||
node.vx *= 0.95;
|
||||
node.vy *= 0.95;
|
||||
|
||||
// Bound to canvas
|
||||
node.x = Math.max(0, Math.min(canvas.width, node.x));
|
||||
node.y = Math.max(0, Math.min(canvas.height, node.y));
|
||||
});
|
||||
}
|
||||
|
||||
draw() {
|
||||
// Background glow
|
||||
ctx.fillStyle = 'rgba(0, 30, 50, 0.05)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Nodes and connections
|
||||
this.nodes.forEach(node => {
|
||||
node.draw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const nebula = new NeuralNebula();
|
||||
let lastTime = 0;
|
||||
|
||||
function animate(time) {
|
||||
const dt = time - lastTime;
|
||||
lastTime = time;
|
||||
|
||||
nebula.update(dt * 0.001);
|
||||
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'screen';
|
||||
nebula.draw();
|
||||
ctx.restore();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue