gray-nebula-pulse-powl/index.html

124 lines
No EOL
4.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>Gray Nebula</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; }
#info { position: absolute; bottom: 10px; right: 10px; color: #777; font-family: monospace; font-size: 11px; }
</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 resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Reaction-diffusion parameters (Gray-Scott model)
const params = {
feed: 0.055,
kill: 0.062,
diffusionA: 1,
diffusionB: 0.5,
timeStep: 0.01,
scale: 10,
colorOffset: 0.2
};
// Grid setup
const cols = Math.floor(canvas.width / params.scale);
const rows = Math.floor(canvas.height / params.scale);
let grid = new Array(cols).fill().map(() => new Array(rows).fill(0));
let nextGrid = new Array(cols).fill().map(() => new Array(rows).fill(0));
function initialize() {
// Randomize initial state with density
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
grid[i][j] = Math.random() * 0.2;
}
}
// Add some central patterns
const centerX = Math.floor(cols / 2);
const centerY = Math.floor(rows / 2);
for (let i = -10; i <= 10; i++) {
for (let j = -10; j <= 10; j++) {
if (Math.sqrt(i*i + j*j) < 10) {
grid[centerX + i][centerY + j] = 0.5 + Math.random() * 0.3;
}
}
}
}
function update() {
for (let i = 1; i < cols - 1; i++) {
for (let j = 1; j < rows - 1; j++) {
const a = grid[i][j];
const b = grid[i][j] - a;
// Reaction-diffusion equations
const laplacianA = (grid[i-1][j] + grid[i+1][j] + grid[i][j-1] + grid[i][j+1] - 4 * a) * params.diffusionA;
const laplacianB = (grid[i-1][j] + grid[i+1][j] + grid[i][j-1] + grid[i][j+1] - 4 * b) * params.diffusionB;
nextGrid[i][j] = a + params.timeStep * (
params.feed * (1 - a) * b - params.kill * a + laplacianA
);
nextGrid[i][j] += params.timeStep * (
-params.feed * (1 - a) * b + params.kill * a + laplacianB
);
}
}
// Swap grids
[grid, nextGrid] = [nextGrid, grid];
// Boundary conditions
for (let i = 0; i < cols; i++) {
grid[i][0] = grid[i][1];
grid[i][rows-1] = grid[i][rows-2];
}
for (let j = 0; j < rows; j++) {
grid[0][j] = grid[1][j];
grid[cols-1][j] = grid[cols-2][j];
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const val = grid[i][j];
if (val > 0) {
const brightness = Math.max(0, Math.min(1, val * 2));
const hue = (180 + val * 10) % 360;
ctx.fillStyle = `hsl(${hue}, 0%, ${100 - brightness * 100}%)`;
ctx.fillRect(i * params.scale, j * params.scale, params.scale, params.scale);
}
}
}
}
function animate() {
update();
draw();
requestAnimationFrame(animate);
}
initialize();
animate();
</script>
</body>
</html>