← back to Spiral Towercompare with ground truth →
model output
Claude Sonnet 4.6
Spiral Tower
A 0.78T 0.27
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>Twisted Box Animation</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9 body {
10 background: #eef0f5;
11 display: flex;
12 justify-content: center;
13 align-items: center;
14 width: 100vw;
15 height: 100vh;
16 overflow: hidden;
17 }
18 canvas {
19 display: block;
20 }
21</style>
22</head>
23<body>
24<canvas id="c"></canvas>
25<script>
26const canvas = document.getElementById('c');
27const ctx = canvas.getContext('2d');
28canvas.width = 1024;
29canvas.height = 768;
30
31// Colors
32const colorTop = '#8aa5a0';
33const colorLeft = '#8faaa5';
34const colorRight = '#6b8a85';
35
36// Isometric projection helpers
37function iso(x, y, z) {
38 // isometric projection
39 const ix = (x - y) * Math.cos(Math.PI / 6);
40 const iy = (x + y) * Math.sin(Math.PI / 6) - z;
41 return { x: ix, y: iy };
42}
43
44// Draw a single horizontal slice (a square at height z, rotated by angle)
45// The square has half-size s, centered at (cx, cy) in 3D, rotated by angle around Z axis
46function getSquarePoints(cx, cy, s, angle) {
47 const corners = [
48 [-s, -s],
49 [ s, -s],
50 [ s, s],
51 [-s, s]
52 ];
53 return corners.map(([x, y]) => {
54 const rx = x * Math.cos(angle) - y * Math.sin(angle) + cx;
55 const ry = x * Math.sin(angle) + y * Math.cos(angle) + cy;
56 return [rx, ry];
57 });
58}
59
60function isoPoint(x, y, z, ox, oy) {
61 const p = iso(x, y, z);
62 return [p.x + ox, p.y + oy];
63}
64
65function drawTwistedBox(twist, ox, oy) {
66 ctx.clearRect(0, 0, canvas.width, canvas.height);
67 ctx.fillStyle = '#eef0f5';
68 ctx.fillRect(0, 0, canvas.width, canvas.height);
69
70 const N = 60; // number of slices
71 const s = 80; // half-size of square
72 const totalHeight = 300; // total height in 3D units
73 const sliceHeight = totalHeight / N;
74
75 // We'll draw slices from bottom to top
76 // For each slice, compute the 4 corners at z and z+dz
77 // Draw the side faces between consecutive slices
78
79 const slices = [];
80 for (let i = 0; i <= N; i++) {
81 const t = i / N;
82 const z = i * sliceHeight;
83 const angle = twist * t; // rotation angle at this level
84 const pts = getSquarePoints(0, 0, s, angle);
85 // Convert to isometric
86 const isoPts = pts.map(([x, y]) => isoPoint(x, y, z, ox, oy));
87 slices.push({ pts, isoPts, z, angle });
88 }
89
90 // Draw side faces from bottom to top
91 // For each slice pair (i, i+1), draw 4 quad faces
92 // We need to determine which faces are visible
93 // In isometric view from front-left, faces on left side (negative x) and front (negative y) are visible
94
95 // Draw back faces first (right side and back), then front faces
96 // Actually let's just draw all quads from bottom to top
97 // The order matters for painter's algorithm
98
99 // For isometric view: we see top, left face (front-left), right face (front-right)
100 // Let's identify faces by their normal direction
101 // Face 0: between corner 0-1 (top edge, y=-s) -> front face (visible)
102 // Face 1: between corner 1-2 (right edge, x=s) -> right face (visible)
103 // Face 2: between corner 2-3 (bottom edge, y=s) -> back face (hidden)
104 // Face 3: between corner 3-0 (left edge, x=-s) -> left face (visible)
105
106 // Draw back faces (face 2) first
107 for (let i = 0; i < N; i++) {
108 const bot = slices[i];
109 const top = slices[i + 1];
110 // Face 2: corners 2 and 3
111 const p = [
112 bot.isoPts[2],
113 bot.isoPts[3],
114 top.isoPts[3],
115 top.isoPts[2]
116 ];
117 ctx.beginPath();
118 ctx.moveTo(p[0][0], p[0][1]);
119 for (let k = 1; k < p.length; k++) ctx.lineTo(p[k][0], p[k][1]);
120 ctx.closePath();
121 ctx.fillStyle = colorRight;
122 ctx.fill();
123 }
124
125 // Draw right faces (face 1)
126 for (let i = 0; i < N; i++) {
127 const bot = slices[i];
128 const top = slices[i + 1];
129 const p = [
130 bot.isoPts[1],
131 bot.isoPts[2],
132 top.isoPts[2],
133 top.isoPts[1]
134 ];
135 ctx.beginPath();
136 ctx.moveTo(p[0][0], p[0][1]);
137 for (let k = 1; k < p.length; k++) ctx.lineTo(p[k][0], p[k][1]);
138 ctx.closePath();
139 ctx.fillStyle = colorRight;
140 ctx.fill();
141 }
142
143 // Draw left faces (face 3)
144 for (let i = 0; i < N; i++) {
145 const bot = slices[i];
146 const top = slices[i + 1];
147 const p = [
148 bot.isoPts[3],
149 bot.isoPts[0],
150 top.isoPts[0],
151 top.isoPts[3]
152 ];
153 ctx.beginPath();
154 ctx.moveTo(p[0][0], p[0][1]);
155 for (let k = 1; k < p.length; k++) ctx.lineTo(p[k][0], p[k][1]);
156 ctx.closePath();
157 ctx.fillStyle = colorLeft;
158 ctx.fill();
159 }
160
161 // Draw front faces (face 0)
162 for (let i = 0; i < N; i++) {
163 const bot = slices[i];
164 const top = slices[i + 1];
165 const p = [
166 bot.isoPts[0],
167 bot.isoPts[1],
168 top.isoPts[1],
169 top.isoPts[0]
170 ];
171 ctx.beginPath();
172 ctx.moveTo(p[0][0], p[0][1]);
173 for (let k = 1; k < p.length; k++) ctx.lineTo(p[k][0], p[k][1]);
174 ctx.closePath();
175 ctx.fillStyle = colorLeft;
176 ctx.fill();
177 }
178
179 // Draw top face
180 const topSlice = slices[N];
181 ctx.beginPath();
182 ctx.moveTo(topSlice.isoPts[0][0], topSlice.isoPts[0][1]);
183 for (let k = 1; k < 4; k++) ctx.lineTo(topSlice.isoPts[k][0], topSlice.isoPts[k][1]);
184 ctx.closePath();
185 ctx.fillStyle = colorTop;
186 ctx.fill();
187}
188
189// Animation
190const duration = 3000; // ms for one full cycle
191let startTime = null;
192
193function easeInOut(t) {
194 return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
195}
196
197function animate(ts) {
198 if (!startTime) startTime = ts;
199 const elapsed = (ts - startTime) % (duration * 2);
200 let t;
201 if (elapsed < duration) {
202 t = easeInOut(elapsed / duration);
203 } else {
204 t = easeInOut(1 - (elapsed - duration) / duration);
205 }
206
207 const maxTwist = Math.PI / 2; // 90 degrees total twist
208 const twist = t * maxTwist;
209
210 // Center the box
211 const ox = canvas.width * 0.55;
212 const oy = canvas.height * 0.72;
213
214 drawTwistedBox(twist, ox, oy);
215 requestAnimationFrame(animate);
216}
217
218requestAnimationFrame(animate);
219</script>
220</body>
221</html>