HI7RAI commited on
Commit
53fee15
·
verified ·
1 Parent(s): 6bb734e

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +746 -735
index.html CHANGED
@@ -1,806 +1,817 @@
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>Nexus Analytics | Modern Dashboard</title>
7
-
8
- <!-- Google 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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
-
13
- <!-- Font Awesome for 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
- /* --- CSS Variables & Reset --- */
18
- :root {
19
- --primary: #6366f1;
20
- --primary-hover: #4f46e5;
21
- --secondary: #ec4899;
22
- --bg-body: #0f172a;
23
- --bg-card: #1e293b;
24
- --bg-sidebar: #020617;
25
- --text-main: #f8fafc;
26
- --text-muted: #94a3b8;
27
- --border-color: #334155;
28
- --success: #10b981;
29
- --warning: #f59e0b;
30
- --danger: #ef4444;
31
- --radius: 12px;
32
- --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
33
- --font-family: 'Inter', sans-serif;
34
- --transition: all 0.3s ease;
35
- }
36
 
37
- [data-theme="light"] {
38
- --bg-body: #f1f5f9;
39
- --bg-card: #ffffff;
40
- --bg-sidebar: #ffffff;
41
- --text-main: #0f172a;
42
- --text-muted: #64748b;
43
- --border-color: #e2e8f0;
44
- --shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
45
- }
46
 
47
- * {
48
- margin: 0;
49
- padding: 0;
50
- box-sizing: border-box;
51
- }
 
 
 
 
 
 
52
 
53
- body {
54
- font-family: var(--font-family);
55
- background-color: var(--bg-body);
56
- color: var(--text-main);
57
- line-height: 1.6;
58
- transition: background-color 0.3s ease, color 0.3s ease;
59
- overflow-x: hidden;
60
- }
61
 
62
- a {
63
- text-decoration: none;
64
- color: inherit;
65
- }
66
 
67
- ul {
68
- list-style: none;
69
- }
 
 
 
 
 
70
 
71
- /* --- Layout --- */
72
- .app-container {
73
- display: grid;
74
- grid-template-columns: 260px 1fr;
75
- min-height: 100vh;
76
- }
77
 
78
- /* --- Sidebar --- */
79
- .sidebar {
80
- background-color: var(--bg-sidebar);
81
- border-right: 1px solid var(--border-color);
82
- padding: 1.5rem;
83
- display: flex;
84
- flex-direction: column;
85
- position: sticky;
86
- top: 0;
87
- height: 100vh;
88
- transition: transform 0.3s ease;
89
- z-index: 100;
90
- }
91
 
92
- .logo {
93
- font-size: 1.5rem;
94
- font-weight: 700;
95
- color: var(--primary);
96
- margin-bottom: 2rem;
97
- display: flex;
98
- align-items: center;
99
- gap: 0.5rem;
100
- }
 
 
101
 
102
- .nav-links {
103
- flex: 1;
104
- display: flex;
105
- flex-direction: column;
106
- gap: 0.5rem;
107
- }
108
 
109
- .nav-item {
110
- display: flex;
111
- align-items: center;
112
- gap: 0.75rem;
113
- padding: 0.75rem 1rem;
114
- border-radius: var(--radius);
115
- color: var(--text-muted);
116
- font-weight: 500;
117
- transition: var(--transition);
118
- cursor: pointer;
119
- }
120
 
121
- .nav-item:hover, .nav-item.active {
122
- background-color: rgba(99, 102, 241, 0.1);
123
- color: var(--primary);
124
- }
 
 
 
125
 
126
- .sidebar-footer {
127
- margin-top: auto;
128
- padding-top: 1rem;
129
- border-top: 1px solid var(--border-color);
130
- }
131
 
132
- .user-profile {
133
- display: flex;
134
- align-items: center;
135
- gap: 0.75rem;
136
- }
137
 
138
- .avatar {
139
- width: 40px;
140
- height: 40px;
141
- border-radius: 50%;
142
- object-fit: cover;
143
- border: 2px solid var(--primary);
144
- }
 
 
145
 
146
- /* --- Main Content --- */
147
- .main-content {
148
- padding: 2rem;
149
- overflow-y: auto;
150
- }
151
 
152
- /* --- Header --- */
153
- header {
154
- display: flex;
155
- justify-content: space-between;
156
- align-items: center;
157
- margin-bottom: 2rem;
158
- }
159
 
160
- .header-title h1 {
161
- font-size: 1.8rem;
162
- font-weight: 700;
163
- }
 
 
164
 
165
- .header-title p {
166
- color: var(--text-muted);
167
- font-size: 0.9rem;
168
- }
 
 
 
 
 
169
 
170
- .header-actions {
171
- display: flex;
172
- align-items: center;
173
- gap: 1rem;
174
- }
 
 
 
 
175
 
176
- .btn {
177
- padding: 0.6rem 1.2rem;
178
- border-radius: var(--radius);
179
- font-weight: 600;
180
- cursor: pointer;
181
- border: none;
182
- transition: var(--transition);
183
- display: inline-flex;
184
- align-items: center;
185
- gap: 0.5rem;
186
- font-family: inherit;
187
- }
188
 
189
- .btn-primary {
190
- background-color: var(--primary);
191
- color: white;
192
- }
193
 
194
- .btn-primary:hover {
195
- background-color: var(--primary-hover);
196
- transform: translateY(-2px);
197
- }
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
- .btn-outline {
200
- background-color: transparent;
201
- border: 1px solid var(--border-color);
202
- color: var(--text-main);
203
- }
204
 
205
- .btn-outline:hover {
206
- border-color: var(--primary);
207
- color: var(--primary);
208
- }
209
 
210
- .brand-link {
211
- font-size: 0.8rem;
212
- color: var(--text-muted);
213
- font-weight: 500;
214
- border-bottom: 1px dashed var(--border-color);
215
- padding-bottom: 2px;
216
- margin-right: 15px;
217
- }
218
 
219
- .brand-link:hover {
220
- color: var(--primary);
221
- border-color: var(--primary);
222
- }
 
223
 
224
- /* --- Dashboard Grid --- */
225
- .dashboard-grid {
226
- display: grid;
227
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
228
- gap: 1.5rem;
229
- margin-bottom: 2rem;
230
- }
 
 
 
 
231
 
232
- .card {
233
- background-color: var(--bg-card);
234
- border-radius: var(--radius);
235
- padding: 1.5rem;
236
- box-shadow: var(--shadow);
237
- border: 1px solid var(--border-color);
238
- position: relative;
239
- overflow: hidden;
240
- transition: var(--transition);
241
- }
 
242
 
243
- .card:hover {
244
- transform: translateY(-5px);
245
- border-color: var(--primary);
246
- }
247
 
248
- .card-header {
249
- display: flex;
250
- justify-content: space-between;
251
- align-items: center;
252
- margin-bottom: 1rem;
253
- }
254
 
