← back to Model Arena

data/artifacts/10c3c876aa67/claude-code.html

223 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fractal Tree — Wind &amp; Falling Leaves</title>
<style>
  html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background: #1a2233; }
  canvas { display: block; }
  #ui {
    position: fixed; top: 14px; left: 14px; z-index: 10;
    background: rgba(10, 16, 28, 0.55); backdrop-filter: blur(6px);
    border: 1px solid rgba(255,255,255,0.15); border-radius: 10px;
    padding: 12px 16px; color: #e8e2d0;
    font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    user-select: none;
  }
  #ui label { display: flex; align-items: center; gap: 10px; }
  #depthVal { min-width: 2ch; text-align: center; font-weight: 600; color: #ffb347; }
  input[type=range] { width: 160px; accent-color: #ffb347; cursor: pointer; }
  #hint { opacity: 0.6; font-size: 11px; margin-top: 4px; }
</style>
</head>
<body>
<div id="ui">
  <label>Branch depth
    <input type="range" id="depth" min="3" max="11" value="9" step="1">
    <span id="depthVal">9</span>
  </label>
  <div id="hint">tree regrows when depth changes</div>
</div>
<canvas id="c"></canvas>
<script>
(function () {
  "use strict";
  var canvas = document.getElementById("c");
  var ctx = canvas.getContext("2d");
  var W = 0, H = 0, DPR = Math.min(window.devicePixelRatio || 1, 2);

  function resize() {
    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);
  }
  window.addEventListener("resize", resize);
  resize();

  var maxDepth = 9;
  var growStart = performance.now();
  var GROW_TIME = 3500;               // ms for full growth
  var slider = document.getElementById("depth");
  var depthVal = document.getElementById("depthVal");
  slider.addEventListener("input", function () {
    maxDepth = parseInt(slider.value, 10);
    depthVal.textContent = maxDepth;
    growStart = performance.now();    // regrow from trunk
    leaves.length = 0;
    ground.length = 0;
  });

  // deterministic pseudo-random from a seed, so the tree shape is stable per frame
  function sr(seed) {
    var x = Math.sin(seed * 127.1 + 311.7) * 43758.5453;
    return x - Math.floor(x);
  }

  // autumn leaf palette
  var LEAF_COLORS = ["#d9542b", "#e8842a", "#f0b429", "#c23b22", "#e0a41c", "#b8551f"];

  // falling leaf particles
  var leaves = [];   // airborne
  var ground = [];   // settled on the ground
  var MAX_LEAVES = 140, MAX_GROUND = 220;
  var tips = [];     // leaf-bearing branch tips gathered each frame

  function spawnLeaf(x, y, wind) {
    if (leaves.length >= MAX_LEAVES) return;
    leaves.push({
      x: x, y: y,
      vx: wind * 12 + (Math.random() - 0.5) * 20,
      vy: 8 + Math.random() * 18,
      rot: Math.random() * Math.PI * 2,
      spin: (Math.random() - 0.5) * 3,
      phase: Math.random() * Math.PI * 2,
      size: 3 + Math.random() * 3.5,
      color: LEAF_COLORS[(Math.random() * LEAF_COLORS.length) | 0]
    });
  }

  function drawLeafShape(x, y, rot, size, color, alpha) {
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(rot);
    ctx.globalAlpha = alpha;
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.ellipse(0, 0, size, size * 0.55, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
    ctx.globalAlpha = 1;
  }

  // recursive branch drawing
  function branch(x, y, angle, len, width, depth, seed, growth, t, wind) {
    // per-level growth: level d grows during its slice of the growth window
    var levelProg = growth * (maxDepth + 1) - depth;
    if (levelProg <= 0) return;
    if (levelProg > 1) levelProg = 1;

    // wind sway: outer, thinner branches sway more; each branch has its own phase
    var flexibility = Math.pow((depth + 1) / (maxDepth + 1), 2);
    var sway = wind * flexibility * 0.22
             + Math.sin(t * 0.0021 + seed * 6.28 + depth) * 0.02 * flexibility;
    var a = angle + sway;

    var drawnLen = len * levelProg;
    var x2 = x + Math.cos(a) * drawnLen;
    var y2 = y + Math.sin(a) * drawnLen;

    ctx.strokeStyle = depth < 2 ? "#4a3626" : (depth < maxDepth - 2 ? "#5b442e" : "#6e5638");
    ctx.lineWidth = Math.max(width * (0.6 + 0.4 * levelProg), 0.5);
    ctx.lineCap = "round";
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x2, y2);
    ctx.stroke();

    if (depth >= maxDepth || len < 4) {
      if (levelProg >= 1) tips.push({ x: x2, y: y2, seed: seed });
      return;
    }
    if (levelProg < 1) return; // children start once this segment finishes

    var r1 = sr(seed * 7.3 + depth), r2 = sr(seed * 13.7 + depth * 2), r3 = sr(seed * 29.1);
    var spread = 0.38 + r1 * 0.28;
    var shrink = 0.68 + r2 * 0.09;

    branch(x2, y2, a - spread + (r3 - 0.5) * 0.2, len * shrink, width * 0.7, depth + 1, seed * 2 + 1, growth, t, wind);
    branch(x2, y2, a + spread + (r1 - 0.5) * 0.2, len * shrink, width * 0.7, depth + 1, seed * 2 + 2, growth, t, wind);
    if (r3 > 0.72 && depth > 1) { // occasional third middle branch
      branch(x2, y2, a + (r2 - 0.5) * 0.25, len * shrink * 0.85, width * 0.6, depth + 1, seed * 3 + 5, growth, t, wind);
    }
  }

  function frame(t) {
    // sky
    var g = ctx.createLinearGradient(0, 0, 0, H);
    g.addColorStop(0, "#2b3a5c");
    g.addColorStop(0.6, "#5a5a72");
    g.addColorStop(1, "#8a6d55");
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, W, H);

    // ground
    var groundY = H - 40;
    ctx.fillStyle = "#3d2f22";
    ctx.fillRect(0, groundY, W, 40);
    ctx.fillStyle = "rgba(255, 200, 120, 0.05)";
    ctx.fillRect(0, groundY, W, 3);

    // gusty wind: layered sines, occasionally strong
    var wind = Math.sin(t * 0.0006) * 0.55
             + Math.sin(t * 0.00023 + 1.7) * 0.35
             + Math.sin(t * 0.0017 + 4.1) * 0.18;

    var growth = Math.min(1, (t - growStart) / GROW_TIME);
    // ease growth
    var eased = 1 - Math.pow(1 - growth, 3);

    tips.length = 0;
    var trunkLen = Math.min(H, 900) * 0.16 + maxDepth * 2;
    branch(W / 2, groundY, -Math.PI / 2, trunkLen, Math.max(4, maxDepth * 1.3), 0, 1, eased, t, wind);

    // foliage at tips
    for (var i = 0; i < tips.length; i++) {
      var tip = tips[i];
      var c = LEAF_COLORS[(sr(tip.seed) * LEAF_COLORS.length) | 0];
      var wob = Math.sin(t * 0.003 + tip.seed * 9) * 1.5 * Math.abs(wind);
      drawLeafShape(tip.x + wob, tip.y, sr(tip.seed * 3) * Math.PI * 2 + wind * 0.4, 3.2 + sr(tip.seed * 5) * 2.5, c, 0.9);
      // occasionally shed a leaf, more in strong gusts
      if (growth >= 1 && Math.random() < 0.0015 + Math.abs(wind) * 0.004) {
        spawnLeaf(tip.x, tip.y, wind);
      }
    }

    // update + draw airborne leaves
    var dt = 1 / 60;
    for (var j = leaves.length - 1; j >= 0; j--) {
      var L = leaves[j];
      L.phase += dt * 4;
      L.vx += (wind * 26 - L.vx) * 0.02 + Math.sin(L.phase) * 6 * dt * 8;
      L.vy += (26 - L.vy) * 0.03; // drift toward terminal velocity
      L.x += L.vx * dt;
      L.y += (L.vy + Math.cos(L.phase * 0.7) * 10) * dt;
      L.rot += L.spin * dt + Math.sin(L.phase) * 0.02;
      if (L.y >= groundY - 2) {
        if (ground.length < MAX_GROUND) {
          ground.push({ x: L.x, y: groundY - 1 + Math.random() * 6, rot: L.rot, size: L.size, color: L.color, born: t });
        }
        leaves.splice(j, 1);
      } else if (L.x < -50 || L.x > W + 50) {
        leaves.splice(j, 1);
      } else {
        drawLeafShape(L.x, L.y, L.rot, L.size, L.color, 0.95);
      }
    }

    // settled leaves slowly fade away
    for (var k = ground.length - 1; k >= 0; k--) {
      var Gl = ground[k];
      var age = (t - Gl.born) / 20000;
      if (age >= 1) { ground.splice(k, 1); continue; }
      drawLeafShape(Gl.x, Gl.y, Gl.rot, Gl.size, Gl.color, 0.8 * (1 - age));
    }

    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
})();
</script>
</body>
</html>