160 lines
No EOL
5.2 KiB
HTML
160 lines
No EOL
5.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Exponential Tendrils</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
background: #0a0a0a;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100vh;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
}
|
|
#attribution {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
color: #555;
|
|
font-size: 10px;
|
|
font-family: 'Courier New', monospace;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="canvas"></canvas>
|
|
<div id="attribution">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();
|
|
|
|
// Reaction-diffusion parameters adjusted for organism parameters
|
|
const params = {
|
|
feedRate: 0.055,
|
|
killRate: 0.062,
|
|
diffusionRateA: 1,
|
|
diffusionRateB: 0.5,
|
|
timeStep: 1,
|
|
cellSize: 4,
|
|
gridWidth: Math.floor(window.innerWidth / 4),
|
|
gridHeight: Math.floor(window.innerHeight / 4),
|
|
color1: '#f0f0f0',
|
|
color2: '#a0a0a0'
|
|
};
|
|
|
|
// Initialize grids
|
|
const grid = [];
|
|
const nextGrid = [];
|
|
|
|
function initGrids() {
|
|
for (let y = 0; y < params.gridHeight; y++) {
|
|
grid[y] = [];
|
|
nextGrid[y] = [];
|
|
for (let x = 0; x < params.gridWidth; x++) {
|
|
// Sparse initial distribution (density=0.5)
|
|
grid[y][x] = Math.random() > 0.95 ? 1 : 0;
|
|
nextGrid[y][x] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Laplacian convolution kernel
|
|
const kernel = [
|
|
[0.05, 0.2, 0.05],
|
|
[0.2, -1, 0.2],
|
|
[0.05, 0.2, 0.05]
|
|
];
|
|
|
|
function update() {
|
|
for (let y = 0; y < params.gridHeight; y++) {
|
|
for (let x = 0; x < params.gridWidth; x++) {
|
|
let sumA = 0;
|
|
let sumB = 0;
|
|
|
|
// Apply kernel
|
|
for (let ky = -1; ky <= 1; ky++) {
|
|
for (let kx = -1; kx <= 1; kx++) {
|
|
const nx = (x + kx + params.gridWidth) % params.gridWidth;
|
|
const ny = (y + ky + params.gridHeight) % params.gridHeight;
|
|
const weight = kernel[ky + 1][kx + 1];
|
|
sumA += grid[ny][nx] * weight;
|
|
sumB += (1 - grid[ny][nx]) * weight;
|
|
}
|
|
}
|
|
|
|
const cell = grid[y][x];
|
|
const laplacianA = sumA;
|
|
const laplacianB = sumB;
|
|
|
|
// Gray-Scott reaction-diffusion equations
|
|
const reaction = cell * laplacianB;
|
|
const newA = cell + (params.diffusionRateA * laplacianA - reaction) * params.timeStep;
|
|
const newB = (1 - cell) + (params.diffusionRateB * laplacianB + reaction) * params.timeStep;
|
|
|
|
// Bounded to [0,1]
|
|
nextGrid[y][x] = Math.min(Math.max(newA, 0), 1);
|
|
}
|
|
}
|
|
|
|
// Swap grids
|
|
[grid, nextGrid] = [nextGrid, grid];
|
|
}
|
|
|
|
function draw() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
const scaleX = canvas.width / params.gridWidth;
|
|
const scaleY = canvas.height / params.gridHeight;
|
|
|
|
for (let y = 0; y < params.gridHeight; y++) {
|
|
for (let x = 0; x < params.gridWidth; x++) {
|
|
const value = grid[y][x];
|
|
if (value > 0.2) {
|
|
// Complexity creates more intricate patterns
|
|
const size = params.cellSize * value * (0.5 + params.complexity);
|
|
const alpha = 0.1 + value * 0.9;
|
|
|
|
// Monochrome dryness
|
|
ctx.fillStyle = `rgba(240, 240, 240, ${alpha})`;
|
|
ctx.beginPath();
|
|
ctx.arc(
|
|
x * scaleX + scaleX / 2,
|
|
y * scaleY + scaleY / 2,
|
|
size / 2,
|
|
0,
|
|
Math.PI * 2
|
|
);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function animate() {
|
|
update();
|
|
draw();
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
// Start animation
|
|
initGrids();
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html> |