birth: Fractal Fronds in Darkness
This commit is contained in:
parent
ab8bc7eca7
commit
4bbf5c5faa
1 changed files with 184 additions and 0 deletions
184
index.html
Normal file
184
index.html
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Neurameba · motd.social</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
font-family: monospace;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
height: 100vh;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
#attribution {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 12px;
|
||||
opacity: 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();
|
||||
|
||||
// Parameters derived from abstract inputs
|
||||
const params = {
|
||||
motion: 0.5,
|
||||
density: 0.5,
|
||||
complexity: 0.5,
|
||||
connectedness: 0.5,
|
||||
lifespan: 0.5,
|
||||
pulse: { avg: 1.1, min: 1.0, max: 1.2 }
|
||||
};
|
||||
|
||||
// L-system rules with dynamic adaptation
|
||||
const lSystem = {
|
||||
axiom: 'A',
|
||||
rules: {
|
||||
'A': 'B[+A][-A]AB',
|
||||
'B': 'A[-B][+B]BA'
|
||||
},
|
||||
angle: Math.PI * 0.3,
|
||||
length: 120,
|
||||
depth: 6,
|
||||
strokeWidth: 1.5,
|
||||
hue: 0,
|
||||
saturation: 0,
|
||||
brightness: 100,
|
||||
opacity: 0.8
|
||||
};
|
||||
|
||||
// Animation state
|
||||
let time = 0;
|
||||
const branches = [];
|
||||
|
||||
// Initialize L-system
|
||||
function initSystem() {
|
||||
const stack = [];
|
||||
let current = lSystem.axiom;
|
||||
const lengthFactor = 0.6;
|
||||
const angleFactor = 1.0;
|
||||
let depth = 0;
|
||||
|
||||
// Calculate depth based on complexity
|
||||
lSystem.depth = Math.floor(4 + params.complexity * 4);
|
||||
|
||||
// Process the string
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
const c = current[i];
|
||||
|
||||
if (c === '[') {
|
||||
stack.push({ pos: [...getCurrentPos()], angle: getCurrentAngle(), length: lSystem.length });
|
||||
} else if (c === ']') {
|
||||
const state = stack.pop();
|
||||
setCurrentPos(state.pos);
|
||||
setCurrentAngle(state.angle);
|
||||
lSystem.length = state.length;
|
||||
} else if (c === '+') {
|
||||
setCurrentAngle(getCurrentAngle() + lSystem.angle * angleFactor * (0.7 + params.motion * 0.3));
|
||||
} else if (c === '-') {
|
||||
setCurrentAngle(getCurrentAngle() - lSystem.angle * angleFactor * (0.7 + params.motion * 0.3));
|
||||
} else if (c === 'A' || c === 'B') {
|
||||
const newLength = lSystem.length * lengthFactor;
|
||||
const startPos = [...getCurrentPos()];
|
||||
const endPos = [
|
||||
getCurrentPos()[0] + newLength * Math.cos(getCurrentAngle()),
|
||||
getCurrentPos()[1] + newLength * Math.sin(getCurrentAngle())
|
||||
];
|
||||
const hue = (lSystem.hue + Math.random() * 30) % 360;
|
||||
|
||||
branches.push({
|
||||
start: startPos,
|
||||
end: endPos,
|
||||
length: newLength,
|
||||
hue: hue,
|
||||
saturation: lSystem.saturation,
|
||||
brightness: lSystem.brightness,
|
||||
opacity: lSystem.opacity * (0.5 + params.lifespan * 0.5),
|
||||
time: time
|
||||
});
|
||||
|
||||
setCurrentPos(endPos);
|
||||
lSystem.length = newLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Position and angle tracking
|
||||
let currentPos = [canvas.width/2, canvas.height/2];
|
||||
let currentAngle = 0;
|
||||
|
||||
function getCurrentPos() { return [...currentPos]; }
|
||||
function getCurrentAngle() { return currentAngle; }
|
||||
function setCurrentPos(pos) { currentPos = [...pos]; }
|
||||
function setCurrentAngle(angle) { currentAngle = angle; }
|
||||
|
||||
// Draw branches with pulsing effect
|
||||
function drawBranches() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const pulseFactor = params.pulse.avg + Math.sin(time * 0.002) * 0.1 * (params.pulse.max - params.pulse.min);
|
||||
const maxBranches = Math.floor(50 + params.density * 150);
|
||||
|
||||
// Filter and draw branches
|
||||
branches.forEach(branch => {
|
||||
if (branch.opacity > 0.01) {
|
||||
const age = time - branch.time;
|
||||
const opacity = branch.opacity * (1 - age * 0.0001);
|
||||
|
||||
if (opacity > 0.01) {
|
||||
ctx.strokeStyle = `hsla(${branch.hue}, ${branch.saturation}%, ${branch.brightness}%, ${opacity})`;
|
||||
ctx.lineWidth = branch.length * 0.05 * pulseFactor * (0.8 + params.motion * 0.2);
|
||||
ctx.lineCap = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(...branch.start);
|
||||
ctx.lineTo(...branch.end);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Prune old branches based on lifespan
|
||||
if (params.lifespan < 0.3) {
|
||||
branches.forEach((branch, i) => {
|
||||
if (time - branch.time > 1000) {
|
||||
branch.opacity *= 0.9;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
time++;
|
||||
drawBranches();
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// Initialization
|
||||
initSystem();
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue