birth: Emergent Bioluminescent Tendrils
This commit is contained in:
parent
6139cf4915
commit
20a94994fc
1 changed files with 169 additions and 0 deletions
169
index.html
Normal file
169
index.html
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<!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>
|
||||
Loading…
Add table
Reference in a new issue