← back to Only CSS: Responsive City Drone View Blackcompare with ground truth →
model output
GPT-5.4
Only CSS: Responsive City Drone View Black
A 0.50T 0.43
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" />
6<title>Dark Pillar Flythrough</title>
7<style>
8 :root{
9 --bg:#000;
10 --face1:#141414;
11 --face2:#1d1d1d;
12 --face3:#262626;
13 }
14 html,body{
15 margin:0;
16 height:100%;
17 overflow:hidden;
18 background:var(--bg);
19 }
20 canvas{
21 display:block;
22 width:100vw;
23 height:100vh;
24 background:#000;
25 }
26</style>
27</head>
28<body>
29<canvas id="c"></canvas>
30<script>
31(() => {
32 const canvas = document.getElementById('c');
33 const ctx = canvas.getContext('2d');
34
35 let w = 0, h = 0, cx = 0, cy = 0;
36 function resize() {
37 const dpr = Math.min(devicePixelRatio || 1, 2);
38 w = canvas.width = Math.round(innerWidth * dpr);
39 h = canvas.height = Math.round(innerHeight * dpr);
40 canvas.style.width = innerWidth + 'px';
41 canvas.style.height = innerHeight + 'px';
42 ctx.setTransform(1,0,0,1,0,0);
43 ctx.scale(dpr, dpr);
44 w = innerWidth;
45 h = innerHeight;
46 cx = w / 2;
47 cy = h / 2;
48 }
49 addEventListener('resize', resize);
50 resize();
51
52 // Fixed sparse arrangement to match the stills:
53 // tall, dark rectangular prisms around a central void.
54 const pillars = [
55 {x:-8.8, z:18, bw:1.8, bd:1.8, h:18, yaw:-0.55},
56 {x:-5.8, z:10, bw:0.9, bd:0.9, h:14, yaw:-0.25},
57 {x:-1.2, z:8.5, bw:1.2, bd:1.2, h:22, yaw:0.02},
58 {x: 0.2, z:7.2, bw:0.7, bd:0.7, h:24, yaw:0.00},
59 {x: 1.8, z:8.8, bw:0.9, bd:0.9, h:20, yaw:0.03},
60 {x: 4.8, z:9.5, bw:0.9, bd:0.9, h:26, yaw:0.18},
61 {x: 7.2, z:12.5, bw:1.0, bd:1.0, h:13, yaw:0.45},
62 {x: 9.0, z:11.0, bw:1.3, bd:1.3, h:16, yaw:0.42},
63 {x: 6.8, z:6.0, bw:1.2, bd:1.2, h:28, yaw:0.22},
64 {x:-0.2, z:5.8, bw:0.55, bd:0.55, h:18, yaw:0.00},
65 {x: 3.2, z:5.2, bw:0.95, bd:0.95, h:21, yaw:0.08},
66 {x:-10.5,z:5.0, bw:2.2, bd:2.2, h:12, yaw:-0.65},
67 // central foreground pillar visible at start, then drops out of frame
68 {x:-0.6, z:2.2, bw:1.8, bd:1.8, h:8.5, yaw:-0.18}
69 ];
70
71 const cam = {
72 x: 0,
73 y: 7.8,
74 z: -1.2,
75 yaw: 0.0,
76 pitch: -0.92, // looking downward
77 fov: 760
78 };
79
80 const duration = 5200; // smooth single pass similar to frame sequence
81 let start = performance.now();
82
83 function rotY(x, z, a){
84 const c = Math.cos(a), s = Math.sin(a);
85 return [x*c - z*s, x*s + z*c];
86 }
87
88 function project(wx, wy, wz){
89 let x = wx - cam.x;
90 let y = wy - cam.y;
91 let z = wz - cam.z;
92
93 // camera yaw
94 let c = Math.cos(-cam.yaw), s = Math.sin(-cam.yaw);
95 let tx = x*c - z*s;
96 let tz = x*s + z*c;
97 x = tx; z = tz;
98
99 // camera pitch
100 c = Math.cos(-cam.pitch); s = Math.sin(-cam.pitch);
101 let ty = y*c - z*s;
102 tz = y*s + z*c;
103 y = ty; z = tz;
104
105 if (z <= 0.05) return null;
106 const scale = cam.fov / z;
107 return { x: cx + x * scale, y: cy + y * scale, z, scale };
108 }
109
110 function drawFace(points, fill){
111 ctx.beginPath();
112 ctx.moveTo(points[0].x, points[0].y);
113 for(let i=1;i<points.length;i++) ctx.lineTo(points[i].x, points[i].y);
114 ctx.closePath();
115 ctx.fillStyle = fill;
116 ctx.fill();
117 }
118
119 function drawPillar(p){
120 const hw = p.bw/2, hd = p.bd/2;
121 const y0 = 0, y1 = p.h;
122
123 const base = [
124 [-hw, y0, -hd], [ hw, y0, -hd], [ hw, y0, hd], [-hw, y0, hd],
125 [-hw, y1, -hd], [ hw, y1, -hd], [ hw, y1, hd], [-hw, y1, hd]
126 ].map(([x,y,z]) => {
127 const r = rotY(x,z,p.yaw);
128 return { x: r[0] + p.x, y, z: r[1] + p.z };
129 });
130
131 const pts = base.map(v => project(v.x, v.y, v.z));
132 if (pts.some(v => !v)) return;
133
134 const faces = [
135 { idx:[0,1,5,4], fill:getComputedStyle(document.documentElement).getPropertyValue('--face1').trim() }, // back-ish
136 { idx:[1,2,6,5], fill:getComputedStyle(document.documentElement).getPropertyValue('--face2').trim() }, // right
137 { idx:[2,3,7,6], fill:getComputedStyle(document.documentElement).getPropertyValue('--face1').trim() }, // front-ish
138 { idx:[3,0,4,7], fill:getComputedStyle(document.documentElement).getPropertyValue('--face2').trim() }, // left
139 { idx:[4,5,6,7], fill:getComputedStyle(document.documentElement).getPropertyValue('--face3').trim() } // top
140 ];
141
142 // Painter's algorithm by average depth
143 faces.sort((a,b)=>{
144 const za = a.idx.reduce((s,i)=>s+pts[i].z,0)/a.idx.length;
145 const zb = b.idx.reduce((s,i)=>s+pts[i].z,0)/b.idx.length;
146 return zb - za;
147 });
148
149 for(const f of faces){
150 drawFace(f.idx.map(i => pts[i]), f.fill);
151 }
152 }
153
154 function frame(now){
155 const t = Math.min(1, (now - start) / duration);
156
157 // Smooth forward drift with slight rightward arc.
158 const ease = t*t*(3 - 2*t); // smoothstep
159 cam.z = -1.2 + ease * 10.8;
160 cam.x = ease * 1.35;
161 cam.y = 7.8 + ease * 0.35;
162 cam.yaw = ease * 0.16;
163 cam.pitch = -0.92 + ease * 0.06;
164
165 ctx.clearRect(0,0,w,h);
166
167 pillars
168 .slice()
169 .sort((a,b) => {
170 const da = (a.z - cam.z) ** 2 + (a.x - cam.x) ** 2;
171 const db = (b.z - cam.z) ** 2 + (b.x - cam.x) ** 2;
172 return db - da;
173 })
174 .forEach(drawPillar);
175
176 requestAnimationFrame(frame);
177 }
178
179 requestAnimationFrame(frame);
180})();
181</script>
182</body>
183</html>