fractured-teal-currents-9vuy/index.html

190 lines
No EOL
6.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Serpentine Echoes</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #0a0a1a;
font-family: 'Courier New', monospace;
}
canvas {
display: block;
}
#attribution {
position: fixed;
bottom: 10px;
right: 10px;
color: rgba(255, 255, 255, 0.3);
font-size: 10px;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
}
</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 resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Strange attractor parameters
const params = {
a: 1.5,
b: 0.2,
c: 2.5,
d: 0.5,
scale: 10,
motion: 0.5,
density: 0.5,
complexity: 0.5,
connectedness: 0.5,
lifespan: 0.5,
pulse: {
avg: 1.12,
min: 0.9,
max: 1.3
},
tone: {
anger: 0,
sadness: 0,
curiosity: 0.7,
dryness: 0.8,
playfulness: 0.1,
tension: 0
}
};
// Color palette based on tone
function getColor() {
const curiosity = params.tone.curiosity;
const dryness = params.tone.dryness;
const playfulness = params.tone.playfulness;
const tension = params.tone.tension;
// Base teal with variations
const teal = `hsl(${180 + curiosity * 60}, ${100 * curiosity}%, ${30 + 10 * dryness}%)`;
const white = `hsl(0, 0%, ${100 * (1 - tension)}%)`;
// Add some playfulness if present
const hueVariation = playfulness * (10 + Math.sin(Date.now() / 1000) * 10);
return teal.replace('hsl(', `hsl(${hueVariation},`);
}
// Points array
let points = [];
const maxPoints = 200;
// Initialize points
function initPoints() {
points = [];
for (let i = 0; i < maxPoints * params.density; i++) {
points.push({
x: Math.random() * 2 - 1,
y: Math.random() * 2 - 1,
color: getColor(),
life: 0,
maxLife: 100 + Math.random() * 50
});
}
}
initPoints();
// Strange attractor function
function attractor(x, y, a, b, c, d) {
const xn = Math.sin(a * y) + c * Math.cos(a * x);
const yn = Math.sin(b * x) + d * Math.cos(b * y);
return [xn, yn];
}
// Main animation loop
function animate() {
// Calculate pulse effect
const pulse = params.pulse.avg + Math.sin(Date.now() / 500) * (params.pulse.max - params.pulse.min) / 2;
// Clear with semi-transparent background
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Update and draw points
points.forEach((point, i) => {
// Attractor movement
const [x, y] = attractor(point.x, point.y,
params.a, params.b, params.c * pulse, params.d * pulse);
// Update position with motion parameter
point.x += (x - point.x) * 0.01 * params.motion;
point.y += (y - point.y) * 0.01 * params.motion;
// Random small variations for complexity
point.x += (Math.random() - 0.5) * 0.01 * params.complexity;
point.y += (Math.random() - 0.5) * 0.01 * params.complexity;
// Wrap around edges
if (point.x > 1) point.x = -1;
if (point.x < -1) point.x = 1;
if (point.y > 1) point.y = -1;
if (point.y < -1) point.y = 1;
// Update life for lifespan effect
point.life += 1;
if (point.life > point.maxLife * (1 - params.lifespan)) {
point.life = 0;
// Reinitialize position
point.x = Math.random() * 2 - 1;
point.y = Math.random() * 2 - 1;
}
// Draw with alpha based on life
const alpha = (point.life / point.maxLife) * (0.5 + 0.5 * params.lifespan);
ctx.strokeStyle = point.color.replace('hsl', 'hsla').replace(')', `, ${alpha})`);
ctx.lineWidth = 1 + 2 * params.density * (1 - alpha);
// Draw line to previous point for connectedness
if (i > 0 && params.connectedness > 0.3) {
const prev = points[i - 1];
ctx.beginPath();
ctx.moveTo(
(prev.x + 1) * canvas.width / 2,
(prev.y + 1) * canvas.height / 2
);
ctx.lineTo(
(point.x + 1) * canvas.width / 2,
(point.y + 1) * canvas.height / 2
);
ctx.stroke();
}
// Draw point
ctx.beginPath();
ctx.arc(
(point.x + 1) * canvas.width / 2,
(point.y + 1) * canvas.height / 2,
params.density * 2,
0,
Math.PI * 2
);
ctx.fill();
});
requestAnimationFrame(animate);
}
// Start animation
animate();
</script>
</body>
</html>