160 lines
No EOL
4.6 KiB
HTML
160 lines
No EOL
4.6 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>Dissolving Patterns</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
background: #0a0a0a;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100vh;
|
|
font-family: monospace;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
}
|
|
#attribution {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
color: rgba(255, 255, 255, 0.3);
|
|
font-size: 10px;
|
|
text-align: center;
|
|
width: 100%;
|
|
}
|
|
</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();
|
|
|
|
// Parameters
|
|
const params = {
|
|
motion: 0.5,
|
|
density: 0.5,
|
|
complexity: 0.5,
|
|
connectedness: 0.5,
|
|
lifespan: 0.5,
|
|
pulse: 1.06,
|
|
tone: { dryness: 0.9, curiosity: 0.1 }
|
|
};
|
|
|
|
// Reaction-diffusion system
|
|
const size = 100;
|
|
const grid = Array.from({length: size}, () =>
|
|
Array.from({length: size}, () => ({
|
|
a: 1,
|
|
b: 0,
|
|
energy: 0
|
|
}))
|
|
);
|
|
|
|
// Initialize with sparse noise
|
|
for (let i = 0; i < size * size * params.density; i++) {
|
|
const x = Math.floor(Math.random() * size);
|
|
const y = Math.floor(Math.random() * size);
|
|
grid[y][x].b = 1;
|
|
}
|
|
|
|
// Gray-Williams reaction
|
|
const Da = 1;
|
|
const Db = 0.5;
|
|
const f = 0.055;
|
|
const k = 0.062;
|
|
|
|
function updateGrid() {
|
|
const newGrid = Array.from({length: size}, () => Array(size).fill().map(() => ({a: 0, b: 0})));
|
|
const kernel = [
|
|
[0.05, 0.2, 0.05],
|
|
[0.2, -1, 0.2],
|
|
[0.05, 0.2, 0.05]
|
|
];
|
|
|
|
for (let y = 1; y < size - 1; y++) {
|
|
for (let x = 1; x < size - 1; x++) {
|
|
// Diffusion
|
|
let a = grid[y][x].a;
|
|
let b = grid[y][x].b;
|
|
|
|
for (let ky = -1; ky <= 1; ky++) {
|
|
for (let kx = -1; kx <= 1; kx++) {
|
|
const factor = kernel[ky + 1][kx + 1];
|
|
a += grid[y + ky][x + kx].a * factor;
|
|
b += grid[y + ky][x + kx].b * factor;
|
|
}
|
|
}
|
|
|
|
// Reaction
|
|
const reaction = a * b * b;
|
|
a -= reaction;
|
|
b += reaction;
|
|
|
|
// Slow decay
|
|
a += params.f * (1 - a);
|
|
b -= params.k * b;
|
|
|
|
newGrid[y][x].a = a;
|
|
newGrid[y][x].b = b;
|
|
}
|
|
}
|
|
|
|
return newGrid;
|
|
}
|
|
|
|
// Drawing with motion blur
|
|
let history = [];
|
|
const maxHistory = 20;
|
|
|
|
function draw() {
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
const newGrid = updateGrid();
|
|
|
|
// Store current state in history
|
|
history.unshift(newGrid);
|
|
if (history.length > maxHistory) history.pop();
|
|
|
|
// Draw with motion blur
|
|
for (let i = 0; i < history.length; i++) {
|
|
const alpha = 0.7 * Math.pow(0.9, i);
|
|
const step = Math.floor(canvas.width / size);
|
|
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
const val = history[i][y][x].b;
|
|
if (val > 0.1) {
|
|
const hue = 180 + (val * 100);
|
|
ctx.fillStyle = `hsla(${hue}, 80%, 50%, ${alpha * val})`;
|
|
ctx.fillRect(x * step, y * step, step, step);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
grid.length = 0;
|
|
grid.push(...newGrid.map(row => [...row]));
|
|
|
|
requestAnimationFrame(draw);
|
|
}
|
|
|
|
draw();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
``` |