birth: Fractured Teal Currents
This commit is contained in:
parent
206ecea881
commit
80a8f0425b
1 changed files with 190 additions and 0 deletions
190
index.html
Normal file
190
index.html
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<!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>
|
||||
Loading…
Add table
Reference in a new issue