136 lines
No EOL
4.7 KiB
HTML
136 lines
No EOL
4.7 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Cellular Pulse</title>
|
|
<style>
|
|
body { margin: 0; overflow: hidden; background: #000; }
|
|
canvas { display: block; }
|
|
#info { position: absolute; bottom: 10px; left: 10px; color: #aaa; font-family: monospace; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="cvs"></canvas>
|
|
<div id="info">neurameba · motd.social</div>
|
|
<script>
|
|
const cvs = document.getElementById('cvs');
|
|
const ctx = cvs.getContext('2d');
|
|
|
|
function resize() {
|
|
cvs.width = window.innerWidth;
|
|
cvs.height = window.innerHeight;
|
|
}
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
// Configuration derived from parameters
|
|
const params = {
|
|
motion: 0.5,
|
|
density: 0.5,
|
|
complexity: 0.5,
|
|
connectedness: 0.5,
|
|
lifespan: 0.5,
|
|
tone: { dryness: 0.8, curiosity: 0.2 },
|
|
pulse: { avg: 1.07 }
|
|
};
|
|
|
|
// Cellular automaton grid
|
|
const gridSize = 60;
|
|
const cellSize = Math.min(cvs.width, cvs.height) / gridSize;
|
|
let grid = Array(gridSize).fill().map(() => Array(gridSize).fill(0));
|
|
|
|
// Initialize with density
|
|
function initGrid() {
|
|
for (let y = 0; y < gridSize; y++) {
|
|
for (let x = 0; x < gridSize; x++) {
|
|
grid[y][x] = Math.random() < params.density ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
initGrid();
|
|
|
|
// CA rules with complexity
|
|
function updateGrid() {
|
|
const newGrid = grid.map(arr => [...arr]);
|
|
|
|
// Conway-like rules with varying birth/death thresholds
|
|
const birthThreshold = 0.3 + params.motion * 0.2;
|
|
const deathThreshold = 0.3 - params.motion * 0.1;
|
|
|
|
for (let y = 0; y < gridSize; y++) {
|
|
for (let x = 0; x < gridSize; x++) {
|
|
const neighbors = [
|
|
[-1,-1], [0,-1], [1,-1],
|
|
[-1,0], [1,0],
|
|
[-1,1], [0,1], [1,1]
|
|
].reduce((sum, [dx, dy]) => {
|
|
const nx = (x + dx + gridSize) % gridSize;
|
|
const ny = (y + dy + gridSize) % gridSize;
|
|
return sum + grid[ny][nx];
|
|
}, 0);
|
|
|
|
if (grid[y][x] === 1) {
|
|
newGrid[y][x] = (neighbors >= deathThreshold * 9) ? 0 : 1;
|
|
} else {
|
|
newGrid[y][x] = (neighbors >= birthThreshold * 9) ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
grid = newGrid;
|
|
|
|
// Add occasional random mutations based on complexity
|
|
if (Math.random() < params.complexity * 0.01) {
|
|
const x = Math.floor(Math.random() * gridSize);
|
|
const y = Math.floor(Math.random() * gridSize);
|
|
grid[y][x] = 1 - grid[y][x];
|
|
}
|
|
}
|
|
|
|
// Drawing with tone
|
|
function draw() {
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
|
|
ctx.fillRect(0, 0, cvs.width, cvs.height);
|
|
|
|
const hue = params.tone.curiosity * 180; // Teals
|
|
const saturation = 50;
|
|
const lightness = 50 + params.tone.dryness * 30;
|
|
|
|
const cellScale = cellSize * params.pulse.avg;
|
|
|
|
for (let y = 0; y < gridSize; y++) {
|
|
for (let x = 0; x < gridSize; x++) {
|
|
if (grid[y][x] === 1) {
|
|
const cx = x * cellScale + cellScale/2;
|
|
const cy = y * cellScale + cellScale/2;
|
|
|
|
ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, cellScale * 0.3, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Add subtle motion trails
|
|
if (params.motion > 0.3) {
|
|
ctx.fillStyle = `hsla(${hue}, ${saturation}%, ${lightness}%, 0.5)`;
|
|
ctx.beginPath();
|
|
ctx.arc(cx + (Math.random() - 0.5) * 5,
|
|
cy + (Math.random() - 0.5) * 5,
|
|
cellScale * 0.2 * (1 - params.lifespan * 0.5),
|
|
0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function animate() {
|
|
updateGrid();
|
|
draw();
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html> |