fractured-light-resonance-7aii/index.html

183 lines
No EOL
5.8 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;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'Courier New', monospace;
}
canvas {
display: block;
}
#attribution {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: #555;
font-size: 10px;
letter-spacing: 1px;
}
</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');
// Set canvas to full window size
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Parameters
const params = {
motion: 0.5,
density: 0.5,
complexity: 0.5,
connectedness: 0.5,
lifespan: 0.5,
pulse: { avg: 1.08, min: 1.05, max: 1.10 },
tone: { anger: 0.00, sadness: 0.00, curiosity: 0.20, dryness: 0.80, playfulness: 0.00, tension: 0.00 }
};
// Voronoi state
const sites = [];
const cells = [];
let lastTime = 0;
let pulseTime = 0;
// Initialize sites
function initSites() {
const count = Math.max(20, Math.floor(params.density * 300) + 50);
sites.length = 0;
for (let i = 0; i < count; i++) {
sites.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 2 * params.motion,
vy: (Math.random() - 0.5) * 2 * params.motion,
size: Math.random() * 8 + 2,
fixed: Math.random() > 0.7,
lifespan: params.lifespan * 5 + Math.random() * 3
});
}
}
// Generate Voronoi cells
function generateVoronoi() {
cells.length = 0;
// For each pixel, find closest site
const imageData = ctx.createImageData(canvas.width, canvas.height);
const data = imageData.data;
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
let minDist = Infinity;
let closestSite = null;
for (const site of sites) {
const dx = x - site.x;
const dy = y - site.y;
const dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
closestSite = site;
}
}
// Store cell data
const idx = (y * canvas.width + x) * 4;
data[idx] = closestSite ? Math.floor(closestSite.size * 25) : 0;
data[idx + 1] = 0;
data[idx + 2] = closestSite ? Math.floor(255 - closestSite.size * 25) : 0;
data[idx + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
}
// Update sites positions
function updateSites(time) {
if (time - lastTime > 1000 / (60 * params.pulse.avg)) {
lastTime = time;
// Update pulse
const pulse = params.pulse.min + (Math.sin(pulseTime / 1000) * 0.5 + 0.5) *
(params.pulse.max - params.pulse.min);
pulseTime += 16 * pulse;
sites.forEach(site => {
if (!site.fixed) {
site.x += site.vx * pulse;
site.y += site.vy * pulse;
// Boundary check
if (site.x < 0 || site.x > canvas.width) site.vx *= -1;
if (site.y < 0 || site.y > canvas.height) site.vy *= -1;
// Slow decay
site.size *= 0.999;
}
});
}
}
// Animation loop
function animate(time) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
updateSites(time);
// Occasionally add new sites
if (Math.random() < 0.05) {
sites.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 2 * params.motion,
vy: (Math.random() - 0.5) * 2 * params.motion,
size: Math.random() * 10 + 5,
fixed: false,
lifespan: Math.random() * 10 + 5
});
}
// Remove old sites
for (let i = sites.length - 1; i >= 0; i--) {
sites[i].lifespan -= 0.01;
if (sites[i].lifespan <= 0) {
sites.splice(i, 1);
}
}
generateVoronoi();
requestAnimationFrame(animate);
}
// Initial setup
initSites();
generateVoronoi();
requestAnimationFrame(animate);
</script>
</body>
</html>