137 lines
No EOL
4.1 KiB
HTML
137 lines
No EOL
4.1 KiB
HTML
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Morphic Seas</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
background-color: #0a0a0a;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
}
|
|
#info {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 20px;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
font-size: 12px;
|
|
}
|
|
</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();
|
|
|
|
// Fallback for non-compliant browsers
|
|
if (!ctx) {
|
|
document.body.innerHTML = '<h1>Canvas not supported</h1><p>Please use a modern browser that supports HTML5 canvas.</p>';
|
|
throw new Error('Canvas not supported');
|
|
}
|
|
|
|
// Reaction-diffusion simulation
|
|
const size = 128;
|
|
let gridA = createRandomGrid(size, 0.5);
|
|
let gridB = createRandomGrid(size, 0.25);
|
|
const dA = 1.0;
|
|
const dB = 0.5;
|
|
const feed = 0.055;
|
|
const kill = 0.062;
|
|
const dt = 1.0;
|
|
|
|
function createRandomGrid(size, density) {
|
|
const grid = new Float64Array(size * size);
|
|
for (let i = 0; i < grid.length; i++) {
|
|
grid[i] = Math.random() < density ? 1 : 0;
|
|
}
|
|
return grid;
|
|
}
|
|
|
|
function laplacian(grid, index, size) {
|
|
const x = index % size;
|
|
const y = Math.floor(index / size);
|
|
let sum = 0;
|
|
let count = 0;
|
|
|
|
for (let dy = -1; dy <= 1; dy++) {
|
|
for (let dx = -1; dx <= 1; dx++) {
|
|
if (dx === 0 && dy === 0) continue;
|
|
const nx = (x + dx + size) % size;
|
|
const ny = (y + dy + size) % size;
|
|
sum += grid[ny * size + nx];
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return sum / count - grid[index];
|
|
}
|
|
|
|
function update() {
|
|
const nextA = new Float64Array(gridA.length);
|
|
const nextB = new Float64Array(gridB.length);
|
|
|
|
for (let i = 0; i < gridA.length; i++) {
|
|
const a = gridA[i];
|
|
const b = gridB[i];
|
|
const la = laplacian(gridA, i, size);
|
|
const lb = laplacian(gridB, i, size);
|
|
|
|
nextA[i] = a + (dA * la - a * b * b + feed * (1 - a)) * dt;
|
|
nextB[i] = b + (dB * lb + a * b * b - (kill + feed) * b) * dt;
|
|
|
|
nextA[i] = Math.max(0, Math.min(1, nextA[i]));
|
|
nextB[i] = Math.max(0, Math.min(1, nextB[i]));
|
|
}
|
|
|
|
gridA = nextA;
|
|
gridB = nextB;
|
|
}
|
|
|
|
function draw() {
|
|
const imageData = ctx.createImageData(size, size);
|
|
const data = imageData.data;
|
|
|
|
for (let i = 0; i < size * size; i++) {
|
|
const idx = i * 4;
|
|
const a = gridA[i];
|
|
const b = gridB[i];
|
|
|
|
// Greyish teal palette (curiosity=0.7, dryness=0.9)
|
|
const value = (a - b) * 0.5 + 0.5;
|
|
const grey = Math.floor(value * 200);
|
|
data[idx] = data[idx + 1] = data[idx + 2] = grey;
|
|
data[idx + 3] = 255;
|
|
}
|
|
|
|
ctx.imageSmoothingEnabled = false;
|
|
ctx.scale(window.innerWidth / size, window.innerHeight / size);
|
|
ctx.putImageData(imageData, 0, 0);
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
}
|
|
|
|
function animate() {
|
|
update();
|
|
draw();
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
``` |