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

model output

Claude Sonnet 4.6

Only CSS: Screw 2

A 0.57T 0.19
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>SCREW Animation</title>
7<style>
8  * { margin: 0; padding: 0; box-sizing: border-box; }
9  
10  body {
11    background-color: #1e2a35;
12    display: flex;
13    align-items: center;
14    justify-content: center;
15    width: 100vw;
16    height: 100vh;
17    overflow: hidden;
18  }
19
20  .scene {
21    position: relative;
22    width: 500px;
23    height: 160px;
24    display: flex;
25    align-items: center;
26    justify-content: center;
27  }
28
29  canvas {
30    position: absolute;
31    top: 50%;
32    left: 50%;
33    transform: translate(-50%, -50%);
34  }
35</style>
36</head>
37<body>
38<canvas id="c"></canvas>
39<script>
40const canvas = document.getElementById('c');
41const ctx = canvas.getContext('2d');
42canvas.width = 1024;
43canvas.height = 576;
44
45const CX = canvas.width / 2;
46const CY = canvas.height / 2;
47
48// Animation state
49let t = 0;
50const PERIOD = 4000; // ms per cycle
51
52// Letter definitions for SCREW
53// Each letter is drawn as a bold, condensed uppercase letter
54// We'll use canvas text rendering with a custom font stack
55
56function drawVinyl(ctx, x, y, r, angle) {
57  ctx.save();
58  ctx.translate(x, y);
59  ctx.rotate(angle);
60  
61  // Outer ring
62  ctx.beginPath();
63  ctx.arc(0, 0, r, 0, Math.PI * 2);
64  ctx.fillStyle = 'white';
65  ctx.fill();
66  
67  // Grooves (dark rings)
68  const grooves = [0.85, 0.72, 0.60, 0.48, 0.36];
69  grooves.forEach(f => {
70    ctx.beginPath();
71    ctx.arc(0, 0, r * f, 0, Math.PI * 2);
72    ctx.fillStyle = '#1e2a35';
73    ctx.fill();
74    ctx.beginPath();
75    ctx.arc(0, 0, r * (f - 0.06), 0, Math.PI * 2);
76    ctx.fillStyle = 'white';
77    ctx.fill();
78  });
79  
80  // Label circle
81  ctx.beginPath();
82  ctx.arc(0, 0, r * 0.28, 0, Math.PI * 2);
83  ctx.fillStyle = '#1e2a35';
84  ctx.fill();
85  
86  // Center hole
87  ctx.beginPath();
88  ctx.arc(0, 0, r * 0.06, 0, Math.PI * 2);
89  ctx.fillStyle = 'white';
90  ctx.fill();
91  
92  ctx.restore();
93}
94
95// Letter fragment system
96// Each letter has segments that can rotate around center
97class LetterFragment {
98  constructor(letter, baseX, baseY, segIndex, totalSegs) {
99    this.letter = letter;
100    this.baseX = baseX;
101    this.baseY = baseY;
102    this.segIndex = segIndex;
103    this.totalSegs = totalSegs;
104    this.angle = (segIndex / totalSegs) * Math.PI * 2;
105    this.radius = 0;
106    this.rotOffset = (segIndex / totalSegs) * Math.PI * 2;
107  }
108}
109
110// Draw a single letter with horizontal stripe effect (vinyl groove look)
111function drawStripedLetter(ctx, letter, x, y, size, stripeAngle, alpha) {
112  ctx.save();
113  ctx.globalAlpha = alpha;
114  ctx.translate(x, y);
115  
116  // Clip to letter shape
117  ctx.save();
118  ctx.font = `bold ${size}px Arial Black, Impact, sans-serif`;
119  ctx.textAlign = 'center';
120  ctx.textBaseline = 'middle';
121  
122  // Draw full letter white
123  ctx.fillStyle = 'white';
124  ctx.fillText(letter, 0, 0);
125  
126  // Draw dark stripes over it
127  ctx.globalCompositeOperation = 'destination-out';
128  const stripeCount = 8;
129  const stripeWidth = size / (stripeCount * 2);
130  
131  for (let i = 0; i < stripeCount; i++) {
132    const sy = -size * 0.6 + i * (size * 1.2 / stripeCount) + stripeWidth * 0.5;
133    ctx.fillStyle = 'rgba(0,0,0,1)';
134    ctx.fillRect(-size, sy, size * 2, stripeWidth * 0.7);
135  }
136  
137  ctx.restore();
138  ctx.restore();
139}
140
141// Segments of each letter that spin outward
142function getLetterSegments(letter, x, y, fontSize) {
143  // Return array of {clipY, clipH, letter, x, y}
144  const segs = [];
145  const n = 7;
146  const h = fontSize * 1.2;
147  for (let i = 0; i < n; i++) {
148    segs.push({
149      letter, x, y,
150      clipY: y - h/2 + i * (h/n),
151      clipH: h/n,
152      index: i,
153      total: n
154    });
155  }
156  return segs;
157}
158
159const letters = ['S', 'C', 'R', 'E', 'W'];
160const fontSize = 110;
161const letterSpacing = 95;
162const totalWidth = (letters.length - 1) * letterSpacing;
163const startX = CX - totalWidth / 2;
164
165// Precompute letter positions
166const letterPositions = letters.map((l, i) => ({
167  letter: l,
168  x: startX + i * letterSpacing,
169  y: CY
170}));
171
172// Vinyl center position (between C and R, roughly center)
173const vinylX = CX + 5;
174const vinylY = CY;
175const vinylR = 52;
176
177let vinylAngle = 0;
178
179function easeInOut(t) {
180  return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
181}
182
183function easeIn(t) {
184  return t * t * t;
185}
186
187function easeOut(t) {
188  return 1 - Math.pow(1-t, 3);
189}
190
191function draw(timestamp) {
192  ctx.clearRect(0, 0, canvas.width, canvas.height);
193  ctx.fillStyle = '#1e2a35';
194  ctx.fillRect(0, 0, canvas.width, canvas.height);
195  
196  const phase = (timestamp % PERIOD) / PERIOD; // 0 to 1
197  
198  // Animation phases:
199  // 0.0 - 0.15: normal display
200  // 0.15 - 0.45: spin out (letters fragment and rotate)
201  // 0.45 - 0.55: fully spun (vortex)
202  // 0.55 - 0.85: spin back in
203  // 0.85 - 1.0: normal display
204  
205  let spinAmount = 0;
206  let scaleAmount = 1;
207  
208  if (phase < 0.15) {
209    spinAmount = 0;
210    scaleAmount = 1;
211  } else if (phase < 0.45) {
212    const p = (phase - 0.15) / 0.30;
213    spinAmount = easeIn(p);
214    scaleAmount = 1;
215  } else if (phase < 0.55) {
216    spinAmount = 1;
217    scaleAmount = 1;
218  } else if (phase < 0.85) {
219    const p = (phase - 0.55) / 0.30;
220    spinAmount = 1 - easeOut(p);
221    scaleAmount = 1;
222  } else {
223    spinAmount = 0;
224    scaleAmount = 1;
225  }
226  
227  // Vinyl rotation speed
228  vinylAngle = timestamp * 0.003 * (1 + spinAmount * 5);
229  
230  const maxRadius = 200;
231  const maxRotation = Math.PI * 3;
232  
233  ctx.save();
234  ctx.font = `bold ${fontSize}px Arial Black, Impact, sans-serif`;
235  ctx.textAlign = 'center';
236  ctx.textBaseline = 'middle';
237  
238  // Draw each letter's segments
239  letterPositions.forEach((lp, li) => {
240    const n = 7;
241    const h = fontSize * 1.2;
242    const segH = h / n;
243    
244    for (let si = 0; si < n; si++) {
245      const segCenterY = lp.y - h/2 + si * segH + segH/2;
246      
247      // Calculate spin for this segment
248      // Segments further from center spin more
249      const distFromCenter = Math.sqrt(
250        Math.pow(lp.x - vinylX, 2) + Math.pow(segCenterY - vinylY, 2)
251      );
252      
253      const angle = Math.atan2(segCenterY - vinylY, lp.x - vinylX);
254      
255      // Spin: rotate around vinyl center
256      const spinRot = spinAmount * maxRotation * (0.5 + distFromCenter / 300);
257      const spinRadius = spinAmount * distFromCenter * 0.8;
258      
259      const newAngle = angle + spinRot;
260      const newDist = distFromCenter + spinRadius * spinAmount;
261      
262      const dx = spinAmount > 0 ? 
263        (Math.cos(newAngle) * newDist - (lp.x - vinylX)) : 0;
264      const dy = spinAmount > 0 ? 
265        (Math.sin(newAngle) * newDist - (segCenterY - vinylY)) : 0;
266      
267      ctx.save();
268      ctx.translate(lp.x + dx, segCenterY + dy);
269      ctx.rotate(spinRot * 0.5);
270      
271      // Clip to segment strip
272      ctx.beginPath();
273      ctx.rect(-fontSize, -segH/2, fontSize * 2, segH);
274      ctx.clip();
275      
276      ctx.translate(0, -(segCenterY - lp.y));
277      
278      // Draw letter white
279      ctx.fillStyle = 'white';
280      ctx.fillText(lp.letter, 0, 0);
281      
282      // Draw dark stripes
283      ctx.globalCompositeOperation = 'destination-out';
284      const stripeCount = 7;
285      const sw = segH * 0.35;
286      for (let k = 0; k < stripeCount; k++) {
287        const sy2 = -h/2 + k * (h/stripeCount) + sw * 0.3;
288        ctx.fillStyle = 'rgba(0,0,0,1)';
289        ctx.fillRect(-fontSize, sy2, fontSize * 2, sw);
290      }
291      ctx.globalCompositeOperation = 'source-over';
292      
293      ctx.restore();
294    }
295  });
296  
297  // Draw vinyl record on top
298  const vinylScale = 1 - spinAmount * 0.3 + spinAmount * 0.3;
299  ctx.save();
300  ctx.translate(vinylX, vinylY);
301  ctx.scale(vinylScale, vinylScale);
302  ctx.rotate(vinylAngle);
303  
304  // Outer vinyl
305  ctx.beginPath();
306  ctx.arc(0, 0, vinylR, 0, Math.PI * 2);
307  ctx.fillStyle = 'white';
308  ctx.fill();
309  
310  // Grooves
311  const grooveRatios = [0.88, 0.76, 0.64, 0.52, 0.40];
312  grooveRatios.forEach(f => {
313    ctx.beginPath();
314    ctx.arc(0, 0, vinylR * f, 0, Math.PI * 2);
315    ctx.fillStyle = '#1e2a35';
316    ctx.fill();
317    ctx.beginPath();
318    ctx.arc(0, 0, vinylR * (f - 0.07), 0, Math.PI * 2);
319    ctx.fillStyle = 'white';
320    ctx.fill();
321  });
322  
323  // Label
324  ctx.beginPath();
325  ctx.arc(0, 0, vinylR * 0.30, 0, Math.PI * 2);
326  ctx.fillStyle = '#1e2a35';
327  ctx.fill();
328  
329  // Center hole
330  ctx.beginPath();
331  ctx.arc(0, 0, vinylR * 0.07, 0, Math.PI * 2);
332  ctx.fillStyle = 'white';
333  ctx.fill();
334  
335  ctx.restore();
336  ctx.restore();
337  
338  requestAnimationFrame(draw);
339}
340
341requestAnimationFrame(draw);
342</script>
343</body>
344</html>