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

model output

GPT-5.4

Only CSS: Joint Animation

A 0.85T 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.0" />
6<title>Rounded Bar Morph</title>
7<style>
8  html, body {
9    margin: 0;
10    width: 100%;
11    height: 100%;
12    background: #fff;
13    overflow: hidden;
14  }
15
16  body {
17    display: grid;
18    place-items: center;
19  }
20
21  .stage {
22    position: relative;
23    width: min(72vw, 520px);
24    aspect-ratio: 1 / 1;
25  }
26
27  svg {
28    width: 100%;
29    height: 100%;
30    overflow: visible;
31  }
32
33  .shape {
34    fill: none;
35    stroke: #3f97c6;
36    stroke-width: 34;
37    stroke-linecap: round;
38    stroke-linejoin: round;
39    vector-effect: non-scaling-stroke;
40    transform-box: fill-box;
41    transform-origin: center;
42    animation: drift 4.8s linear infinite;
43  }
44
45  @keyframes drift {
46    0%   { transform: translate(58px, 8px) rotate(0deg); }
47    14%  { transform: translate(78px, 42px) rotate(-8deg); }
48    28%  { transform: translate(-34px, 18px) rotate(-18deg); }
49    42%  { transform: translate(18px, -78px) rotate(10deg); }
50    57%  { transform: translate(-92px, -34px) rotate(-6deg); }
51    71%  { transform: translate(-8px, 92px) rotate(4deg); }
52    85%  { transform: translate(62px, 54px) rotate(0deg); }
53    100% { transform: translate(58px, 8px) rotate(0deg); }
54  }
55</style>
56</head>
57<body>
58  <div class="stage">
59    <svg viewBox="0 0 400 400" aria-hidden="true">
60      <path id="morphPath" class="shape" d="M110 200 L290 200"></path>
61    </svg>
62  </div>
63
64<script>
65(() => {
66  const path = document.getElementById('morphPath');
67
68  // Key poses approximated from the provided frames.
69  // Single stroked polyline with round caps/join recreates the thick rounded bent bar.
70  const poses = [
71    { t: 0.00, d: "M110 200 L290 200" },                 // horizontal
72    { t: 0.16, d: "M145 150 L190 230 L285 265" },        // bent down-right
73    { t: 0.32, d: "M120 145 L205 225" },                 // short diagonal
74    { t: 0.48, d: "M150 270 L215 155 L305 70" },         // bent up-right
75    { t: 0.64, d: "M95 245 L170 165 L245 245" },         // ^
76    { t: 0.80, d: "M175 150 L195 265 L255 165" },        // v
77    { t: 1.00, d: "M120 235 L245 235 L255 120" }         // ┘
78  ];
79
80  const duration = 4800; // ms
81  const ease = t => 0.5 - 0.5 * Math.cos(Math.PI * t); // smooth in-out between poses
82
83  function lerp(a, b, t) { return a + (b - a) * t; }
84
85  function parsePath(d) {
86    return d.match(/[ML]\s*[-\d.]+\s+[-\d.]+/g).map(seg => {
87      const [cmd, x, y] = seg.match(/[ML]|[-\d.]+/g);
88      return { cmd, x: +x, y: +y };
89    });
90  }
91
92  const parsed = poses.map(p => ({ t: p.t, pts: parsePath(p.d) }));
93
94  function buildPath(pts) {
95    return pts.map((p, i) => `${i ? 'L' : 'M'}${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(' ');
96  }
97
98  function samplePose(progress) {
99    for (let i = 0; i < parsed.length - 1; i++) {
100      const a = parsed[i], b = parsed[i + 1];
101      if (progress >= a.t && progress <= b.t) {
102        const local = (progress - a.t) / (b.t - a.t);
103        const k = ease(local);
104        const count = Math.min(a.pts.length, b.pts.length);
105        const pts = [];
106        for (let j = 0; j < count; j++) {
107          pts.push({
108            x: lerp(a.pts[j].x, b.pts[j].x, k),
109            y: lerp(a.pts[j].y, b.pts[j].y, k)
110          });
111        }
112        return buildPath(pts);
113      }
114    }
115    return poses[0].d;
116  }
117
118  let start;
119  function frame(ts) {
120    if (!start) start = ts;
121    const p = ((ts - start) % duration) / duration;
122    path.setAttribute('d', samplePose(p));
123    requestAnimationFrame(frame);
124  }
125  requestAnimationFrame(frame);
126})();
127</script>
128</body>
129</html>