Passakorn43 commited on
Commit
7176381
·
verified ·
1 Parent(s): 637dd0b

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +685 -19
index.html CHANGED
@@ -1,19 +1,685 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Ultimate FPS Benchmark</title>
7
+
8
+ <!-- Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet">
12
+
13
+ <!-- Icons -->
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
15
+
16
+ <style>
17
+ :root {
18
+ --primary: #00f3ff;
19
+ --secondary: #bc13fe;
20
+ --accent: #ffee00;
21
+ --bg-dark: #050507;
22
+ --panel-bg: rgba(10, 10, 14, 0.75);
23
+ --border: rgba(255, 255, 255, 0.1);
24
+ --text-main: #e0e0e0;
25
+ --font-display: 'Orbitron', sans-serif;
26
+ --font-mono: 'JetBrains Mono', monospace;
27
+ --glass: blur(12px) saturate(180%);
28
+ }
29
+
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ user-select: none;
35
+ }
36
+
37
+ body {
38
+ background-color: var(--bg-dark);
39
+ color: var(--text-main);
40
+ font-family: var(--font-mono);
41
+ overflow: hidden; /* Canvas covers everything */
42
+ height: 100vh;
43
+ width: 100vw;
44
+ }
45
+
46
+ /* --- Header --- */
47
+ header {
48
+ position: absolute;
49
+ top: 0;
50
+ left: 0;
51
+ width: 100%;
52
+ display: flex;
53
+ justify-content: space-between;
54
+ align-items: center;
55
+ padding: 1rem 2rem;
56
+ z-index: 100;
57
+ pointer-events: none; /* Let clicks pass through to canvas where needed */
58
+ }
59
+
60
+ .brand {
61
+ font-family: var(--font-display);
62
+ font-size: 1.5rem;
63
+ font-weight: 900;
64
+ letter-spacing: 2px;
65
+ color: white;
66
+ text-shadow: 0 0 10px var(--primary);
67
+ pointer-events: auto;
68
+ }
69
+
70
+ .brand span {
71
+ color: var(--primary);
72
+ }
73
+
74
+ .anycoder-link {
75
+ font-family: var(--font-mono);
76
+ font-size: 0.8rem;
77
+ color: var(--text-main);
78
+ text-decoration: none;
79
+ background: rgba(255, 255, 255, 0.05);
80
+ padding: 8px 16px;
81
+ border-radius: 4px;
82
+ border: 1px solid var(--border);
83
+ transition: all 0.3s ease;
84
+ pointer-events: auto;
85
+ backdrop-filter: blur(5px);
86
+ }
87
+
88
+ .anycoder-link:hover {
89
+ background: rgba(0, 243, 255, 0.15);
90
+ border-color: var(--primary);
91
+ box-shadow: 0 0 15px rgba(0, 243, 255, 0.3);
92
+ color: white;
93
+ }
94
+
95
+ /* --- Main Canvas --- */
96
+ #renderCanvas {
97
+ position: fixed;
98
+ top: 0;
99
+ left: 0;
100
+ width: 100%;
101
+ height: 100%;
102
+ z-index: 1;
103
+ }
104
+
105
+ /* --- HUD / Dashboard --- */
106
+ .hud-container {
107
+ position: absolute;
108
+ top: 80px;
109
+ left: 2rem;
110
+ width: 320px;
111
+ z-index: 10;
112
+ display: flex;
113
+ flex-direction: column;
114
+ gap: 1rem;
115
+ pointer-events: none;
116
+ }
117
+
118
+ .panel {
119
+ background: var(--panel-bg);
120
+ backdrop-filter: var(--glass);
121
+ -webkit-backdrop-filter: var(--glass);
122
+ border: 1px solid var(--border);
123
+ border-left: 4px solid var(--primary);
124
+ padding: 1.5rem;
125
+ border-radius: 4px;
126
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
127
+ pointer-events: auto;
128
+ transition: transform 0.2s;
129
+ }
130
+
131
+ .panel:hover {
132
+ border-color: rgba(255,255,255,0.2);
133
+ }
134
+
135
+ .fps-display {
136
+ text-align: center;
137
+ }
138
+
139
+ .fps-value {
140
+ font-family: var(--font-display);
141
+ font-size: 4rem;
142
+ font-weight: 900;
143
+ line-height: 1;
144
+ color: white;
145
+ text-shadow: 0 0 20px rgba(0, 243, 255, 0.5);
146
+ }
147
+
148
+ .fps-label {
149
+ color: var(--primary);
150
+ font-size: 0.9rem;
151
+ text-transform: uppercase;
152
+ letter-spacing: 4px;
153
+ margin-bottom: 10px;
154
+ }
155
+
156
+ .stats-grid {
157
+ display: grid;
158
+ grid-template-columns: 1fr 1fr;
159
+ gap: 10px;
160
+ margin-top: 1rem;
161
+ border-top: 1px solid var(--border);
162
+ padding-top: 1rem;
163
+ }
164
+
165
+ .stat-item {
166
+ display: flex;
167
+ flex-direction: column;
168
+ align-items: center;
169
+ }
170
+
171
+ .stat-item span:first-child {
172
+ font-size: 0.7rem;
173
+ color: #888;
174
+ text-transform: uppercase;
175
+ }
176
+
177
+ .stat-item span:last-child {
178
+ font-size: 1.1rem;
179
+ font-weight: 700;
180
+ color: white;
181
+ }
182
+
183
+ /* --- Graph Panel --- */
184
+ .graph-panel {
185
+ height: 150px;
186
+ display: flex;
187
+ flex-direction: column;
188
+ padding: 1rem;
189
+ }
190
+
191
+ .graph-title {
192
+ font-size: 0.8rem;
193
+ color: #aaa;
194
+ margin-bottom: 0.5rem;
195
+ display: flex;
196
+ justify-content: space-between;
197
+ }
198
+
199
+ canvas#fpsGraph {
200
+ width: 100%;
201
+ height: 100%;
202
+ border-bottom: 1px solid var(--border);
203
+ border-left: 1px solid var(--border);
204
+ }
205
+
206
+ /* --- Controls --- */
207
+ .controls-container {
208
+ position: absolute;
209
+ bottom: 2rem;
210
+ left: 2rem;
211
+ right: 2rem;
212
+ display: flex;
213
+ justify-content: center;
214
+ align-items: flex-end;
215
+ z-index: 10;
216
+ pointer-events: none;
217
+ }
218
+
219
+ .control-panel {
220
+ background: var(--panel-bg);
221
+ backdrop-filter: var(--glass);
222
+ -webkit-backdrop-filter: var(--glass);
223
+ border: 1px solid var(--border);
224
+ border-top: 4px solid var(--secondary);
225
+ padding: 1.5rem 2rem;
226
+ border-radius: 12px;
227
+ pointer-events: auto;
228
+ width: 100%;
229
+ max-width: 800px;
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: 1.5rem;
233
+ }
234
+
235
+ .control-row {
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: space-between;
239
+ gap: 2rem;
240
+ flex-wrap: wrap;
241
+ }
242
+
243
+ .slider-group {
244
+ flex: 1;
245
+ min-width: 200px;
246
+ }
247
+
248
+ .slider-label {
249
+ display: flex;
250
+ justify-content: space-between;
251
+ margin-bottom: 0.5rem;
252
+ font-size: 0.9rem;
253
+ color: var(--secondary);
254
+ font-weight: 700;
255
+ }
256
+
257
+ input[type="range"] {
258
+ -webkit-appearance: none;
259
+ width: 100%;
260
+ height: 6px;
261
+ background: rgba(255,255,255,0.1);
262
+ border-radius: 3px;
263
+ outline: none;
264
+ }
265
+
266
+ input[type="range"]::-webkit-slider-thumb {
267
+ -webkit-appearance: none;
268
+ appearance: none;
269
+ width: 20px;
270
+ height: 20px;
271
+ border-radius: 50%;
272
+ background: var(--secondary);
273
+ cursor: pointer;
274
+ box-shadow: 0 0 10px var(--secondary);
275
+ transition: transform 0.2s;
276
+ }
277
+
278
+ input[type="range"]::-webkit-slider-thumb:hover {
279
+ transform: scale(1.2);
280
+ }
281
+
282
+ .btn-group {
283
+ display: flex;
284
+ gap: 1rem;
285
+ }
286
+
287
+ .btn {
288
+ background: transparent;
289
+ border: 1px solid var(--border);
290
+ color: white;
291
+ padding: 10px 20px;
292
+ border-radius: 4px;
293
+ font-family: var(--font-mono);
294
+ cursor: pointer;
295
+ transition: all 0.2s;
296
+ text-transform: uppercase;
297
+ font-size: 0.85rem;
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 8px;
301
+ }
302
+
303
+ .btn:hover {
304
+ background: rgba(255,255,255,0.05);
305
+ border-color: white;
306
+ }
307
+
308
+ .btn-primary {
309
+ background: var(--secondary);
310
+ border-color: var(--secondary);
311
+ color: black;
312
+ font-weight: bold;
313
+ }
314
+
315
+ .btn-primary:hover {
316
+ background: #d45aff;
317
+ box-shadow: 0 0 15px rgba(188, 19, 254, 0.4);
318
+ }
319
+
320
+ /* --- Responsive --- */
321
+ @media (max-width: 768px) {
322
+ header {
323
+ padding: 1rem;
324
+ }
325
+ .hud-container {
326
+ left: 1rem;
327
+ right: 1rem;
328
+ width: auto;
329
+ top: 70px;
330
+ }
331
+ .fps-value {
332
+ font-size: 3rem;
333
+ }
334
+ .controls-container {
335
+ bottom: 1rem;
336
+ left: 1rem;
337
+ right: 1rem;
338
+ }
339
+ .control-panel {
340
+ padding: 1rem;
341
+ }
342
+ .control-row {
343
+ flex-direction: column;
344
+ align-items: stretch;
345
+ gap: 1rem;
346
+ }
347
+ .btn-group {
348
+ justify-content: center;
349
+ }
350
+ }
351
+ </style>
352
+ </head>
353
+ <body>
354
+
355
+ <header>
356
+ <div class="brand">
357
+ <i class="fa-solid fa-bolt"></i> FPS<span>TESTER</span>
358
+ </div>
359
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
360
+ Built with anycoder
361
+ </a>
362
+ </header>
363
+
364
+ <canvas id="renderCanvas"></canvas>
365
+
366
+ <div class="hud-container">
367
+ <!-- FPS Panel -->
368
+ <div class="panel">
369
+ <div class="fps-display">
370
+ <div class="fps-label">Current FPS</div>
371
+ <div class="fps-value" id="fpsDisplay">00</div>
372
+ </div>
373
+ <div class="stats-grid">
374
+ <div class="stat-item">
375
+ <span>Avg</span>
376
+ <span id="avgFps">0</span>
377
+ </div>
378
+ <div class="stat-item">
379
+ <span>Min 1%</span>
380
+ <span id="minFps">0</span>
381
+ </div>
382
+ <div class="stat-item">
383
+ <span>Objects</span>
384
+ <span id="objCount">0</span>
385
+ </div>
386
+ <div class="stat-item">
387
+ <span>Res</span>
388
+ <span id="resDisplay">1080p</span>
389
+ </div>
390
+ </div>
391
+ </div>
392
+
393
+ <!-- Graph Panel -->
394
+ <div class="panel graph-panel">
395
+ <div class="graph-title">
396
+ <span>FPS HISTORY</span>
397
+ <span style="color:var(--primary)">60s</span>
398
+ </div>
399
+ <canvas id="fpsGraph"></canvas>
400
+ </div>
401
+ </div>
402
+
403
+ <div class="controls-container">
404
+ <div class="control-panel">
405
+ <div class="control-row">
406
+ <div class="slider-group">
407
+ <div class="slider-label">
408
+ <span><i class="fa-solid fa-cubes"></i> STRESS LEVEL (Particles)</span>
409
+ <span id="sliderValue">1000</span>
410
+ </div>
411
+ <input type="range" id="particleSlider" min="100" max="20000" step="100" value="1000">
412
+ </div>
413
+ </div>
414
+
415
+ <div class="control-row">
416
+ <div class="btn-group">
417
+ <button class="btn" id="togglePhysics"><i class="fa-solid fa-magnet"></i> Physics</button>
418
+ <button class="btn" id="toggleTrails"><i class="fa-solid fa-wind"></i> Trails</button>
419
+ </div>
420
+ <button class="btn btn-primary" id="resetBtn"><i class="fa-solid fa-rotate-right"></i> Reset Stats</button>
421
+ </div>
422
+ </div>
423
+ </div>
424
+
425
+ <script>
426
+ /**
427
+ * FPS TESTER APPLICATION
428
+ *
429
+ * Logic:
430
+ * 1. Render Loop: Uses requestAnimationFrame.
431
+ * 2. Particle System: Spawns objects to stress the GPU/CPU.
432
+ * 3. Monitoring: Calculates frame delta time to determine FPS.
433
+ */
434
+
435
+ class ParticleSystem {
436
+ constructor(canvas) {
437
+ this.canvas = canvas;
438
+ this.ctx = canvas.getContext('2d', { alpha: false }); // Optimize for speed
439
+ this.particles = [];
440
+ this.physicsEnabled = true;
441
+ this.trailsEnabled = false;
442
+ this.colors = ['#00f3ff', '#bc13fe', '#ffee00', '#ffffff'];
443
+
444
+ this.resize();
445
+ window.addEventListener('resize', () => this.resize());
446
+ }
447
+
448
+ resize() {
449
+ this.width = window.innerWidth;
450
+ this.height = window.innerHeight;
451
+ this.canvas.width = this.width;
452
+ this.canvas.height = this.height;
453
+ document.getElementById('resDisplay').innerText = `${this.width}x${this.height}`;
454
+ }
455
+
456
+ createParticle() {
457
+ const size = Math.random() * 3 + 1;
458
+ return {
459
+ x: Math.random() * this.width,
460
+ y: Math.random() * this.height,
461
+ vx: (Math.random() - 0.5) * 4,
462
+ vy: (Math.random() - 0.5) * 4,
463
+ size: size,
464
+ color: this.colors[Math.floor(Math.random() * this.colors.length)],
465
+ history: []
466
+ };
467
+ }
468
+
469
+ updateCount(count) {
470
+ const currentLen = this.particles.length;
471
+ if (count > currentLen) {
472
+ for (let i = 0; i < count - currentLen; i++) {
473
+ this.particles.push(this.createParticle());
474
+ }
475
+ } else {
476
+ this.particles.length = count;
477
+ }
478
+ document.getElementById('objCount').innerText = this.particles.length.toLocaleString();
479
+ }
480
+
481
+ render() {
482
+ // Clear Canvas (or create trail effect)
483
+ if (this.trailsEnabled) {
484
+ this.ctx.fillStyle = 'rgba(5, 5, 7, 0.2)'; // Trail fade
485
+ this.ctx.fillRect(0, 0, this.width, this.height);
486
+ } else {
487
+ this.ctx.fillStyle = '#050507';
488
+ this.ctx.fillRect(0, 0, this.width, this.height);
489
+ }
490
+
491
+ this.ctx.lineWidth = 1;
492
+
493
+ for (let i = 0; i < this.particles.length; i++) {
494
+ let p = this.particles[i];
495
+
496
+ // Physics Update
497
+ if (this.physicsEnabled) {
498
+ p.x += p.vx;
499
+ p.y += p.vy;
500
+
501
+ // Boundary Check
502
+ if (p.x < 0 || p.x > this.width) p.vx *= -1;
503
+ if (p.y < 0 || p.y > this.height) p.vy *= -1;
504
+ }
505
+
506
+ // Render
507
+ this.ctx.fillStyle = p.color;
508
+ this.ctx.beginPath();
509
+ this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
510
+ this.ctx.fill();
511
+ }
512
+ }
513
+ }
514
+
515
+ class FPSMonitor {
516
+ constructor() {
517
+ this.fpsDisplay = document.getElementById('fpsDisplay');
518
+ this.avgDisplay = document.getElementById('avgFps');
519
+ this.minDisplay = document.getElementById('minFps');
520
+
521
+ this.frames = 0;
522
+ this.lastTime = performance.now();
523
+ this.fpsHistory = [];
524
+ this.minHistory = [];
525
+
526
+ // Graph setup
527
+ this.graphCanvas = document.getElementById('fpsGraph');
528
+ this.graphCtx = this.graphCanvas.getContext('2d');
529
+ this.graphData = new Array(60).fill(0); // 60 data points
530
+
531
+ this.resizeGraph();
532
+ window.addEventListener('resize', () => this.resizeGraph());
533
+
534
+ // Reset logic
535
+ this.startTime = performance.now();
536
+ this.totalFrames = 0;
537
+ }
538
+
539
+ resizeGraph() {
540
+ const rect = this.graphCanvas.parentElement.getBoundingClientRect();
541
+ this.graphCanvas.width = rect.width - 32; // padding adjustment
542
+ this.graphCanvas.height = rect.height - 30;
543
+ }
544
+
545
+ reset() {
546
+ this.fpsHistory = [];
547
+ this.minHistory = [];
548
+ this.totalFrames = 0;
549
+ this.startTime = performance.now();
550
+ this.graphData.fill(0);
551
+ this.updateUI(0, 0, 0);
552
+ }
553
+
554
+ update() {
555
+ const now = performance.now();
556
+ this.frames++;
557
+ this.totalFrames++;
558
+
559
+ if (now >= this.lastTime + 1000) {
560
+ const fps = Math.round((this.frames * 1000) / (now - this.lastTime));
561
+
562
+ this.fpsHistory.push(fps);
563
+ if (this.fpsHistory.length > 60) this.fpsHistory.shift();
564
+
565
+ // Calculate Stats
566
+ const avg = Math.round((this.totalFrames * 1000) / (now - this.startTime));
567
+ const min = Math.min(...this.fpsHistory);
568
+
569
+ this.updateUI(fps, avg, min);
570
+ this.updateGraph(fps);
571
+
572
+ this.frames = 0;
573
+ this.lastTime = now;
574
+ }
575
+ }
576
+
577
+ updateUI(fps, avg, min) {
578
+ this.fpsDisplay.innerText = fps;
579
+ this.avgDisplay.innerText = avg;
580
+ this.minDisplay.innerText = min;
581
+
582
+ // Color coding
583
+ if(fps >= 55) this.fpsDisplay.style.color = '#00f3ff'; // Good
584
+ else if(fps >= 30) this.fpsDisplay.style.color = '#ffee00'; // Warning
585
+ else this.fpsDisplay.style.color = '#ff0055'; // Bad
586
+ }
587
+
588
+ updateGraph(currentFps) {
589
+ this.graphData.shift();
590
+ this.graphData.push(currentFps);
591
+
592
+ const ctx = this.graphCtx;
593
+ const w = this.graphCanvas.width;
594
+ const h = this.graphCanvas.height;
595
+
596
+ ctx.clearRect(0, 0, w, h);
597
+
598
+ // Draw Guide Lines
599
+ ctx.strokeStyle = '#333';
600
+ ctx.lineWidth = 1;
601
+ ctx.beginPath();
602
+ ctx.moveTo(0, h/2);
603
+ ctx.lineTo(w, h/2); // 30 FPS line roughly
604
+ ctx.stroke();
605
+
606
+ // Draw Graph
607
+ ctx.strokeStyle = '#00f3ff';
608
+ ctx.lineWidth = 2;
609
+ ctx.beginPath();
610
+
611
+ const step = w / (this.graphData.length - 1);
612
+ const maxVal = 144; // Normalize to 144hz for visual scaling
613
+
614
+ for (let i = 0; i < this.graphData.length; i++) {
615
+ const val = this.graphData[i];
616
+ const y = h - (Math.min(val, maxVal) / maxVal) * h;
617
+
618
+ if (i === 0) ctx.moveTo(0, y);
619
+ else ctx.lineTo(i * step, y);
620
+ }
621
+
622
+ ctx.stroke();
623
+
624
+ // Fill gradient under
625
+ ctx.lineTo(w, h);
626
+ ctx.lineTo(0, h);
627
+ ctx.closePath();
628
+ const grad = ctx.createLinearGradient(0, 0, 0, h);
629
+ grad.addColorStop(0, 'rgba(0, 243, 255, 0.2)');
630
+ grad.addColorStop(1, 'rgba(0, 243, 255, 0)');
631
+ ctx.fillStyle = grad;
632
+ ctx.fill();
633
+ }
634
+ }
635
+
636
+ // --- Init ---
637
+ const canvas = document.getElementById('renderCanvas');
638
+ const particleSystem = new ParticleSystem(canvas);
639
+ const fpsMonitor = new FPSMonitor();
640
+
641
+ // UI Inputs
642
+ const slider = document.getElementById('particleSlider');
643
+ const sliderVal = document.getElementById('sliderValue');
644
+ const resetBtn = document.getElementById('resetBtn');
645
+ const physicsBtn = document.getElementById('togglePhysics');
646
+ const trailsBtn = document.getElementById('toggleTrails');
647
+
648
+ // Initial Load
649
+ particleSystem.updateCount(parseInt(slider.value));
650
+
651
+ // Event Listeners
652
+ slider.addEventListener('input', (e) => {
653
+ const val = parseInt(e.target.value);
654
+ sliderVal.innerText = val.toLocaleString();
655
+ particleSystem.updateCount(val);
656
+ });
657
+
658
+ resetBtn.addEventListener('click', () => {
659
+ fpsMonitor.reset();
660
+ });
661
+
662
+ physicsBtn.addEventListener('click', (e) => {
663
+ particleSystem.physicsEnabled = !particleSystem.physicsEnabled;
664
+ e.currentTarget.classList.toggle('btn-primary');
665
+ e.currentTarget.style.borderColor = particleSystem.physicsEnabled ? 'var(--border)' : '#555';
666
+ e.currentTarget.style.opacity = particleSystem.physicsEnabled ? '1' : '0.5';
667
+ });
668
+
669
+ trailsBtn.addEventListener('click', (e) => {
670
+ particleSystem.trailsEnabled = !particleSystem.trailsEnabled;
671
+ e.currentTarget.classList.toggle('btn-primary');
672
+ });
673
+
674
+ // Main Loop
675
+ function animate() {
676
+ particleSystem.render();
677
+ fpsMonitor.update();
678
+ requestAnimationFrame(animate);
679
+ }
680
+
681
+ animate();
682
+
683
+ </script>
684
+ </body>
685
+ </html>