animation2code benchmark
For best compatibility, please view this dashboard in a Chrome browser.
← back to Merry Christmas Tree!

model output

Claude Sonnet 4.6

Merry Christmas Tree!

A 0.67T 0.30
compare with ground truth →
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<title>Christmas Tree Animation</title>
7<style>
8  * { margin: 0; padding: 0; box-sizing: border-box; }
9  
10  body {
11    background: #0d1117;
12    width: 1024px;
13    height: 768px;
14    overflow: hidden;
15    position: relative;
16  }
17
18  canvas {
19    position: absolute;
20    top: 0; left: 0;
21  }
22
23  #text-overlay {
24    position: absolute;
25    bottom: 60px;
26    width: 100%;
27    text-align: center;
28    color: white;
29    font-family: 'Segoe Script', 'Comic Sans MS', cursive;
30    font-size: 22px;
31    opacity: 0;
32    animation: fadeInText 1s ease forwards;
33    animation-delay: 9s;
34  }
35
36  @keyframes fadeInText {
37    to { opacity: 1; }
38  }
39</style>
40</head>
41<body>
42<canvas id="c" width="1024" height="768"></canvas>
43<div id="text-overlay">This is available to buy at my shop</div>
44
45<script>
46const canvas = document.getElementById('c');
47const ctx = canvas.getContext('2d');
48const W = 1024, H = 768;
49
50// Tree path definition - the golden outline of a Christmas tree
51// Center around x=480, tree top at y=100, bottom at y=680
52const cx = 480;
53
54// Define the tree path as a series of bezier curve points
55// The path traces the tree outline as a single continuous stroke
56function getTreePath() {
57  // Returns array of points along the tree path
58  const points = [];
59  
60  // We'll define the path parametrically
61  // Starting from bottom center, going up left side, across top, down right side
62  // Then the base swirl
63  
64  // The tree path (approximate from frames):
65  // Starts at bottom ~(430, 660) curves up in S-shape to top (480, 110)
66  // Then comes back down right side
67  // Then base oval
68  
69  return points;
70}
71
72// Tree path as SVG-like bezier segments
73// We'll use a canvas path and animate stroke-dashoffset equivalent
74const treePath = new Path2D();
75
76// Draw the full tree shape
77function buildTreePath(p) {
78  // Starting from bottom left of trunk area, going counterclockwise
79  // Trunk bottom
80  p.moveTo(455, 665); // trunk bottom left
81  // trunk
82  p.bezierCurveTo(445, 650, 440, 640, 435, 625);
83  // base swirl left
84  p.bezierCurveTo(380, 620, 310, 615, 290, 610);
85  p.bezierCurveTo(270, 605, 265, 595, 290, 590);
86  p.bezierCurveTo(320, 585, 380, 590, 430, 595);
87  // lower left side going up
88  p.bezierCurveTo(410, 570, 390, 545, 370, 520);
89  // lower left notch
90  p.bezierCurveTo(350, 510, 330, 505, 340, 495);
91  p.bezierCurveTo(355, 485, 390, 490, 420, 500);
92  // mid left going up
93  p.bezierCurveTo(400, 470, 375, 440, 355, 410);
94  // mid left notch
95  p.bezierCurveTo(335, 395, 315, 390, 325, 378);
96  p.bezierCurveTo(340, 365, 375, 372, 410, 385);
97  // upper left going to top
98  p.bezierCurveTo(390, 355, 370, 320, 360, 285);
99  p.bezierCurveTo(350, 250, 355, 210, 370, 180);
100  // top left to apex
101  p.bezierCurveTo(390, 150, 430, 125, 480, 115);
102  // apex to top right
103  p.bezierCurveTo(530, 125, 570, 150, 590, 180);
104  // upper right going down
105  p.bezierCurveTo(605, 210, 610, 250, 600, 285);
106  p.bezierCurveTo(590, 320, 570, 355, 550, 385);
107  // mid right notch
108  p.bezierCurveTo(585, 372, 620, 365, 635, 378);
109  p.bezierCurveTo(645, 390, 625, 395, 605, 410);
110  // mid right going down
111  p.bezierCurveTo(585, 440, 560, 470, 540, 500);
112  // lower right notch
113  p.bezierCurveTo(570, 490, 605, 485, 620, 495);
114  p.bezierCurveTo(630, 505, 610, 510, 590, 520);
115  // lower right going down
116  p.bezierCurveTo(570, 545, 550, 570, 530, 595);
117  // base swirl right
118  p.bezierCurveTo(580, 590, 640, 585, 670, 590);
119  p.bezierCurveTo(695, 595, 690, 605, 670, 610);
120  p.bezierCurveTo(645, 615, 575, 620, 525, 625);
121  // trunk right
122  p.bezierCurveTo(520, 640, 515, 650, 505, 665);
123  // trunk bottom
124  p.bezierCurveTo(495, 672, 465, 672, 455, 665);
125}
126
127// Sample points along a path
128function samplePath(pathFn, numPoints) {
129  // Create offscreen canvas to measure
130  const off = document.createElement('canvas');
131  off.width = W; off.height = H;
132  const octx = off.getContext('2d');
133  
134  const p = new Path2D();
135  pathFn(p);
136  
137  // We'll manually define key waypoints along the tree
138  // and interpolate between them
139  return null;
140}
141
142// Manual waypoints along the tree path (t: 0 to 1)
143const waypoints = [
144  // Starting from bottom, going up left side, over top, down right, base
145  {x: 455, y: 665}, // 0 - trunk bottom left
146  {x: 435, y: 625}, // trunk
147  {x: 290, y: 610}, // base left far
148  {x: 290, y: 590}, // base left return
149  {x: 430, y: 595}, // base left end
150  {x: 370, y: 520}, // lower left
151  {x: 340, y: 495}, // lower notch
152  {x: 420, y: 500}, // lower notch end
153  {x: 355, y: 410}, // mid left
154  {x: 325, y: 378}, // mid notch
155  {x: 410, y: 385}, // mid notch end
156  {x: 360, y: 285}, // upper left
157  {x: 370, y: 180}, // upper left top
158  {x: 480, y: 115}, // apex
159  {x: 590, y: 180}, // upper right top
160  {x: 600, y: 285}, // upper right
161  {x: 550, y: 385}, // mid right
162  {x: 635, y: 378}, // mid right notch
163  {x: 605, y: 410}, // mid right notch end
164  {x: 540, y: 500}, // lower right
165  {x: 620, y: 495}, // lower right notch
166  {x: 590, y: 520}, // lower right notch end
167  {x: 530, y: 595}, // lower right
168  {x: 670, y: 610}, // base right far
169  {x: 670, y: 590}, // base right return  -- wait, this goes wrong direction
170  {x: 525, y: 625}, // base right end
171  {x: 505, y: 665}, // trunk right
172  {x: 455, y: 665}, // trunk bottom close
173];
174
175// Better approach: use canvas to draw the full path, then sample it
176// We'll use a different technique - draw with stroke-dasharray animation
177
178// Total path length estimation
179let pathPoints = [];
180let totalLength = 0;
181
182function initPath() {
183  // Generate smooth path points using the bezier curves
184  // We'll approximate by sampling the bezier segments
185  
186  const segments = [
187    // [type, ...coords]
188    ['M', 455, 665],
189    ['C', 445, 650, 440, 640, 435, 625],
190    ['C', 380, 620, 310, 615, 290, 610],
191    ['C', 270, 605, 265, 595, 290, 590],
192    ['C', 320, 585, 380, 590, 430, 595],
193    ['C', 410, 570, 390, 545, 370, 520],
194    ['C', 350, 510, 330, 505, 340, 495],
195    ['C', 355, 485, 390, 490, 420, 500],
196    ['C', 400, 470, 375, 440, 355, 410],
197    ['C', 335, 395, 315, 390, 325, 378],
198    ['C', 340, 365, 375, 372, 410, 385],
199    ['C', 390, 355, 370, 320, 360, 285],
200    ['C', 350, 250, 355, 210, 370, 180],
201    ['C', 390, 150, 430, 125, 480, 115],
202    ['C', 530, 125, 570, 150, 590, 180],
203    ['C', 605, 210, 610, 250, 600, 285],
204    ['C', 590, 320, 570, 355, 550, 385],
205    ['C', 585, 372, 620, 365, 635, 378],
206    ['C', 645, 390, 625, 395, 605, 410],
207    ['C', 585, 440, 560, 470, 540, 500],
208    ['C', 570, 490, 605, 485, 620, 495],
209    ['C', 630, 505, 610, 510, 590, 520],
210    ['C', 570, 545, 550, 570, 530, 595],
211    ['C', 580, 590, 640, 585, 670, 590],
212    ['C', 695, 595, 690, 605, 670, 610],
213    ['C', 645, 615, 575, 620, 525, 625],
214    ['C', 520, 640, 515, 650, 505, 665],
215    ['C', 495, 672, 465, 672, 455, 665],
216  ];
217  
218  let cx0 = 0, cy0 = 0;
219  pathPoints = [];
220  
221  for (let seg of segments) {
222    if (seg[0] === 'M') {
223      cx0 = seg[1]; cy0 = seg[2];
224      pathPoints.push({x: cx0, y: cy0});
225    } else if (seg[0] === 'C') {
226      const [, x1, y1, x2, y2, x3, y3] = seg;
227      const steps = 30;
228      for (let i = 1; i <= steps; i++) {
229        const t = i / steps;
230        const mt = 1 - t;
231        const x = mt*mt*mt*cx0 + 3*mt*mt*t*x1 + 3*mt*t*t*x2 + t*t*t*x3;
232        const y = mt*mt*mt*cy0 + 3*mt*mt*t*y1 + 3*mt*t*t*y2 + t*t*t*y3;
233        pathPoints.push({x, y});
234      }
235      cx0 = x3; cy0 = y3;
236    }
237  }
238  
239  // Calculate cumulative lengths
240  totalLength = 0;
241  for (let i = 1; i < pathPoints.length; i++) {
242    const dx = pathPoints[i].x - pathPoints[i-1].x;
243    const dy = pathPoints[i].y - pathPoints[i-1].y;
244    totalLength += Math.sqrt(dx*dx + dy*dy);
245    pathPoints[i].cumLen = totalLength;
246  }
247  pathPoints[0].cumLen = 0;
248}
249
250function getPointAtLength(len) {
251  if (len <= 0) return pathPoints[0];
252  if (len >= totalLength) return pathPoints[pathPoints.length-1];
253  
254  let lo = 0, hi = pathPoints.length - 1;
255  while (lo < hi - 1) {
256    const mid = (lo + hi) >> 1;
257    if (pathPoints[mid].cumLen < len) lo = mid;
258    else hi = mid;
259  }
260  
261  const t = (len - pathPoints[lo].cumLen) / (pathPoints[hi].cumLen - pathPoints[lo].cumLen);
262  return {
263    x: pathPoints[lo].x + t * (pathPoints[hi].x - pathPoints[lo].x),
264    y: pathPoints[lo].y + t * (pathPoints[hi].y - pathPoints[lo].y)
265  };
266}
267
268// Particles/decorations
269const particles = [];
270const colors = ['#e74c3c', '#2ecc71', '#3498db', '#f1c40f', '#9b59b6', '#ecf0f1', '#1abc9c'];
271const starColors = ['#e74c3c', '#2ecc71', '#aaccff', '#ffffff', '#cc3366', '#66ccaa'];
272
273function createParticle(x, y) {
274  const angle = Math.random() * Math.PI * 2;
275  const speed = 0.5 + Math.random() * 2;
276  const type = Math.random() < 0.6 ? 'star' : 'dot';
277  return {
278    x, y,
279    vx: Math.cos(angle) * speed,
280    vy: Math.sin(angle) * speed,
281    color: starColors[Math.floor(Math.random() * starColors.length)],
282    size: type === 'star' ? (4 + Math.random() * 8) : (2 + Math.random() * 3),
283    type,
284    life: 1,
285    decay: 0.005 + Math.random() * 0.01,
286    rotation: Math.random() * Math.PI * 2,
287    rotSpeed: (Math.random() - 0.5) * 0.1
288  };
289}
290
291function drawStar(ctx, x, y, r, color, rotation, alpha) {
292  ctx.save();
293  ctx.globalAlpha = alpha;
294  ctx.fillStyle = color;
295  ctx.translate(x, y);
296  ctx.rotate(rotation);
297  ctx.beginPath();
298  for (let i = 0; i < 5; i++) {
299    const angle = (i * 4 * Math.PI / 5) - Math.PI/2;
300    const innerAngle = angle + 2 * Math.PI / 10;
301    if (i === 0) ctx.moveTo(Math.cos(angle)*r, Math.sin(angle)*r);
302    else ctx.lineTo(Math.cos(angle)*r, Math.sin(angle)*r);
303    ctx.lineTo(Math.cos(innerAngle)*r*0.4, Math.sin(innerAngle)*r*0.4);
304  }
305  ctx.closePath();
306  ctx.fill();
307  ctx.restore();
308}
309
310function drawTopStar(ctx, x, y, r, alpha) {
311  ctx.save();
312  ctx.globalAlpha = alpha;
313  ctx.fillStyle = '#c8956c';
314  ctx.strokeStyle = 'white';
315  ctx.lineWidth = 2;
316  ctx.translate(x, y);
317  ctx.beginPath();
318  for (let i = 0; i < 5; i++) {
319    const angle = (i * 4 * Math.PI / 5) - Math.PI/2;
320    const innerAngle = angle + 2 * Math.PI / 10;
321    if (i === 0) ctx.moveTo(Math.cos(angle)*r, Math.sin(angle)*r);
322    else ctx.lineTo(Math.cos(angle)*r, Math.sin(angle)*r);
323    ctx.lineTo(Math.cos(innerAngle)*r*0.45, Math.sin(innerAngle)*r*0.45);
324  }
325  ctx.closePath();
326  ctx.fill();
327  ctx.restore();
328}
329
330initPath();
331
332const startTime = performance.now();
333// Animation phases:
334// 0-5s: draw the tree path (the glowing dot traces the path)
335// 5-8s: dot continues around the base
336// 8-9s: fade in complete tree, top star appears
337// 9s+: text appears, star glows
338
339const DRAW_DURATION = 7000; // ms to draw full path
340const TOTAL_ANIM = 12000;
341
342let lastParticleTime = 0;
343
344function drawBackground() {
345  ctx.fillStyle = '#0d1117';
346  ctx.fillRect(0, 0, W, H);
347}
348
349function drawTreeStroke(progress) {
350  // Draw the tree path up to progress (0-1)
351  const len = totalLength * progress;
352  
353  ctx.save();
354  ctx.strokeStyle = '#c8956c';
355  ctx.lineWidth = 5;
356  ctx.lineCap = 'round';
357  ctx.lineJoin = 'round';
358  
359  ctx.beginPath();
360  let drawn = 0;
361  ctx.moveTo(pathPoints[0].x, pathPoints[0].y);
362  
363  for (let i = 1; i < pathPoints.length; i++) {
364    const dx = pathPoints[i].x - pathPoints[i-1].x;
365    const dy = pathPoints[i].y - pathPoints[i-1].y;
366    const segLen = Math.sqrt(dx*dx + dy*dy);
367    
368    if (drawn + segLen <= len) {
369      ctx.lineTo(pathPoints[i].x, pathPoints[i].y);
370      drawn += segLen;
371    } else {
372      const t = (len - drawn) / segLen;
373      ctx.lineTo(
374        pathPoints[i-1].x + t * dx,
375        pathPoints[i-1].y + t * dy
376      );
377      break;
378    }
379  }
380  
381  ctx.stroke();
382  ctx.restore();
383}
384
385function drawGlowDot(x, y, alpha) {
386  ctx.save();
387  ctx.globalAlpha = alpha;
388  
389  // Outer glow
390  const grad = ctx.createRadialGradient(x, y, 0, x, y, 30);
391  grad.addColorStop(0, 'rgba(100, 180, 255, 0.8)');
392  grad.addColorStop(0.3, 'rgba(100, 180, 255, 0.4)');
393  grad.addColorStop(1, 'rgba(100, 180, 255, 0)');
394  ctx.fillStyle = grad;
395  ctx.beginPath();
396  ctx.arc(x, y, 30, 0, Math.PI*2);
397  ctx.fill();
398  
399  // Inner bright
400  ctx.fillStyle = 'rgba(200, 230, 255, 0.9)';
401  ctx.beginPath();
402  ctx.arc(x, y, 5, 0, Math.PI*2);
403  ctx.fill();
404  
405  ctx.restore();
406}
407
408function animate(now) {
409  const elapsed = now - startTime;
410  const progress = Math.min(elapsed / DRAW_DURATION, 1);
411  
412  drawBackground();
413  
414  // Draw tree stroke
415  drawTreeStroke(progress);
416  
417  // Current position of the drawing dot
418  const currentLen = totalLength * progress;
419  const dotPos = getPointAtLength(currentLen);
420  
421  // Draw glow dot
422  if (progress < 1) {
423    drawGlowDot(dotPos.x, dotPos.y, 1);
424  }
425  
426  // Spawn particles near the dot
427  if (progress < 1 && elapsed - lastParticleTime > 80) {
428    for (let i = 0; i < 3; i++) {
429      particles.push(createParticle(dotPos.x + (Math.random()-0.5)*10, dotPos.y + (Math.random()-0.5)*10));
430    }
431    lastParticleTime = elapsed;
432  }
433  
434  // Update and draw particles
435  for (let i = particles.length - 1; i >= 0; i--) {
436    const p = particles[i];
437    p.x += p.vx;
438    p.y += p.vy;
439    p.vy += 0.02; // slight gravity
440    p.life -= p.decay;
441    p.rotation += p.rotSpeed;
442    
443    if (p.life <= 0) {
444      particles.splice(i, 1);
445      continue;
446    }
447    
448    if (p.type === 'star') {
449      drawStar(ctx, p.x, p.y, p.size, p.color, p.rotation, p.life * 0.8);
450    } else {
451      ctx.save();
452      ctx.globalAlpha = p.life * 0.6;
453      ctx.fillStyle = p.color;
454      ctx.beginPath();
455      ctx.arc(p.x, p.y, p.size, 0, Math.PI*2);
456      ctx.fill();
457      ctx.restore();
458    }
459  }
460  
461  // Top star - appears when path reaches apex
462  const apexLen = pathPoints.find(p => p.y < 120) ? 
463    pathPoints.filter(p => p.y < 120)[0]?.cumLen || totalLength * 0.45 : totalLength * 0.45;
464  
465  if (currentLen >= apexLen * 0.9 || progress >= 0.45) {
466    const starAlpha = Math.min((progress - 0.43) / 0.05, 1);
467    if (starAlpha > 0) {
468      // Find the apex point
469      const apexProgress = elapsed > DRAW_DURATION ? 1 : Math.min((elapsed - DRAW_DURATION * 0.43) / (DRAW_DURATION * 0.05), 1);
470      
471      // Top star glow
472      const starX = 480, starY = 80;
473      
474      if (elapsed > DRAW_DURATION * 0.43) {
475        const sa = Math.min((elapsed - DRAW_DURATION * 0.43) / 500, 1);
476        
477        // Glow effect on star after path completes
478        if (elapsed > DRAW_DURATION) {
479          const glowPulse = 0.7 + 0.3 * Math.sin(elapsed * 0.003);
480          ctx.save();
481          ctx.globalAlpha = glowPulse * 0.3;
482          const grad = ctx.createRadialGradient(starX, starY, 0, starX, starY, 40);
483          grad.addColorStop(0, 'rgba(200, 180, 100, 0.8)');
484          grad.addColorStop(1, 'rgba(200, 180, 100, 0)');
485          ctx.fillStyle = grad;
486          ctx.beginPath();
487          ctx.arc(starX, starY, 40, 0, Math.PI*2);
488          ctx.fill();
489          ctx.restore();
490        }
491        
492        drawTopStar(ctx, starX, starY, 28, sa);
493      }
494    }
495  }
496  
497  requestAnimationFrame(animate);
498}
499
500requestAnimationFrame(animate);
501</script>
502</body>
503</html>