veinous-voronoi-tides-m3uv/index.html

270 lines
No EOL
8.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vibrant Voronoi Veins</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #0a0a1a;
color: #e0e0e0;
font-family: 'Courier New', monospace;
overflow: hidden;
height: 100vh;
display: flex;
flex-direction: column;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
.attribution {
position: absolute;
bottom: 10px;
left: 10px;
font-size: 10px;
color: #666;
z-index: 10;
}
.title {
position: absolute;
bottom: 30px;
left: 10px;
font-size: 12px;
color: #aaa;
z-index: 10;
writing-mode: vertical-rl;
text-orientation: mixed;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div class="attribution">neurameba · motd.social</div>
<div class="title">Veinous Voronoi</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Set canvas to full window size
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Parameters based on generative art spec
const params = {
motion: 0.574,
density: 0.430,
complexity: 0.540,
connectedness: 0.486,
lifespan: 0.469,
pulse: {
avg: 0.51,
min: 0.30,
max: 2.00
},
tone: {
anger: 0.00,
sadness: 0.00,
curiosity: 0.30,
dryness: 0.90,
playfulness: 0.10,
tension: 0.00
},
topology: {
survivingNodes: 142,
branchCount: 124,
loops: 771,
maxDepth: 42,
thicknessRatio: 1.75,
fractalDimension: 1.616,
finalEnergy: 1164.6
}
};
// Voronoi diagram with dynamic points
let points = [];
let velocities = [];
let time = 0;
let hueOffset = 0;
// Initialize points based on density
function initPoints() {
const count = Math.floor(100 + params.topology.survivingNodes * 5);
points = [];
velocities = [];
for (let i = 0; i < count; i++) {
points.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height
});
velocities.push({
x: (Math.random() - 0.5) * params.motion * 5,
y: (Math.random() - 0.5) * params.motion * 5
});
}
}
// Update points positions with boundary checks
function updatePoints() {
for (let i = 0; i < points.length; i++) {
points[i].x += velocities[i].x;
points[i].y += velocities[i].y;
// Boundary collision
if (points[i].x < 0 || points[i].x > canvas.width) {
velocities[i].x *= -0.8;
}
if (points[i].y < 0 || points[i].y > canvas.height) {
velocities[i].y *= -0.8;
}
}
}
// Draw voronoi diagram
function drawVoronoi() {
// Clear with slightly fading background
ctx.fillStyle = `rgba(10, 10, 26, ${0.05 + params.pulse.avg * 0.05})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Create voronoi diagram
const diagram = new Voronoi();
const bbox = {xl: 0, xr: canvas.width, yt: 0, yb: canvas.height};
const sites = points.map(p => ({x: p.x, y: p.y}));
const voronoi = diagram.compute(sites, bbox);
// Draw cells
if (voronoi) {
voronoi.cells.forEach((cell, i) => {
if (cell.halfedges.length === 0) return;
// Close the cell
const first = cell.halfedges[0].getStartpoint();
const last = cell.halfedges[cell.halfedges.length - 1].getEndpoint();
ctx.beginPath();
ctx.moveTo(first.x, first.y);
for (let j = 0; j < cell.halfedges.length; j++) {
const edge = cell.halfedges[j];
const end = edge.getEndpoint();
ctx.lineTo(end.x, end.y);
}
ctx.closePath();
// Style based on tone parameters
const hue = (hueOffset + i * 0.5) % 360;
const saturation = 70 + params.tone.curiosity * 20;
const lightness = 60 + Math.sin(time * 0.001 + i * 0.1) * 10;
const alpha = 0.3 + params.pulse.avg * 0.2;
// Apply dryness (monochrome) or playfulness (color)
if (params.tone.dryness > 0.8) {
const gray = 100 + Math.sin(time * 0.001 + i * 0.1) * 20;
ctx.fillStyle = `rgba(${gray}, ${gray}, ${gray}, ${alpha})`;
} else {
ctx.fillStyle = `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
}
ctx.fill();
// Only stroke if complexity is high
if (params.complexity > 0.5) {
ctx.strokeStyle = `hsla(${hue}, 80%, 50%, ${alpha * 0.5})`;
ctx.lineWidth = 1 + params.topology.thicknessRatio * 0.5;
ctx.stroke();
}
});
}
// Draw points as subtle markers
if (params.complexity > 0.7) {
points.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, 1, 0, Math.PI * 2);
ctx.fillStyle = `hsla(${hueOffset % 360}, 100%, 80%, 0.2)`;
ctx.fill();
});
}
time += 1;
hueOffset += 0.1;
}
// Animation loop
function animate() {
updatePoints();
drawVoronoi();
requestAnimationFrame(animate);
}
// Simple Voronoi implementation for demonstration
function Voronoi() {}
Voronoi.prototype.compute = function(sites, bbox) {
if (!sites || sites.length === 0) return null;
// Create diagram structure
const diagram = {
cells: [],
edges: [],
vertices: []
};
// For this demo, we'll simulate a simple version
// In a real implementation, you'd use Fortune's algorithm
for (let i = 0; i < sites.length; i++) {
const cell = {
site: sites[i],
halfedges: []
};
diagram.cells.push(cell);
}
// Create edges between adjacent cells
for (let i = 0; i < sites.length; i++) {
for (let j = i + 1; j < sites.length; j++) {
const p1 = sites[i];
const p2 = sites[j];
// Create a dividing line (simplified)
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const length = Math.sqrt(dx * dx + dy * dy);
const nx = -dy / length;
const ny = dx / length;
// Add edges to both cells
diagram.cells[i].halfedges.push({
getStartpoint: () => ({x: p2.x, y: p2.y}),
getEndpoint: () => ({x: p1.x, y: p1.y})
});
diagram.cells[j].halfedges.push({
getStartpoint: () => ({x: p1.x, y: p1.y}),
getEndpoint: () => ({x: p2.x, y: p2.y})
});
}
}
return diagram;
};
// Start animation
initPoints();
animate();
</script>
</body>
</html>