morikomorizz commited on
Commit
5d2b924
·
verified ·
1 Parent(s): 2135787

Upload result/GLM-5.2-IQ2_M.html

Browse files
Files changed (1) hide show
  1. result/GLM-5.2-IQ2_M.html +826 -0
result/GLM-5.2-IQ2_M.html ADDED
@@ -0,0 +1,826 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Sunset Drive</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
10
+ canvas { display: block; }
11
+ .label {
12
+ position: fixed;
13
+ top: 28px;
14
+ left: 32px;
15
+ color: rgba(255, 200, 160, 0.55);
16
+ font-family: 'Georgia', serif;
17
+ font-size: 13px;
18
+ letter-spacing: 4px;
19
+ text-transform: uppercase;
20
+ pointer-events: none;
21
+ z-index: 10;
22
+ text-shadow: 0 1px 2px rgba(0,0,0,0.5);
23
+ }
24
+ .label::before {
25
+ content: '';
26
+ display: block;
27
+ width: 32px;
28
+ height: 1px;
29
+ background: rgba(255, 200, 160, 0.55);
30
+ margin-bottom: 10px;
31
+ }
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <canvas id="c"></canvas>
36
+ <div class="label">Sunset Drive</div>
37
+ <script>
38
+ // === Canvas Setup ===
39
+ const canvas = document.getElementById('c');
40
+ const ctx = canvas.getContext('2d');
41
+ let W = 0, H = 0, DPR = 1;
42
+ let worldOffset = 0;
43
+ let time = 0;
44
+ let lastTime = 0;
45
+ const SPEED = 220;
46
+ let dustParticles = [];
47
+ let carScale = 1;
48
+
49
+ function resize() {
50
+ DPR = Math.min(window.devicePixelRatio || 1, 2);
51
+ W = window.innerWidth;
52
+ H = window.innerHeight;
53
+ canvas.width = W * DPR;
54
+ canvas.height = H * DPR;
55
+ canvas.style.width = W + 'px';
56
+ canvas.style.height = H + 'px';
57
+ ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
58
+ carScale = Math.max(0.45, Math.min(W / 800, H / 600, 1.3));
59
+ }
60
+ resize();
61
+ window.addEventListener('resize', resize);
62
+
63
+ // === Helpers ===
64
+ function hash(n) {
65
+ const s = Math.sin(n * 12.9898 + 78.233) * 43758.5453;
66
+ return s - Math.floor(s);
67
+ }
68
+
69
+ function layout() {
70
+ return {
71
+ horizonY: H * 0.62,
72
+ roadTop: H * 0.72,
73
+ wheelY: H * 0.82,
74
+ carX: W * 0.42,
75
+ sunX: W * 0.72,
76
+ sunY: H * 0.62 - 25,
77
+ };
78
+ }
79
+
80
+ // === Sky ===
81
+ function drawSky() {
82
+ const L = layout();
83
+ const horizonY = L.horizonY;
84
+
85
+ // Sky gradient (sunset)
86
+ const g = ctx.createLinearGradient(0, 0, 0, horizonY);
87
+ g.addColorStop(0, '#0a0820');
88
+ g.addColorStop(0.15, '#1c1230');
89
+ g.addColorStop(0.4, '#3e1f4a');
90
+ g.addColorStop(0.65, '#7a2845');
91
+ g.addColorStop(0.85, '#c64a36');
92
+ g.addColorStop(1, '#e87838');
93
+ ctx.fillStyle = g;
94
+ ctx.fillRect(0, 0, W, horizonY);
95
+
96
+ // Sun
97
+ const sunX = L.sunX;
98
+ const sunY = L.sunY;
99
+ const sunR = Math.min(W, H) * 0.06;
100
+
101
+ // Sun halo
102
+ const halo = ctx.createRadialGradient(sunX, sunY, 0, sunX, sunY, sunR * 12);
103
+ halo.addColorStop(0, 'rgba(255, 200, 130, 0.4)');
104
+ halo.addColorStop(0.1, 'rgba(255, 170, 110, 0.22)');
105
+ halo.addColorStop(0.3, 'rgba(255, 130, 90, 0.08)');
106
+ halo.addColorStop(1, 'rgba(255, 130, 90, 0)');
107
+ ctx.fillStyle = halo;
108
+ ctx.fillRect(0, 0, W, horizonY + 30);
109
+
110
+ // Sun disc
111
+ const sunGrad = ctx.createRadialGradient(sunX, sunY, 0, sunX, sunY, sunR);
112
+ sunGrad.addColorStop(0, 'rgba(255, 248, 220, 1)');
113
+ sunGrad.addColorStop(0.5, 'rgba(255, 220, 150, 1)');
114
+ sunGrad.addColorStop(0.85, 'rgba(255, 170, 100, 0.9)');
115
+ sunGrad.addColorStop(1, 'rgba(255, 150, 90, 0)');
116
+ ctx.fillStyle = sunGrad;
117
+ ctx.beginPath();
118
+ ctx.arc(sunX, sunY, sunR, 0, Math.PI * 2);
119
+ ctx.fill();
120
+
121
+ // Stars
122
+ for (let i = 0; i < 80; i++) {
123
+ const sx = hash(i * 23.3) * W;
124
+ const sy = hash(i * 7.7) * horizonY * 0.45;
125
+ const sr = hash(i * 11.1) * 1.2 + 0.3;
126
+ const tw = (Math.sin(time * 1.5 + i * 2.1) * 0.5 + 0.5);
127
+ const fade = 1 - sy / (horizonY * 0.45);
128
+ const alpha = tw * 0.6 * fade;
129
+ ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`;
130
+ ctx.beginPath();
131
+ ctx.arc(sx, sy, sr, 0, Math.PI * 2);
132
+ ctx.fill();
133
+ }
134
+
135
+ // Clouds
136
+ drawClouds(0.015, horizonY);
137
+
138
+ // Horizon glow
139
+ const refGrad = ctx.createLinearGradient(0, horizonY - 8, 0, horizonY + 25);
140
+ refGrad.addColorStop(0, 'rgba(255, 180, 100, 0)');
141
+ refGrad.addColorStop(0.4, 'rgba(255, 150, 80, 0.5)');
142
+ refGrad.addColorStop(1, 'rgba(120, 60, 50, 0)');
143
+ ctx.fillStyle = refGrad;
144
+ ctx.fillRect(0, horizonY - 8, W, 35);
145
+ }
146
+
147
+ function drawClouds(speed, horizonY) {
148
+ const interval = 320;
149
+ const offset = worldOffset * speed;
150
+ const firstWorldX = Math.floor(offset / interval) * interval;
151
+ const lastWorldX = Math.ceil((offset + W + interval) / interval) * interval;
152
+
153
+ for (let worldX = firstWorldX; worldX <= lastWorldX; worldX += interval) {
154
+ const screenX = worldX - offset;
155
+ const idx = Math.round(worldX / interval);
156
+
157
+ const cx = screenX + hash(idx * 5.3) * 200 - 100;
158
+ const cy = hash(idx * 7.7) * horizonY * 0.5 + 20;
159
+ const cw = 180 + hash(idx * 11.1) * 220;
160
+ const ch = 18 + hash(idx * 13.3) * 22;
161
+ const opacity = 0.25 + hash(idx * 17.7) * 0.3;
162
+
163
+ drawSoftCloud(cx, cy, cw, ch, opacity);
164
+ }
165
+ }
166
+
167
+ function drawSoftCloud(x, y, w, h, opacity) {
168
+ ctx.fillStyle = `rgba(70, 35, 55, ${opacity})`;
169
+ ctx.beginPath();
170
+ ctx.ellipse(x, y, w * 0.5, h, 0, 0, Math.PI * 2);
171
+ ctx.fill();
172
+ ctx.beginPath();
173
+ ctx.ellipse(x + w * 0.2, y - h * 0.3, w * 0.35, h * 0.8, 0, 0, Math.PI * 2);
174
+ ctx.fill();
175
+ ctx.beginPath();
176
+ ctx.ellipse(x - w * 0.25, y + h * 0.1, w * 0.3, h * 0.7, 0, 0, Math.PI * 2);
177
+ ctx.fill();
178
+ ctx.beginPath();
179
+ ctx.ellipse(x + w * 0.4, y + h * 0.2, w * 0.25, h * 0.5, 0, 0, Math.PI * 2);
180
+ ctx.fill();
181
+ }
182
+
183
+ // === Birds ===
184
+ function drawBirds() {
185
+ const speed = 0.03;
186
+ const offset = worldOffset * speed;
187
+
188
+ for (let i = 0; i < 5; i++) {
189
+ const baseX = i * 380 + hash(i * 13.7) * 200;
190
+ const totalRange = W + 400;
191
+ const screenX = ((baseX - offset) % totalRange + totalRange) % totalRange - 200;
192
+ const birdY = hash(i * 7.3) * H * 0.22 + 40;
193
+ const flap = Math.sin(time * 4 + i * 1.3) * 4;
194
+ const size = 4 + hash(i * 31.7) * 3;
195
+
196
+ if (screenX > -30 && screenX < W + 30) {
197
+ ctx.strokeStyle = 'rgba(30, 20, 35, 0.7)';
198
+ ctx.lineWidth = 1.5;
199
+ ctx.beginPath();
200
+ ctx.moveTo(screenX - size, birdY + flap * 0.5);
201
+ ctx.lineTo(screenX, birdY - flap * 0.5);
202
+ ctx.lineTo(screenX + size, birdY + flap * 0.5);
203
+ ctx.stroke();
204
+ }
205
+ }
206
+ }
207
+
208
+ // === Mountains ===
209
+ function drawMountainLayer(speed, color, baseY, params) {
210
+ const offset = worldOffset * speed;
211
+ ctx.fillStyle = color;
212
+ ctx.beginPath();
213
+ ctx.moveTo(-10, H + 50);
214
+ for (let x = -10; x <= W + 10; x += 4) {
215
+ const wx = x + offset;
216
+ let h = 0;
217
+ for (const p of params) {
218
+ h += Math.sin(wx * p.freq + p.phase) * p.amp;
219
+ }
220
+ const height = Math.max(15, h + 80);
221
+ ctx.lineTo(x, baseY - height);
222
+ }
223
+ ctx.lineTo(W + 10, H + 50);
224
+ ctx.closePath();
225
+ ctx.fill();
226
+ }
227
+
228
+ function drawMountains() {
229
+ const L = layout();
230
+ const horizonY = L.horizonY;
231
+
232
+ drawMountainLayer(0.04, 'rgba(60, 30, 60, 0.55)', horizonY + 5, [
233
+ { freq: 0.003, amp: 35, phase: 0 },
234
+ { freq: 0.007, amp: 22, phase: 1.5 },
235
+ { freq: 0.013, amp: 10, phase: 2.8 },
236
+ ]);
237
+
238
+ drawMountainLayer(0.08, 'rgba(45, 22, 48, 0.75)', horizonY + 12, [
239
+ { freq: 0.004, amp: 45, phase: 0.7 },
240
+ { freq: 0.009, amp: 22, phase: 2.1 },
241
+ { freq: 0.018, amp: 10, phase: 4.2 },
242
+ ]);
243
+
244
+ drawMountainLayer(0.15, 'rgba(50, 25, 35, 0.85)', horizonY + 22, [
245
+ { freq: 0.006, amp: 30, phase: 1.2 },
246
+ { freq: 0.014, amp: 15, phase: 3.5 },
247
+ { freq: 0.025, amp: 7, phase: 5.7 },
248
+ ]);
249
+ }
250
+
251
+ // === Far treeline ===
252
+ function drawFarTreeline() {
253
+ const L = layout();
254
+ const baseY = L.horizonY + 22;
255
+ const offset = worldOffset * 0.22;
256
+
257
+ ctx.fillStyle = 'rgba(15, 10, 20, 0.85)';
258
+ ctx.beginPath();
259
+ ctx.moveTo(-10, H + 50);
260
+ ctx.lineTo(-10, baseY);
261
+ for (let x = -10; x <= W + 10; x += 3) {
262
+ const wx = x + offset;
263
+ const t1 = Math.sin(wx * 0.05) * 4;
264
+ const t2 = Math.sin(wx * 0.13) * 3;
265
+ const t3 = Math.sin(wx * 0.27) * 2;
266
+ const t4 = Math.max(0, Math.sin(wx * 0.02) - 0.3) * 15;
267
+ const h = 6 + Math.abs(t1 + t2 + t3) + t4;
268
+ ctx.lineTo(x, baseY - h);
269
+ }
270
+ ctx.lineTo(W + 10, H + 50);
271
+ ctx.closePath();
272
+ ctx.fill();
273
+ }
274
+
275
+ // === Mid trees ===
276
+ function drawMidTrees() {
277
+ const L = layout();
278
+ const baseY = L.horizonY + 35;
279
+ const interval = 200;
280
+ const speed = 0.42;
281
+ const offset = worldOffset * speed;
282
+
283
+ const firstWorldX = Math.floor(offset / interval) * interval;
284
+ const lastWorldX = Math.ceil((offset + W + interval) / interval) * interval;
285
+
286
+ for (let worldX = firstWorldX; worldX <= lastWorldX; worldX += interval) {
287
+ const screenX = worldX - offset;
288
+ const idx = Math.round(worldX / interval);
289
+
290
+ if (hash(idx * 3.7) < 0.25) continue;
291
+
292
+ const treeType = hash(idx * 11.7);
293
+ const treeX = screenX + hash(idx * 5.3) * 100 - 50;
294
+ const treeHeight = 35 + hash(idx * 7.7) * 30;
295
+ const treeY = baseY + hash(idx * 13.3) * 8;
296
+
297
+ if (treeType < 0.5) {
298
+ drawPineTree(treeX, treeY, treeHeight);
299
+ } else {
300
+ drawRoundTree(treeX, treeY, treeHeight);
301
+ }
302
+ }
303
+ }
304
+
305
+ function drawPineTree(x, y, h) {
306
+ ctx.fillStyle = '#1a0e15';
307
+ ctx.fillRect(x - 2, y - h * 0.25, 4, h * 0.25);
308
+
309
+ ctx.fillStyle = '#1a2018';
310
+ const layers = 4;
311
+ for (let i = 0; i < layers; i++) {
312
+ const ly = y - h * 0.25 - i * h * 0.2;
313
+ const lw = (layers - i) * 7 + 4;
314
+ ctx.beginPath();
315
+ ctx.moveTo(x - lw, ly);
316
+ ctx.lineTo(x + lw, ly);
317
+ ctx.lineTo(x, ly - h * 0.25);
318
+ ctx.closePath();
319
+ ctx.fill();
320
+ }
321
+ }
322
+
323
+ function drawRoundTree(x, y, h) {
324
+ ctx.fillStyle = '#1a0e15';
325
+ ctx.fillRect(x - 2, y - h * 0.4, 4, h * 0.4);
326
+
327
+ ctx.fillStyle = '#1a2018';
328
+ ctx.beginPath();
329
+ ctx.arc(x, y - h * 0.5, h * 0.32, 0, Math.PI * 2);
330
+ ctx.fill();
331
+ ctx.beginPath();
332
+ ctx.arc(x - h * 0.18, y - h * 0.4, h * 0.22, 0, Math.PI * 2);
333
+ ctx.fill();
334
+ ctx.beginPath();
335
+ ctx.arc(x + h * 0.18, y - h * 0.4, h * 0.22, 0, Math.PI * 2);
336
+ ctx.fill();
337
+ ctx.beginPath();
338
+ ctx.arc(x, y - h * 0.7, h * 0.22, 0, Math.PI * 2);
339
+ ctx.fill();
340
+ }
341
+
342
+ // === Poles ===
343
+ function drawPoles() {
344
+ const L = layout();
345
+ const baseY = L.horizonY + 32;
346
+ const interval = 280;
347
+ const speed = 0.65;
348
+ const offset = worldOffset * speed;
349
+
350
+ const firstIdx = Math.floor(offset / interval);
351
+ const lastIdx = Math.ceil((offset + W) / interval) + 1;
352
+
353
+ // Wires
354
+ ctx.strokeStyle = 'rgba(15, 8, 12, 0.7)';
355
+ ctx.lineWidth = 1;
356
+
357
+ for (let i = firstIdx; i <= lastIdx; i++) {
358
+ const x1 = i * interval - offset + hash(i * 5.3) * 50 - 25;
359
+ const x2 = (i + 1) * interval - offset + hash((i + 1) * 5.3) * 50 - 25;
360
+ const h1 = 90 + hash(i * 7.7) * 20;
361
+ const h2 = 90 + hash((i + 1) * 7.7) * 20;
362
+
363
+ if (x2 < -50 || x1 > W + 50) continue;
364
+
365
+ for (let w = 0; w < 3; w++) {
366
+ const y1 = baseY - h1 + 10 + w * 12;
367
+ const y2 = baseY - h2 + 10 + w * 12;
368
+ const sag = 6 + w * 2;
369
+
370
+ ctx.beginPath();
371
+ ctx.moveTo(x1, y1);
372
+ ctx.quadraticCurveTo((x1 + x2) / 2, (y1 + y2) / 2 + sag, x2, y2);
373
+ ctx.stroke();
374
+ }
375
+ }
376
+
377
+ // Poles
378
+ for (let i = firstIdx; i <= lastIdx; i++) {
379
+ const screenX = i * interval - offset;
380
+ const x = screenX + hash(i * 5.3) * 50 - 25;
381
+ const h = 90 + hash(i * 7.7) * 20;
382
+
383
+ if (x < -30 || x > W + 30) continue;
384
+
385
+ drawPole(x, baseY, h);
386
+ }
387
+ }
388
+
389
+ function drawPole(x, baseY, h) {
390
+ ctx.fillStyle = '#1f1015';
391
+ ctx.fillRect(x - 2, baseY - h, 4, h);
392
+
393
+ ctx.fillRect(x - 14, baseY - h + 8, 28, 3);
394
+ ctx.fillRect(x - 10, baseY - h + 18, 20, 3);
395
+
396
+ ctx.fillStyle = '#3a2a28';
397
+ ctx.beginPath();
398
+ ctx.arc(x - 12, baseY - h + 8, 2.5, 0, Math.PI * 2);
399
+ ctx.fill();
400
+ ctx.beginPath();
401
+ ctx.arc(x + 12, baseY - h + 8, 2.5, 0, Math.PI * 2);
402
+ ctx.fill();
403
+ ctx.beginPath();
404
+ ctx.arc(x - 8, baseY - h + 18, 2.5, 0, Math.PI * 2);
405
+ ctx.fill();
406
+ ctx.beginPath();
407
+ ctx.arc(x + 8, baseY - h + 18, 2.5, 0, Math.PI * 2);
408
+ ctx.fill();
409
+ }
410
+
411
+ // === Road ===
412
+ function drawRoad() {
413
+ const L = layout();
414
+ const roadTop = L.roadTop;
415
+ const wheelY = L.wheelY;
416
+ const sunX = L.sunX;
417
+
418
+ // Road surface
419
+ const roadGrad = ctx.createLinearGradient(0, roadTop, 0, H);
420
+ roadGrad.addColorStop(0, '#1a1520');
421
+ roadGrad.addColorStop(0.3, '#252028');
422
+ roadGrad.addColorStop(1, '#0e0a14');
423
+ ctx.fillStyle = roadGrad;
424
+ ctx.fillRect(0, roadTop, W, H - roadTop);
425
+
426
+ // Warm sky light on road
427
+ const skyLight = ctx.createLinearGradient(0, roadTop, 0, roadTop + 30);
428
+ skyLight.addColorStop(0, 'rgba(180, 100, 80, 0.35)');
429
+ skyLight.addColorStop(1, 'rgba(120, 60, 50, 0)');
430
+ ctx.fillStyle = skyLight;
431
+ ctx.fillRect(0, roadTop, W, 30);
432
+
433
+ // Sun reflection on road
434
+ const sunRefGrad = ctx.createRadialGradient(sunX, wheelY, 0, sunX, wheelY, 180);
435
+ sunRefGrad.addColorStop(0, 'rgba(255, 180, 100, 0.22)');
436
+ sunRefGrad.addColorStop(0.5, 'rgba(255, 150, 80, 0.08)');
437
+ sunRefGrad.addColorStop(1, 'rgba(255, 150, 80, 0)');
438
+ ctx.fillStyle = sunRefGrad;
439
+ ctx.fillRect(sunX - 180, roadTop, 360, H - roadTop);
440
+
441
+ // Dashed center line
442
+ const dashLen = 50;
443
+ const dashGap = 50;
444
+ const dashInterval = dashLen + dashGap;
445
+ const dashOffset = worldOffset % dashInterval;
446
+
447
+ ctx.fillStyle = 'rgba(220, 170, 90, 0.4)';
448
+ for (let x = -dashOffset; x < W; x += dashInterval) {
449
+ ctx.fillRect(x, wheelY + 8, dashLen, 3);
450
+ }
451
+
452
+ // Road texture (small dots)
453
+ const texInterval = 40;
454
+ const texOffset = worldOffset % texInterval;
455
+ ctx.fillStyle = 'rgba(60, 50, 60, 0.4)';
456
+ for (let x = -texOffset - texInterval; x < W + texInterval; x += texInterval) {
457
+ const idx = Math.floor((x + worldOffset) / texInterval);
458
+ const px = x + hash(idx * 31.7) * 30 - 15;
459
+ const py = roadTop + 35 + hash(idx * 17.3) * (H - roadTop - 50);
460
+ ctx.fillRect(px, py, 1, 1);
461
+ }
462
+
463
+ // Road edge line (warm)
464
+ ctx.fillStyle = 'rgba(180, 110, 70, 0.4)';
465
+ ctx.fillRect(0, roadTop - 1, W, 2);
466
+ }
467
+
468
+ // === Car ===
469
+ function drawCar() {
470
+ const L = layout();
471
+ const carX = L.carX;
472
+ const groundY = L.wheelY;
473
+
474
+ // Body bob (suspension)
475
+ const bobY = Math.sin(time * 7) * 1.2 + Math.sin(time * 11) * 0.4;
476
+ const tiltAngle = Math.sin(time * 3) * 0.006;
477
+
478
+ ctx.save();
479
+ ctx.translate(carX, groundY + bobY);
480
+ ctx.scale(carScale, carScale);
481
+ ctx.rotate(tiltAngle);
482
+
483
+ const wheelR = 28;
484
+ const wheel1X = -85;
485
+ const wheel2X = 85;
486
+
487
+ // Shadow under car
488
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
489
+ ctx.beginPath();
490
+ ctx.ellipse(0, 2, 145, 7, 0, 0, Math.PI * 2);
491
+ ctx.fill();
492
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
493
+ ctx.beginPath();
494
+ ctx.ellipse(0, 5, 165, 12, 0, 0, Math.PI * 2);
495
+ ctx.fill();
496
+
497
+ // === Car body ===
498
+ ctx.fillStyle = '#1e1428';
499
+ ctx.beginPath();
500
+ ctx.moveTo(115, -10);
501
+ ctx.quadraticCurveTo(125, -8, 128, -25);
502
+ ctx.quadraticCurveTo(128, -40, 115, -48);
503
+ ctx.lineTo(50, -55);
504
+ ctx.quadraticCurveTo(30, -62, 18, -88);
505
+ ctx.lineTo(-25, -92);
506
+ ctx.quadraticCurveTo(-40, -88, -52, -60);
507
+ ctx.lineTo(-100, -55);
508
+ ctx.quadraticCurveTo(-118, -50, -120, -28);
509
+ ctx.quadraticCurveTo(-125, -10, -115, -10);
510
+ ctx.closePath();
511
+ ctx.fill();
512
+
513
+ // === Warm rim light along the top ===
514
+ ctx.fillStyle = 'rgba(220, 130, 80, 0.4)';
515
+ ctx.beginPath();
516
+ ctx.moveTo(115, -45);
517
+ ctx.lineTo(50, -52);
518
+ ctx.quadraticCurveTo(30, -59, 18, -85);
519
+ ctx.lineTo(-25, -89);
520
+ ctx.quadraticCurveTo(-40, -85, -52, -57);
521
+ ctx.lineTo(-100, -52);
522
+ ctx.lineTo(-100, -49);
523
+ ctx.lineTo(-52, -54);
524
+ ctx.quadraticCurveTo(-40, -82, -25, -86);
525
+ ctx.lineTo(18, -82);
526
+ ctx.quadraticCurveTo(30, -56, 50, -49);
527
+ ctx.lineTo(115, -42);
528
+ ctx.closePath();
529
+ ctx.fill();
530
+
531
+ // === Side body line ===
532
+ ctx.strokeStyle = 'rgba(180, 100, 60, 0.3)';
533
+ ctx.lineWidth = 1.5;
534
+ ctx.beginPath();
535
+ ctx.moveTo(118, -22);
536
+ ctx.lineTo(-118, -22);
537
+ ctx.stroke();
538
+
539
+ // Lower body shadow
540
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
541
+ ctx.beginPath();
542
+ ctx.moveTo(115, -10);
543
+ ctx.lineTo(-115, -10);
544
+ ctx.lineTo(-115, -16);
545
+ ctx.lineTo(115, -16);
546
+ ctx.closePath();
547
+ ctx.fill();
548
+
549
+ // === Windows ===
550
+ ctx.fillStyle = '#0a0815';
551
+ ctx.beginPath();
552
+ ctx.moveTo(48, -55);
553
+ ctx.quadraticCurveTo(30, -62, 20, -85);
554
+ ctx.lineTo(-22, -88);
555
+ ctx.lineTo(-22, -58);
556
+ ctx.closePath();
557
+ ctx.fill();
558
+
559
+ // Window glass
560
+ const winGrad = ctx.createLinearGradient(0, -85, 0, -55);
561
+ winGrad.addColorStop(0, 'rgba(180, 110, 90, 0.75)');
562
+ winGrad.addColorStop(0.5, 'rgba(120, 70, 80, 0.65)');
563
+ winGrad.addColorStop(1, 'rgba(80, 50, 70, 0.7)');
564
+ ctx.fillStyle = winGrad;
565
+ ctx.beginPath();
566
+ ctx.moveTo(46, -56);
567
+ ctx.quadraticCurveTo(30, -62, 22, -83);
568
+ ctx.lineTo(-20, -86);
569
+ ctx.lineTo(-20, -58);
570
+ ctx.closePath();
571
+ ctx.fill();
572
+
573
+ // Window divider (B-pillar)
574
+ ctx.fillStyle = '#1a1020';
575
+ ctx.fillRect(-2, -88, 4, 30);
576
+
577
+ // Door line
578
+ ctx.strokeStyle = 'rgba(20, 10, 20, 0.5)';
579
+ ctx.lineWidth = 1;
580
+ ctx.beginPath();
581
+ ctx.moveTo(0, -58);
582
+ ctx.lineTo(0, -10);
583
+ ctx.stroke();
584
+
585
+ // === Wheel wells ===
586
+ ctx.fillStyle = '#080510';
587
+ ctx.beginPath();
588
+ ctx.arc(wheel1X, -wheelR, wheelR + 5, Math.PI, 2 * Math.PI, false);
589
+ ctx.closePath();
590
+ ctx.fill();
591
+ ctx.beginPath();
592
+ ctx.arc(wheel2X, -wheelR, wheelR + 5, Math.PI, 2 * Math.PI, false);
593
+ ctx.closePath();
594
+ ctx.fill();
595
+
596
+ // === Wheels ===
597
+ const wheelAngle = time * 9;
598
+ drawWheel(wheel1X, -wheelR, wheelR, wheelAngle);
599
+ drawWheel(wheel2X, -wheelR, wheelR, wheelAngle);
600
+
601
+ // === Headlight ===
602
+ const hlX = 122;
603
+ const hlY = -32;
604
+ const hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, 40);
605
+ hlGrad.addColorStop(0, 'rgba(255, 230, 150, 0.7)');
606
+ hlGrad.addColorStop(0.2, 'rgba(255, 200, 100, 0.3)');
607
+ hlGrad.addColorStop(1, 'rgba(255, 180, 80, 0)');
608
+ ctx.fillStyle = hlGrad;
609
+ ctx.beginPath();
610
+ ctx.arc(hlX, hlY, 40, 0, Math.PI * 2);
611
+ ctx.fill();
612
+
613
+ ctx.fillStyle = 'rgba(255, 240, 180, 0.95)';
614
+ ctx.beginPath();
615
+ ctx.ellipse(hlX, hlY, 5, 4, 0, 0, Math.PI * 2);
616
+ ctx.fill();
617
+
618
+ // === Taillight ===
619
+ const tlX = -120;
620
+ const tlY = -28;
621
+ const tlGrad = ctx.createRadialGradient(tlX, tlY, 0, tlX, tlY, 22);
622
+ tlGrad.addColorStop(0, 'rgba(220, 60, 60, 0.7)');
623
+ tlGrad.addColorStop(0.3, 'rgba(180, 40, 40, 0.3)');
624
+ tlGrad.addColorStop(1, 'rgba(180, 40, 40, 0)');
625
+ ctx.fillStyle = tlGrad;
626
+ ctx.beginPath();
627
+ ctx.arc(tlX, tlY, 22, 0, Math.PI * 2);
628
+ ctx.fill();
629
+
630
+ ctx.fillStyle = 'rgba(220, 80, 80, 0.95)';
631
+ ctx.fillRect(-126, -32, 6, 8);
632
+
633
+ // === Light beam from headlight ===
634
+ ctx.save();
635
+ ctx.globalCompositeOperation = 'lighter';
636
+ const beamGrad = ctx.createLinearGradient(120, -10, 280, 5);
637
+ beamGrad.addColorStop(0, 'rgba(255, 220, 150, 0.18)');
638
+ beamGrad.addColorStop(1, 'rgba(255, 220, 150, 0)');
639
+ ctx.fillStyle = beamGrad;
640
+ ctx.beginPath();
641
+ ctx.moveTo(118, -28);
642
+ ctx.lineTo(280, -15);
643
+ ctx.lineTo(280, 8);
644
+ ctx.lineTo(118, 2);
645
+ ctx.closePath();
646
+ ctx.fill();
647
+ ctx.restore();
648
+
649
+ ctx.restore();
650
+ }
651
+
652
+ function drawWheel(wx, wy, r, angle) {
653
+ ctx.save();
654
+ ctx.translate(wx, wy);
655
+
656
+ // Outer tire
657
+ ctx.fillStyle = '#080810';
658
+ ctx.beginPath();
659
+ ctx.arc(0, 0, r, 0, Math.PI * 2);
660
+ ctx.fill();
661
+
662
+ // Tire outer ring
663
+ ctx.strokeStyle = '#1a1a1f';
664
+ ctx.lineWidth = 2;
665
+ ctx.beginPath();
666
+ ctx.arc(0, 0, r, 0, Math.PI * 2);
667
+ ctx.stroke();
668
+
669
+ // Rim base
670
+ ctx.fillStyle = '#2a2a35';
671
+ ctx.beginPath();
672
+ ctx.arc(0, 0, r * 0.65, 0, Math.PI * 2);
673
+ ctx.fill();
674
+
675
+ // Rim outer ring
676
+ ctx.strokeStyle = '#4a4a55';
677
+ ctx.lineWidth = 1.5;
678
+ ctx.beginPath();
679
+ ctx.arc(0, 0, r * 0.65, 0, Math.PI * 2);
680
+ ctx.stroke();
681
+
682
+ // Motion blur ring (subtle haze inside rim)
683
+ ctx.fillStyle = 'rgba(140, 140, 155, 0.12)';
684
+ ctx.beginPath();
685
+ ctx.arc(0, 0, r * 0.6, 0, Math.PI * 2);
686
+ ctx.fill();
687
+
688
+ // Rotating spokes
689
+ ctx.save();
690
+ ctx.rotate(angle);
691
+ ctx.strokeStyle = 'rgba(180, 180, 195, 0.55)';
692
+ ctx.lineWidth = 2.5;
693
+ for (let i = 0; i < 5; i++) {
694
+ const a = (i / 5) * Math.PI * 2;
695
+ ctx.beginPath();
696
+ ctx.moveTo(0, 0);
697
+ ctx.lineTo(Math.cos(a) * r * 0.55, Math.sin(a) * r * 0.55);
698
+ ctx.stroke();
699
+ }
700
+
701
+ // Hub
702
+ ctx.fillStyle = '#5a5a65';
703
+ ctx.beginPath();
704
+ ctx.arc(0, 0, r * 0.15, 0, Math.PI * 2);
705
+ ctx.fill();
706
+ ctx.fillStyle = '#8a8a95';
707
+ ctx.beginPath();
708
+ ctx.arc(0, 0, r * 0.08, 0, Math.PI * 2);
709
+ ctx.fill();
710
+ ctx.restore();
711
+
712
+ // Warm highlight on tire (from sunset)
713
+ ctx.fillStyle = 'rgba(220, 140, 80, 0.15)';
714
+ ctx.beginPath();
715
+ ctx.arc(0, 0, r, -Math.PI * 0.25, Math.PI * 0.25);
716
+ ctx.lineTo(0, 0);
717
+ ctx.closePath();
718
+ ctx.fill();
719
+
720
+ ctx.restore();
721
+ }
722
+
723
+ // === Dust particles ===
724
+ function updateAndDrawDust(dt) {
725
+ const L = layout();
726
+
727
+ // Spawn new particles (exhaust smoke)
728
+ if (dustParticles.length < 50 && Math.random() < 0.4) {
729
+ dustParticles.push({
730
+ x: L.carX - 115 * carScale + (Math.random() - 0.5) * 8,
731
+ y: L.wheelY - 8 * carScale + (Math.random() - 0.5) * 6,
732
+ vx: -SPEED * 0.5 - Math.random() * 50,
733
+ vy: -10 - Math.random() * 15,
734
+ size: 3 + Math.random() * 5,
735
+ life: 1.0,
736
+ decay: 0.008 + Math.random() * 0.012,
737
+ });
738
+ }
739
+
740
+ for (let i = dustParticles.length - 1; i >= 0; i--) {
741
+ const p = dustParticles[i];
742
+ p.x += p.vx * dt;
743
+ p.y += p.vy * dt;
744
+ p.vy += 5 * dt;
745
+ p.vy *= 0.99;
746
+ p.life -= p.decay;
747
+ p.size += 6 * dt;
748
+
749
+ if (p.life <= 0 || p.x < -50) {
750
+ dustParticles.splice(i, 1);
751
+ continue;
752
+ }
753
+
754
+ ctx.fillStyle = `rgba(140, 110, 100, ${p.life * 0.22})`;
755
+ ctx.beginPath();
756
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
757
+ ctx.fill();
758
+ }
759
+ }
760
+
761
+ // === Foreground ===
762
+ function drawForeground() {
763
+ const baseY = H - 5;
764
+
765
+ // Dark ground silhouette
766
+ ctx.fillStyle = '#06040a';
767
+ ctx.beginPath();
768
+ ctx.moveTo(0, H);
769
+ ctx.lineTo(0, baseY - 15);
770
+
771
+ const offset = worldOffset * 1.5;
772
+ for (let x = 0; x <= W; x += 4) {
773
+ const wx = x + offset;
774
+ const h = 15 + Math.sin(wx * 0.05) * 6 + Math.sin(wx * 0.13) * 4 + Math.sin(wx * 0.3) * 2;
775
+ ctx.lineTo(x, baseY - h);
776
+ }
777
+ ctx.lineTo(W, H);
778
+ ctx.closePath();
779
+ ctx.fill();
780
+
781
+ // Grass blades
782
+ const bladeInterval = 15;
783
+ const bladeOffset = (worldOffset * 1.5) % bladeInterval;
784
+
785
+ for (let x = -bladeOffset - bladeInterval; x < W + bladeInterval; x += bladeInterval) {
786
+ const idx = Math.floor((x + worldOffset * 1.5) / bladeInterval);
787
+ const bx = x + hash(idx * 7.7) * 10 - 5;
788
+ const bh = 10 + hash(idx * 11.3) * 18;
789
+ const slant = hash(idx * 23.1) * 6 - 3;
790
+
791
+ ctx.strokeStyle = `rgba(15, 10, 20, ${0.7 + hash(idx * 31.7) * 0.3})`;
792
+ ctx.lineWidth = 1.5;
793
+ ctx.beginPath();
794
+ ctx.moveTo(bx, baseY - 5);
795
+ ctx.lineTo(bx + slant, baseY - 5 - bh);
796
+ ctx.stroke();
797
+ }
798
+ }
799
+
800
+ // === Main loop ===
801
+ function animate(now) {
802
+ if (!lastTime) lastTime = now;
803
+ const dt = Math.min(0.05, (now - lastTime) / 1000);
804
+ lastTime = now;
805
+ time += dt;
806
+ worldOffset += SPEED * dt;
807
+
808
+ ctx.clearRect(0, 0, W, H);
809
+
810
+ drawSky();
811
+ drawBirds();
812
+ drawMountains();
813
+ drawFarTreeline();
814
+ drawMidTrees();
815
+ drawPoles();
816
+ drawRoad();
817
+ drawCar();
818
+ updateAndDrawDust(dt);
819
+ drawForeground();
820
+
821
+ requestAnimationFrame(animate);
822
+ }
823
+ requestAnimationFrame(animate);
824
+ </script>
825
+ </body>
826
+ </html>