← back to Wavy Loading Animationscompare with ground truth →
model output
GPT-5.4
Wavy Loading Animations
A 0.61T 0.21
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>Minimal Dashed Arc Animation</title>
7<style>
8 :root{
9 --bg:#f2f2f2;
10 --dash:#2f2f2f;
11 --dash-fade:#bdbdbd;
12 }
13
14 html,body{
15 margin:0;
16 width:100%;
17 height:100%;
18 overflow:hidden;
19 background:var(--bg);
20 }
21
22 body{
23 display:grid;
24 place-items:center;
25 }
26
27 svg{
28 width:100vw;
29 height:100vh;
30 display:block;
31 background:var(--bg);
32 }
33
34 .trail{
35 fill:none;
36 stroke-linecap:butt;
37 stroke-width:2.2;
38 stroke-dasharray:5 3;
39 vector-effect:non-scaling-stroke;
40 }
41
42 .ghost{
43 stroke:var(--dash-fade);
44 opacity:.9;
45 }
46
47 .main{
48 stroke:var(--dash);
49 opacity:1;
50 }
51</style>
52</head>
53<body>
54<svg viewBox="0 0 1024 768" preserveAspectRatio="none" aria-label="animated dashed line">
55 <defs>
56 <!-- Slightly arched path matching the frames near the top-center -->
57 <path id="arcPath" d="M 484 66 Q 514 48 542 49" pathLength="100"/>
58 </defs>
59
60 <!-- faint previous position -->
61 <use href="#arcPath" class="trail ghost" id="ghostPath"/>
62 <!-- current position -->
63 <use href="#arcPath" class="trail main" id="mainPath"/>
64</svg>
65
66<script>
67 const main = document.getElementById('mainPath');
68 const ghost = document.getElementById('ghostPath');
69
70 // Approximate motion inferred from frames:
71 // starts as a short horizontal dashed segment,
72 // then alternates between a slight upward-arched and downward-sloped pose.
73 const cycle = 1200;
74
75 const poses = [
76 { t: 0.00, x: 0, y: 0, r: 0, len: 60, off: 0 },
77 { t: 0.18, x: 0, y: 0, r: -1, len: 42, off: 8 },
78 { t: 0.36, x: 0, y: 0, r: 11, len: 34, off: 16 },
79 { t: 0.54, x: 0, y: 0, r: -1, len: 42, off: 8 },
80 { t: 0.72, x: 0, y: 0, r: 11, len: 34, off: 16 },
81 { t: 1.00, x: 0, y: 0, r: -1, len: 42, off: 8 }
82 ];
83
84 function lerp(a,b,t){ return a + (b-a)*t; }
85
86 function samplePose(p){
87 let i = 0;
88 while (i < poses.length - 1 && p > poses[i + 1].t) i++;
89 const a = poses[i];
90 const b = poses[Math.min(i + 1, poses.length - 1)];
91 const span = Math.max(0.0001, b.t - a.t);
92 const u = (p - a.t) / span;
93 // smooth easing for soft in-between motion
94 const e = u * u * (3 - 2 * u);
95 return {
96 x: lerp(a.x, b.x, e),
97 y: lerp(a.y, b.y, e),
98 r: lerp(a.r, b.r, e),
99 len: lerp(a.len, b.len, e),
100 off: lerp(a.off, b.off, e)
101 };
102 }
103
104 function apply(el, pose){
105 el.style.transformOrigin = '513px 57px';
106 el.style.transformBox = 'fill-box';
107 el.setAttribute('transform', `translate(${pose.x} ${pose.y}) rotate(${pose.r} 513 57)`);
108 el.style.strokeDashoffset = `${pose.off}px`;
109 // show only a short dashed segment of the path
110 el.style.strokeDasharray = `5 3 ${Math.max(0, pose.len)} 999`;
111 }
112
113 function frame(now){
114 const p = (now % cycle) / cycle;
115 const current = samplePose(p);
116 const previous = samplePose((p - 0.10 + 1) % 1);
117
118 apply(main, current);
119 apply(ghost, previous);
120
121 requestAnimationFrame(frame);
122 }
123
124 requestAnimationFrame(frame);
125</script>
126</body>
127</html>