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

model output

Claude Sonnet 4.6

Only CSS: Cry Baby

A 0.61T 0.21
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>Crying Eyes Animation</title>
7<style>
8  * { margin: 0; padding: 0; box-sizing: border-box; }
9  
10  body {
11    background: #000;
12    width: 100vw;
13    height: 100vh;
14    display: flex;
15    align-items: center;
16    justify-content: center;
17    overflow: hidden;
18  }
19
20  canvas {
21    display: block;
22  }
23</style>
24</head>
25<body>
26<canvas id="c"></canvas>
27<script>
28const canvas = document.getElementById('c');
29const ctx = canvas.getContext('2d');
30canvas.width = 1024;
31canvas.height = 768;
32
33// Animation state
34let t = 0;
35const duration = 6000; // total cycle ms
36let startTime = null;
37
38// Eye positions (center of each eye)
39const leftEye = { x: 380, y: 320 };
40const rightEye = { x: 580, y: 320 };
41
42function easeInOut(t) {
43  return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
44}
45
46function drawEyelid(ctx, cx, cy, openAmount, flip) {
47  // openAmount: 0 = fully closed (just line), 1 = open showing white
48  // flip: -1 for right eye (mirror)
49  ctx.save();
50  ctx.translate(cx, cy);
51  if (flip) ctx.scale(-1, 1);
52  
53  const w = 70; // half-width
54  const h = 18; // max open height
55  
56  // Draw the white part (eye white) - visible when open
57  if (openAmount > 0.05) {
58    ctx.beginPath();
59    ctx.moveTo(-w, 0);
60    // bottom curve (lower lid)
61    ctx.bezierCurveTo(-w*0.5, h*openAmount*0.8, w*0.3, h*openAmount*0.9, w*0.7, h*openAmount*0.3);
62    // top curve (upper lid)  
63    ctx.bezierCurveTo(w*0.3, -h*openAmount*0.1, -w*0.3, -h*openAmount*0.2, -w, 0);
64    ctx.fillStyle = 'white';
65    ctx.fill();
66  }
67  
68  // Upper eyelid curve
69  ctx.beginPath();
70  ctx.moveTo(-w, 0);
71  ctx.bezierCurveTo(-w*0.3, -8 - openAmount*5, w*0.4, -10 - openAmount*5, w*0.7, -2);
72  ctx.lineWidth = 3;
73  ctx.strokeStyle = 'white';
74  ctx.stroke();
75  
76  // Draw lashes on upper lid
77  const lashCount = 7;
78  for (let i = 0; i < lashCount; i++) {
79    const frac = i / (lashCount - 1);
80    // Position along the upper lid curve
81    const lx = -w + frac * (w*0.7 + w);
82    // Approximate y on the curve
83    const t3 = frac;
84    const ly = cubicBezierY(0, -8 - openAmount*5, -10 - openAmount*5, -2, t3) * 1;
85    
86    const angle = -Math.PI/2 - (frac - 0.5) * 0.8;
87    const lashLen = 8 + (1 - Math.abs(frac - 0.5)*2) * 6;
88    
89    ctx.beginPath();
90    ctx.moveTo(lx, ly - 1);
91    ctx.lineTo(lx + Math.cos(angle) * lashLen, ly + Math.sin(angle) * lashLen);
92    ctx.lineWidth = 1.5;
93    ctx.strokeStyle = 'white';
94    ctx.stroke();
95  }
96  
97  ctx.restore();
98}
99
100function cubicBezierY(y0, y1, y2, y3, t) {
101  const mt = 1 - t;
102  return mt*mt*mt*y0 + 3*mt*mt*t*y1 + 3*mt*t*t*y2 + t*t*t*y3;
103}
104
105// Tear drop class
106class Tear {
107  constructor(x, startY, delay, speed) {
108    this.x = x;
109    this.startY = startY;
110    this.y = startY;
111    this.delay = delay;
112    this.speed = speed || 1;
113    this.active = false;
114    this.length = 0;
115    this.maxLength = 60 + Math.random() * 40;
116    this.dropped = false;
117    this.dropY = 0;
118    this.dropSize = 0;
119    this.dropFade = 1;
120    this.phase = 0; // 0=growing, 1=falling, 2=dropped
121    this.fallY = 0;
122    this.fallSpeed = 0;
123  }
124  
125  reset(startY) {
126    this.y = startY || this.startY;
127    this.length = 0;
128    this.dropped = false;
129    this.dropY = 0;
130    this.dropSize = 0;
131    this.dropFade = 1;
132    this.phase = 0;
133    this.fallY = 0;
134    this.fallSpeed = 0;
135    this.active = true;
136  }
137  
138  update(dt) {
139    if (!this.active) return;
140    
141    if (this.phase === 0) {
142      // Growing down from eye
143      this.length += dt * 0.04 * this.speed;
144      if (this.length >= this.maxLength) {
145        this.phase = 1;
146        this.fallY = this.y + this.length;
147        this.fallSpeed = 0.5;
148      }
149    } else if (this.phase === 1) {
150      // Falling drop
151      this.fallSpeed += dt * 0.001;
152      this.fallY += dt * this.fallSpeed;
153      this.length = Math.max(0, this.length - dt * 0.02);
154      if (this.length <= 0) {
155        this.phase = 2;
156        this.dropY = this.fallY;
157        this.dropSize = 5;
158      }
159    } else if (this.phase === 2) {
160      // Fading drop
161      this.fallY += dt * this.fallSpeed;
162      this.fallSpeed += dt * 0.001;
163      this.dropFade -= dt * 0.002;
164      if (this.dropFade <= 0) {
165        this.active = false;
166      }
167    }
168  }
169  
170  draw(ctx) {
171    if (!this.active) return;
172    
173    if (this.phase === 0 || this.phase === 1) {
174      // Draw tear stream
175      if (this.length > 0) {
176        const tearW = 7;
177        ctx.beginPath();
178        ctx.moveTo(this.x - tearW/2, this.y);
179        ctx.lineTo(this.x - tearW/2, this.y + this.length * 0.7);
180        ctx.quadraticCurveTo(this.x - tearW/2, this.y + this.length, this.x, this.y + this.length + tearW/2);
181        ctx.quadraticCurveTo(this.x + tearW/2, this.y + this.length, this.x + tearW/2, this.y + this.length * 0.7);
182        ctx.lineTo(this.x + tearW/2, this.y);
183        ctx.fillStyle = 'white';
184        ctx.fill();
185      }
186      
187      if (this.phase === 1) {
188        // Falling drop
189        const r = 5;
190        ctx.beginPath();
191        ctx.ellipse(this.x, this.fallY, r*0.6, r, 0, 0, Math.PI*2);
192        ctx.fillStyle = 'white';
193        ctx.fill();
194      }
195    } else if (this.phase === 2) {
196      // Fading drop
197      ctx.globalAlpha = Math.max(0, this.dropFade);
198      const r = 4;
199      ctx.beginPath();
200      ctx.ellipse(this.x, this.fallY, r*0.6, r, 0, 0, Math.PI*2);
201      ctx.fillStyle = 'white';
202      ctx.fill();
203      ctx.globalAlpha = 1;
204    }
205  }
206}
207
208// Tears
209const tears = [];
210// Left eye tears
211tears.push(new Tear(leftEye.x - 5, leftEye.y + 10, 0, 1.2));
212tears.push(new Tear(leftEye.x + 8, leftEye.y + 12, 500, 0.9));
213// Right eye tears
214tears.push(new Tear(rightEye.x + 5, rightEye.y + 10, 200, 1.0));
215tears.push(new Tear(rightEye.x - 8, rightEye.y + 12, 700, 1.1));
216
217let lastTime = 0;
218let cycleTime = 0;
219let tearStarted = false;
220
221function animate(timestamp) {
222  if (!startTime) startTime = timestamp;
223  const elapsed = timestamp - startTime;
224  const dt = timestamp - lastTime;
225  lastTime = timestamp;
226  
227  cycleTime = elapsed % 8000;
228  const progress = cycleTime / 8000;
229  
230  ctx.clearRect(0, 0, canvas.width, canvas.height);
231  ctx.fillStyle = '#000';
232  ctx.fillRect(0, 0, canvas.width, canvas.height);
233  
234  // Eye open amount based on animation
235  // 0-0.1: closed (sleeping)
236  // 0.1-0.3: slowly opening a bit (squinting/sad)
237  // 0.3-0.5: tears start
238  // 0.5-0.8: tears flowing
239  // 0.8-1.0: closing back
240  
241  let openAmount = 0;
242  
243  if (progress < 0.05) {
244    openAmount = 0;
245  } else if (progress < 0.2) {
246    openAmount = easeInOut((progress - 0.05) / 0.15) * 0.15;
247  } else if (progress < 0.35) {
248    openAmount = 0.15;
249  } else if (progress < 0.5) {
250    openAmount = 0.15 + easeInOut((progress - 0.35) / 0.15) * 0.1;
251  } else if (progress < 0.75) {
252    openAmount = 0.25;
253  } else if (progress < 0.9) {
254    openAmount = 0.25 - easeInOut((progress - 0.75) / 0.15) * 0.25;
255  } else {
256    openAmount = 0;
257  }
258  
259  // Start tears when eyes start opening
260  if (progress > 0.25 && progress < 0.85) {
261    if (!tearStarted) {
262      tearStarted = true;
263      tears.forEach((tear, i) => {
264        setTimeout(() => tear.reset(), i * 300);
265      });
266    }
267    tears.forEach(tear => tear.update(dt));
268  } else if (progress < 0.1 || progress > 0.9) {
269    tearStarted = false;
270    tears.forEach(tear => { tear.active = false; });
271  }
272  
273  // Draw tears behind eyes
274  tears.forEach(tear => tear.draw(ctx));
275  
276  // Draw eyes
277  drawEyelid(ctx, leftEye.x, leftEye.y, openAmount, false);
278  drawEyelid(ctx, rightEye.x, rightEye.y, openAmount, true);
279  
280  requestAnimationFrame(animate);
281}
282
283requestAnimationFrame(animate);
284</script>
285</body>
286</html>