animation2code benchmark
For best compatibility, please view this dashboard in a Chrome browser.
← back to Only CSS: Peacock

model output

GPT-5.4

Only CSS: Peacock

A 0.58T 0.18
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" />
6<title>Spiral Blade Animation</title>
7<style>
8  html, body {
9    margin: 0;
10    width: 100%;
11    height: 100%;
12    overflow: hidden;
13    background: #000;
14  }
15  canvas {
16    display: block;
17    width: 100vw;
18    height: 100vh;
19    background: #000;
20  }
21</style>
22</head>
23<body>
24<canvas id="c"></canvas>
25<script>
26(() => {
27  const canvas = document.getElementById('c');
28  const ctx = canvas.getContext('2d');
29
30  function resize() {
31    const dpr = Math.min(devicePixelRatio || 1, 2);
32    canvas.width = innerWidth * dpr;
33    canvas.height = innerHeight * dpr;
34    canvas.style.width = innerWidth + 'px';
35    canvas.style.height = innerHeight + 'px';
36    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
37  }
38  addEventListener('resize', resize);
39  resize();
40
41  const TAU = Math.PI * 2;
42  const ease = t => 1 - Math.pow(1 - t, 3); // easeOutCubic
43
44  function lerp(a, b, t) { return a + (b - a) * t; }
45
46  function hsl(h, s, l, a = 1) {
47    return `hsla(${h},${s}%,${l}%,${a})`;
48  }
49
50  function drawToothWedge(cx, cy, angle, r0, r1, widthA, teeth, toothAmp, color, alpha=1) {
51    ctx.save();
52    ctx.translate(cx, cy);
53    ctx.rotate(angle);
54    ctx.beginPath();
55
56    const a0 = -widthA / 2;
57    const a1 = widthA / 2;
58
59    // inner arc
60    ctx.arc(0, 0, r0, a0, a1, false);
61
62    // outer jagged edge back
63    for (let i = teeth; i >= 0; i--) {
64      const t = i / teeth;
65      const a = lerp(a0, a1, t);
66      const rr = r1 + ((i % 2 === 0) ? toothAmp : -toothAmp * 0.15);
67      ctx.lineTo(Math.cos(a) * rr, Math.sin(a) * rr);
68    }
69
70    ctx.closePath();
71    ctx.globalAlpha = alpha;
72    ctx.fillStyle = color;
73    ctx.fill();
74    ctx.restore();
75  }
76
77  function drawSegment(cx, cy, angle, radius, len, thickness, color, alpha=0.55) {
78    ctx.save();
79    ctx.translate(cx, cy);
80    ctx.rotate(angle);
81    ctx.beginPath();
82    ctx.moveTo(radius, -thickness * 0.52);
83    ctx.lineTo(radius + len * 0.82, -thickness * 0.52);
84    ctx.quadraticCurveTo(radius + len, 0, radius + len * 0.82, thickness * 0.52);
85    ctx.lineTo(radius, thickness * 0.52);
86    ctx.arc(0, 0, radius, 0.12, -0.12, true);
87    ctx.closePath();
88    ctx.globalAlpha = alpha;
89    ctx.fillStyle = color;
90    ctx.fill();
91    ctx.restore();
92  }
93
94  function drawCore(cx, cy, t) {
95    const spin = t * 1.8;
96    const baseR = 16;
97    for (let i = 0; i < 4; i++) {
98      const a = spin + i * Math.PI / 2;
99      drawToothWedge(
100        cx, cy, a,
101        baseR * 0.55,
102        baseR * 1.55,
103        0.95,
104        11,
105        6,
106        'rgb(255,0,0)',
107        1
108      );
109    }
110    ctx.save();
111    ctx.beginPath();
112    ctx.arc(cx, cy, 8.5, 0, TAU);
113    ctx.fillStyle = '#000';
114    ctx.fill();
115    ctx.restore();
116  }
117
118  function drawSpiral(cx, cy, t) {
119    // Growth tuned to match the frame sequence:
120    // starts as a tiny red core, then grows into a clockwise spiral
121    // with magenta body and blue-violet tail.
122    const grow = Math.max(0, (t - 0.12) / 0.88);
123    const g = ease(Math.min(grow, 1));
124
125    const turns = lerp(0.15, 1.55, g);
126    const segs = Math.floor(lerp(4, 42, g));
127    const maxR = lerp(18, Math.min(innerWidth, innerHeight) * 0.23, g);
128    const angleSpan = turns * TAU;
129
130    for (let i = 0; i < segs; i++) {
131      const p = i / Math.max(1, segs - 1);
132      const a = -Math.PI / 2 + p * angleSpan;
133      const r = lerp(18, maxR, p);
134      const len = lerp(18, 42, p);
135      const thick = lerp(18, 34, p);
136
137      // red -> magenta -> violet/blue
138      const hue = lerp(0, 245, Math.pow(p, 0.9));
139      const sat = 100;
140      const light = lerp(50, 56, p);
141      const alpha = lerp(0.95, 0.48, p);
142
143      drawSegment(cx, cy, a, r, len, thick, hsl(hue, sat, light, alpha), alpha);
144
145      // subtle circular glow on each segment to mimic overlapping translucent discs
146      ctx.save();
147      ctx.globalAlpha = alpha * 0.35;
148      ctx.fillStyle = hsl(hue, sat, 60, 1);
149      const px = cx + Math.cos(a) * (r + len * 0.55);
150      const py = cy + Math.sin(a) * (r + len * 0.02);
151      ctx.beginPath();
152      ctx.arc(px, py, thick * 0.52, 0, TAU);
153      ctx.fill();
154      ctx.restore();
155    }
156
157    // inner serrated spiral cluster
158    const innerCount = Math.floor(lerp(0, 22, g));
159    for (let i = 0; i < innerCount; i++) {
160      const p = i / Math.max(1, innerCount - 1);
161      const a = 0.4 + p * 3.8;
162      const rr = 18 + p * 58;
163      drawToothWedge(
164        cx, cy,
165        a,
166        rr * 0.18,
167        rr * 0.52,
168        lerp(0.9, 0.42, p),
169        8,
170        lerp(5, 2, p),
171        hsl(lerp(350, 320, p), 100, 52, 0.9),
172        lerp(0.95, 0.45, p)
173      );
174    }
175  }
176
177  const duration = 3200; // close to the observed sequence pacing
178  let start = performance.now();
179
180  function frame(now) {
181    const elapsed = (now - start) % duration;
182    const t = elapsed / duration;
183
184    ctx.clearRect(0, 0, innerWidth, innerHeight);
185
186    const cx = innerWidth * 0.505;
187    const cy = innerHeight * 0.515;
188
189    drawCore(cx, cy, t * TAU);
190    drawSpiral(cx, cy, t);
191
192    requestAnimationFrame(frame);
193  }
194
195  requestAnimationFrame(frame);
196})();
197</script>
198</body>
199</html>