← back to Model Arena

data/artifacts/10c3c876aa67/kimi.html

290 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Recursive Fractal Tree</title>
<style>
body {
  margin: 0;
  overflow: hidden;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#canvas {
  display: block;
}
#controls {
  position: absolute;
  top: 15px;
  left: 15px;
  background: rgba(255, 255, 255, 0.95);
  padding: 15px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
  z-index: 10;
}
label {
  font-weight: bold;
  color: #333;
  display: block;
  margin-bottom: 5px;
  font-size: 14px;
}
input[type=range] {
  width: 200px;
  cursor: pointer;
}
#seasonText {
  margin-top: 8px;
  font-size: 13px;
  color: #666;
  font-weight: 600;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="controls">
  <label for="depthSlider">Branch Depth: <span id="depthVal">8</span></label>
  <input type="range" id="depthSlider" min="1" max="12" value="8">
  <div id="seasonText">Season: Spring</div>
</div>

<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width, height;

function resize() {
  width = canvas.width = window.innerWidth;
  height = canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();

// Configuration
let maxDepth = 8;
let growth = 0; // 0 to 1 for grow animation
let time = 0;
let seasonPhase = 0; // Continuous 0-4
let branchEnds = [];
let particles = [];

const seasons = [
  { 
    name: 'Spring', 
    leafColors: ['#90EE90', '#98FB98', '#00FA9A', '#7CFC00'],
    bgTop: '#87CEEB', 
    bgBottom: '#E0F6FF',
    ground: '#90EE90'
  },
  { 
    name: 'Summer', 
    leafColors: ['#228B22', '#006400', '#32CD32', '#00FF00'],
    bgTop: '#1E90FF', 
    bgBottom: '#87CEEB',
    ground: '#228B22'
  },
  { 
    name: 'Autumn', 
    leafColors: ['#FF8C00', '#DC143C', '#FFD700', '#FF6347', '#D2691E'],
    bgTop: '#B0C4DE', 
    bgBottom: '#F5F5DC',
    ground: '#D2B48C'
  },
  { 
    name: 'Winter', 
    leafColors: ['#FFFFFF', '#F0F8FF', '#E6E6FA', '#D3D3D3'],
    bgTop: '#696969', 
    bgBottom: '#D3D3D3',
    ground: '#FFFAFA'
  }
];

class LeafParticle {
  constructor(x, y, isAmbient = false) {
    this.x = x;
    this.y = y;
    this.isAmbient = isAmbient;
    
    const s = seasons[Math.floor(seasonPhase) % 4];
    this.color = s.leafColors[Math.floor(Math.random() * s.leafColors.length)];
    this.size = Math.random() * 4 + 2;
    this.vx = (Math.random() - 0.5) * (isAmbient ? 2 : 1);
    this.vy = Math.random() * 1.5 + 0.5;
    this.rotation = Math.random() * Math.PI * 2;
    this.rotSpeed = (Math.random() - 0.5) * 0.08;
    this.life = 1.0;
    this.decay = Math.random() * 0.003 + 0.002;
    this.swayOffset = Math.random() * Math.PI * 2;
  }
  
  update() {
    // Wind effect increases with height
    const wind = Math.sin(time * 2 + this.y * 0.005 + this.swayOffset) * 0.5 + 
                 Math.sin(time * 0.5 + this.swayOffset) * 0.2;
    this.x += this.vx + wind;
    this.y += this.vy;
    this.rotation += this.rotSpeed;
    this.life -= this.decay;
    
    // Ground collision
    if (this.y > height - 15) {
      this.y = height - 15;
      this.vy = 0;
      this.vx *= 0.95;
      this.life -= 0.02;
    }
  }
  
  draw() {
    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.rotate(this.rotation);
    ctx.fillStyle = this.color;
    ctx.globalAlpha = Math.max(0, Math.min(1, this.life));
    
    // Draw leaf shape
    ctx.beginPath();
    ctx.ellipse(0, 0, this.size, this.size * 0.6, 0, 0, Math.PI * 2);
    ctx.fill();
    
    ctx.restore();
    ctx.globalAlpha = 1;
  }
}

function drawTree(x, y, length, angle, depth) {
  if (depth <= 0) return;
  
  // Wind calculation: composite sine waves for natural movement
  // Tips move more than trunk
  const depthFactor = (maxDepth - depth + 1) / maxDepth;
  const windAngle = (Math.sin(time * 1.2 + depth * 0.3) * 0.04 + 
                     Math.sin(time * 2.7 - depth * 0.5) * 0.02) * depthFactor;
  
  const actualAngle = angle + windAngle;
  const currentLength = length * Math.min(1, growth + (maxDepth - depth) * 0.05); // Lower branches grow first slightly
  
  const endX = x + Math.cos(actualAngle) * currentLength;
  const endY = y + Math.sin(actualAngle) * currentLength;
  
  // Draw branch with tapering
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineTo(endX, endY);
  ctx.lineWidth = depth * 1.5;
  ctx.strokeStyle = `rgb(${50 + depth * 5}, ${30 + depth * 3}, ${10 + depth * 2})`;
  ctx.lineCap = 'round';
  ctx.stroke();
  
  // Leaves at branch ends (only when grown)
  if (depth === 1 && growth > 0.6) {
    const seasonIdx = Math.floor(seasonPhase) % 4;
    
    // Store position for particle spawning
    if (Math.random() < 0.3) {
      branchEnds.push({x: endX, y: endY});
    }
    
    // Draw static canopy leaves (except winter)
    if (seasonIdx !== 3) {
      ctx.fillStyle = seasons[seasonIdx].leafColors[0];
      ctx.beginPath();
      ctx.arc(endX, endY, 4 * growth, 0, Math.PI * 2);
      ctx.fill();
    }
  }
  
  // Recursive calls
  const newLength = length * 0.75;
  const branchAngle = 0.4; // 23 degrees approx
  
  drawTree(endX, endY, newLength, actualAngle - branchAngle, depth - 1);
  drawTree(endX, endY, newLength, actualAngle + branchAngle, depth - 1);
}

function drawBackground() {
  const seasonIdx = Math.floor(seasonPhase) % 4;
  const s = seasons[seasonIdx];
  
  // Sky gradient
  const grad = ctx.createLinearGradient(0, 0, 0, height);
  grad.addColorStop(0, s.bgTop);
  grad.addColorStop(1, s.bgBottom);
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, width, height);
  
  // Ground
  ctx.fillStyle = s.ground;
  ctx.fillRect(0, height - 15, width, 15);
}

