← back to Only CSS: 3D Scancompare with ground truth →
model output
Claude Sonnet 4.6
Only CSS: 3D Scan
A 0.76T 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>3D Cube with Spiral Rings</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9 body {
10 background: radial-gradient(ellipse at center, #0a1628 0%, #050d1a 100%);
11 display: flex;
12 align-items: center;
13 justify-content: center;
14 width: 100vw;
15 height: 100vh;
16 overflow: hidden;
17 }
18 canvas { display: block; }
19</style>
20</head>
21<body>
22<canvas id="c"></canvas>
23<script>
24const canvas = document.getElementById('c');
25const ctx = canvas.getContext('2d');
26canvas.width = 1024;
27canvas.height = 768;
28
29const W = canvas.width, H = canvas.height;
30const cx = W * 0.5, cy = H * 0.5;
31
32// Isometric cube parameters
33const cubeSize = 160;
34const layers = 18;
35const layerStep = 7;
36
37// Isometric projection
38function iso(x, y, z) {
39 return {
40 x: cx + (x - z) * Math.cos(Math.PI / 6) * 1.0,
41 y: cy + (x + z) * Math.sin(Math.PI / 6) * 0.85 - y * 0.9
42 };
43}
44
45function drawCube(t) {
46 const s = cubeSize;
47
48 for (let i = layers; i >= 0; i--) {
49 const offset = i * layerStep;
50 const alpha = 0.08 + (i / layers) * 0.12;
51 ctx.strokeStyle = `rgba(180, 210, 240, ${alpha})`;
52 ctx.lineWidth = 0.7;
53
54 // Draw one cube outline layer
55 // Front face (y=0 plane, varying x,z)
56 const corners = [
57 iso(-s + offset, -s + offset*0.3, -s + offset),
58 iso( s - offset, -s + offset*0.3, -s + offset),
59 iso( s - offset, s - offset*0.3, -s + offset),
60 iso(-s + offset, s - offset*0.3, -s + offset),
61 ];
62
63 // Top face
64 const top = [
65 iso(-s + offset, -s + offset*0.3, -s + offset),
66 iso( s - offset, -s + offset*0.3, -s + offset),
67 iso( s - offset, -s + offset*0.3, s - offset),
68 iso(-s + offset, -s + offset*0.3, s - offset),
69 ];
70
71 // Right face
72 const right = [
73 iso( s - offset, -s + offset*0.3, -s + offset),
74 iso( s - offset, -s + offset*0.3, s - offset),
75 iso( s - offset, s - offset*0.3, s - offset),
76 iso( s - offset, s - offset*0.3, -s + offset),
77 ];
78
79 [corners, top, right].forEach(face => {
80 ctx.beginPath();
81 ctx.moveTo(face[0].x, face[0].y);
82 for (let j = 1; j < face.length; j++) ctx.lineTo(face[j].x, face[j].y);
83 ctx.closePath();
84 ctx.stroke();
85 });
86 }
87}
88
89// Draw the back face glow
90function drawBackFaceGlow() {
91 const s = cubeSize - layers * layerStep;
92 const tl = iso(-s, -s*0.3, s);
93 const tr = iso( s, -s*0.3, s);
94 const br = iso( s, s*0.3, s);
95 const bl = iso(-s, s*0.3, s);
96
97 ctx.beginPath();
98 ctx.moveTo(tl.x, tl.y);
99 ctx.lineTo(tr.x, tr.y);
100 ctx.lineTo(br.x, br.y);
101 ctx.lineTo(bl.x, bl.y);
102 ctx.closePath();
103
104 const grad = ctx.createLinearGradient(tl.x, tl.y, br.x, br.y);
105 grad.addColorStop(0, 'rgba(20, 80, 120, 0.3)');
106 grad.addColorStop(1, 'rgba(10, 40, 80, 0.5)');
107 ctx.fillStyle = grad;
108 ctx.fill();
109}
110
111// Spiral rings animation
112function drawRings(t) {
113 const duration = 6; // seconds for full cycle
114 const phase = (t % duration) / duration; // 0 to 1
115
116 // Animation phases:
117 // 0.0 - 0.5: rings are together, spiraling (contracting)
118 // 0.5 - 0.7: rings split into two groups
119 // 0.7 - 1.0: rings come back together
120
121 const numRings = 12;
122 const maxRadius = 110;
123 const minRadius = 15;
124
125 // Center of the face in screen coords
126 const faceCenter = iso(0, 0, cubeSize - layers * layerStep);
127
128 let splitAmount = 0;
129 let ringScale = 1;
130
131 if (phase < 0.5) {
132 // Contracting phase
133 ringScale = 1 - phase * 0.5;
134 splitAmount = 0;
135 } else if (phase < 0.7) {
136 // Split phase
137 const sp = (phase - 0.5) / 0.2;
138 ringScale = 0.75 - sp * 0.2;
139 splitAmount = sp;
140 } else {
141 // Rejoin and expand
142 const rp = (phase - 0.7) / 0.3;
143 ringScale = 0.55 + rp * 0.45;
144 splitAmount = 1 - rp;
145 }
146
147 const rotAngle = t * 0.8;
148
149 // Draw rings
150 for (let i = 0; i < numRings; i++) {
151 const frac = i / (numRings - 1);
152 const baseRadius = minRadius + frac * (maxRadius - minRadius);
153 const r = baseRadius * ringScale;
154
155 // Color: gradient from cyan/teal to purple/blue
156 const colorT = frac + rotAngle * 0.1;
157 const hue1 = 170 + Math.sin(colorT) * 30; // teal
158 const hue2 = 240 + Math.sin(colorT + 1) * 20; // blue/purple
159 const hue = hue1 + (hue2 - hue1) * frac;
160
161 // Determine if split
162 let offsetX = 0;
163 if (splitAmount > 0) {
164 // Two groups: even rings go left, odd go right
165 const dir = (i % 2 === 0) ? -1 : 1;
166 offsetX = dir * splitAmount * 60;
167 }
168
169 // Ellipse to simulate 3D perspective on the face
170 const scaleY = 0.85;
171
172 ctx.save();
173 ctx.translate(faceCenter.x + offsetX, faceCenter.y);
174 ctx.rotate(rotAngle * 0.3 + frac * 0.5);
175
176 // Color based on position
177 let color;
178 if (frac < 0.3) {
179 color = `rgba(50, 220, 180, 0.8)`;
180 } else if (frac < 0.7) {
181 const blend = (frac - 0.3) / 0.4;
182 const r2 = Math.round(50 + blend * 80);
183 const g2 = Math.round(220 - blend * 120);
184 const b2 = Math.round(180 + blend * 75);
185 color = `rgba(${r2}, ${g2}, ${b2}, 0.8)`;
186 } else {
187 color = `rgba(100, 80, 255, 0.9)`;
188 }
189
190 ctx.strokeStyle = color;
191 ctx.lineWidth = 1.5;
192 ctx.beginPath();
193 ctx.ellipse(0, 0, r, r * scaleY, 0, 0, Math.PI * 2);
194 ctx.stroke();
195 ctx.restore();
196 }
197}
198
199let startTime = null;
200
201function animate(ts) {
202 if (!startTime) startTime = ts;
203 const t = (ts - startTime) / 1000;
204
205 ctx.clearRect(0, 0, W, H);
206
207 // Background
208 const bg = ctx.createRadialGradient(cx, cy, 0, cx, cy, W * 0.7);
209 bg.addColorStop(0, '#0d2035');
210 bg.addColorStop(1, '#050d18');
211 ctx.fillStyle = bg;
212 ctx.fillRect(0, 0, W, H);
213
214 drawBackFaceGlow();
215 drawRings(t);
216 drawCube(t);
217
218 requestAnimationFrame(animate);
219}
220
221requestAnimationFrame(animate);
222</script>
223</body>
224</html>