255
- .card-title {
256
- font-size: 1.1rem;
257
- font-weight: 600;
258
- }
 
 
 
 
 
 
259
 
260
- .card-icon {
261
- width: 40px;
262
- height: 40px;
263
- border-radius: 10px;
264
- display: flex;
265
- align-items: center;
266
- justify-content: center;
267
- font-size: 1.2rem;
268
- }
269
 
270
- .icon-blue { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
271
- .icon-green { background: rgba(16, 185, 129, 0.1); color: #10b981; }
272
- .icon-purple { background: rgba(139, 92, 246, 0.1); color: #8b5cf6; }
273
- .icon-orange { background: rgba(249, 115, 22, 0.1); color: #f97316; }
274
 
275
- .stat-value {
276
- font-size: 2rem;
277
- font-weight: 700;
278
- margin-bottom: 0.5rem;
279
- }
280
 
281
- .stat-trend {
282
- font-size: 0.85rem;
283
- display: flex;
284
- align-items: center;
285
- gap: 0.25rem;
286
- }
287
 
288
- .trend-up { color: var(--success); }
289
- .trend-down { color: var(--danger); }
 
 
 
 
 
 
 
290
 
291
- /* --- Chart Section (Pure CSS) --- */
292
- .chart-container {
293
- grid-column: 1 / -1;
294
- min-height: 300px;
295
- display: flex;
296
- flex-direction: column;
297
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
- .bar-chart {
300
- flex: 1;
301
- display: flex;
302
- align-items: flex-end;
303
- justify-content: space-around;
304
- padding-top: 2rem;
305
- gap: 1rem;
306
- }
 
 
 
 
 
 
307
 
308
- .bar-group {
309
- display: flex;
310
- flex-direction: column;
311
- align-items: center;
312
- width: 100%;
313
- height: 100%;
314
- justify-content: flex-end;
315
- position: relative;
316
- }
317
 
318
- .bar {
319
- width: 60%;
320
- background: linear-gradient(to top, var(--primary), #818cf8);
321
- border-radius: 4px 4px 0 0;
322
- transition: height 1s ease-out;
323
- position: relative;
324
- cursor: pointer;
325
- }
326
-
327
- /* Animation for bars growing on load */
328
- @keyframes growUp {
329
- from { height: 0; opacity: 0; }
330
- to { opacity: 1; }
331
- }
332
-
333
- .bar.animate {
334
- animation: growUp 1s ease-out forwards;
335
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
- .bar:hover {
338
- filter: brightness(1.2);
339
- }
 
 
340
 
341
- .bar::after {
342
- content: attr(data-value);
343
- position: absolute;
344
- top: -25px;
345
- left: 50%;
346
- transform: translateX(-50%);
347
- background: var(--bg-sidebar);
348
- padding: 2px 6px;
349
- border-radius: 4px;
350
- font-size: 0.75rem;
351
- opacity: 0;
352
- transition: opacity 0.2s;
353
- pointer-events: none;
354
- white-space: nowrap;
355
- }
356
 
357
- .bar:hover::after {
358
- opacity: 1;
359
- }
360
 
361
- .bar-label {
362
- margin-top: 0.75rem;
363
- color: var(--text-muted);
364
- font-size: 0.85rem;
365
- }
366
 
367
- /* --- Recent Activity Table --- */
368
- .table-container {
369
- grid-column: 1 / -1;
370
- overflow-x: auto;
371
- }
372
 
373
- table {
374
- width: 100%;
375
- border-collapse: collapse;
376
- }
377
 
378
- th {
379
- text-align: left;
380
- padding: 1rem;
381
- color: var(--text-muted);
382
- font-weight: 500;
383
- border-bottom: 1px solid var(--border-color);
384
- font-size: 0.9rem;
385
- }
386
 
387
- td {
388
- padding: 1rem;
389
- border-bottom: 1px solid var(--border-color);
390
- font-size: 0.95rem;
391
- }
392
 
393
- tr:last-child td {
394
- border-bottom: none;
395
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
- .status-badge {
398
- padding: 0.25rem 0.75rem;
399
- border-radius: 20px;
400
- font-size: 0.75rem;
401
- font-weight: 600;
402
- }
403
 
404
- .status-completed { background: rgba(16, 185, 129, 0.2); color: var(--success); }
405
- .status-pending { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
406
- .status-failed { background: rgba(239, 68, 68, 0.2); color: var(--danger); }
407
-
408
- /* --- Toast Notification --- */
409
- .toast-container {
410
- position: fixed;
411
- bottom: 2rem;
412
- right: 2rem;
413
- z-index: 1000;
414
- display: flex;
415
- flex-direction: column;
416
- gap: 1rem;
417
- }
418
 
419
- .toast {
420
- background-color: var(--bg-card);
421
- border-left: 4px solid var(--primary);
422
- padding: 1rem 1.5rem;
423
- border-radius: 6px;
424
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
425
- display: flex;
426
- align-items: center;
427
- gap: 1rem;
428
- min-width: 300px;
429
- transform: translateX(120%);
430
- transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
431
- }
432
 
433
- .toast.show {
434
- transform: translateX(0);
435
- }
436
 
437
- .toast i { font-size: 1.2rem; }
438
- .toast-success { border-color: var(--success); }
439
- .toast-success i { color: var(--success); }
440
-
441
- /* --- Mobile Responsive --- */
442
- .menu-toggle {
443
- display: none;
444
- font-size: 1.5rem;
445
- background: none;
446
- border: none;
447
- color: var(--text-main);
448
- cursor: pointer;
449
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
 
451
- @media (max-width: 768px) {
452
- .app-container {
453
- grid-template-columns: 1fr;
454
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
 
456
- .sidebar {
457
- position: fixed;
458
- left: -100%;
459
- width: 80%;
460
- max-width: 300px;
461
- box-shadow: var(--shadow);
462
- }
 
463
 
464
- .sidebar.active {
465
- left: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  }
 
 
 
 
 
 
 
 
 
 
467
 
468
- .menu-toggle {
469
- display: block;
 
 
 
 
 
470
  }
471
-
472
- .header-title h1 {
473
- font-size: 1.4rem;
474
  }
475
-
476
- .brand-link {
477
- display: none; /* Hide on very small screens to save space */
 
 
478
  }
479
- }
480
- </style>
481
- </head>
482
- <body>
483
-
484
- <div class="app-container">
485
- <!-- Sidebar -->
486
- <aside class="sidebar" id="sidebar">
487
- <div class="logo">
488
- <i class="fa-solid fa-layer-group"></i>
489
- Nexus
490
- </div>
491
-
492
- <nav class="nav-links">
493
- <div class="nav-item active" onclick="setActive(this)">
494
- <i class="fa-solid fa-house"></i> Dashboard
495
- </div>
496
- <div class="nav-item" onclick="setActive(this)">
497
- <i class="fa-solid fa-chart-pie"></i> Analytics
498
- </div>
499
- <div class="nav-item" onclick="setActive(this)">
500
- <i class="fa-solid fa-users"></i> Customers
501
- </div>
502
- <div class="nav-item" onclick="setActive(this)">
503
- <i class="fa-solid fa-wallet"></i> Sales
504
- </div>
505
- <div class="nav-item" onclick="setActive(this)">
506
- <i class="fa-solid fa-gear"></i> Settings
507
- </div>
508
- </nav>
509
-
510
- <div class="sidebar-footer">
511
- <div class="user-profile">
512
- <img src="https://picsum.photos/seed/user1/100/100" alt="User" class="avatar">
513
- <div>
514
- <div style="font-weight: 600; font-size: 0.9rem;">Alex Morgan</div>
515
- <div style="font-size: 0.8rem; color: var(--text-muted);">Admin</div>
516
- </div>
517
- </div>
518
- </div>
519
- </aside>
520
-
521
- <!-- Main Content -->
522
- <main class="main-content">
523
- <header>
524
- <div style="display: flex; align-items: center; gap: 1rem;">
525
- <button class="menu-toggle" id="menuToggle">
526
- <i class="fa-solid fa-bars"></i>
527
- </button>
528
- <div class="header-title">
529
- <h1>Dashboard Overview</h1>
530
- <p>Welcome back, here's what's happening today.</p>
531
- </div>
532
- </div>
533
-
534
- <div class="header-actions">
535
- <!-- Mandatory Link -->
536
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">
537
- Built with anycoder
538
- </a>
539
-
540
- <button class="btn btn-outline" id="themeToggle" title="Toggle Theme">
541
- <i class="fa-solid fa-moon"></i>
542
- </button>
543
- <button class="btn btn-primary" onclick="showToast('Report generated successfully!', 'success')">
544
- <i class="fa-solid fa-download"></i> Report
545
- </button>
546
- </div>
547
- </header>
548
-
549
- <!-- Stats Grid -->
550
- <section class="dashboard-grid">
551
- <div class="card">
552
- <div class="card-header">
553
- <div class="card-title">Total Revenue</div>
554
- <div class="card-icon icon-blue"><i class="fa-solid fa-dollar-sign"></i></div>
555
- </div>
556
- <div class="stat-value">$54,230</div>
557
- <div class="stat-trend trend-up">
558
- <i class="fa-solid fa-arrow-trend-up"></i>
559
- <span>12.5% from last month</span>
560
- </div>
561
- </div>
562
-
563
- <div class="card">
564
- <div class="card-header">
565
- <div class="card-title">Active Users</div>
566
- <div class="card-icon icon-purple"><i class="fa-solid fa-user-group"></i></div>
567
- </div>
568
- <div class="stat-value">2,453</div>
569
- <div class="stat-trend trend-up">
570
- <i class="fa-solid fa-arrow-trend-up"></i>
571
- <span>8.1% from last month</span>
572
- </div>
573
- </div>
574
-
575
- <div class="card">
576
- <div class="card-header">
577
- <div class="card-title">Bounce Rate</div>
578
- <div class="card-icon icon-orange"><i class="fa-solid fa-chart-simple"></i></div>
579
- </div>
580
- <div class="stat-value">42.3%</div>
581
- <div class="stat-trend trend-down">
582
- <i class="fa-solid fa-arrow-trend-down"></i>
583
- <span>3.2% lower is good</span>
584
- </div>
585
- </div>
586
-
587
- <div class="card">
588
- <div class="card-header">
589
- <div class="card-title">New Orders</div>
590
- <div class="card-icon icon-green"><i class="fa-solid fa-bag-shopping"></i></div>
591
- </div>
592
- <div class="stat-value">865</div>
593
- <div class="stat-trend trend-up">
594
- <i class="fa-solid fa-arrow-trend-up"></i>
595
- <span>24% increase</span>
596
- </div>
597
- </div>
598
-
599
- <!-- Chart Section -->
600
- <div class="card chart-container">
601
- <div class="card-header">
602
- <div class="card-title">Weekly Performance</div>
603
- <div class="btn-group">
604
- <select style="padding: 5px; border-radius: 6px; border: 1px solid var(--border-color); background: var(--bg-body); color: var(--text-main);">
605
- <option>This Week</option>
606
- <option>Last Week</option>
607
- </select>
608
- </div>
609
- </div>
610
- <div class="bar-chart">
611
- <!-- Bars with animation delay set via style -->
612
- <div class="bar-group">
613
- <div class="bar animate" style="height: 40%; animation-delay: 0.1s;" data-value="$4k"></div>
614
- <div class="bar-label">Mon</div>
615
- </div>
616
- <div class="bar-group">
617
- <div class="bar animate" style="height: 65%; animation-delay: 0.2s;" data-value="$6.5k"></div>
618
- <div class="bar-label">Tue</div>
619
- </div>
620
- <div class="bar-group">
621
- <div class="bar animate" style="height: 55%; animation-delay: 0.3s;" data-value="$5.5k"></div>
622
- <div class="bar-label">Wed</div>
623
- </div>
624
- <div class="bar-group">
625
- <div class="bar animate" style="height: 80%; animation-delay: 0.4s;" data-value="$8k"></div>
626
- <div class="bar-label">Thu</div>
627
- </div>
628
- <div class="bar-group">
629
- <div class="bar animate" style="height: 95%; animation-delay: 0.5s;" data-value="$9.5k"></div>
630
- <div class="bar-label">Fri</div>
631
- </div>
632
- <div class="bar-group">
633
- <div class="bar animate" style="height: 70%; animation-delay: 0.6s;" data-value="$7k"></div>
634
- <div class="bar-label">Sat</div>
635
- </div>
636
- <div class="bar-group">
637
- <div class="bar animate" style="height: 50%; animation-delay: 0.7s;" data-value="$5k"></div>
638
- <div class="bar-label">Sun</div>
639
- </div>
640
- </div>
641
- </div>
642
-
643
- <!-- Recent Activity Table -->
644
- <div class="card table-container">
645
- <div class="card-header">
646
- <div class="card-title">Recent Transactions</div>
647
- <button class="btn btn-outline" style="font-size: 0.8rem; padding: 0.4rem 0.8rem;">View All</button>
648
- </div>
649
- <table>
650
- <thead>
651
- <tr>
652
- <th>ID</th>
653
- <th>Customer</th>
654
- <th>Date</th>
655
- <th>Amount</th>
656
- <th>Status</th>
657
- <th>Action</th>
658
- </tr>
659
- </thead>
660
- <tbody>
661
- <tr>
662
- <td>#TRX-001</td>
663
- <td>Sarah Connor</td>
664
- <td>Oct 24, 2023</td>
665
- <td>$120.50</td>
666
- <td><span class="status-badge status-completed">Completed</span></td>
667
- <td><i class="fa-solid fa-ellipsis" style="color: var(--text-muted); cursor: pointer;"></i></td>
668
- </tr>
669
- <tr>
670
- <td>#TRX-002</td>
671
- <td>John Wick</td>
672
- <td>Oct 24, 2023</td>
673
- <td>$450.00</td>
674
- <td><span class="status-badge status-pending">Pending</span></td>
675
- <td><i class="fa-solid fa-ellipsis" style="color: var(--text-muted); cursor: pointer;"></i></td>
676
- </tr>
677
- <tr>
678
- <td>#TRX-003</td>
679
- <td>Ellen Ripley</td>
680
- <td>Oct 23, 2023</td>
681
- <td>$89.99</td>
682
- <td><span class="status-badge status-completed">Completed</span></td>
683
- <td><i class="fa-solid fa-ellipsis" style="color: var(--text-muted); cursor: pointer;"></i></td>
684
- </tr>
685
- <tr>
686
- <td>#TRX-004</td>
687
- <td>Marty McFly</td>
688
- <td>Oct 22, 2023</td>
689
- <td>$1,200.00</td>
690
- <td><span class="status-badge status-failed">Failed</span></td>
691
- <td><i class="fa-solid fa-ellipsis" style="color: var(--text-muted); cursor: pointer;"></i></td>
692
- </tr>
693
- </tbody>
694
- </table>
695
- </div>
696
- </section>
697
- </main>
698
- </div>
699
-
700
- <!-- Toast Container -->
701
- <div class="toast-container" id="toastContainer"></div>
702
-
703
- <script>
704
- // --- Navigation Logic ---
705
- function setActive(element) {
706
- // Remove active class from all items
707
- document.querySelectorAll('.nav-item').forEach(item => {
708
- item.classList.remove('active');
709
- });
710
- // Add active class to clicked item
711
- element.classList.add('active');
712
-
713
- // On mobile, close sidebar after selection
714
- if (window.innerWidth <= 768) {
715
- document.getElementById('sidebar').classList.remove('active');
716
- }
717
  }
718
 
719
- // --- Mobile Menu Toggle ---
720
- const menuToggle = document.getElementById('menuToggle');
721
- const sidebar = document.getElementById('sidebar');
722
-
723
- menuToggle.addEventListener('click', () => {
724
- sidebar.classList.toggle('active');
725
- });
726
-
727
- // --- Theme Toggle Logic ---
728
- const themeToggle = document.getElementById('themeToggle');
729
- const themeIcon = themeToggle.querySelector('i');
730
-
731
- // Check for saved preference
732
- const savedTheme = localStorage.getItem('theme') || 'dark';
733
- document.documentElement.setAttribute('data-theme', savedTheme);
734
- updateThemeIcon(savedTheme);
735
-
736
- themeToggle.addEventListener('click', () => {
737
- const currentTheme = document.documentElement.getAttribute('data-theme');
738
- const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
739
-
740
- document.documentElement.setAttribute('data-theme', newTheme);
741
- localStorage.setItem('theme', newTheme);
742
- updateThemeIcon(newTheme);
743
- });
744
-
745
- function updateThemeIcon(theme) {
746
- if (theme === 'light') {
747
- themeIcon.classList.remove('fa-moon');
748
- themeIcon.classList.add('fa-sun');
749
- } else {
750
- themeIcon.classList.remove('fa-sun');
751
- themeIcon.classList.add('fa-moon');
752
- }
753
  }
754
 
755
- // --- Toast Notification System ---
756
- function showToast(message, type = 'success') {
757
- const container = document.getElementById('toastContainer');
758
-
759
- // Create toast element
760
- const toast = document.createElement('div');
761
- toast.className = `toast toast-${type}`;
762
-
763
- const icon = type === 'success' ? 'fa-circle-check' : 'fa-circle-info';
764
-
765
- toast.innerHTML = `
766
- <i class="fa-solid ${icon}"></i>
767
- <span>${message}</span>
768
- `;
769
-
770
- container.appendChild(toast);
771
-
772
- // Trigger animation (small delay to allow DOM render)
773
- setTimeout(() => {
774
- toast.classList.add('show');
775
- }, 10);
776
-
777
- // Remove after 3 seconds
778
- setTimeout(() => {
779
- toast.classList.remove('show');
780
- setTimeout(() => {
781
- toast.remove();
782
- }, 300); // Wait for transition out
783
- }, 3000);
784
- }
785
-
786
- // --- Click Outside Logic for Mobile Sidebar ---
787
- document.addEventListener('click', (e) => {
788
- if (window.innerWidth <= 768) {
789
- if (!sidebar.contains(e.target) && !menuToggle.contains(e.target) && sidebar.classList.contains('active')) {
790
- sidebar.classList.remove('active');
791
- }
792
- }
793
- });
794
-
795
- // --- Interactive Bar Chart Click ---
796
- document.querySelectorAll('.bar').forEach(bar => {
797
- bar.addEventListener('click', () => {
798
- const value = bar.getAttribute('data-value');
799
- const label = bar.parentElement.querySelector('.bar-label').innerText;
800
- showToast(`Revenue for ${label}: ${value}`, 'success');
801
- });
802
- });
803
- </script>
804
-
805
  </body>
806
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
  <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
+ <title>UltraFX: Dynamic Image & Video Processor</title>
8
+
9
+ <!-- Core Libraries via CDN -->
10
+ <script src="https://cdn.jsdelivr.net/npm/mathjs@11.8.0/lib/browser/math.js"></script>
11
+ <!-- p5 and Three/Pixi are loaded as requested, though glfx handles the heavy WebGL lifting here -->
12
+ <script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.min.js"></script>
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/glfx.js@0.0.4/glfx.min.js"></script>
15
+
16
+ <!-- UI Styling -->
17
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
18
+ <link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" rel="stylesheet">
19
+ <link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;600;700&display=swap" rel="stylesheet">
20
+
21
+ <style>
22
+ :root {
23
+ --neon-blue: #00f3ff;
24
+ --neon-pink: #ff00ff;
25
+ --dark-bg: #0a0a12;
26
+ --panel-bg: rgba(15, 15, 20, 0.95);
27
+ --border-color: rgba(0, 243, 255, 0.3);
28
+ }
 
 
 
 
 
 
 
 
29
 
30
+ body {
31
+ background-color: var(--dark-bg);
32
+ color: #fff;
33
+ font-family: 'Rajdhani', sans-serif;
34
+ overflow: hidden;
35
+ height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ }
39
 
40
+ /* --- Header / Nav --- */
41
+ .top-bar {
42
+ height: 50px;
43
+ background: rgba(0, 0, 0, 0.8);
44
+ border-bottom: 1px solid var(--border-color);
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: space-between;
48
+ padding: 0 1rem;
49
+ z-index: 200;
50
+ }
51
 
52
+ .brand {
53
+ font-weight: 700;
54
+ font-size: 1.2rem;
55
+ color: #fff;
56
+ letter-spacing: 1px;
57
+ }
 
 
58
 
59
+ .brand span {
60
+ color: var(--neon-blue);
61
+ }
 
62
 
63
+ .anycoder-link {
64
+ font-size: 0.8rem;
65
+ color: var(--text-muted);
66
+ text-decoration: none;
67
+ border-bottom: 1px dashed #555;
68
+ margin-left: auto;
69
+ margin-right: 15px;
70
+ }
71
 
72
+ .anycoder-link:hover {
73
+ color: var(--neon-blue);
74
+ border-color: var(--neon-blue);
75
+ }
 
 
76
 
77
+ /* --- Main Layout --- */
78
+ .app-wrapper {
79
+ display: flex;
80
+ flex: 1;
81
+ position: relative;
82
+ overflow: hidden;
83
+ }
 
 
 
 
 
 
84
 
85
+ /* --- Canvas Area --- */
86
+ #canvas-wrapper {
87
+ flex-grow: 1;
88
+ position: relative;
89
+ display: flex;
90
+ justify-content: center;
91
+ align-items: center;
92
+ background: radial-gradient(circle at center, #1a1a2e 0%, #000 100%);
93
+ overflow: hidden;
94
+ padding: 20px;
95
+ }
96
 
97
+ canvas {
98
+ box-shadow: 0 0 30px rgba(0, 243, 255, 0.1);
99
+ max-width: 100%;
100
+ max-height: 100%;
101
+ border: 1px solid #333;
102
+ }
103
 
104
+ /* --- Controls Sidebar --- */
105
+ #controls {
106
+ width: 320px;
107
+ background: var(--panel-bg);
108
+ border-left: 1px solid var(--border-color);
109
+ display: flex;
110
+ flex-direction: column;
111
+ backdrop-filter: blur(10px);
112
+ z-index: 100;
113
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
114
+ }
115
 
116
+ .sidebar-header {
117
+ padding: 15px;
118
+ border-bottom: 1px solid #333;
119
+ display: flex;
120
+ justify-content: space-between;
121
+ align-items: center;
122
+ }
123
 
124
+ .sidebar-title {
125
+ font-weight: 600;
126
+ color: var(--neon-blue);
127
+ font-size: 1rem;
128
+ }
129
 
130
+ #dynamic-controls {
131
+ flex: 1;
132
+ overflow-y: auto;
133
+ padding: 15px;
134
+ }
135
 
136
+ /* --- Control Groups --- */
137
+ .control-group {
138
+ background: rgba(255, 255, 255, 0.03);
139
+ border-radius: 6px;
140
+ padding: 10px;
141
+ margin-bottom: 12px;
142
+ border: 1px solid rgba(255, 255, 255, 0.05);
143
+ transition: border-color 0.2s;
144
+ }
145
 
146
+ .control-group:hover {
147
+ border-color: rgba(0, 243, 255, 0.2);
148
+ }
 
 
149
 
150
+ .control-header {
151
+ display: flex;
152
+ justify-content: space-between;
153
+ align-items: center;
154
+ margin-bottom: 8px;
155
+ }
 
156
 
157
+ .control-label {
158
+ font-weight: 600;
159
+ color: #ddd;
160
+ font-size: 0.85rem;
161
+ text-transform: uppercase;
162
+ }
163
 
164
+ /* --- Inputs & Sliders --- */
165
+ input[type=range] {
166
+ width: 100%;
167
+ height: 4px;
168
+ background: #333;
169
+ border-radius: 2px;
170
+ outline: none;
171
+ -webkit-appearance: none;
172
+ }
173
 
174
+ input[type=range]::-webkit-slider-thumb {
175
+ -webkit-appearance: none;
176
+ width: 14px;
177
+ height: 14px;
178
+ background: var(--neon-blue);
179
+ border-radius: 50%;
180
+ cursor: pointer;
181
+ box-shadow: 0 0 10px var(--neon-blue);
182
+ }
183
 
184
+ .math-input {
185
+ background: #000;
186
+ border: 1px solid #333;
187
+ color: var(--neon-pink);
188
+ font-family: 'Courier New', monospace;
189
+ width: 100%;
190
+ font-size: 0.75rem;
191
+ padding: 6px;
192
+ margin-top: 6px;
193
+ border-radius: 4px;
194
+ }
 
195
 
196
+ .math-input:focus {
197
+ border-color: var(--neon-pink);
198
+ outline: none;
199
+ }
200
 
201
+ /* --- Buttons --- */
202
+ .btn-neon {
203
+ background: rgba(0, 243, 255, 0.1);
204
+ border: 1px solid var(--neon-blue);
205
+ color: var(--neon-blue);
206
+ text-transform: uppercase;
207
+ font-weight: 700;
208
+ font-size: 0.8rem;
209
+ letter-spacing: 1px;
210
+ transition: all 0.2s;
211
+ width: 100%;
212
+ margin-bottom: 8px;
213
+ padding: 8px;
214
+ border-radius: 4px;
215
+ cursor: pointer;
216
+ }
217
 
218
+ .btn-neon:hover {
219
+ background: var(--neon-blue);
220
+ color: #000;
221
+ box-shadow: 0 0 15px rgba(0, 243, 255, 0.4);
222
+ }
223
 
224
+ .btn-neon i {
225
+ margin-right: 6px;
226
+ }
 
227
 
228
+ /* --- Toggle Switch --- */
229
+ .toggle-switch {
230
+ position: relative;
231
+ display: inline-block;
232
+ width: 34px;
233
+ height: 18px;
234
+ }
 
235
 
236
+ .toggle-switch input {
237
+ opacity: 0;
238
+ width: 0;
239
+ height: 0;
240
+ }
241
 
242
+ .slider {
243
+ position: absolute;
244
+ cursor: pointer;
245
+ top: 0;
246
+ left: 0;
247
+ right: 0;
248
+ bottom: 0;
249
+ background-color: #333;
250
+ transition: .4s;
251
+ border-radius: 18px;
252
+ }
253
 
254
+ .slider:before {
255
+ position: absolute;
256
+ content: "";
257
+ height: 14px;
258
+ width: 14px;
259
+ left: 2px;
260
+ bottom: 2px;
261
+ background-color: white;
262
+ transition: .4s;
263
+ border-radius: 50%;
264
+ }
265
 
266
+ input:checked+.slider {
267
+ background-color: var(--neon-pink);
268
+ }
 
269
 
270
+ input:checked+.slider:before {
271
+ transform: translateX(16px);
272
+ }
 
 
 
273
 
274
+ /* --- Floating Action Buttons (Mobile/Desktop) --- */
275
+ .fab-container {
276
+ position: absolute;
277
+ top: 20px;
278
+ left: 20px;
279
+ z-index: 50;
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 10px;
283
+ }
284
 
285
+ /* --- Scrollbar --- */
286
+ ::-webkit-scrollbar {
287
+ width: 6px;
288
+ }
 
 
 
 
 
289
 
290
+ ::-webkit-scrollbar-track {
291
+ background: #0a0a12;
292
+ }
 
293
 
294
+ ::-webkit-scrollbar-thumb {
295
+ background: #333;
296
+ border-radius: 3px;
297
+ }
 
298
 
299
+ ::-webkit-scrollbar-thumb:hover {
300
+ background: var(--neon-blue);
301
+ }
 
 
 
302
 
303
+ /* --- Responsive --- */
304
+ .menu-toggle-btn {
305
+ display: none;
306
+ background: none;
307
+ border: none;
308
+ color: #fff;
309
+ font-size: 1.2rem;
310
+ cursor: pointer;
311
+ }
312
 
313
+ @media (max-width: 768px) {
314
+ #controls {
315
+ position: absolute;
316
+ right: 0;
317
+ top: 0;
318
+ bottom: 0;
319
+ transform: translateX(100%);
320
+ box-shadow: -5px 0 15px rgba(0,0,0,0.5);
321
+ }
322
+
323
+ #controls.active {
324
+ transform: translateX(0);
325
+ }
326
+
327
+ .menu-toggle-btn {
328
+ display: block;
329
+ }
330
+
331
+ .brand {
332
+ font-size: 1rem;
333
+ }
334
+
335
+ .anycoder-link {
336
+ display: none;
337
+ }
338
+ }
339
+
340
+ .hidden-file {
341
+ display: none;
342
+ }
343
 
344
+ #fps-counter {
345
+ position: absolute;
346
+ bottom: 10px;
347
+ left: 10px;
348
+ color: #0f0;
349
+ font-family: monospace;
350
+ background: rgba(0,0,0,0.6);
351
+ padding: 4px 8px;
352
+ border-radius: 4px;
353
+ font-size: 0.75rem;
354
+ pointer-events: none;
355
+ }
356
+ </style>
357
+ </head>
358
 
359
+ <body>
 
 
 
 
 
 
 
 
360
 
361
+ <!-- Top Navigation -->
362
+ <div class="top-bar">
363
+ <div class="brand">
364
+ <button class="menu-toggle-btn me-2" id="menuToggle"><i class="fas fa-bars"></i></button>
365
+ ULTRA<span>FX</span>
366
+ </div>
367
+
368
+ <!-- Mandatory Link -->
369
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
370
+ Built with anycoder
371
+ </a>
372
+ </div>
373
+
374
+ <div class="app-wrapper">
375
+ <!-- Main Canvas Area -->
376
+ <div id="canvas-wrapper">
377
+ <!-- Floating Action Buttons -->
378
+ <div class="fab-container">
379
+ <button class="btn-neon" onclick="document.getElementById('file-input').click()">
380
+ <i class="fas fa-upload"></i> Import Media
381
+ </button>
382
+ <button class="btn-neon" id="export-btn">
383
+ <i class="fas fa-camera"></i> Snapshot
384
+ </button>
385
+ <button class="btn-neon" id="auto-detect-btn">
386
+ <i class="fas fa-magic"></i> Reset FX
387
+ </button>
388
+ <input type="file" id="file-input" class="hidden-file" accept="image/*,video/*">
389
+ </div>
390
+
391
+ <!-- Canvas injected here -->
392
+ <div id="canvas-container"></div>
393
+ <div id="fps-counter">FPS: 0</div>
394
+ </div>
395
+
396
+ <!-- Sidebar Controls -->
397
+ <div id="controls">
398
+ <div class="sidebar-header">
399
+ <span class="sidebar-title"><i class="fas fa-sliders-h"></i> FX RACK</span>
400
+ <button class="btn-neon" style="width: auto; padding: 4px 8px; font-size: 0.7rem;" id="clear-all-btn">Clear All</button>
401
+ </div>
402
+ <div id="dynamic-controls">
403
+ <!-- Controls injected here -->
404
+ <div style="text-align: center; color: #666; margin-top: 50px;">
405
+ <i class="fas fa-photo-video fa-3x mb-3"></i>
406
+ <p>Import an image or video to start processing.</p>
407
+ </div>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ <script>
413
+ /**
414
+ * UltraFX Core Engine
415
+ * Handles WebGL context, Library Introspection, and Rendering Pipeline
416
+ */
417
+
418
+ // --- Global State ---
419
+ const state = {
420
+ canvas: null,
421
+ ctx: null,
422
+ glfxCanvas: null,
423
+ texture: null,
424
+ mediaType: null, // 'image' or 'video'
425
+ videoElement: null,
426
+ sourceImage: null,
427
+ filters: {}, // Stores active filter configurations
428
+ animationId: null,
429
+ time: 0,
430
+ width: 800,
431
+ height: 600,
432
+ isPlaying: true
433
+ };
434
+
435
+ // --- Library Definitions & Metadata ---
436
+ // Defines which methods from glfx.js act as filters and their parameters
437
+ const libraryMeta = {
438
+ glfx: {
439
+ instance: null,
440
+ methods: [
441
+ { name: 'ink', params: [{ name: 'strength', min: 0, max: 1, def: 0.25 }] },
442
+ { name: 'vignette', params: [{ name: 'size', min: 0, max: 1, def: 0.5 }, { name: 'amount', min: 0, max: 1, def: 0.5 }] },
443
+ { name: 'zoomBlur', params: [{ name: 'centerX', min: 0, max: 1, def: 0.5 }, { name: 'centerY', min: 0, max: 1, def: 0.5 }, { name: 'strength', min: 0, max: 1, def: 0.3 }] },
444
+ { name: 'colorHalftone', params: [{ name: 'centerX', min: 0, max: 1, def: 0.5 }, { name: 'centerY', min: 0, max: 1, def: 0.5 }, { name: 'angle', min: 0, max: 1.57, def: 0.25 }, { name: 'size', min: 3, max: 20, def: 4 }] },
445
+ { name: 'hexagonalPixelate', params: [{ name: 'centerX', min: 0, max: 1, def: 0.5 }, { name: 'centerY', min: 0, max: 1, def: 0.5 }, { name: 'scale', min: 5, max: 50, def: 20 }] },
446
+ { name: 'noise', params: [{ name: 'amount', min: 0, max: 1, def: 0.5 }] },
447
+ { name: 'sepia', params: [{ name: 'amount', min: 0, max: 1, def: 1 }] },
448
+ { name: 'brightnessContrast', params: [{ name: 'brightness', min: -1, max: 1, def: 0 }, { name: 'contrast', min: -1, max: 1, def: 0 }] },
449
+ { name: 'hueSaturation', params: [{ name: 'hue', min: -1, max: 1, def: 0 }, { name: 'saturation', min: -1, max: 1, def: 0 }] },
450
+ { name: 'denoise', params: [{ name: 'exponent', min: 0, max: 20, def: 10 }] }
451
+ ]
452
+ },
453
+ custom: {
454
+ methods: [
455
+ { name: 'glitch', params: [{ name: 'intensity', min: 0, max: 100, def: 0 }, { name: 'speed', min: 0, max: 50, def: 10 }] },
456
+ { name: 'strobo', params: [{ name: 'freq', min: 0, max: 60, def: 0 }] },
457
+ { name: 'rgbSplit', params: [{ name: 'amount', min: 0, max: 20, def: 0 }] }
458
+ ]
459
+ }
460
+ };
461
+
462
+ // --- Initialization ---
463
+ window.onload = function () {
464
+ initCanvas();
465
+ setupEventListeners();
466
+ renderLoop();
467
+ };
468
+
469
+ function initCanvas() {
470
+ const container = document.getElementById('canvas-container');
471
+
472
+ // Initialize glfx.js canvas (WebGL)
473
+ try {
474
+ state.glfxCanvas = fx.canvas();
475
+ libraryMeta.glfx.instance = state.glfxCanvas;
476
+ container.appendChild(state.glfxCanvas);
477
+ state.canvas = state.glfxCanvas;
478
+ } catch (e) {
479
+ console.error("WebGL not supported", e);
480
+ alert("WebGL is not supported in this browser. UltraFX cannot run.");
481
+ return;
482
+ }
483
+ }
484
 
485
+ function resizeCanvas(w, h) {
486
+ // Limit max size for performance
487
+ const max = 1280;
488
+ let width = w;
489
+ let height = h;
490
 
491
+ if (width > max) {
492
+ const ratio = height / width;
493
+ width = max;
494
+ height = max * ratio;
495
+ }
 
 
 
 
 
 
 
 
 
 
496
 
497
+ state.width = width;
498
+ state.height = height;
499
+ }
500
 
501
+ // --- UI Generation ---
502
+ function generateControls() {
503
+ const container = document.getElementById('dynamic-controls');
504
+ container.innerHTML = ''; // Clear existing
 
505
 
506
+ // 1. GLFX Library
507
+ const glfxHeader = document.createElement('div');
508
+ glfxHeader.innerHTML = '<h6 style="color:var(--neon-blue); margin: 10px 0;">WEBGL FILTERS</h6>';
509
+ container.appendChild(glfxHeader);
 
510
 
511
+ libraryMeta.glfx.methods.forEach(effect => {
512
+ createControlGroup(container, effect.name, effect.name, effect.params, 'glfx');
513
+ });
 
514
 
515
+ // 2. Custom JS Effects
516
+ const customHeader = document.createElement('div');
517
+ customHeader.innerHTML = '<h6 style="color:var(--neon-pink); margin: 20px 0 10px 0;">CUSTOM FX</h6>';
518
+ container.appendChild(customHeader);
 
 
 
 
519
 
520
+ libraryMeta.custom.methods.forEach(effect => {
521
+ createControlGroup(container, effect.name, effect.name, effect.params, 'custom');
522
+ });
523
+ }
 
524
 
525
+ function createControlGroup(parent, label, key, params, type) {
526
+ const group = document.createElement('div');
527
+ group.className = 'control-group';
528
+
529
+ // Header with Toggle
530
+ const header = document.createElement('div');
531
+ header.className = 'control-header';
532
+ header.innerHTML = `
533
+ <span class="control-label">${label}</span>
534
+ <label class="toggle-switch">
535
+ <input type="checkbox" id="toggle-${key}">
536
+ <span class="slider"></span>
537
+ </label>
538
+ `;
539
+ group.appendChild(header);
540
+
541
+ // Initialize State
542
+ state.filters[key] = {
543
+ active: false,
544
+ type: type,
545
+ params: {}
546
+ };
547
+
548
+ // Toggle Listener
549
+ header.querySelector('input').addEventListener('change', (e) => {
550
+ state.filters[key].active = e.target.checked;
551
+ });
552
+
553
+ // Parameters
554
+ params.forEach(param => {
555
+ state.filters[key].params[param.name] = {
556
+ value: param.def,
557
+ formula: ''
558
+ };
559
+
560
+ const paramWrapper = document.createElement('div');
561
+ paramWrapper.style.marginBottom = '10px';
562
+
563
+ // Label & Value Display
564
+ const info = document.createElement('div');
565
+ info.style.display = 'flex';
566
+ info.style.justifyContent = 'space-between';
567
+ info.style.fontSize = '0.75rem';
568
+ info.style.color = '#aaa';
569
+ info.innerHTML = `<span>${param.name}</span><span id="val-${key}-${param.name}">${param.def}</span>`;
570
+ paramWrapper.appendChild(info);
571
+
572
+ // Slider
573
+ const slider = document.createElement('input');
574
+ slider.type = 'range';
575
+ slider.min = param.min;
576
+ slider.max = param.max;
577
+ slider.step = (param.max - param.min) / 100;
578
+ slider.value = param.def;
579
+
580
+ slider.addEventListener('input', (e) => {
581
+ const val = parseFloat(e.target.value);
582
+ state.filters[key].params[param.name].value = val;
583
+ document.getElementById(`val-${key}-${param.name}`).innerText = val.toFixed(2);
584
+ });
585
+ paramWrapper.appendChild(slider);
586
 
587
+ // Math Formula Input
588
+ const formulaInput = document.createElement('input');
589
+ formulaInput.type = 'text';
590
+ formulaInput.className = 'math-input';
591
+ formulaInput.placeholder = `Math.js (e.g. sin(t)*${param.max})`;
592
+ formulaInput.title = "Use variables: t (time), random (0-1)";
593
 
594
+ formulaInput.addEventListener('change', (e) => {
595
+ state.filters[key].params[param.name].formula = e.target.value;
596
+ });
597
+ paramWrapper.appendChild(formulaInput);
 
 
 
 
 
 
 
 
 
 
598
 
599
+ group.appendChild(paramWrapper);
600
+ });
 
 
 
 
 
 
 
 
 
 
 
601
 
602
+ parent.appendChild(group);
603
+ }
 
604
 
605
+ // --- Event Listeners ---
606
+ function setupEventListeners() {
607
+ const fileInput = document.getElementById('file-input');
608
+ fileInput.addEventListener('change', handleFileSelect);
609
+
610
+ document.getElementById('export-btn').addEventListener('click', exportCanvas);
611
+ document.getElementById('clear-all-btn').addEventListener('click', clearAllFilters);
612
+ document.getElementById('auto-detect-btn').addEventListener('click', () => {
613
+ clearAllFilters();
614
+ showToast("FX Reset complete");
615
+ });
616
+
617
+ // Mobile Menu
618
+ document.getElementById('menuToggle').addEventListener('click', () => {
619
+ document.getElementById('controls').classList.toggle('active');
620
+ });
621
+
622
+ // Math.js Scope
623
+ window.mathScope = {
624
+ t: 0,
625
+ sin: Math.sin,
626
+ cos: Math.cos,
627
+ tan: Math.tan,
628
+ abs: Math.abs,
629
+ random: Math.random,
630
+ PI: Math.PI
631
+ };
632
+ }
633
 
634
+ function handleFileSelect(e) {
635
+ const file = e.target.files[0];
636
+ if (!file) return;
637
+
638
+ const url = URL.createObjectURL(file);
639
+
640
+ if (file.type.startsWith('image')) {
641
+ state.mediaType = 'image';
642
+ const img = new Image();
643
+ img.onload = () => {
644
+ resizeCanvas(img.width, img.height);
645
+ state.sourceImage = img;
646
+ state.texture = state.glfxCanvas.texture(img);
647
+ generateControls();
648
+ };
649
+ img.src = url;
650
+ } else if (file.type.startsWith('video')) {
651
+ state.mediaType = 'video';
652
+ const vid = document.createElement('video');
653
+ vid.src = url;
654
+ vid.loop = true;
655
+ vid.muted = true;
656
+ vid.play();
657
+ vid.crossOrigin = "anonymous";
658
+
659
+ vid.onloadedmetadata = () => {
660
+ resizeCanvas(vid.videoWidth, vid.videoHeight);
661
+ state.videoElement = vid;
662
+ state.texture = state.glfxCanvas.texture(vid);
663
+ generateControls();
664
+ };
665
+ }
666
+ }
667
 
668
+ function clearAllFilters() {
669
+ // Uncheck all toggles visually
670
+ document.querySelectorAll('.toggle-switch input').forEach(input => input.checked = false);
671
+ // Reset internal state
672
+ Object.keys(state.filters).forEach(key => {
673
+ state.filters[key].active = false;
674
+ });
675
+ }
676
 
677
+ // --- Rendering Pipeline ---
678
+ let lastTime = 0;
679
+ let frameCount = 0;
680
+ const fpsElem = document.getElementById('fps-counter');
681
+
682
+ function renderLoop(timestamp) {
683
+ requestAnimationFrame(renderLoop);
684
+
685
+ // FPS Calculation
686
+ if (timestamp - lastTime >= 1000) {
687
+ fpsElem.innerText = `FPS: ${frameCount}`;
688
+ frameCount = 0;
689
+ lastTime = timestamp;
690
+ }
691
+ frameCount++;
692
+
693
+ // Update Time
694
+ state.time += 0.05;
695
+ window.mathScope.t = state.time;
696
+
697
+ if (!state.texture) return;
698
+
699
+ // Update Texture if Video
700
+ if (state.mediaType === 'video' && state.videoElement) {
701
+ try {
702
+ state.texture.loadContentsOf(state.videoElement);
703
+ } catch (e) { }
704
+ }
705
+
706
+ // Start Drawing Chain
707
+ state.glfxCanvas.draw(state.texture);
708
+
709
+ // Apply Active Filters
710
+ for (const [key, filter] of Object.entries(state.filters)) {
711
+ if (!filter.active) continue;
712
+
713
+ const appliedParams = [];
714
+ const metaParams = libraryMeta[filter.type].methods.find(m => m.name === key).params;
715
+
716
+ metaParams.forEach(p => {
717
+ let val = filter.params[p.name].value;
718
+ const formula = filter.params[p.name].formula;
719
+
720
+ if (formula && formula.trim() !== "") {
721
+ try {
722
+ // Evaluate Math.js expression
723
+ val = math.evaluate(formula, window.mathScope);
724
+ // Clamp value to slider range for safety
725
+ val = Math.max(p.min, Math.min(p.max, val));
726
+ } catch (err) {
727
+ // Silent fail on math errors to prevent loop crash
728
  }
729
+ }
730
+ appliedParams.push(val);
731
+ });
732
+
733
+ // Apply GLFX Filters
734
+ if (filter.type === 'glfx') {
735
+ if (state.glfxCanvas[key]) {
736
+ state.glfxCanvas[key](...appliedParams);
737
+ }
738
+ }
739
 
740
+ // Apply Custom Filters (Simulated using GLFX primitives)
741
+ if (filter.type === 'custom') {
742
+ if (key === 'glitch') {
743
+ const intensity = appliedParams[0] / 100;
744
+ // Random slices simulation using hue/saturation shift and noise
745
+ if (Math.random() < intensity) {
746
+ state.glfxCanvas.hueSaturation(Math.random() - 0.5, 0);
747
  }
748
+ if (Math.random() < intensity * 0.5) {
749
+ state.glfxCanvas.noise(0.1);
 
750
  }
751
+ }
752
+ if (key === 'strobo') {
753
+ const freq = appliedParams[0];
754
+ if (freq > 0 && Math.floor(state.time * freq) % 2 === 0) {
755
+ state.glfxCanvas.brightnessContrast(-1, 0); // Black out
756
  }
757
+ }
758
+ if (key === 'rgbSplit') {
759
+ // Simulated shift by modifying hue drastically on specific channels (hack via colorHalftone or similar effects if available)
760
+ // Since glfx doesn't have direct channel split, we simulate via chromatic aberration approximation or just noise
761
+ const amt = appliedParams[0];
762
+ if(amt > 0) state.glfxCanvas.hexagonalPixelate(0.5, 0.5, amt + 10);
763
+ }
764
+ }
765
+ }
766
+
767
+ // Finalize
768
+ state.glfxCanvas.update();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769
  }
770
 
771
+ // --- Export ---
772
+ function exportCanvas() {
773
+ if (!state.texture) {
774
+ showToast("No media loaded!", "error");
775
+ return;
776
+ }
777
+ state.glfxCanvas.update();
778
+ const dataURL = state.glfxCanvas.toDataURL('image/png');
779
+ const link = document.createElement('a');
780
+ link.download = `ultrafx_${Date.now()}.png`;
781
+ link.href = dataURL;
782
+ link.click();
783
+ showToast("Snapshot saved!");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
  }
785
 
786
+ // --- Toast Notification ---
787
+ function showToast(msg, type = 'success') {
788
+ const toast = document.createElement('div');
789
+ toast.style.position = 'fixed';
790
+ toast.style.bottom = '20px';
791
+ toast.style.left = '50%';
792
+ toast.style.transform = 'translateX(-50%)';
793
+ toast.style.background = type === 'error' ? '#ef4444' : 'var(--neon-blue)';
794
+ toast.style.color = type === 'error' ? '#fff' : '#000';
795
+ toast.style.padding = '10px 20px';
796
+ toast.style.borderRadius = '30px';
797
+ toast.style.fontWeight = 'bold';
798
+ toast.style.zIndex = '9999';
799
+ toast.style.boxShadow = '0 5px 15px rgba(0,0,0,0.5)';
800
+ toast.style.opacity = '0';
801
+ toast.style.transition = 'opacity 0.3s';
802
+ toast.innerText = msg;
803
+
804
+ document.body.appendChild(toast);
805
+
806
+ // Animate in
807
+ setTimeout(() => toast.style.opacity = '1', 10);
808
+
809
+ // Remove
810
+ setTimeout(() => {
811
+ toast.style.opacity = '0';
812
+ setTimeout(() => toast.remove(), 300);
813
+ }, 3000);
814
+ }
815
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
816
  </body>
817
  </html>