148 lines
No EOL
4.3 KiB
HTML
148 lines
No EOL
4.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>Drifting Strange Attractor</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
background: #0a0a0a;
|
|
font-family: 'Courier New', monospace;
|
|
color: #aaa;
|
|
}
|
|
canvas {
|
|
display: block;
|
|
}
|
|
#info {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 20px;
|
|
font-size: 10px;
|
|
opacity: 0.5;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="canvas"></canvas>
|
|
<div id="info">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 based on input
|
|
const params = {
|
|
a: 0.524 * 2.5 - 1.0,
|
|
b: 0.454 * 2.5,
|
|
c: 0.520 * 3.0 + 1.0,
|
|
d: 0.488 * 2.5 - 1.0,
|
|
motion: 0.58,
|
|
density: 0.454,
|
|
trailLength: 25 + Math.floor(0.454 * 30),
|
|
maxDepth: 15,
|
|
pulse: 0.58
|
|
};
|
|
|
|
// Color palette based on tone
|
|
const colors = {
|
|
curiosity: '#40e0d0', // teal
|
|
dryness: '#e0e0e0', // monochrome
|
|
playfulness: '#ff8888' // slightly vibrant
|
|
};
|
|
|
|
// Point system
|
|
class Point {
|
|
constructor(x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.history = [];
|
|
this.maxLength = params.trailLength;
|
|
this.pulse = params.pulse * (0.7 + Math.random() * 0.6);
|
|
this.depth = 0;
|
|
}
|
|
|
|
update(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
const xn = Math.sin(ax * this.y) - Math.cos(bx * this.x);
|
|
const yn = Math.sin(cy * this.x) - Math.cos(dx * this.y);
|
|
|
|
this.x += xn * 0.01 * this.pulse;
|
|
this.y += yn * 0.01 * this.pulse;
|
|
|
|
// Add to history
|
|
this.history.push({x: this.x, y: this.y});
|
|
if (this.history.length > this.maxLength) {
|
|
this.history.shift();
|
|
}
|
|
|
|
this.depth = Math.min(this.depth + 0.01, params.maxDepth);
|
|
}
|
|
|
|
draw() {
|
|
ctx.beginPath();
|
|
|
|
// Draw trail
|
|
for (let i = 0; i < this.history.length; i++) {
|
|
const p = this.history[i];
|
|
const alpha = i / this.history.length;
|
|
const size = 1 + (this.maxLength - i) / this.maxLength * 0.8;
|
|
|
|
ctx.globalAlpha = alpha * 0.7;
|
|
ctx.fillStyle = colors.dryness;
|
|
ctx.beginPath();
|
|
ctx.arc(p.x * canvas.width, p.y * canvas.height, size, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.globalAlpha = 1.0;
|
|
}
|
|
}
|
|
|
|
// Create points based on density
|
|
const points = [];
|
|
const count = 50 + Math.floor(params.density * 150);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
points.push(new Point(
|
|
Math.random(),
|
|
Math.random()
|
|
));
|
|
}
|
|
|
|
// Animation loop
|
|
let frame = 0;
|
|
function animate() {
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Update and draw points
|
|
for (const p of points) {
|
|
p.update(
|
|
params.a + Math.sin(frame * 0.01) * 0.1,
|
|
params.b,
|
|
params.c,
|
|
params.d + Math.cos(frame * 0.02) * 0.05,
|
|
params.a,
|
|
params.b * 0.5,
|
|
params.c,
|
|
params.d * 1.2
|
|
);
|
|
p.draw();
|
|
}
|
|
|
|
frame++;
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html> |