← back to Model Arena
data/artifacts/10c3c876aa67/gpt.html
302 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fractal Tree with Wind and Seasons</title>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
background: radial-gradient(circle at top, #101522 0%, #05060a 60%, #020308 100%);
height: 100%;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: #eee;
}
#ui {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 16px;
background: rgba(5, 8, 20, 0.8);
padding: 8px 14px;
border-radius: 999px;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 6px 18px rgba(0,0,0,0.6);
backdrop-filter: blur(8px);
border: 1px solid rgba(180,210,255,0.16);
}
#label {
font-size: 13px;
opacity: 0.9;
white-space: nowrap;
}
#depthValue {
font-variant-numeric: tabular-nums;
margin-left: 2px;
}
#slider {
width: 200px;
accent-color: #71c4ff;
}
#season {
border-radius: 999px;
border: 1px solid rgba(180,210,255,0.3);
background: rgba(10,14,30,0.9);
color: #f0f4ff;
padding: 2px 6px;
font-size: 12px;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="ui">
<span id="label">Branch depth: <span id="depthValue">9</span></span>
<input id="slider" type="range" min="6" max="11" value="9">
<select id="season" title="Season">
<option value="spring">Spring</option>
<option value="summer" selected>Summer</option>
<option value="autumn">Autumn</option>
<option value="winter">Winter</option>
</select>
</div>
<script>
(function(){
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const slider = document.getElementById('slider');
const depthValue = document.getElementById('depthValue');
const seasonSelect = document.getElementById('season');
let W = 0, H = 0, cx = 0, baseY = 0;
let maxDepth = parseInt(slider.value, 10);
let growProgress = 0;
let lastTime = performance.now();
let windPhase = 0;
let windSpeed = 0.00045;
let leaves = [];
const maxLeaves = 180;
function resize() {
const dpr = window.devicePixelRatio || 1;
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
cx = W / 2;
baseY = H * 0.96;
}
window.addEventListener('resize', resize);
resize();
slider.addEventListener('input', () => {
maxDepth = parseInt(slider.value, 10);
depthValue.textContent = maxDepth;
});
seasonSelect.addEventListener('change', () => {
resetLeaves();
});
function resetLeaves() {
leaves = [];
const target = seasonLeafCount();
for (let i = 0; i < target; i++) {
leaves.push(makeLeaf(true));
}
}
function seasonLeafCount() {
const s = seasonSelect.value;
if (s === 'winter') return 8;
if (s === 'autumn') return 70;
if (s === 'spring') return 140;
return 180;
}
function leafColor() {
const s = seasonSelect.value;
if (s === 'winter') return 'rgba(220,230,255,0.5)';
if (s === 'autumn') {
const r = 180 + Math.random()*60|0;
const g = 80 + Math.random()*60|0;
const b = 20 + Math.random()*40|0;
return `rgba(${r},${g},${b},0.9)`;
}
if (s === 'spring') {
const g = 160 + Math.random()*70|0;
return `rgba(${80+Math.random()*20|0},${g},${90+Math.random()*40|0},0.9)`;
}
// summer
const g = 130 + Math.random()*80|0;
return `rgba(${40+Math.random()*40|0},${g},${40+Math.random()*40|0},0.9)`;
}
function makeLeaf(initial) {
const angle = -Math.PI/2 + (Math.random()*0.8 - 0.4);
let r = H * (0.18 + Math.random()*0.3);
const x = cx + Math.cos(angle) * r * (0.4 + Math.random()*0.8);
const y = baseY - r * (0.9 + Math.random()*0.4);
return {
x,
y: initial ? y - Math.random()*H*0.6 : (Math.random() < 0.5 ? -20 : y),
vy: 0.3 + Math.random()*0.6,
vx: -0.3 + Math.random()*0.6,
wobblePhase: Math.random()*Math.PI*2,
wobbleSpeed: 0.002 + Math.random()*0.002,
size: 3 + Math.random()*4,
color: leafColor(),
life: 0
};
}
function updateLeaves(dt, wind) {
const targetCount = seasonLeafCount();
if (leaves.length < targetCount && Math.random() < 0.3) {
leaves.push(makeLeaf(false));
}
for (let i = leaves.length - 1; i >= 0; i--) {
const L = leaves[i];
L.life += dt;
L.vx += wind * 0.002;
L.vy += 0.0006 * dt;
L.wobblePhase += L.wobbleSpeed * dt;
L.x += (L.vx + Math.sin(L.wobblePhase)*0.03*dt) * (dt*0.03);
L.y += L.vy * (dt*0.03);
if (L.y - L.size > H + 40 || L.x < -60 || L.x > W + 60 || L.life > 60000) {
leaves.splice(i,1);
}
}
}
function drawLeaves() {
for (const L of leaves) {
ctx.save();
ctx.translate(L.x, L.y);
ctx.rotate(Math.sin(L.wobblePhase)*0.6);
ctx.fillStyle = L.color;
ctx.beginPath();
ctx.ellipse(0, 0, L.size*0.7, L.size, 0, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
}
function branchColor(depth, maxDepth) {
const t = depth / maxDepth;
const br = 80 + t*70;
const bg = 55 + t*35;
const bb = 40 + t*20;
return `rgba(${br|0},${bg|0},${bb|0},${0.94 - t*0.4})`;
}
function drawTree(time) {
ctx.save();
ctx.translate(cx, baseY);
const trunkLen = H * 0.24;
const initialProgress = growProgress;
const maxGrowDepth = Math.max(1, Math.round(initialProgress * maxDepth));
function recurse(len, angle, depth) {
if (depth > maxGrowDepth || len < 2) return;
const localT = depth / maxDepth;
const wind = Math.sin(windPhase + depth*0.6) * (0.08 + 0.12*localT);
const a = angle + wind;
const x2 = Math.cos(a) * len;
const y2 = Math.sin(a) * len;
ctx.strokeStyle = branchColor(depth, maxDepth);
ctx.lineWidth = Math.max(1, (maxDepth - depth + 1) * 0.9);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(x2,y2);
ctx.stroke();
ctx.save();
ctx.translate(x2,y2);
if (depth < maxGrowDepth) {
const nextLen = len * (0.72 + 0.06*Math.sin(time*0.0004 + depth));
const spread = 0.42 + 0.16*Math.sin(time*0.0002 + depth*1.3);
recurse(nextLen, a - spread, depth+1);
recurse(nextLen*0.86, a + spread*0.9, depth+1);
if (depth === maxDepth-1 && Math.random()<0.12) {
recurse(nextLen*0.7, a + (Math.random()*0.6-0.3), depth+1);
}
}
ctx.restore();
}
recurse(trunkLen, -Math.PI/2, 1);
ctx.restore();
}
function drawBackground(time) {
const g = ctx.createLinearGradient(0,0,0,H);
const t = 0.5 + 0.5*Math.sin(time*0.00005);
const top = `rgb(${8+10*t|0},${18+30*t|0},${38+80*t|0})`;
const mid = `rgb(${4+8*t|0},${10+16*t|0},${20+40*t|0})`;
ctx.fillStyle = g;
g.addColorStop(0, top);
g.addColorStop(0.4, mid);
g.addColorStop(1, "#020308");
ctx.fillRect(0,0,W,H);
ctx.save();
ctx.fillStyle = "rgba(255,255,255,0.5)";
for (let i=0;i<40;i++){
const px = (i*97 + time*0.01) % W;
const py = (i*53 + time*0.012) % (H*0.5);
ctx.globalAlpha = 0.25 + 0.4*Math.sin(time*0.0003 + i);
ctx.beginPath();
ctx.arc(px, py, 0.8 + (i%4)*0.3, 0, Math.PI*2);
ctx.fill();
}
ctx.restore();
ctx.save();
ctx.fillStyle = "#020308";
ctx.beginPath();
ctx.moveTo(0,H);
ctx.quadraticCurveTo(W*0.25,H*0.8,W*0.5,H*0.88);
ctx.quadraticCurveTo(W*0.8,H*0.96,W,H*0.9);
ctx.lineTo(W,H);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function loop(now) {
const dt = now - lastTime;
lastTime = now;
growProgress += dt * 0.00008;
if (growProgress > 1) growProgress = 1;
windPhase += dt * windSpeed * (0.8 + 0.4*Math.sin(now*0.0002));
const wind = Math.sin(windPhase)*1.6 + Math.sin(windPhase*0.7)*0.7;
drawBackground(now);
ctx.save();
ctx.shadowColor = "rgba(0,0,0,0.8)";
ctx.shadowBlur = 12;
drawTree(now);
ctx.restore();
updateLeaves(dt, wind);
drawLeaves();
requestAnimationFrame(loop);
}
resetLeaves();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>