emergent-bioluminescent-ten.../index.html

169 lines
No EOL
5.4 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bioluminescent Tendrils</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: #4a4a4a;
font-size: 10px;
font-family: monospace;
}
</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 resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Parameters
const params = {
motion: 0.5,
density: 0.5,
complexity: 0.5,
connectedness: 0.5,
lifespan: 0.5,
pulse: 1.08,
tones: { anger: 0, sadness: 0, curiosity: 0.1, dryness: 0.9, playfulness: 0, tension: 0 }
};
// L-system rules
const rules = [
{ axiom: 'X', productions: [
{ pattern: 'X', replacement: 'F-[[X]+X]+F[+FX]-X' },
{ pattern: 'F', replacement: 'FF' }
], angle: 25, step: 5 }
];
// System state
let generations = [];
let currentGeneration = 0;
const maxGenerations = 10;
const colors = [
'hsl(180, 30%, 80%)', // teal (curiosity)
'hsl(0, 0%, 80%)', // white (dryness)
'hsl(120, 10%, 60%)', // muted green
'hsl(210, 15%, 70%)' // muted blue
];
// Initialize
function init() {
generations = [];
currentGeneration = 0;
const startSymbol = rules[0].axiom;
let currentString = startSymbol;
// Generate L-system strings
for (let i = 0; i < maxGenerations; i++) {
let nextString = '';
for (const c of currentString) {
let replaced = false;
for (const rule of rules[0].productions) {
if (c === rule.pattern) {
nextString += rule.replacement;
replaced = true;
break;
}
}
if (!replaced) nextString += c;
}
generations.push({ string: nextString, angle: rules[0].angle });
currentString = nextString;
}
}
// Draw
function draw() {
// Clear with fade
ctx.fillStyle = 'rgba(10, 10, 26, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw current generation
if (currentGeneration < generations.length) {
const gen = generations[currentGeneration];
const angle = gen.angle;
const step = rules[0].step * (0.8 + 0.2 * Math.sin(Date.now() * 0.001 * params.pulse));
ctx.save();
ctx.translate(canvas.width/2, canvas.height);
ctx.strokeStyle = colors[currentGeneration % colors.length];
ctx.lineWidth = 1 + 0.5 * Math.sin(Date.now() * 0.0005 * params.pulse);
ctx.lineCap = 'round';
let stack = [];
let pos = { x: 0, y: 0 };
let dir = -Math.PI/2;
for (const c of gen.string) {
switch (c) {
case 'F':
const x1 = pos.x;
const y1 = pos.y;
pos.x += step * Math.cos(dir);
pos.y += step * Math.sin(dir);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
break;
case '+':
dir += angle * Math.PI/180;
break;
case '-':
dir -= angle * Math.PI/180;
break;
case '[':
stack.push({ pos: { ...pos }, dir });
break;
case ']':
const state = stack.pop();
pos = state.pos;
dir = state.dir;
break;
case 'X':
// Branch point - do nothing
break;
}
}
ctx.restore();
currentGeneration++;
} else {
// Loop back
init();
}
}
// Animation loop
function animate() {
draw();
requestAnimationFrame(animate);
}
init();
animate();
</script>
</body>
</html>