fractured-hues-in-motion-z534/index.html

270 lines
No EOL
9.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>Voronoi Fracture</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #0a0a0a;
font-family: 'Courier New', monospace;
color: #555;
}
#attribution {
position: fixed;
bottom: 20px;
left: 20px;
font-size: 10px;
opacity: 0.5;
pointer-events: none;
}
canvas {
display: block;
}
</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 resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Voronoi parameters
const params = {
points: [],
relaxationIterations: 0,
motion: 0.5,
density: 0.5,
complexity: 0.5,
connectedness: 0.5,
lifespan: 0.5,
palette: {
dryness: 0.8,
curiosity: 0.1,
baseHue: 0,
saturation: 0.1 + Math.random() * 0.2,
value: 0.8 + Math.random() * 0.1
},
pulse: 1.0,
time: 0
};
// Initialize points
function initPoints() {
params.points = [];
const count = Math.floor(50 + params.density * 300);
for (let i = 0; i < count; i++) {
params.points.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * params.motion * 2,
vy: (Math.random() - 0.5) * params.motion * 2,
lifespan: Date.now() + (params.lifespan * 2 + 0.5) * 5000,
energy: 0.5 + Math.random() * 0.5
});
}
}
// Relax points to create better voronoi cells
function relaxPoints() {
const counts = new Array(params.points.length).fill(0);
const sums = params.points.map(() => ({x: 0, y: 0}));
// Find centroids of each cell
for (let y = 0; y < canvas.height; y += 5) {
for (let x = 0; x < canvas.width; x += 5) {
const closest = findClosestPoint(x, y);
sums[closest].x += x;
sums[closest].y += y;
counts[closest]++;
}
}
// Move points toward centroids
for (let i = 0; i < params.points.length; i++) {
if (counts[i] > 0) {
params.points[i].x = sums[i].x / counts[i];
params.points[i].y = sums[i].y / counts[i];
}
}
}
function findClosestPoint(x, y) {
let minDist = Infinity;
let closest = 0;
for (let i = 0; i < params.points.length; i++) {
const dx = x - params.points[i].x;
const dy = y - params.points[i].y;
const dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
closest = i;
}
}
return closest;
}
function drawVoronoi() {
// Sort points by position for better performance
const sortedPoints = [...params.points].sort((a, b) => a.y - b.y);
// Create edge map
const edges = {};
for (let i = 0; i < sortedPoints.length; i++) {
const p1 = sortedPoints[i];
for (let j = i + 1; j < sortedPoints.length; j++) {
const p2 = sortedPoints[j];
if (Math.abs(p1.y - p2.y) > canvas.height * 0.5) continue;
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 20 && dist < 200) {
const midX = (p1.x + p2.x) * 0.5;
const midY = (p1.y + p2.y) * 0.5;
// Check if edge is valid by sampling perpendicular points
if (isValidEdge(midX, midY, dx, dy, dist)) {
const edge = edges[midX + ',' + midY] || [];
edge.push({p1, p2, dist});
edges[midX + ',' + midY] = edge;
}
}
}}
// Draw edges
ctx.strokeStyle = getColor();
ctx.lineWidth = 1 + params.palette.value * 2;
for (const key in edges) {
const edgeGroup = edges[key];
edgeGroup.sort((a, b) => b.dist - a.dist);
for (const edge of edgeGroup.slice(0, 3)) {
ctx.beginPath();
ctx.moveTo(edge.p1.x, edge.p1.y);
ctx.lineTo(edge.p2.x, edge.p2.y);
ctx.stroke();
}
}
// Draw points
ctx.fillStyle = getColor(0.3);
for (const point of params.points) {
if (point.energy > 0.2) {
ctx.beginPath();
ctx.arc(point.x, point.y, 2 + point.energy * 3, 0, Math.PI * 2);
ctx.fill();
}
}
}
function isValidEdge(mx, my, dx, dy, dist) {
const step = 5;
for (let i = 0; i < dist; i += step) {
const t = i / dist;
const x = mx - dy * 0.5 * t;
const y = my + dx * 0.5 * t;
const closest = findClosestPoint(x, y);
const px = params.points[closest].x;
const py = params.points[closest].y;
const d = Math.sqrt((x - px) * (x - px) + (y - py) * (y - py));
if (d > step * 1.5) return false;
}
return true;
}
function updatePoints() {
params.time += 0.01;
for (const point of params.points) {
// Update position
point.x += point.vx * params.motion;
point.y += point.vy * params.motion;
// Boundary check
if (point.x < 0 || point.x > canvas.width) point.vx *= -1;
if (point.y < 0 || point.y > canvas.height) point.vy *= -1;
// Slow down over time
point.vx *= 0.9;
point.vy *= 0.9;
// Energy decay
point.energy *= 0.99;
// Fade out points
if (params.lifespan < 0.5 && Date.now() > point.lifespan) {
point.energy *= 0.95;
}
}
}
function getColor(factor = 1) {
const hue = (params.palette.baseHue + Math.sin(params.time * 0.1) * 20) % 360;
const sat = Math.min(0.8, params.palette.saturation * (1 + Math.sin(params.time * 0.2) * 0.2));
const val = params.palette.value * factor * (0.7 + Math.sin(params.time * 0.15) * 0.3);
return `hsl(${hue}, ${sat * 100}%, ${val * 100}%)`;
}
function drawBackground() {
ctx.fillStyle = '#020202';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function animate() {
drawBackground();
updatePoints();
// Adjust complexity
if (Math.random() < 0.02 * params.complexity) {
const count = Math.floor(1 + params.complexity * 3);
for (let i = 0; i < count; i++) {
params.points.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * params.motion * 2,
vy: (Math.random() - 0.5) * params.motion * 2,
lifespan: Date.now() + (params.lifespan * 2 + 0.5) * 5000,
energy: 0.5 + Math.random() * 0.5
});
}
}
// Adjust connectedness
if (Math.random() < 0.01 * params.connectedness) {
const p1 = Math.floor(Math.random() * params.points.length);
const p2 = Math.floor(Math.random() * params.points.length);
if (p1 !== p2) {
const dx = params.points[p2].x - params.points[p1].x;
const dy = params.points[p2].y - params.points[p1].y;
const dist = Math.sqrt(dx * dx + dy * dy);
params.points[p1].vx += dx / dist * 0.1 * params.connectedness;
params.points[p1].vy += dy / dist * 0.1 * params.connectedness;
}
}
drawVoronoi();
requestAnimationFrame(animate);
}
initPoints();
for (let i = 0; i < 3; i++) {
relaxPoints();
}
animate();
</script>
</body>
</html>