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>Isometric Road Animation</title>
7<style>
8 * { margin: 0; padding: 0; box-sizing: border-box; }
9
10 body {
11 background: #5bc8d8;
12 width: 1024px;
13 height: 768px;
14 overflow: hidden;
15 display: flex;
16 align-items: center;
17 justify-content: center;
18 }
19
20 .scene {
21 position: relative;
22 width: 600px;
23 height: 400px;
24 transform-style: preserve-3d;
25 animation: sceneRotate 6s linear infinite;
26 }
27
28 @keyframes sceneRotate {
29 0% { transform: rotateX(55deg) rotateZ(-30deg) translateY(-30px); }
30 40% { transform: rotateX(55deg) rotateZ(-30deg) translateY(-30px); }
31 50% { transform: rotateX(75deg) rotateZ(-30deg) translateY(20px); }
32 60% { transform: rotateX(55deg) rotateZ(-30deg) translateY(-30px); }
33 100% { transform: rotateX(55deg) rotateZ(-30deg) translateY(-30px); }
34 }
35
36 .road-block {
37 position: absolute;
38 width: 500px;
39 height: 200px;
40 left: 50px;
41 top: 100px;
42 transform-style: preserve-3d;
43 }
44
45 /* Top face */
46 .face-top {
47 position: absolute;
48 width: 500px;
49 height: 200px;
50 background: #888;
51 transform: rotateX(0deg) translateZ(0px);
52 top: 0; left: 0;
53 }
54
55 /* Road markings on top */
56 .dash {
57 position: absolute;
58 width: 40px;
59 height: 10px;
60 background: white;
61 top: 95px;
62 }
63
64 .dash1 { left: 60px; }
65 .dash2 { left: 200px; }
66 .dash3 { left: 340px; }
67
68 /* Front face */
69 .face-front {
70 position: absolute;
71 width: 500px;
72 height: 80px;
73 background: #6e6e6e;
74 transform: rotateX(-90deg) translateZ(-80px);
75 transform-origin: top center;
76 top: 200px; left: 0;
77 }
78
79 /* Front face road marking */
80 .face-front-dash {
81 position: absolute;
82 width: 10px;
83 height: 30px;
84 background: white;
85 left: 60px;
86 top: 25px;
87 }
88
89 /* Right face */
90 .face-right {
91 position: absolute;
92 width: 80px;
93 height: 200px;
94 background: #7a7a7a;
95 transform: rotateY(90deg) translateZ(500px);
96 transform-origin: left center;
97 top: 0; left: 0;
98 }
99
100 /* Truck */
101 .truck-wrapper {
102 position: absolute;
103 top: 0; left: 0;
104 width: 500px;
105 height: 200px;
106 transform-style: preserve-3d;
107 animation: truckMove 6s linear infinite;
108 }
109
110 @keyframes truckMove {
111 0% { transform: translateZ(1px); }
112 100% { transform: translateZ(1px); }
113 }
114
115 .truck {
116 position: absolute;
117 width: 60px;
118 height: 50px;
119 top: 65px;
120 animation: truckDrive 6s linear infinite;
121 }
122
123 @keyframes truckDrive {
124 0% { left: 350px; }
125 100% { left: 50px; }
126 }
127
128 /* Truck SVG drawn with divs */
129 .truck-body {
130 position: absolute;
131 bottom: 8px;
132 left: 0;
133 width: 60px;
134 height: 32px;
135 background: #e84a5f;
136 border-radius: 4px 4px 0 0;
137 }
138
139 .truck-cab {
140 position: absolute;
141 bottom: 8px;
142 left: 0;
143 width: 28px;
144 height: 38px;
145 background: #e84a5f;
146 border-radius: 4px 4px 0 0;
147 }
148
149 .truck-cargo {
150 position: absolute;
151 bottom: 8px;
152 left: 26px;
153 width: 34px;
154 height: 32px;
155 background: #f5f5f5;
156 border-radius: 0 3px 0 0;
157 }
158
159 .truck-window {
160 position: absolute;
161 bottom: 26px;
162 left: 4px;
163 width: 18px;
164 height: 10px;
165 background: #a8d8ea;
166 border-radius: 2px;
167 }
168
169 .truck-wheel-l {
170 position: absolute;
171 bottom: 0;
172 left: 6px;
173 width: 14px;
174 height: 14px;
175 background: #3d3d6b;
176 border-radius: 50%;
177 }
178
179 .truck-wheel-r {
180 position: absolute;
181 bottom: 0;
182 left: 38px;
183 width: 14px;
184 height: 14px;
185 background: #3d3d6b;
186 border-radius: 50%;
187 }
188
189 .truck-wheel-l::after, .truck-wheel-r::after {
190 content: '';
191 position: absolute;
192 top: 3px; left: 3px;
193 width: 8px; height: 8px;
194 background: #666;
195 border-radius: 50%;
196 }
197
198 .truck-grill {
199 position: absolute;
200 bottom: 12px;
201 left: 0;
202 width: 5px;
203 height: 8px;
204 background: #c0392b;
205 border-radius: 1px;
206 }
207
208 .truck-light {
209 position: absolute;
210 bottom: 14px;
211 left: 1px;
212 width: 4px;
213 height: 4px;
214 background: #f39c12;
215 border-radius: 1px;
216 }
217
218</style>
219</head>
220<body>
221
222<div class="scene">
223 <div class="road-block">
224 <!-- Top face with road markings -->
225 <div class="face-top">
226 <div class="dash dash1"></div>
227 <div class="dash dash2"></div>
228 <div class="dash dash3"></div>
229 </div>
230 <!-- Front face -->
231 <div class="face-front">
232 <div class="face-front-dash"></div>
233 </div>
234 <!-- Right face -->
235 <div class="face-right"></div>
236
237 <!-- Truck on top -->
238 <div class="truck-wrapper">
239 <div class="truck">
240 <div class="truck-cab"></div>
241 <div class="truck-cargo"></div>
242 <div class="truck-window"></div>
243 <div class="truck-grill"></div>
244 <div class="truck-light"></div>
245 <div class="truck-wheel-l"></div>
246 <div class="truck-wheel-r"></div>
247 </div>
248 </div>
249 </div>
250</div>
251
252<script>
253 // Use canvas for more accurate isometric rendering
254 const canvas = document.createElement('canvas');
255 canvas.width = 1024;
256 canvas.height = 768;
257 document.body.innerHTML = '';
258 document.body.style.cssText = 'margin:0;padding:0;overflow:hidden;background:#5bc8d8;';
259 document.body.appendChild(canvas);
260 const ctx = canvas.getContext('2d');
261
262 let time = 0;
263 const duration = 6000; // ms for full cycle
264
265 function lerp(a, b, t) { return a + (b - a) * t; }
266
267 function easeInOut(t) {
268 return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
269 }
270
271 // Isometric projection
272 function iso(x, y, z) {
273 // Standard isometric: rotate 45deg around Y, then tilt
274 const ix = (x - z) * Math.cos(Math.PI / 6);
275 const iy = (x + z) * Math.sin(Math.PI / 6) - y;
276 return { x: ix, y: iy };
277 }
278
279 function project(x, y, z, cx, cy, rotY, tiltX) {
280 // rotY: rotation around vertical axis
281 // tiltX: tilt (camera elevation)
282 const cosY = Math.cos(rotY);
283 const sinY = Math.sin(rotY);
284
285 // Rotate around Y axis
286 const rx = x * cosY - z * sinY;
287 const rz = x * sinY + z * cosY;
288
289 // Isometric tilt
290 const cosX = Math.cos(tiltX);
291 const sinX = Math.sin(tiltX);
292
293 const px = rx;
294 const py = y * cosX - rz * sinX;
295 const pz = y * sinX + rz * cosX;
296
297 return { x: cx + px, y: cy + py, z: pz };
298 }
299
300 // Road block dimensions
301 const RW = 280; // road width (along road direction)
302 const RD = 100; // road depth (perpendicular)
303 const RH = 50; // road height
304
305 // The animation: camera orbits around the road block
306 // Frames show: starts with top-right view, rotates to show front/side, then back
307
308 function drawScene(t) {
309 ctx.clearRect(0, 0, 1024, 768);
310
311 // Background
312 ctx.fillStyle = '#5bc8d8';
313 ctx.fillRect(0, 0, 1024, 768);
314
315 const cx = 512, cy = 384;
316
317 // Animation cycle:
318 // 0-0.4: normal isometric view, truck moves
319 // 0.4-0.6: rotate to show side/front
320 // 0.6-1.0: rotate back, truck continues
321
322 let rotY, tiltX, camY;
323
324 // Full rotation cycle
325 const angle = t * Math.PI * 2;
326
327 // Base isometric angles
328 const baseRotY = -Math.PI / 4; // -45 degrees
329 const baseTiltX = Math.PI / 6; // 30 degrees tilt
330
331 // The scene rotates: camera goes around
332 rotY = baseRotY + angle * 0.3;
333 tiltX = baseTiltX;
334
335 // Actually looking at frames more carefully:
336 // The road block itself rotates/tilts showing different faces
337 // It's more like the whole block rotates around its center
338
339 // Let me use a simpler approach: rotate the whole scene
340 const phase = t; // 0 to 1
341
342 // Camera rotation: starts at one angle, goes around
343 const camAngle = phase * Math.PI * 2 * 0.5; // half rotation per cycle
344
345 drawRoadScene(ctx, cx, cy - 30, phase);
346 }
347
348 function drawRoadScene(ctx, cx, cy, phase) {
349 // Road block: 500 wide, 150 deep, 60 tall
350 const W = 250; // half width
351 const D = 75; // half depth
352 const H = 55; // height
353
354 // Camera orbit angle
355 // From frames: starts showing top-right, rotates to show more front
356 // The rotation seems to be around the Z axis (vertical in isometric)
357
358 // Isometric base: rotY around vertical
359 // Phase 0: rotY = -30deg (standard iso)
360 // Phase 0.4-0.6: rotY changes to show front face more
361
362 let rotAngle;
363 if (phase < 0.35) {
364 rotAngle = 0;
365 } else if (phase < 0.65) {
366 const p = (phase - 0.35) / 0.3;
367 rotAngle = easeInOut(p) * Math.PI * 0.5;
368 } else if (phase < 0.85) {
369 const p = (phase - 0.65) / 0.2;
370 rotAngle = (1 - easeInOut(p)) * Math.PI * 0.5;
371 } else {
372 rotAngle = 0;
373 }
374
375 // Truck position along road (0=right end, 1=left end)
376 const truckT = (phase * 1.5) % 1.0;
377
378 // Draw using polygon projection
379 // Road runs along X axis, depth along Z, height along Y (up)
380
381 // Isometric projection function
382 // Standard iso: x right-forward, z left-forward, y up
383 const isoAngle = Math.PI / 6; // 30 degrees
384 const isoTilt = Math.PI / 4; // 45 degrees elevation
385
386 // Base rotation for isometric view
387 const baseRot = -Math.PI / 6 + rotAngle;
388
389 function proj(x, y, z) {
390 // Rotate around Y (vertical) axis
391 const rx = x * Math.cos(baseRot) - z * Math.sin(baseRot);
392 const rz = x * Math.sin(baseRot) + z * Math.cos(baseRot);
393
394 // Project isometrically
395 const screenX = cx + (rx - rz) * Math.cos(Math.PI/6) * 1.8;
396 const screenY = cy + (rx + rz) * Math.sin(Math.PI/6) * 1.8 - y * 1.8;
397
398 return [screenX, screenY];
399 }
400
401 // Road block corners
402 // Bottom face: y=0
403 // Top face: y=H
404 // X: -W to W (road length)
405 // Z: -D to D (road width)
406
407 const corners = {
408 // Bottom
409 b0: proj(-W, 0, -D),
410 b1: proj( W, 0, -D),
411 b2: proj( W, 0, D),
412 b3: proj(-W, 0, D),
413 // Top
414 t0: proj(-W, H, -D),
415 t1: proj( W, H, -D),
416 t2: proj( W, H, D),
417 t3: proj(-W, H, D),
418 };
419
420 function poly(pts, color) {
421 ctx.beginPath();
422 ctx.moveTo(pts[0][0], pts[0][1]);
423 for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i][0], pts[i][1]);
424 ctx.closePath();
425 ctx.fillStyle = color;
426 ctx.fill();
427 }
428
429 // Determine which faces to draw based on rotation
430 // Draw back faces first, then front faces
431
432 // Left face (x = -W): visible when looking from right
433 poly([corners.b0, corners.t0, corners.t3, corners.b3], '#6e6e6e');
434
435 // Back face (z = -D):
436 poly([corners.b0, corners.b1, corners.t1, corners.t0], '#7a7a7a');
437
438 // Top face
439 poly([corners.t0, corners.t1, corners.t2, corners.t3], '#888888');
440
441 // Road markings on top
442 const dashCount = 3;
443 for (let i = 0; i < dashCount; i++) {
444 const xPos = -W + W * 0.3 + i * (W * 1.2 / dashCount);
445 const dw = 30;
446 const dz = 8;
447 const d0 = proj(xPos - dw, H + 1, -dz);
448 const d1 = proj(xPos + dw, H + 1, -dz);
449 const d2 = proj(xPos + dw, H + 1, dz);
450 const d3 = proj(xPos - dw, H + 1, dz);
451 poly([d0, d1, d2, d3], 'white');
452 }
453
454 // Right face (x = W)
455 poly([corners.b1, corners.b2, corners.t2, corners.t1], '#7a7a7a');
456
457 // Front face (z = D)
458 poly([corners.b3, corners.b2, corners.t2, corners.t3], '#6e6e6e');
459
460 // Front face road marking
461 {
462 const xPos = -W + W * 0.3;
463 const dw = 8;
464 const dh = 20;
465 const f0 = proj(xPos - dw, H * 0.3, D + 0.5);
466 const f1 = proj(xPos + dw, H * 0.3, D + 0.5);
467 const f2 = proj(xPos + dw, H * 0.3 + dh, D + 0.5);
468 const f3 = proj(xPos - dw, H * 0.3 + dh, D + 0.5);
469 poly([f0, f1, f2, f3], 'white');
470 }
471
472 // Draw truck
473 drawTruck(ctx, proj, truckT, W, D, H);
474 }
475
476 function drawTruck(ctx, proj, t, W, D, H) {
477 // Truck position along road
478 const tx = W - t * W * 2 - 20;
479 const ty = H;
480 const tz = -D * 0.2; // slightly off center
481
482 const tw = 28; // truck width (along road)
483 const td = 22; // truck depth
484 const th = 28; // truck height
485 const cabW = 14;
486
487 function p(x, y, z) { return proj(tx + x, ty + y, tz + z); }
488
489 function poly(pts, color) {
490 ctx.beginPath();
491 ctx.moveTo(pts[0][0], pts[0][1]);
492 for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i][0], pts[i][1]);
493 ctx.closePath();
494 ctx.fillStyle = color;
495 ctx.fill();
496 }
497
498 // Cargo box (back part)
499 const cargoX = 0;
500 const cargoW = tw - cabW;
501
502 // Cargo - back face
503 poly([p(cargoX, 0, -td/2), p(cargoX, 0, td/2), p(cargoX, th, td/2), p(cargoX, th, -td/2)], '#c0392b');
504 // Cargo - top
505 poly([p(cargoX, th, -td/2), p(cargoX, th, td/2), p(cargoX+cargoW, th, td/2), p(cargoX+cargoW, th, -td/2)], '#e84a5f');
506 // Cargo - right side
507 poly([p(cargoX, 0, td/2), p(cargoX+cargoW, 0, td/2), p(cargoX+cargoW, th, td/2), p(cargoX, th, td/2)], '#c0392b');
508 // Cargo - front face (white)
509 poly([p(cargoX+cargoW, 0, -td/2), p(cargoX+cargoW, 0, td/2), p(cargoX+cargoW, th, td/2), p(cargoX+cargoW, th, -td/2)], '#f0f0f0');
510
511 // Cab
512 const cabX = cargoW;
513 const cabH = th * 0.85;
514
515 // Cab top
516 poly([p(cabX, cabH, -td/2), p(cabX, cabH, td/2), p(cabX+cabW, cabH, td/2), p(cabX+cabW, cabH, -td/2)], '#e84a5f');
517 // Cab front
518 poly([p(cabX+cabW, 0, -td/2), p(cabX+cabW, 0, td/2), p(cabX+cabW, cabH, td/2), p(cabX+cabW, cabH, -td/2)], '#e84a5f');
519 // Cab right side
520 poly([p(cabX, 0, td/2), p(cabX+cabW, 0, td/2), p(cabX+cabW, cabH, td/2), p(cabX, cabH, td/2)], '#c0392b');
521 // Cab left side
522 poly([p(cabX, 0, -td/2), p(cabX+cabW, 0, -td/2), p(cabX+cabW, cabH, -td/2), p(cabX, cabH, -td/2)], '#d44');
523
524 // Window on cab front
525 const ww = cabW * 0.6, wh = cabH * 0.3;
526 const wx = cabX + cabW * 0.2, wy = cabH * 0.45;
527 poly([p(wx+ww, wy, -td/2+1), p(wx+ww, wy, td/2-1), p(wx+ww, wy+wh, td/2-1), p(wx+ww, wy+wh, -td/2+1)], '#a8d8ea');
528
529 // Wheels
530 function wheel(wx, wz) {
531 const wr = 7;
532 const wpts = [];
533 for (let a = 0; a < 8; a++) {
534 const ang = (a / 8) * Math.PI * 2;
535 wpts.push(p(wx, wr * Math.sin(ang) + wr, wz + wr * Math.cos(ang) * 0.3));
536 }
537 poly(wpts, '#3d3d6b');
538 }
539
540 // Draw wheels as ellipses
541 function drawWheel(wx, wz) {
542 const wpts1 = proj(tx + wx, ty + 2, tz + wz);
543 ctx.beginPath();
544 ctx.ellipse(wpts1[0], wpts1[1], 8, 5, -0.3, 0, Math.PI * 2);
545 ctx.fillStyle = '#3d3d6b';
546 ctx.fill();
547 }
548
549 drawWheel(5, -td/2 - 2);
550 drawWheel(5, td/2 + 2);
551 drawWheel(tw - 8, -td/2 - 2);
552 drawWheel(tw - 8, td/2 + 2);
553 }
554
555 function easeInOut(t) {
556 return t < 0.5 ? 2*t*t : -1+(4-2*t)*t;
557 }
558
559 let startTime = null;
560 const cycleDuration = 6000;
561
562 function animate(timestamp) {
563 if (!startTime) startTime = timestamp;
564 const elapsed = (timestamp - startTime) % cycleDuration;
565 const phase = elapsed / cycleDuration;
566
567 drawScene(phase);
568 requestAnimationFrame(animate);
569 }
570
571 function drawScene(phase) {
572 ctx.clearRect(0, 0, 1024, 768);
573 ctx.fillStyle = '#5bc8d8';
574 ctx.fillRect(0, 0, 1024, 768);
575
576 drawRoadScene(ctx, 512, 384, phase);
577 }
578
579 requestAnimationFrame(animate);
580</script>
581</body>
582</html>