animation2code benchmark
For best compatibility, please view this dashboard in a Chrome browser.
← back to Cloudy Spiral CSS animation

model output

GPT-5.4

Cloudy Spiral CSS animation

A 0.75T 0.31
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>Spiral Light Trail</title>
7<style>
8  :root{
9    --bg:#4d7caf;
10    --glow: rgba(220,240,255,.95);
11    --soft: rgba(210,232,255,.22);
12    --beam: rgba(210,232,255,.08);
13  }
14
15  html,body{
16    margin:0;
17    width:100%;
18    height:100%;
19    overflow:hidden;
20    background:var(--bg);
21  }
22
23  body{
24    position:relative;
25    font-family:sans-serif;
26  }
27
28  #scene{
29    position:fixed;
30    inset:0;
31    width:100vw;
32    height:100vh;
33    display:block;
34    filter: blur(.2px);
35  }
36</style>
37</head>
38<body>
39<canvas id="scene"></canvas>
40
41<script>
42(() => {
43  const canvas = document.getElementById('scene');
44  const ctx = canvas.getContext('2d');
45
46  let w = 0, h = 0, dpr = Math.min(devicePixelRatio || 1, 2);
47
48  function resize(){
49    w = innerWidth;
50    h = innerHeight;
51    canvas.width = Math.round(w * dpr);
52    canvas.height = Math.round(h * dpr);
53    canvas.style.width = w + 'px';
54    canvas.style.height = h + 'px';
55    ctx.setTransform(dpr,0,0,dpr,0,0);
56  }
57  addEventListener('resize', resize, {passive:true});
58  resize();
59
60  const TAU = Math.PI * 2;
61  const dots = 42;
62  const cycle = 4.8; // close to the repeating cadence in the frames
63
64  function easeInOutSine(x){
65    return -(Math.cos(Math.PI * x) - 1) / 2;
66  }
67
68  function lerp(a,b,t){ return a + (b-a)*t; }
69
70  function pathPoint(u, t){
71    // Spiral-like loop with a long sweeping tail.
72    // u: 0=head, 1=oldest trail
73    const head = t * TAU;
74    const a = head - u * 1.55 * TAU;
75
76    // Radius grows strongly for older points to create the giant off-screen discs.
77    const r = 18 + Math.pow(u, 2.15) * Math.min(w,h) * 1.05;
78
79    // Slight ellipse and drift to match the off-center loop.
80    const cx = w * 0.515;
81    const cy = h * 0.53;
82
83    const x = cx + Math.cos(a) * r * 1.18 + Math.cos(head * 0.5) * 18;
84    const y = cy + Math.sin(a) * r * 0.82 - Math.sin(head * 0.5) * 10;
85
86    return {x,y,a,r};
87  }
88
89  function drawGlow(x,y,r,alpha){
90    const g = ctx.createRadialGradient(x,y,0,x,y,r*2.8);
91    g.addColorStop(0, `rgba(235,245,255,${alpha})`);
92    g.addColorStop(0.28, `rgba(225,238,255,${alpha*0.55})`);
93    g.addColorStop(1, `rgba(225,238,255,0)`);
94    ctx.fillStyle = g;
95    ctx.beginPath();
96    ctx.arc(x,y,r*2.8,0,TAU);
97    ctx.fill();
98  }
99
100  function drawBeam(x1,y1,x2,y2,width,alpha){
101    const dx = x2 - x1, dy = y2 - y1;
102    const len = Math.hypot(dx,dy) || 1;
103    const nx = -dy / len, ny = dx / len;
104
105    ctx.beginPath();
106    ctx.moveTo(x1 + nx * width, y1 + ny * width);
107    ctx.lineTo(x1 - nx * width, y1 - ny * width);
108    ctx.lineTo(x2 - nx * width * 0.18, y2 - ny * width * 0.18);
109    ctx.lineTo(x2 + nx * width * 0.18, y2 + ny * width * 0.18);
110    ctx.closePath();
111
112    const grad = ctx.createLinearGradient(x1,y1,x2,y2);
113    grad.addColorStop(0, `rgba(220,238,255,${alpha})`);
114    grad.addColorStop(1, `rgba(220,238,255,0)`);
115    ctx.fillStyle = grad;
116    ctx.fill();
117  }
118
119  function render(now){
120    const time = now * 0.001;
121    const p = (time % cycle) / cycle;
122    const t = easeInOutSine(p);
123
124    ctx.clearRect(0,0,w,h);
125    ctx.fillStyle = '#4d7caf';
126    ctx.fillRect(0,0,w,h);
127
128    // Fade-in at start like the frame sequence.
129    const intro = Math.min(1, Math.max(0, (time - 0.35) / 1.2));
130
131    const pts = [];
132    for(let i=0;i<dots;i++){
133      const u = i / (dots - 1);
134      pts.push(pathPoint(u, t));
135    }
136
137    // Soft translucent wedges/beam from large discs toward the loop.
138    ctx.globalCompositeOperation = 'screen';
139    for(let i = dots - 1; i > 8; i -= 2){
140      const a = pts[i];
141      const b = pts[Math.max(0, i - 8)];
142      const width = lerp(120, 18, 1 - i / (dots - 1));
143      const alpha = intro * lerp(0.08, 0.02, 1 - i / (dots - 1));
144      drawBeam(a.x, a.y, b.x, b.y, width, alpha);
145    }
146
147    // Draw trail from oldest to newest so the head sits on top.
148    for(let i = dots - 1; i >= 0; i--){
149      const u = i / (dots - 1);
150      const pt = pts[i];
151
152      // Large at the tail, tiny around the loop.
153      const r = lerp(3.2, 72, Math.pow(u, 2.35));
154      const alpha = intro * lerp(0.42, 0.16, u);
155
156      drawGlow(pt.x, pt.y, r, alpha * 0.55);
157
158      ctx.fillStyle = `rgba(232,242,255,${alpha})`;
159      ctx.beginPath();
160      ctx.arc(pt.x, pt.y, r, 0, TAU);
161      ctx.fill();
162    }
163
164    // Extra bright head.
165    const head = pts[0];
166    drawGlow(head.x, head.y, 34, intro * 0.55);
167    ctx.fillStyle = `rgba(245,250,255,${intro * 0.9})`;
168    ctx.beginPath();
169    ctx.arc(head.x, head.y, 12, 0, TAU);
170    ctx.fill();
171
172    requestAnimationFrame(render);
173  }
174
175  requestAnimationFrame(render);
176})();
177</script>
178</body>
179</html>