← back to Model Arena

data/artifacts/10c3c876aa67/grok.html

213 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</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{overflow:hidden;background:#0a0a12;font-family:system-ui,sans-serif}
canvas{display:block;width:100vw;height:100vh}
.ui{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(20,20,35,0.85);backdrop-filter:blur(12px);padding:14px 28px;border-radius:16px;border:1px solid rgba(255,255,255,0.08);display:flex;align-items:center;gap:16px;z-index:10;box-shadow:0 8px 32px rgba(0,0,0,0.4)}
.ui label{color:#c8c8d8;font-size:13px;white-space:nowrap;letter-spacing:0.03em}
.ui input[type=range]{width:160px;accent-color:#7cb87c;cursor:pointer}
.ui span{color:#8fbc8f;font-size:13px;font-variant-numeric:tabular-nums;min-width:18px}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div class="ui">
<label>Branch Depth</label>
<input type="range" id="depth" min="1" max="12" value="9" step="1">
<span id="dval">9</span>
</div>
<script>
const canvas=document.getElementById('c');
const ctx=canvas.getContext('2d');
const depthSlider=document.getElementById('depth');
const dval=document.getElementById('dval');

let W,H,maxDepth=9,time=0,growProgress=0;
const leaves=[];
const falling=[];
const SEASON_COLORS=[
  ['#2d5a27','#3d7a37','#4a8f3f','#5ba84a'],
  ['#c45c26','#d4763a','#e8a04a','#b84020','#8b4513','#cd853f'],
  ['#e8e8f0','#d0d0e0','#c0c8d8','#a8b0c0'],
  ['#3a7a30','#4a9a3a','#5cb84a','#2d6a28','#6bc45a']
];
let season=0;
let seasonTimer=0;

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

depthSlider.addEventListener('input',()=>{
  maxDepth=+depthSlider.value;
  dval.textContent=maxDepth;
  growProgress=0;
  leaves.length=0;
  falling.length=0;
});

function wind(y,t){
  return Math.sin(t*0.7+y*0.008)*0.12+Math.sin(t*1.3+y*0.015)*0.06+Math.sin(t*0.3)*0.04;
}

function drawBranch(x,y,len,angle,depth,t){
  if(depth>maxDepth||len<2)return;
  const grow=Math.min(1,Math.max(0,(growProgress-depth*0.08)/(0.5)));
  if(grow<=0)return;
  const w=wind(y,t)*(1+depth*0.15);
  const a=angle+w;
  const el=len*grow;
  const x2=x+Math.sin(a)*el;
  const y2=y-Math.cos(a)*el;

  const trunk=depth===0;
  const lw=Math.max(0.5, (maxDepth-depth+1)*1.6*grow);
  const tcol=Math.floor(40+depth*8);
  ctx.beginPath();
  ctx.moveTo(x,y);
  ctx.lineTo(x2,y2);
  ctx.strokeStyle=trunk?`rgb(${90+tcol*0.3},${60+tcol*0.2},${30})`:`rgb(${70+tcol},${50+tcol*0.6},${25+depth*2})`;
  ctx.lineWidth=lw;
  ctx.lineCap='round';
  ctx.stroke();

  if(depth>=maxDepth-3&&grow>0.7){
    const cols=SEASON_COLORS[season%4];
    const n=depth===maxDepth?3:1;
    for(let i=0;i<n;i++){
      const lx=x2+(Math.random()-0.5)*8;
      const ly=y2+(Math.random()-0.5)*8;
      const col=cols[Math.floor(Math.random()*cols.length)];
      const r=2+Math.random()*3;
      leaves.push({x:lx,y:ly,r,col,ox:lx,oy:ly,phase:Math.random()*Math.PI*2,depth});
      if(leaves.length>800)leaves.shift();
    }
  }

  const shrink=0.68+Math.sin(depth+t*0.1)*0.04;
  const spread=0.45+Math.sin(depth*1.3)*0.08;
  drawBranch(x2,y2,len*shrink,a-spread,depth+1,t);
  drawBranch(x2,y2,len*shrink,a+spread,depth+1,t);
  if(depth>2&&depth<maxDepth-1&&Math.sin(depth*7.3)>0.3){
    drawBranch(x2,y2,len*shrink*0.75,a+spread*0.3*(depth%2?1:-1),depth+1,t);
  }
}

function spawnFalling(){
  if(leaves.length===0)return;
  if(Math.random()>0.15)return;
  const src=leaves[Math.floor(Math.random()*leaves.length)];
  const cols=SEASON_COLORS[season%4];
  falling.push({
    x:src.x,y:src.y,
    vx:(Math.random()-0.5)*1.2,
    vy:0.3+Math.random()*0.6,
    r:src.r*(0.7+Math.random()*0.5),
    col:cols[Math.floor(Math.random()*cols.length)],
    rot:Math.random()*Math.PI*2,
    rv:(Math.random()-0.5)*0.08,
    life:1
  });
  if(falling.length>300)falling.shift();
}

function updateFalling(t){
  for(let i=falling.length-1;i>=0;i--){
    const p=falling[i];
    const w=wind(p.y,t)*40;
    p.vx+=w*0.02;
    p.vx*=0.98;
    p.vy+=0.015;
    p.x+=p.vx+Math.sin(t*2+p.rot)*0.4;
    p.y+=p.vy;
    p.rot+=p.rv;
    p.life-=0.002;
    if(p.y>H+20||p.life<=0)falling.splice(i,1);
  }
}

function drawLeaves(t){
  for(const l of leaves){
    const w=wind(l.y,t);
    const sx=l.ox+Math.sin(t*1.5+l.phase)*3+w*20;
    const sy=l.oy+Math.cos(t*1.2+l.phase)*2;
    l.x=sx;l.y=sy;
    ctx.beginPath();
    ctx.arc(sx,sy,l.r,0,Math.PI*2);
    ctx.fillStyle=l.col;
    ctx.globalAlpha=0.75;
    ctx.fill();
  }
  ctx.globalAlpha=1;
}

function drawFalling(){
  for(const p of falling){
    ctx.save();
    ctx.translate(p.x,p.y);
    ctx.rotate(p.rot);
    ctx.globalAlpha=Math.min(1,p.life)*0.85;
    ctx.fillStyle=p.col;
    ctx.beginPath();
    ctx.ellipse(0,0,p.r,p.r*0.6,0,0,Math.PI*2);
    ctx.fill();
    ctx.restore();
  }
  ctx.globalAlpha=1;
}

function drawGround(){
  const grd=ctx.createLinearGradient(0,H*0.85,0,H);
  grd.addColorStop(0,'rgba(20,40,20,0)');
  grd.addColorStop(0.3,'rgba(25,50,25,0.4)');
  grd.addColorStop(1,'rgba(15,35,15,0.7)');
  ctx.fillStyle=grd;
  ctx.fillRect(0,H*0.85,W,H*0.15);
}

function drawSky(){
  const grd=ctx.createLinearGradient(0,0,0,H);
  const s=season%4;
  if(s===0){grd.addColorStop(0,'#0c1a10');grd.addColorStop(1,'#1a2a18');}
  else if(s===1){grd.addColorStop(0,'#1a1008');grd.addColorStop(1,'#2a1a10');}
  else if(s===2){grd.addColorStop(0,'#0a0e18');grd.addColorStop(1,'#121828');}
  else{grd.addColorStop(0,'#0a1810');grd.addColorStop(1,'#142818');}
  ctx.fillStyle=grd;
  ctx.fillRect(0,0,W,H);
}

function frame(ts){
  time=ts*0.001;
  growProgress=Math.min(1.5,growProgress+0.006);
  seasonTimer+=0.001;
  if(seasonTimer>12){seasonTimer=0;season++;leaves.length=0;}

  drawSky();
  drawGround();

  const baseX=W/2;
  const baseY=H*0.88;
  const trunkLen=Math.min(W,H)*0.18;

  leaves.length=0;
  drawBranch(baseX,baseY,trunkLen,0,0,time);
  drawLeaves(time);

  spawnFalling();
  updateFalling(time);
  drawFalling();

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