← back to Only CSS: 3D Scan 2compare with ground truth →
model output
Claude Sonnet 4.6
Only CSS: 3D Scan 2
A 0.63T 0.33
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>3D Cube Animation</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9 body {
10 background: radial-gradient(ellipse at center, #0d2233 0%, #071520 60%, #050e18 100%);
11 width: 100vw; height: 100vh;
12 display: flex; align-items: center; justify-content: center;
13 overflow: hidden;
14 }
15 canvas { display: block; }
16</style>
17</head>
18<body>
19<canvas id="c"></canvas>
20<script>
21const canvas = document.getElementById('c');
22const ctx = canvas.getContext('2d');
23canvas.width = 1024;
24canvas.height = 576;
25
26const W = canvas.width, H = canvas.height;
27const cx = W * 0.5, cy = H * 0.5;
28
29// Isometric cube parameters
30const cubeSize = 160;
31// Isometric projection vectors
32// Right: (cos30, sin30), Left: (-cos30, sin30), Up: (0, -1)
33const cos30 = Math.cos(Math.PI/6);
34const sin30 = Math.sin(Math.PI/6);
35
36// Cube center offset
37const ox = cx - 20, oy = cy + 10;
38
39// Convert 3D cube coords (x,y,z) in [-1,1] to 2D isometric
40function iso(x, y, z) {
41 // x: right, y: up, z: depth (toward viewer)
42 const px = ox + (x - z) * cos30 * cubeSize;
43 const py = oy + (x + z) * sin30 * cubeSize - y * cubeSize;
44 return [px, py];
45}
46
47// Draw the wireframe cube layers
48function drawCubeWireframe(layers) {
49 ctx.strokeStyle = 'rgba(200,220,255,0.15)';
50 ctx.lineWidth = 0.5;
51
52 for (let i = 0; i < layers; i++) {
53 const t = i / (layers - 1); // 0 to 1
54 const s = 0.3 + t * 0.7; // scale from 0.3 to 1
55
56 // Draw cube edges at scale s
57 const corners = [
58 [-s, -s, -s], [s, -s, -s], [s, s, -s], [-s, s, -s],
59 [-s, -s, s], [s, -s, s], [s, s, s], [-s, s, s]
60 ];
61
62 const edges = [
63 [0,1],[1,2],[2,3],[3,0], // back face
64 [4,5],[5,6],[6,7],[7,4], // front face
65 [0,4],[1,5],[2,6],[3,7] // connecting
66 ];
67
68 edges.forEach(([a,b]) => {
69 const [ax,ay] = iso(corners[a][0], corners[a][1], corners[a][2]);
70 const [bx,by] = iso(corners[b][0], corners[b][1], corners[b][2]);
71 ctx.beginPath();
72 ctx.moveTo(ax, ay);
73 ctx.lineTo(bx, by);
74 ctx.stroke();
75 });
76 }
77}
78
79// Draw cube faces (filled)
80function drawCubeFaces() {
81 // Bottom face
82 ctx.fillStyle = 'rgba(10,40,70,0.4)';
83 ctx.beginPath();
84 const bl = iso(-1,-1,-1), br = iso(1,-1,-1), fr = iso(1,-1,1), fl = iso(-1,-1,1);
85 ctx.moveTo(bl[0],bl[1]); ctx.lineTo(br[0],br[1]); ctx.lineTo(fr[0],fr[1]); ctx.lineTo(fl[0],fl[1]);
86 ctx.closePath(); ctx.fill();
87
88 // Left face
89 ctx.fillStyle = 'rgba(10,50,80,0.3)';
90 ctx.beginPath();
91 const tl = iso(-1,1,-1), tlf = iso(-1,1,1);
92 ctx.moveTo(bl[0],bl[1]); ctx.lineTo(fl[0],fl[1]); ctx.lineTo(tlf[0],tlf[1]); ctx.lineTo(tl[0],tl[1]);
93 ctx.closePath(); ctx.fill();
94
95 // Front face
96 ctx.fillStyle = 'rgba(15,60,100,0.35)';
97 ctx.beginPath();
98 const tr = iso(1,1,1), trf = iso(-1,1,1);
99 ctx.moveTo(fl[0],fl[1]); ctx.lineTo(fr[0],fr[1]); ctx.lineTo(tr[0],tr[1]); ctx.lineTo(tlf[0],tlf[1]);
100 ctx.closePath(); ctx.fill();
101}
102
103// Circle positions in 3x3 grid on front face (z=1)
104// Grid positions: col -1,0,1 and row -1,0,1 mapped to x,y
105const gridPositions = [];
106for (let row = -1; row <= 1; row++) {
107 for (let col = -1; col <= 1; col++) {
108 gridPositions.push({col, row});
109 }
110}
111
112// Colors cycling: green (#3dff8f) and purple (#9b59ff)
113const greenColor = '#3dff8f';
114const purpleColor = '#9b59ff';
115const blueColor = '#4466ff';
116
117// Each circle has its own animation phase
118// The animation shows circles pulsing/growing with color cycling
119// From frames: circles animate between green and purple, some are blue transitioning
120
121function getCircleColor(t, phase) {
122 // t is global time 0-1 cycling
123 const p = (t + phase) % 1;
124 if (p < 0.5) {
125 // green to purple
126 const f = p / 0.5;
127 const r = Math.round(61 + (155-61)*f);
128 const g = Math.round(255 + (89-255)*f);
129 const b = Math.round(143 + (255-143)*f);
130 return `rgb(${r},${g},${b})`;
131 } else {
132 // purple to green
133 const f = (p - 0.5) / 0.5;
134 const r = Math.round(155 + (61-155)*f);
135 const g = Math.round(89 + (255-89)*f);
136 const b = Math.round(255 + (143-255)*f);
137 return `rgb(${r},${g},${b})`;
138 }
139}
140
141// Draw a circle on the isometric front face
142function drawIsoCircle(col, row, radius, color, alpha, numRings) {
143 // Center of circle in 3D
144 const cx3 = col * 0.62;
145 const cy3 = -row * 0.62;
146 const cz3 = 1.0;
147
148 const [pcx, pcy] = iso(cx3, cy3, cz3);
149
150 // In isometric view, circles on the front face appear as ellipses
151 // The front face normal is along (1,1,0) direction in iso
152 // Scale x by cos30, y by sin30 for the face
153 const scaleX = cos30 * cubeSize * 0.62;
154 const scaleY = sin30 * cubeSize * 0.62;
155
156 ctx.save();
157 ctx.globalAlpha = alpha;
158 ctx.strokeStyle = color;
159 ctx.lineWidth = 1.5;
160
161 for (let r = 0; r < numRings; r++) {
162 const rScale = radius * (0.4 + r * 0.2);
163 ctx.beginPath();
164 // Draw ellipse approximating circle on isometric face
165 ctx.ellipse(pcx, pcy, rScale * 0.85, rScale * 0.85, 0, 0, Math.PI * 2);
166 ctx.stroke();
167 }
168 ctx.restore();
169}
170
171// Animation state
172let startTime = null;
173const CYCLE = 4000; // ms per full cycle
174
175function easeInOut(t) {
176 return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
177}
178
179function animate(ts) {
180 if (!startTime) startTime = ts;
181 const elapsed = (ts - startTime) % CYCLE;
182 const t = elapsed / CYCLE; // 0 to 1
183
184 ctx.clearRect(0, 0, W, H);
185
186 // Background
187 const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, W*0.7);
188 grad.addColorStop(0, '#0d2233');
189 grad.addColorStop(0.6, '#071520');
190 grad.addColorStop(1, '#050e18');
191 ctx.fillStyle = grad;
192 ctx.fillRect(0, 0, W, H);
193
194 // Draw cube faces
195 drawCubeFaces();
196
197 // Draw wireframe layers
198 drawCubeWireframe(18);
199
200 // Draw circles on front face (3x3 grid)
201 // Each circle has phase offset
202 const phases = [
203 0.0, 0.15, 0.3,
204 0.45, 0.6, 0.75,
205 0.9, 0.05, 0.2
206 ];
207
208 // Determine which circles are visible based on animation
209 // From frames: circles grow/shrink and color cycles
210 // Some circles disappear (scale to 0) at certain phases
211
212 const positions = [
213 {col:-1, row:1}, {col:0, row:1}, {col:1, row:1},
214 {col:-1, row:0}, {col:0, row:0}, {col:1, row:0},
215 {col:-1, row:-1}, {col:0, row:-1}, {col:1, row:-1}
216 ];
217
218 positions.forEach((pos, i) => {
219 const phase = phases[i];
220 const p = (t + phase) % 1;
221
222 // Size animation: pulse
223 let size = 0.5 + 0.3 * Math.sin(p * Math.PI * 2);
224
225 // Visibility: some circles fade in/out
226 let alpha = 0.8 + 0.2 * Math.sin(p * Math.PI * 2 + 1);
227
228 // Color
229 const color = getCircleColor(t, phase);
230
231 // Number of rings based on size
232 const numRings = Math.floor(3 + size * 4);
233
234 drawIsoCircle(pos.col, pos.row, size * 55, color, alpha, numRings);
235 });
236
237 // Also draw smaller circles on left face
238 const leftPositions = [
239 {x:-1, y:1}, {x:-1, y:-1}
240 ];
241
242 leftPositions.forEach((pos, i) => {
243 const phase = 0.1 + i * 0.3;
244 const p = (t + phase) % 1;
245 const size = 0.3 + 0.2 * Math.sin(p * Math.PI * 2);
246 const color = getCircleColor(t, phase + 0.5);
247
248 // Left face: x=-1, y varies, z varies
249 const [pcx, pcy] = iso(-1, pos.y * 0.5, pos.x * 0.5);
250
251 ctx.save();
252 ctx.globalAlpha = 0.5;
253 ctx.strokeStyle = color;
254 ctx.lineWidth = 1;
255 for (let r = 0; r < 3; r++) {
256 const rr = size * 30 * (0.5 + r * 0.25);
257 ctx.beginPath();
258 ctx.ellipse(pcx, pcy, rr * 0.4, rr * 0.9, -Math.PI/6, 0, Math.PI*2);
259 ctx.stroke();
260 }
261 ctx.restore();
262 });
263
264 requestAnimationFrame(animate);
265}
266
267requestAnimationFrame(animate);
268</script>
269</body>
270</html>