function animate() {
  ctx.clearRect(0, 0, width, height);
  
  // Update time and seasons
  time += 0.016;
  seasonPhase += 0.0005; // Slow season cycle
  
  // Growth animation on load/reset
  if (growth < 1) {
    growth += 0.008;
    if (growth > 1) growth = 1;
  }
  
  // Update UI
  const currentSeason = seasons[Math.floor(seasonPhase) % 4];
  document.getElementById('seasonText').textContent = `Season: ${currentSeason.name}`;
  
  drawBackground();
  
  // Reset branch tracking
  branchEnds = [];
  
  // Draw fractal tree
  const trunkLength = height * 0.22;
  drawTree(width / 2, height - 10, trunkLength, -Math.PI / 2, maxDepth);
  
  // Spawn falling leaves from branch ends
  if (branchEnds.length > 0 && Math.random() < 0.4) {
    const spawnCount = Math.floor(Math.random() * 2) + 1;
    for (let i = 0; i < spawnCount; i++) {
      const source = branchEnds[Math.floor(Math.random() * branchEnds.length)];
      particles.push(new LeafParticle(source.x, source.y));
    }
  }
  
  // Ambient particles (snow in winter, pollen in spring, etc)
  if (Math.random() < 0.15) {
    particles.push(new LeafParticle(Math.random() * width, -10, true));
  }
  
  // Update and draw particles
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.update();
    p.draw();
    if (p.life <= 0) {
      particles.splice(i, 1);
    }
  }
  
  requestAnimationFrame(animate);
}

// Controls
const slider = document.getElementById('depthSlider');
const depthVal = document.getElementById('depthVal');

slider.addEventListener('input', (e) => {
  maxDepth = parseInt(e.target.value);
  depthVal.textContent = maxDepth;
  growth = 0.3; // Partial regrowth animation for effect
});

// Start
animate();
</script>
</body>
</html>