← back to Motion Table - Orbitcompare with ground truth →
model output
Claude Sonnet 4.6
Motion Table - Orbit
A 0.89T 0.32
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>Target Animation</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9
10 body {
11 background-color: #555252;
12 display: flex;
13 justify-content: center;
14 align-items: center;
15 width: 100vw;
16 height: 100vh;
17 overflow: hidden;
18 }
19
20 .scene {
21 position: relative;
22 width: 200px;
23 height: 200px;
24 }
25
26 canvas {
27 position: absolute;
28 top: 50%;
29 left: 50%;
30 transform: translate(-50%, -50%);
31 }
32</style>
33</head>
34<body>
35<canvas id="c" width="300" height="300"></canvas>
36
37<script>
38const canvas = document.getElementById('c');
39const ctx = canvas.getContext('2d');
40const W = canvas.width;
41const H = canvas.height;
42const cx = W / 2;
43const cy = H / 2;
44
45// Orbit dots - 3 dots orbiting at different speeds/phases
46// From frames: dots orbit around center on a circle of radius ~50px
47// There appear to be 3 dots orbiting, with occasional "ghost" trail effect
48
49const DOT_RADIUS = 6;
50const ORBIT_R1 = 50; // outer orbit
51const ORBIT_R2 = 28; // inner orbit (for second set)
52
53// Crosshair/target rings
54function drawTarget() {
55 ctx.save();
56
57 // Outer circle (large)
58 ctx.beginPath();
59 ctx.arc(cx, cy, 55, 0, Math.PI * 2);
60 ctx.strokeStyle = 'rgba(220, 220, 220, 0.6)';
61 ctx.lineWidth = 1;
62 ctx.stroke();
63
64 // Middle circle
65 ctx.beginPath();
66 ctx.arc(cx, cy, 30, 0, Math.PI * 2);
67 ctx.strokeStyle = 'rgba(220, 220, 220, 0.7)';
68 ctx.lineWidth = 1;
69 ctx.stroke();
70
71 // Inner circle
72 ctx.beginPath();
73 ctx.arc(cx, cy, 15, 0, Math.PI * 2);
74 ctx.strokeStyle = 'rgba(220, 220, 220, 0.8)';
75 ctx.lineWidth = 1;
76 ctx.stroke();
77
78 // Tiny center circle
79 ctx.beginPath();
80 ctx.arc(cx, cy, 4, 0, Math.PI * 2);
81 ctx.strokeStyle = 'rgba(220, 220, 220, 0.9)';
82 ctx.lineWidth = 1;
83 ctx.stroke();
84
85 // Center dot
86 ctx.beginPath();
87 ctx.arc(cx, cy, 2, 0, Math.PI * 2);
88 ctx.fillStyle = 'rgba(220, 220, 220, 0.9)';
89 ctx.fill();
90
91 // Dashed crosshair lines
92 ctx.setLineDash([3, 3]);
93 ctx.strokeStyle = 'rgba(220, 220, 220, 0.6)';
94 ctx.lineWidth = 1;
95
96 // Horizontal line
97 ctx.beginPath();
98 ctx.moveTo(cx - 70, cy);
99 ctx.lineTo(cx + 70, cy);
100 ctx.stroke();
101
102 // Vertical line
103 ctx.beginPath();
104 ctx.moveTo(cx, cy - 70);
105 ctx.lineTo(cx, cy + 70);
106 ctx.stroke();
107
108 ctx.setLineDash([]);
109 ctx.restore();
110}
111
112// Dots configuration
113// 3 dots orbiting, one on outer ring, two on inner-ish
114// From frames analysis: dots rotate clockwise
115// Frame 1: left, right, bottom positions (3 dots + 1 = 4 dots visible)
116// Actually 4 dots: top, bottom, left, right at start, then rotating
117
118// Let's do: 3 dots on outer orbit rotating, 1 dot on inner orbit
119// Frame analysis shows 3-4 red dots orbiting
120
121const dots = [
122 { angle: Math.PI, speed: 0.8, r: 50, color: '#b84040' }, // left
123 { angle: 0, speed: 0.8, r: 50, color: '#b84040' }, // right
124 { angle: Math.PI/2, speed: 0.8, r: 50, color: '#b84040' }, // bottom
125 { angle: -Math.PI/2, speed: 0.8, r: 50, color: '#b84040' }, // top - sometimes hidden
126];
127
128// Trail history for ghost effect
129const trailHistory = dots.map(() => []);
130const MAX_TRAIL = 8;
131
132let lastTime = null;
133let totalTime = 0;
134
135// Ghost circles that appear periodically
136let ghostCircles = [];
137
138function spawnGhosts(dot) {
139 // spawn a fading circle at dot position
140 ghostCircles.push({
141 x: dot.x,
142 y: dot.y,
143 r: 0,
144 maxR: 30,
145 alpha: 0.4,
146 life: 0,
147 maxLife: 0.8
148 });
149}
150
151let ghostTimer = 0;
152
153function animate(timestamp) {
154 if (!lastTime) lastTime = timestamp;
155 const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
156 lastTime = timestamp;
157 totalTime += dt;
158 ghostTimer += dt;
159
160 ctx.clearRect(0, 0, W, H);
161
162 // Update dots
163 dots.forEach((dot, i) => {
164 dot.angle += dot.speed * dt;
165 dot.x = cx + Math.cos(dot.angle) * dot.r;
166 dot.y = cy + Math.sin(dot.angle) * dot.r;
167
168 // Store trail
169 trailHistory[i].push({ x: dot.x, y: dot.y, alpha: 0.3 });
170 if (trailHistory[i].length > MAX_TRAIL) {
171 trailHistory[i].shift();
172 }
173 });
174
175 // Spawn ghost circles occasionally
176 if (ghostTimer > 1.5) {
177 ghostTimer = 0;
178 dots.forEach(dot => {
179 ghostCircles.push({
180 x: dot.x,
181 y: dot.y,
182 r: 5,
183 maxR: 35,
184 alpha: 0.35,
185 life: 0,
186 maxLife: 1.0,
187 color: dot.color
188 });
189 });
190 }
191
192 // Update and draw ghost circles
193 ghostCircles = ghostCircles.filter(g => g.life < g.maxLife);
194 ghostCircles.forEach(g => {
195 g.life += dt;
196 const progress = g.life / g.maxLife;
197 const currentR = g.r + (g.maxR - g.r) * progress;
198 const currentAlpha = g.alpha * (1 - progress);
199
200 ctx.beginPath();
201 ctx.arc(g.x, g.y, currentR, 0, Math.PI * 2);
202 ctx.strokeStyle = `rgba(180, 180, 180, ${currentAlpha})`;
203 ctx.lineWidth = 1;
204 ctx.stroke();
205
206 // Also fill with color
207 ctx.beginPath();
208 ctx.arc(g.x, g.y, currentR * 0.7, 0, Math.PI * 2);
209 ctx.fillStyle = `rgba(80, 120, 140, ${currentAlpha * 0.5})`;
210 ctx.fill();
211 });
212
213 // Draw target
214 drawTarget();
215
216 // Draw dot trails
217 dots.forEach((dot, i) => {
218 trailHistory[i].forEach((pos, j) => {
219 const alpha = (j / trailHistory[i].length) * 0.15;
220 ctx.beginPath();
221 ctx.arc(pos.x, pos.y, DOT_RADIUS * 0.7, 0, Math.PI * 2);
222 ctx.fillStyle = `rgba(184, 64, 64, ${alpha})`;
223 ctx.fill();
224 });
225 });
226
227 // Draw dots
228 // Only show 3 dots (hide the 4th one - top dot fades in/out)
229 dots.forEach((dot, i) => {
230 // 4th dot (top) is sometimes not visible - make it always visible for simplicity
231 // but from frames it looks like 3-4 dots
232 ctx.beginPath();
233 ctx.arc(dot.x, dot.y, DOT_RADIUS, 0, Math.PI * 2);
234 ctx.fillStyle = dot.color;
235 ctx.fill();
236 });
237
238 requestAnimationFrame(animate);
239}
240
241requestAnimationFrame(animate);
242</script>
243</body>
244</html>