fractal-tremors-unfurl-aa4t/index.html

182 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>Fractal Tremors</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #0a0a0a;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
canvas {
display: block;
}
#attribution {
position: fixed;
bottom: 20px;
color: #555;
font-family: monospace;
font-size: 11px;
text-align: center;
width: 100%;
}
</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 derived from abstract values
const params = {
motion: 0.5,
density: 0.5,
complexity: 0.5,
connectedness: 0.5,
lifespan: 0.5,
pulse: 1.05,
tone: { anger: 0, sadness: 0, curiosity: 0.1, dryness: 0.9, playfulness: 0, tension: 0 }
};
// Palette based on tone
const hue = 200 * params.tone.curiosity; // Teal range
const saturation = params.tone.dryness > 0.8 ? 0 : 100;
const brightness = 50 + (params.tone.dryness > 0.8 ? 0 : 30);
// Fractal parameters
let depth = 4 + Math.floor(params.complexity * 3);
const angle = Math.PI * 0.4 + params.motion * 0.3;
const lengthBase = 100;
const lengthVariance = 0.3;
const branches = 2 + Math.floor(params.connectedness * 3);
const branchThickness = 1 + params.density * 3;
const decay = 0.7 - params.motion * 0.2;
// Animation state
let time = 0;
const particles = [];
const maxParticles = 500 * params.density;
// Initialize particles
for (let i = 0; i < maxParticles; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 2 + 1,
speed: 0.1 + Math.random() * 0.5,
angle: Math.random() * Math.PI * 2,
life: Math.random() * 100,
maxLife: Math.random() * 100 + 50,
color: `hsl(${hue}, ${saturation}%, ${brightness}%)`,
thickness: branchThickness * 0.5
});
}
// Fractal branch function
function drawFractal(x, y, length, angle, depth, parentThickness) {
if (depth <= 0 || length < 1) return;
const endX = x + Math.cos(angle) * length;
const endY = y + Math.sin(angle) * length;
// Calculate color based on depth and tone
const currentDepth = maxDepth - depth;
const colorValue = 10 + (1 - currentDepth / maxDepth) * 30;
const alpha = params.lifespan * 0.7 + 0.3;
ctx.strokeStyle = `hsla(${hue}, ${saturation}%, ${colorValue}%, ${alpha})`;
ctx.lineWidth = parentThickness;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(endX, endY);
ctx.stroke();
// Recursive branches
for (let i = 0; i < branches; i++) {
const branchAngle = angle + (Math.random() - 0.5) * Math.PI * params.motion * 0.5;
const branchLength = length * (0.6 + (Math.random() * 0.4 - 0.2) * params.motion);
const branchThickness = parentThickness * decay * (0.8 + Math.random() * 0.4);
drawFractal(endX, endY, branchLength, branchAngle, depth - 1, branchThickness);
}
}
// Main animation loop
function animate() {
// Clear with slight fade for trails
ctx.fillStyle = 'rgba(5, 5, 10, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
time += 0.01 * params.pulse;
// Draw fractal structure
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const maxDepth = depth;
// Adjust fractal parameters based on time
const scale = 0.5 + Math.sin(time * 0.3) * 0.3;
const currentLength = lengthBase * scale;
drawFractal(centerX, centerY, currentLength, -Math.PI/2, depth, branchThickness);
// Update and draw particles
particles.forEach(particle => {
// Move particle
particle.x += Math.cos(particle.angle) * particle.speed;
particle.y += Math.sin(particle.angle) * particle.speed;
// Bounce off edges
if (particle.x < 0 || particle.x > canvas.width) particle.angle = Math.PI - particle.angle;
if (particle.y < 0 || particle.y > canvas.height) particle.angle = -particle.angle;
// Fade over life
particle.life++;
const fade = Math.min(1, particle.life / particle.maxLife);
ctx.fillStyle = particle.color.replace(')', `, ${1 - fade * fade})`);
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size * fade, 0, Math.PI * 2);
ctx.fill();
});
// Occasionally add new particles
if (Math.random() < 0.1 && particles.length < maxParticles) {
particles.push({
x: centerX,
y: centerY,
size: Math.random() * 2 + 1,
speed: 0.1 + Math.random() * 0.5,
angle: Math.random() * Math.PI * 2,
life: 0,
maxLife: Math.random() * 100 + 50,
color: `hsl(${hue}, ${saturation}%, ${brightness}%)`,
thickness: branchThickness * 0.5
});
}
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>