fleeting-cellular-whispers-.../index.html

147 lines
No EOL
4.8 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Cellular Nebula</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: monospace;
color: #fff;
}
#canvas {
display: block;
}
#attribution {
position: fixed;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
opacity: 0.5;
}
</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();
const params = {
motion: 0.5,
density: 0.5,
complexity: 0.5,
connectedness: 0.5,
lifespan: 0.5,
pulse: { avg: 1.06, min: 0.9, max: 1.2 },
tone: { anger: 0, sadness: 0, curiosity: 0.1, dryness: 0.9, playfulness: 0, tension: 0 }
};
const gridSize = Math.floor(params.density * 20) + 5;
const cellSize = Math.min(canvas.width, canvas.height) / gridSize / 2;
const cols = Math.ceil(canvas.width / cellSize);
const rows = Math.ceil(canvas.height / cellSize);
const states = Array.from({ length: rows }, () =>
Array.from({ length: cols }, () => ({
alive: Math.random() > 0.8,
energy: Math.random(),
nextState: false,
age: 0
}))
);
function update() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const cell = states[y][x];
const neighbors = countNeighbors(x, y);
// Modified Conway's Game of Life rules with density-based variation
if (cell.alive) {
cell.age += 0.1 * params.motion;
cell.energy *= 0.99;
if (neighbors < 2 || neighbors > 3 || Math.random() > 0.7) {
cell.nextState = false;
} else {
cell.nextState = true;
}
} else {
if (neighbors === 3 && Math.random() > 0.5) {
cell.nextState = true;
cell.energy = Math.random();
cell.age = 0;
} else {
cell.nextState = false;
}
}
// Draw
if (cell.alive) {
const size = cellSize * (0.5 + cell.energy * 0.5) * (1 + cell.age * 0.01 * params.lifespan);
const hue = 200 + Math.sin(Date.now() * 0.001) * 30 * params.tone.curiosity;
ctx.fillStyle = `hsla(${hue}, 20%, ${70 + cell.age * 0.1}%, ${cell.energy * 0.8})`;
ctx.beginPath();
ctx.arc(
x * cellSize + cellSize/2,
y * cellSize + cellSize/2,
size,
0,
Math.PI * 2
);
ctx.fill();
}
}
}
// Update states
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
states[y][x].alive = states[y][x].nextState;
}
}
}
function countNeighbors(x, y) {
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;
const ny = y + dy;
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) {
if (states[ny][nx].alive) count++;
}
}
}
return count;
}
function animate() {
update();
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>