← 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>