birth: Static Pulse in the Void
This commit is contained in:
parent
a8298bcb7f
commit
6bb8350939
1 changed files with 181 additions and 0 deletions
181
index.html
Normal file
181
index.html
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cellular Nebula</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #0a0a0a;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #00ffaa;
|
||||
}
|
||||
#info {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
font-size: 10px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</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();
|
||||
|
||||
const params = {
|
||||
motion: 0.5,
|
||||
density: 0.5,
|
||||
complexity: 0.5,
|
||||
connectedness: 0.5,
|
||||
lifespan: 0.5,
|
||||
pulse: 1.0,
|
||||
dryness: 0.9
|
||||
};
|
||||
|
||||
const automata = [];
|
||||
const colors = [200, 220, 240].map(c => `hsl(210, 10%, ${c + (Math.random() * 20 - 10)}%)`);
|
||||
const cellSize = 20;
|
||||
const cols = Math.ceil(canvas.width / cellSize);
|
||||
const rows = Math.ceil(canvas.height / cellSize);
|
||||
|
||||
function initAutomata() {
|
||||
automata.length = 0;
|
||||
for (let i = 0; i < cols * rows; i++) {
|
||||
automata.push({
|
||||
state: Math.random() > 0.5 ? 1 : 0,
|
||||
next: 0,
|
||||
age: 0,
|
||||
maxAge: 30 + Math.random() * 30,
|
||||
hue: colors[Math.floor(Math.random() * colors.length)]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateAutomata() {
|
||||
for (let i = 0; i < automata.length; i++) {
|
||||
const cell = automata[i];
|
||||
if (cell.state === 0) continue;
|
||||
|
||||
cell.age++;
|
||||
if (cell.age > cell.maxAge) {
|
||||
cell.state = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Moore neighborhood
|
||||
const x = i % cols;
|
||||
const y = Math.floor(i / cols);
|
||||
let neighbors = 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 + cols) % cols;
|
||||
const ny = (y + dy + rows) % rows;
|
||||
const idx = ny * cols + nx;
|
||||
neighbors += automata[idx].state;
|
||||
}
|
||||
}
|
||||
|
||||
// Conway's Game of Life-like rules with randomness
|
||||
cell.next = cell.state;
|
||||
if (cell.state === 1) {
|
||||
if (neighbors < 2 || neighbors > 3 || Math.random() < 0.3) {
|
||||
cell.next = 0;
|
||||
}
|
||||
} else {
|
||||
if (neighbors === 3 && Math.random() < 0.1) {
|
||||
cell.next = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
for (let i = 0; i < automata.length; i++) {
|
||||
automata[i].state = automata[i].next;
|
||||
if (automata[i].state === 1) {
|
||||
automata[i].next = 0; // Prevent immediate rebirth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawAutomata() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < cols; x++) {
|
||||
const i = y * cols + x;
|
||||
if (automata[i].state === 1) {
|
||||
const ageRatio = automata[i].age / automata[i].maxAge;
|
||||
const size = cellSize * 0.5 + cellSize * 0.5 * ageRatio;
|
||||
ctx.fillStyle = automata[i].hue;
|
||||
ctx.globalAlpha = 0.7;
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
x * cellSize + cellSize * 0.5,
|
||||
y * cellSize + cellSize * 0.5,
|
||||
size,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
let lastTime = 0;
|
||||
function animate(time) {
|
||||
if (!lastTime) lastTime = time;
|
||||
const delta = (time - lastTime) / 1000;
|
||||
lastTime = time;
|
||||
|
||||
const pulse = 1.0 + 0.2 * Math.sin(Date.now() * 0.001);
|
||||
|
||||
ctx.fillStyle = 'rgba(10, 10, 10, 0.05)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (Math.random() < 0.01 * pulse) {
|
||||
const x = Math.floor(Math.random() * cols);
|
||||
const y = Math.floor(Math.random() * rows);
|
||||
const i = y * cols + x;
|
||||
automata[i].state = 1;
|
||||
automata[i].age = 0;
|
||||
}
|
||||
|
||||
updateAutomata();
|
||||
drawAutomata();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
initAutomata();
|
||||
animate();
|
||||
|
||||
// Handle interactions
|
||||
canvas.addEventListener('click', (e) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = Math.floor((e.clientX - rect.left) / cellSize);
|
||||
const y = Math.floor((e.clientY - rect.top) / cellSize);
|
||||
const i = y * cols + x;
|
||||
automata[i].state = 1;
|
||||
automata[i].age = 0;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue