← back to Only CSS: Responsive City Drone View Blackcompare with ground truth →
model output
Claude Sonnet 4.6
Only CSS: Responsive City Drone View Black
A 0.49T 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>Dark Pillars Animation</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9 body { background: #000; overflow: hidden; width: 100vw; height: 100vh; }
10 canvas { display: block; }
11</style>
12</head>
13<body>
14<canvas id="c"></canvas>
15<script>
16const canvas = document.getElementById('c');
17const ctx = canvas.getContext('2d');
18canvas.width = window.innerWidth;
19canvas.height = window.innerHeight;
20
21const W = canvas.width;
22const H = canvas.height;
23
24// Vanishing point
25const VP = { x: W * 0.47, y: H * 0.52 };
26
27// Pillar definitions: [x3d, z_start, z_end, width3d, colorTop, colorSide]
28// We'll define pillars in 3D space and project them
29// Each pillar: { x, width, zNear, zFar, colorFace, colorSide }
30
31class Pillar {
32 constructor(x, width, zNear, zFar, brightness) {
33 this.x = x; // 3D x position
34 this.width = width; // 3D width
35 this.zNear = zNear;
36 this.zFar = zFar;
37 this.brightness = brightness;
38 this.height3d = 400; // tall pillars
39 }
40}
41
42// Define pillars spread around the scene
43const pillarsData = [
44 // x, w, zNear, zFar, brightness
45 [-600, 80, 200, 2000, 0.18],
46 [-300, 60, 200, 2000, 0.15],
47 [-150, 50, 200, 2000, 0.14],
48 [-60, 45, 200, 2000, 0.13],
49 [0, 50, 200, 2000, 0.16],
50 [60, 45, 200, 2000, 0.13],
51 [150, 50, 200, 2000, 0.14],
52 [300, 60, 200, 2000, 0.15],
53 [500, 70, 200, 2000, 0.17],
54 [700, 80, 200, 2000, 0.19],
55 [-800, 90, 200, 2000, 0.20],
56];
57
58// Camera z position - starts far back, moves forward
59let cameraZ = 0;
60const totalDuration = 4000; // ms
61let startTime = null;
62
63function project(x3d, y3d, z3d) {
64 const fov = 600;
65 const dz = z3d - cameraZ;
66 if (dz <= 0) return null;
67 const scale = fov / dz;
68 const sx = VP.x + x3d * scale;
69 const sy = VP.y + y3d * scale;
70 return { x: sx, y: sy, scale };
71}
72
73function drawPillar(p) {
74 const pillarTop = -p.height3d;
75 const pillarBottom = 200; // ground level in 3D
76
77 // Project the 4 vertical edges of the pillar
78 const zNear = p.zNear;
79 const zFar = p.zFar;
80
81 // Use a single z slice - the pillar extends from zNear to zFar
82 // We'll draw the front face and one side face
83
84 const x = p.x;
85 const hw = p.width / 2;
86
87 // Front face corners (at zNear)
88 const tl = project(x - hw, pillarTop, zNear);
89 const tr = project(x + hw, pillarTop, zNear);
90 const bl = project(x - hw, pillarBottom, zNear);
91 const br = project(x + hw, pillarBottom, zNear);
92
93 // Back face corners (at zFar)
94 const tlb = project(x - hw, pillarTop, zFar);
95 const trb = project(x + hw, pillarTop, zFar);
96 const blb = project(x - hw, pillarBottom, zFar);
97 const brb = project(x + hw, pillarBottom, zFar);
98
99 if (!tl || !tr || !bl || !br || !tlb || !trb || !blb || !brb) return;
100
101 const b = p.brightness;
102 const col = `rgb(${Math.round(b*255)},${Math.round(b*255)},${Math.round(b*255)})`;
103 const colDark = `rgb(${Math.round(b*180)},${Math.round(b*180)},${Math.round(b*180)})`;
104 const colTop = `rgb(${Math.round(b*220)},${Math.round(b*220)},${Math.round(b*220)})`;
105
106 // Draw top face
107 ctx.beginPath();
108 ctx.moveTo(tl.x, tl.y);
109 ctx.lineTo(tr.x, tr.y);
110 ctx.lineTo(trb.x, trb.y);
111 ctx.lineTo(tlb.x, tlb.y);
112 ctx.closePath();
113 ctx.fillStyle = colTop;
114 ctx.fill();
115
116 // Draw front face (left side of pillar)
117 ctx.beginPath();
118 ctx.moveTo(tl.x, tl.y);
119 ctx.lineTo(tr.x, tr.y);
120 ctx.lineTo(br.x, br.y);
121 ctx.lineTo(bl.x, bl.y);
122 ctx.closePath();
123 ctx.fillStyle = col;
124 ctx.fill();
125
126 // Draw right side face
127 ctx.beginPath();
128 ctx.moveTo(tr.x, tr.y);
129 ctx.lineTo(trb.x, trb.y);
130 ctx.lineTo(brb.x, brb.y);
131 ctx.lineTo(br.x, br.y);
132 ctx.closePath();
133 ctx.fillStyle = colDark;
134 ctx.fill();
135}
136
137// More detailed pillar setup matching the video
138const pillars = [
139 // Far left large pillar
140 new Pillar(-900, 120, 300, 3000, 0.16),
141 // Left-center pillars
142 new Pillar(-400, 70, 300, 3000, 0.14),
143 new Pillar(-200, 55, 300, 3000, 0.13),
144 // Center pillars (the main ones visible throughout)
145 new Pillar(-80, 50, 300, 3000, 0.15),
146 new Pillar(-20, 45, 300, 3000, 0.14),
147 new Pillar(40, 45, 300, 3000, 0.14),
148 new Pillar(100, 50, 300, 3000, 0.15),
149 // Right-center pillars
150 new Pillar(250, 60, 300, 3000, 0.14),
151 new Pillar(450, 75, 300, 3000, 0.15),
152 // Far right pillars
153 new Pillar(700, 100, 300, 3000, 0.17),
154 new Pillar(950, 120, 300, 3000, 0.18),
155 // Extra far sides
156 new Pillar(-1200, 150, 300, 3000, 0.19),
157];
158
159function animate(timestamp) {
160 if (!startTime) startTime = timestamp;
161 const elapsed = timestamp - startTime;
162 const t = Math.min(elapsed / totalDuration, 1);
163
164 // Camera moves forward - easeIn
165 // Start at z=0, end at z=1800 (close to pillars)
166 cameraZ = t * t * 1600;
167
168 ctx.fillStyle = '#000';
169 ctx.fillRect(0, 0, W, H);
170
171 // Draw ground plane hint
172 // Sort pillars by distance (far to near)
173 const sorted = [...pillars].sort((a, b) => {
174 const da = (a.zNear + a.zFar) / 2 - cameraZ;
175 const db = (b.zNear + b.zFar) / 2 - cameraZ;
176 return db - da;
177 });
178
179 sorted.forEach(p => drawPillar(p));
180
181 if (t < 1) {
182 requestAnimationFrame(animate);
183 } else {
184 // Restart
185 startTime = null;
186 requestAnimationFrame(animate);
187 }
188}
189
190requestAnimationFrame(animate);
191
192window.addEventListener('resize', () => {
193 canvas.width = window.innerWidth;
194 canvas.height = window.innerHeight;
195});
196</script>
197</body>
198</html>