voronoi-pulse-in-monochrome.../index.html

184 lines
No EOL
6.3 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neurameba Cellular Voronoi</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #0a0a0a;
color: #fff;
font-family: 'Courier New', monospace;
}
canvas {
display: block;
}
.attribution {
position: fixed;
bottom: 20px;
right: 20px;
font-size: 10px;
opacity: 0.5;
pointer-events: none;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div class="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();
// Configuration parameters
const params = {
density: 0.5,
complexity: 0.5,
motion: 0.5,
connectedness: 0.5,
lifespan: 0.5,
pulseAvg: 1.25,
pulseMin: 1.0,
pulseMax: 1.55,
dryness: 0.9,
curiosity: 0.1
};
// Voronoi cell structure
class VoronoiCell {
constructor(x, y, size, velocity, persistence) {
this.x = x;
this.y = y;
this.originX = x;
this.originY = y;
this.size = size;
this.velocity = velocity;
this.persistence = persistence;
this.age = 0;
this.maxAge = 100 + Math.random() * 200;
this.nodes = [];
this.generateNodes();
}
generateNodes() {
const nodeCount = 3 + Math.floor(params.complexity * 8);
for (let i = 0; i < nodeCount; i++) {
const angle = (i / nodeCount) * Math.PI * 2;
const distance = this.size * 0.3 * (0.7 + Math.random() * 0.6);
this.nodes.push({
x: this.x + Math.cos(angle) * distance,
y: this.y + Math.sin(angle) * distance,
baseX: this.x + Math.cos(angle) * distance,
baseY: this.y + Math.sin(angle) * distance,
size: this.size * (0.3 + Math.random() * 0.4),
persistence: this.persistence * (0.7 + Math.random() * 0.6)
});
}
}
update(pulseFactor) {
this.age++;
const pulseLerp = (Math.sin(Date.now() * params.pulseAvg * 0.001) * 0.5 + 0.5) *
(params.pulseMax - params.pulseMin) + params.pulseMin;
this.x = this.originX + (Math.sin(Date.now() * 0.0005 * pulseFactor) * 50);
this.y = this.originY + (Math.cos(Date.now() * 0.0003 * pulseFactor) * 30);
this.nodes.forEach(node => {
node.x = node.baseX + Math.sin(Date.now() * 0.0004 * node.persistence) * 3;
node.y = node.baseY + Math.cos(Date.now() * 0.0002 * node.persistence) * 2;
});
}
draw() {
ctx.beginPath();
const gradient = ctx.createRadialGradient(
this.x, this.y, 0,
this.x, this.y, this.size
);
const alpha = this.age / this.maxAge;
const adjustedAlpha = alpha * (0.7 + params.dryness * 0.3);
gradient.addColorStop(0, `rgba(255, 255, 255, ${adjustedAlpha})`);
gradient.addColorStop(1, `rgba(255, 255, 255, 0)`);
ctx.fillStyle = gradient;
ctx.moveTo(this.nodes[0].x, this.nodes[0].y);
for (let i = 1; i <= this.nodes.length; i++) {
const node = this.nodes[i % this.nodes.length];
ctx.lineTo(node.x, node.y);
}
ctx.closePath();
ctx.fill();
}
}
// Create voronoi cells with density constraints
const cells = [];
function initCells() {
const count = Math.floor(params.density * 150) + 50;
for (let i = 0; i < count; i++) {
const size = 10 + Math.random() * 30;
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
// Avoid overlapping initial positions
let tooClose = false;
for (const cell of cells) {
const dist = Math.sqrt((x - cell.x) ** 2 + (y - cell.y) ** 2);
if (dist < (size + cell.size) * 0.5) {
tooClose = true;
break;
}
}
if (!tooClose) {
const velocity = 0.0005 + Math.random() * 0.001;
const persistence = 0.5 + Math.random() * 0.5;
cells.push(new VoronoiCell(x, y, size, velocity, persistence));
}
}
}
let lastPulse = 0;
function animate() {
// Clear with slight fade
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Calculate pulse factor for motion variation
const pulseNow = Date.now();
const pulseFactor = (Math.sin(pulseNow * 0.0002) * 0.5 + 0.5) *
(params.pulseMax - params.pulseMin) + params.pulseMin;
// Update and draw cells
cells.forEach(cell => {
cell.update(pulseFactor);
cell.draw();
});
// Occasionally add new cells to maintain density
if (Math.random() < params.density * 0.01 && cells.length < 200) {
initCells();
}
requestAnimationFrame(animate);
}
// Start animation
initCells();
animate();
</script>
</body>
</html>