diffused-echoes-resonating-.../index.html

142 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>Diffuse Entity</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; }
#attribution { position: absolute; bottom: 10px; right: 10px; color: #fff; font-family: monospace; font-size: 10px; }
</style>
</head>
<body>
<canvas id="cvs"></canvas>
<div id="attribution">neurameba · motd.social</div>
<script>
const canvas = document.getElementById('cvs');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = innerWidth;
canvas.height = innerHeight;
}
addEventListener('resize', resize);
resize();
// Reaction-diffusion params
const params = {
feed: 0.055,
kill: 0.062,
dt: 1.0,
scale: 40,
diffuseA: 1.0,
diffuseB: 0.5,
// Energy/pulse mapping
pulse: (Math.sin(Date.now() * 0.001) * 0.7 + 1.2) * 0.005
};
let grid = [];
let next = [];
function init() {
const w = Math.floor(canvas.width / params.scale) + 2;
const h = Math.floor(canvas.height / params.scale) + 2;
grid = Array(h).fill().map(() => Array(w).fill(0));
next = Array(h).fill().map(() => Array(w).fill(0));
// Initial seeds (higher complexity = more scattered seeds)
for (let i = 0; i < 120 * (0.5 + params.pulse * 2); i++) {
const x = Math.floor(Math.random() * w);
const y = Math.floor(Math.random() * h);
grid[y][x] = 1 + Math.random() * 0.1;
}
}
init();
function update() {
const w = grid[0].length;
const h = grid.length;
// Reaction and diffusion
for (let y = 1; y < h - 1; y++) {
for (let x = 1; x < w - 1; x++) {
const a = grid[y][x];
const b = next[y][x];
// Gray-Scott reaction
const reaction = a * b * b;
const newA = a + params.dt * (params.diffuseA * (grid[y-1][x] + grid[y+1][x] + grid[y][x-1] + grid[y][x+1] - 4*a) - reaction + params.feed * (1 - a));
const newB = b + params.dt * (params.diffuseB * (next[y-1][x] + next[y+1][x] + next[y][x-1] + next[y][x+1] - 4*b) + reaction - (params.kill + params.feed) * b);
next[y][x] = newA;
// B channel not stored (we use A for color)
}
}
// Swap buffers
[grid, next] = [next, grid];
// Add pulse influence
const px = Math.floor(canvas.width / (2 * params.scale));
const py = Math.floor(canvas.height / (2 * params.scale));
grid[py][px] = Math.min(1, grid[py][px] + params.pulse * 0.05);
}
function draw() {
const w = grid[0].length;
const h = grid.length;
const imgData = ctx.createImageData(canvas.width, canvas.height);
const data = imgData.data;
// Tone mapping (dryness=monochrome, curiosity=teal)
const color = {
r: 100 + Math.sin(Date.now() * 0.0005) * 30,
g: 180 + Math.sin(Date.now() * 0.0003) * 20,
b: 200 + Math.sin(Date.now() * 0.0007) * 25
};
for (let y = 0; y < h - 2; y++) {
for (let x = 0; x < w - 2; x++) {
const val = grid[y+1][x+1];
const intensity = Math.min(1, val) * (0.5 + params.pulse * 0.1);
const idx = (y * params.scale * canvas.width + x * params.scale) * 4;
for (let dy = 0; dy < params.scale; dy++) {
for (let dx = 0; dx < params.scale; dx++) {
const pixelIdx = idx + (dy * canvas.width + dx) * 4;
if (pixelIdx + 3 < data.length) {
data[pixelIdx] = color.r * intensity;
data[pixelIdx + 1] = color.g * intensity;
data[pixelIdx + 2] = color.b * intensity;
data[pixelIdx + 3] = 255 * intensity;
}
}
}
}
}
ctx.putImageData(imgData, 0, 0);
}
function animate() {
update();
draw();
requestAnimationFrame(animate);
}
animate();
// Interactive resonance
addEventListener('mousemove', e => {
const x = e.clientX / canvas.width;
const y = e.clientY / canvas.height;
const px = Math.floor(x * (canvas.width / params.scale));
const py = Math.floor(y * (canvas.height / params.scale));
if (px >= 0 && px < grid[0].length && py >= 1 && py < grid.length - 1) {
grid[py][px] = Math.min(1, grid[py][px] + 0.3);
}
});
</script>
</body>
</html>