Ezequield commited on
Commit
ac7bb3d
·
verified ·
1 Parent(s): 0cb2c0d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +541 -613
index.html CHANGED
@@ -3,811 +3,739 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>VegaEdit Pro - Video Effects Studio</title>
7
- <!-- Importing FontAwesome for Icons -->
 
 
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
 
10
  <style>
11
  :root {
12
- --bg-dark: #1e1e1e;
13
- --bg-panel: #252526;
14
- --bg-header: #333333;
15
- --accent: #007acc;
16
- --accent-hover: #0062a3;
17
- --text-main: #d4d4d4;
18
- --text-muted: #858585;
19
- --border: #3e3e42;
20
- --timeline-track: #1e1e1e;
21
- --timeline-clips: #3a3d41;
22
- --playhead: #f44336;
23
  }
24
 
25
  * {
26
  box-sizing: border-box;
27
  margin: 0;
28
  padding: 0;
29
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
30
  }
31
 
32
  body {
33
- background-color: var(--bg-dark);
34
- color: var(--text-main);
35
- height: 100vh;
 
 
 
 
36
  display: flex;
37
  flex-direction: column;
38
- overflow: hidden;
39
  }
40
 
41
- /* --- Header --- */
42
  header {
43
- height: 50px;
44
- background-color: var(--bg-header);
45
  display: flex;
46
- align-items: center;
47
- padding: 0 20px;
48
  justify-content: space-between;
49
- border-bottom: 1px solid var(--border);
 
 
 
 
 
 
 
50
  }
51
 
52
  .logo {
53
- font-weight: bold;
54
- font-size: 1.2rem;
55
- color: var(--accent);
 
56
  display: flex;
57
  align-items: center;
58
  gap: 10px;
59
  }
60
 
61
- .logo i { font-size: 1.4rem; }
62
-
63
- .header-controls {
64
- display: flex;
65
- gap: 15px;
66
  }
67
 
68
- .btn {
69
- background-color: var(--bg-panel);
70
- color: var(--text-main);
71
- border: 1px solid var(--border);
72
- padding: 6px 12px;
73
- border-radius: 4px;
74
- cursor: pointer;
75
  font-size: 0.9rem;
76
- transition: all 0.2s;
77
- display: flex;
78
- align-items: center;
79
- gap: 8px;
80
- text-decoration: none;
81
- }
82
-
83
- .btn:hover {
84
- background-color: var(--border);
85
  }
86
 
87
- .btn-primary {
88
- background-color: var(--accent);
89
- border-color: var(--accent);
90
- color: white;
 
91
  }
92
 
93
- .btn-primary:hover {
94
- background-color: var(--accent-hover);
 
95
  }
96
 
97
- /* --- Main Layout --- */
98
  main {
99
  flex: 1;
100
- display: grid;
101
- grid-template-columns: 280px 1fr 300px;
102
- grid-template-rows: 1fr 200px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  overflow: hidden;
104
  }
105
 
106
- /* Areas */
107
- .sidebar-left { grid-row: 1 / 2; grid-column: 1 / 2; border-right: 1px solid var(--border); display: flex; flex-direction: column; }
108
- .preview-area { grid-row: 1 / 2; grid-column: 2 / 3; background-color: #111; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; padding: 20px; }
109
- .sidebar-right { grid-row: 1 / 2; grid-column: 3 / 4; border-left: 1px solid var(--border); padding: 15px; overflow-y: auto; }
110
- .timeline-area { grid-row: 2 / 3; grid-column: 1 / 4; background-color: var(--bg-panel); border-top: 1px solid var(--border); display: flex; flex-direction: column; }
111
 
112
- /* --- Sidebar Left (Effects Library) --- */
113
- .panel-header {
114
- padding: 10px 15px;
115
- background-color: var(--bg-header);
116
- font-weight: 600;
117
- font-size: 0.9rem;
118
- text-transform: uppercase;
119
- letter-spacing: 0.5px;
120
- border-bottom: 1px solid var(--border);
121
  }
122
 
123
- .effects-list {
124
- padding: 10px;
125
- overflow-y: auto;
 
126
  }
127
 
128
- .effect-item {
129
- display: flex;
130
- align-items: center;
131
- padding: 10px;
132
- margin-bottom: 5px;
133
- background-color: var(--bg-dark);
134
- border: 1px solid transparent;
135
- border-radius: 4px;
136
- cursor: grab;
137
- transition: 0.2s;
138
  }
139
 
140
- .effect-item:hover {
141
- border-color: var(--accent);
142
- background-color: #2d2d30;
143
  }
144
 
145
- .effect-item i { margin-right: 10px; color: var(--accent); width: 20px; text-align: center; }
 
 
146
 
147
- /* --- Preview Area --- */
148
- .video-container {
 
149
  width: 100%;
150
- max-width: 800px;
151
- aspect-ratio: 16/9;
152
- background-color: #000;
153
- border-radius: 4px;
154
- box-shadow: 0 10px 30px rgba(0,0,0,0.5);
155
- position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
156
  overflow: hidden;
 
 
 
157
  display: flex;
158
- align-items: center;
159
  justify-content: center;
 
160
  }
161
 
162
  video {
163
  width: 100%;
164
  height: 100%;
165
  object-fit: contain;
166
- /* Filters will be applied here via JS */
167
  }
168
 
169
- .upload-placeholder {
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  text-align: center;
171
- color: var(--text-muted);
172
- display: flex;
173
- flex-direction: column;
174
- align-items: center;
175
- gap: 15px;
176
  z-index: 10;
 
177
  }
178
 
179
- .upload-placeholder i { font-size: 3rem; opacity: 0.5; }
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- /* --- Sidebar Right (Controls) --- */
182
  .control-group {
183
- margin-bottom: 20px;
184
- background: var(--bg-dark);
185
- padding: 10px;
186
- border-radius: 4px;
187
- border: 1px solid var(--border);
188
  }
189
 
190
- .control-label {
 
 
 
191
  display: flex;
192
  justify-content: space-between;
193
- font-size: 0.85rem;
194
- margin-bottom: 8px;
195
- color: var(--text-main);
196
  }
197
 
198
  .value-display {
199
- color: var(--accent);
200
- font-family: monospace;
201
  }
202
 
203
  /* Custom Range Slider */
204
- input[type=range] {
205
  -webkit-appearance: none;
206
  width: 100%;
207
- background: transparent;
 
 
 
208
  }
209
 
210
- input[type=range]:focus { outline: none; }
211
-
212
- input[type=range]::-webkit-slider-thumb {
213
  -webkit-appearance: none;
214
- height: 14px;
215
- width: 14px;
 
216
  border-radius: 50%;
217
- background: var(--accent);
218
  cursor: pointer;
219
- margin-top: -5px;
220
- box-shadow: 0 0 5px rgba(0,0,0,0.5);
221
  }
222
 
223
- input[type=range]::-webkit-slider-runnable-track {
224
- width: 100%;
225
- height: 4px;
226
- cursor: pointer;
227
- background: var(--border);
228
- border-radius: 2px;
229
  }
230
 
231
- /* --- Timeline --- */
232
- .timeline-controls {
233
- height: 40px;
234
  display: flex;
235
- align-items: center;
236
- padding: 0 15px;
237
- background-color: var(--bg-header);
238
- gap: 15px;
239
- border-bottom: 1px solid var(--border);
240
  }
241
 
242
- .timeline-tracks {
 
 
 
 
 
 
 
 
 
 
 
243
  flex: 1;
244
- position: relative;
245
- overflow-x: auto;
246
- background-color: var(--timeline-track);
247
- padding: 10px 0;
248
  }
249
 
250
- .track {
251
- height: 40px;
252
- background-color: #2a2a2a;
253
- margin: 5px 20px;
254
- position: relative;
255
- border-radius: 4px;
256
  }
257
 
258
- .clip {
259
- position: absolute;
260
- left: 0;
261
- top: 0;
262
- height: 100%;
263
- background-color: var(--timeline-clips);
264
- border: 1px solid #555;
265
- border-radius: 4px;
266
- overflow: hidden;
267
- display: flex;
268
- align-items: center;
269
- padding-left: 10px;
270
- font-size: 0.75rem;
271
- color: #ccc;
272
- box-shadow: 1px 1px 3px rgba(0,0,0,0.3);
273
  }
274
 
275
- .clip.active {
276
- background-color: #4a4a50;
277
- border-color: var(--accent);
278
  }
279
 
280
- .playhead-container {
281
- position: absolute;
282
- top: 0;
283
- bottom: 0;
284
- left: 20px; /* Matches track margin */
285
- right: 20px;
286
- pointer-events: none;
 
 
 
 
 
 
 
 
 
287
  }
288
 
289
- .playhead {
290
  position: absolute;
 
291
  top: 0;
 
 
292
  bottom: 0;
293
- width: 2px;
294
- background-color: var(--playhead);
295
- z-index: 10;
296
- left: 0%;
297
  }
298
 
299
- .playhead::before {
300
- content: '';
301
  position: absolute;
302
- top: 0;
303
- left: -5px;
304
- border-left: 6px solid transparent;
305
- border-right: 6px solid transparent;
306
- border-top: 8px solid var(--playhead);
 
 
 
307
  }
308
 
309
- .time-display {
310
- font-family: monospace;
311
- font-size: 1rem;
312
- color: var(--accent);
313
- background: #000;
314
- padding: 2px 6px;
315
- border-radius: 2px;
316
  }
317
 
318
- /* --- Toast Notification --- */
319
- .toast {
320
- position: fixed;
321
- bottom: 20px;
322
- right: 20px;
323
- background-color: var(--accent);
324
- color: white;
325
- padding: 10px 20px;
326
- border-radius: 4px;
327
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
328
- transform: translateY(100px);
329
- transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
330
- z-index: 1000;
331
  }
332
 
333
- .toast.show {
334
- transform: translateY(0);
 
 
 
335
  }
336
 
337
- /* --- Responsive --- */
338
  @media (max-width: 900px) {
339
- main {
340
- grid-template-columns: 200px 1fr;
341
- grid-template-rows: 1fr 200px;
342
  }
343
- .sidebar-right {
344
- display: none; /* Hide properties on tablet for simplicity, or move to modal */
 
345
  }
346
- }
347
 
348
- @media (max-width: 600px) {
349
- main {
350
- grid-template-columns: 1fr;
351
- grid-template-rows: auto 1fr auto;
352
- overflow-y: auto;
353
  }
354
- .sidebar-left { display: none; } /* Hide library on mobile */
355
- .timeline-area { height: 150px; }
356
- .header-controls span { display: none; } /* Hide text on buttons */
357
- }
358
-
359
- /* Glitch Effect Class (Special) */
360
- .glitch-active {
361
- animation: glitch-anim 0.3s infinite;
362
- }
363
-
364
- @keyframes glitch-anim {
365
- 0% { transform: translate(0) }
366
- 20% { transform: translate(-2px, 2px) }
367
- 40% { transform: translate(-2px, -2px) }
368
- 60% { transform: translate(2px, 2px) }
369
- 80% { transform: translate(2px, -2px) }
370
- 100% { transform: translate(0) }
371
  }
372
-
373
- /* Custom Scrollbar */
374
- ::-webkit-scrollbar { width: 8px; height: 8px; }
375
- ::-webkit-scrollbar-track { background: var(--bg-dark); }
376
- ::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }
377
- ::-webkit-scrollbar-thumb:hover { background: #555; }
378
-
379
  </style>
380
  </head>
381
  <body>
382
 
383
- <!-- Header -->
384
  <header>
385
  <div class="logo">
386
- <i class="fa-solid fa-film"></i>
387
- <span>VegaEdit Pro</span>
388
  </div>
389
- <div class="header-controls">
390
- <!-- Hidden File Input -->
391
- <input type="file" id="videoInput" accept="video/*" style="display: none;">
392
-
393
- <button class="btn btn-primary" onclick="document.getElementById('videoInput').click()">
394
- <i class="fa-solid fa-upload"></i> <span>Open Video</span>
395
- </button>
396
-
397
- <button class="btn" id="snapshotBtn" title="Take Snapshot">
398
- <i class="fa-solid fa-camera"></i>
399
- </button>
400
-
401
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="btn" style="color: #aaa; font-size: 0.8rem; border: none;">
402
- Built with anycoder
403
- </a>
404
  </div>
405
  </header>
406
 
407
- <!-- Main Workspace -->
408
  <main>
409
- <!-- Sidebar: Effects Library -->
410
- <aside class="sidebar-left">
411
- <div class="panel-header">FX Library</div>
412
- <div class="effects-list">
413
- <div class="effect-item" onclick="resetEffect('all')">
414
- <i class="fa-solid fa-ban"></i> Reset All
415
- </div>
416
- <div class="effect-item" onclick="setPreset('bw')">
417
- <i class="fa-solid fa-eye-dropper"></i> Black & White
418
- </div>
419
- <div class="effect-item" onclick="setPreset('sepia')">
420
- <i class="fa-solid fa-mug-hot"></i> Vintage Sepia
421
- </div>
422
- <div class="effect-item" onclick="setPreset('cyber')">
423
- <i class="fa-solid fa-robot"></i> Cyberpunk
424
- </div>
425
- <div class="effect-item" onclick="toggleGlitch()">
426
- <i class="fa-solid fa-bolt"></i> Glitch Toggle
427
- </div>
428
- <div class="effect-item" onclick="setPreset('vhs')">
429
- <i class="fa-solid fa-tv"></i> VHS Static
430
- </div>
431
- <div class="effect-item" onclick="setPreset('invert')">
432
- <i class="fa-solid fa-circle-half-stroke"></i> Invert Colors
433
- </div>
434
  </div>
435
- <div class="panel-header">Project Media</div>
436
- <div style="padding: 10px; font-size: 0.8rem; color: #666; text-align: center;">
437
- Drag & Drop video here<br>(Not implemented in demo)
438
- </div>
439
- </aside>
440
-
441
- <!-- Preview Area -->
442
- <section class="preview-area">
443
- <div class="video-container" id="videoWrapper">
444
- <div class="upload-placeholder" id="uploadPlaceholder">
445
- <i class="fa-solid fa-cloud-arrow-up"></i>
446
- <h3>No Video Loaded</h3>
447
- <p>Click "Open Video" to start editing</p>
448
- <button class="btn btn-primary" style="margin-top:10px;" onclick="document.getElementById('videoInput').click()">Browse Files</button>
449
- </div>
450
- <video id="mainVideo" playsinline></video>
451
- </div>
452
- </section>
453
 
454
- <!-- Sidebar: Properties -->
455
- <aside class="sidebar-right">
456
- <div class="panel-header">Video Event FX</div>
457
-
458
- <!-- Brightness -->
459
- <div class="control-group">
460
- <div class="control-label">
461
- <span>Brightness</span>
462
- <span class="value-display" id="val-brightness">100%</span>
463
- </div>
464
- <input type="range" id="brightness" min="0" max="200" value="100" oninput="updateEffect('brightness', this.value)">
465
  </div>
466
 
467
- <!-- Contrast -->
468
- <div class="control-group">
469
- <div class="control-label">
470
- <span>Contrast</span>
471
- <span class="value-display" id="val-contrast">100%</span>
 
 
472
  </div>
473
- <input type="range" id="contrast" min="0" max="200" value="100" oninput="updateEffect('contrast', this.value)">
474
- </div>
475
 
476
- <!-- Saturation -->
477
- <div class="control-group">
478
- <div class="control-label">
479
- <span>Saturation</span>
480
- <span class="value-display" id="val-saturate">100%</span>
481
  </div>
482
- <input type="range" id="saturate" min="0" max="200" value="100" oninput="updateEffect('saturate', this.value)">
483
- </div>
484
 
485
- <!-- Hue Rotate -->
486
- <div class="control-group">
487
- <div class="control-label">
488
- <span>Hue Rotate</span>
489
- <span class="value-display" id="val-hue-rotate">0deg</span>
490
  </div>
491
- <input type="range" id="hue-rotate" min="0" max="360" value="0" oninput="updateEffect('hue-rotate', this.value)">
492
- </div>
493
 
494
- <!-- Blur -->
495
- <div class="control-group">
496
- <div class="control-label">
497
- <span>Blur</span>
498
- <span class="value-display" id="val-blur">0px</span>
499
  </div>
500
- <input type="range" id="blur" min="0" max="20" value="0" step="0.5" oninput="updateEffect('blur', this.value)">
501
- </div>
502
 
503
- <!-- Sepia -->
504
- <div class="control-group">
505
- <div class="control-label">
506
- <span>Sepia</span>
507
- <span class="value-display" id="val-sepia">0%</span>
508
- </div>
509
- <input type="range" id="sepia" min="0" max="100" value="0" oninput="updateEffect('sepia', this.value)">
510
- </div>
511
-
512
- <!-- Invert -->
513
- <div class="control-group">
514
- <div class="control-label">
515
- <span>Invert</span>
516
- <span class="value-display" id="val-invert">0%</span>
517
  </div>
518
- <input type="range" id="invert" min="0" max="100" value="0" oninput="updateEffect('invert', this.value)">
519
- </div>
520
 
521
- </aside>
522
-
523
- <!-- Timeline -->
524
- <section class="timeline-area">
525
- <div class="timeline-controls">
526
- <button class="btn" id="playPauseBtn" title="Play/Pause (Space)">
527
- <i class="fa-solid fa-play"></i>
528
- </button>
529
- <button class="btn" id="stopBtn" title="Stop">
530
- <i class="fa-solid fa-stop"></i>
531
- </button>
532
- <div class="time-display" id="timeDisplay">00:00 / 00:00</div>
533
- <div style="flex:1"></div>
534
- <button class="btn" id="zoomOutBtn"><i class="fa-solid fa-magnifying-glass-minus"></i></button>
535
- <button class="btn" id="zoomInBtn"><i class="fa-solid fa-magnifying-glass-plus"></i></button>
536
- </div>
537
-
538
- <div class="timeline-tracks" id="timelineTracks">
539
- <div class="playhead-container">
540
- <div class="playhead" id="playhead"></div>
541
  </div>
542
- <!-- Video Track -->
543
- <div class="track">
544
- <div class="clip" id="videoClip" style="width: 0%;">
545
- <i class="fa-solid fa-film" style="margin-right:5px; opacity:0.5;"></i> Video 1
546
- </div>
547
- </div>
548
- <!-- FX Track (Visual only for this demo) -->
549
- <div class="track">
550
- <div class="clip" style="width: 100%; background: #2d2d30; border-left: none; border-right: none; color: #555;">
551
- <i class="fa-solid fa-wand-magic-sparkles"></i> FX Chain
552
- </div>
553
- </div>
554
- </div>
555
- </section>
556
  </main>
557
 
558
- <!-- Toast Notification -->
559
- <div class="toast" id="toast">Snapshot Saved!</div>
560
-
561
  <script>
562
- // DOM Elements
563
- const videoInput = document.getElementById('videoInput');
564
- const video = document.getElementById('mainVideo');
565
- const uploadPlaceholder = document.getElementById('uploadPlaceholder');
566
- const playPauseBtn = document.getElementById('playPauseBtn');
567
- const stopBtn = document.getElementById('stopBtn');
568
- const timeDisplay = document.getElementById('timeDisplay');
569
- const playhead = document.getElementById('playhead');
570
- const timelineTracks = document.getElementById('timelineTracks');
571
- const videoClip = document.getElementById('videoClip');
572
- const toast = document.getElementById('toast');
573
- const snapshotBtn = document.getElementById('snapshotBtn');
574
- const videoWrapper = document.getElementById('videoWrapper');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
 
576
  // State
577
  let isPlaying = false;
578
- let effects = {
579
- brightness: 100,
580
- contrast: 100,
581
- saturate: 100,
582
- 'hue-rotate': 0,
583
- blur: 0,
584
- sepia: 0,
585
- invert: 0
586
- };
587
- let isGlitching = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
 
589
  // --- Event Listeners ---
590
 
591
- // Upload Video
592
- videoInput.addEventListener('change', function(e) {
593
- const file = e.target.files[0];
594
- if (file) {
595
- const fileURL = URL.createObjectURL(file);
596
- video.src = fileURL;
597
- video.load();
598
- uploadPlaceholder.style.display = 'none';
599
- showToast('Video Loaded Successfully');
600
- }
601
  });
602
 
603
- // Video Events
604
- video.addEventListener('loadedmetadata', () => {
605
- updateTimeDisplay();
606
- videoClip.style.width = '100%'; // Visual representation
607
  });
608
 
609
- video.addEventListener('timeupdate', () => {
610
- updateTimeDisplay();
611
- updatePlayhead();
 
 
 
 
 
 
612
  });
613
 
614
- video.addEventListener('ended', () => {
615
- isPlaying = false;
616
- playPauseBtn.innerHTML = '<i class="fa-solid fa-play"></i>';
617
  });
618
 
619
- // Controls
620
- playPauseBtn.addEventListener('click', togglePlay);
621
- stopBtn.addEventListener('click', stopVideo);
622
-
623
- // Timeline Scrubbing
624
- timelineTracks.addEventListener('click', (e) => {
625
- if(!video.duration) return;
626
- const rect = timelineTracks.getBoundingClientRect();
627
- const offsetX = e.clientX - rect.left;
628
- const percentage = Math.max(0, Math.min(1, offsetX / rect.width));
629
- video.currentTime = percentage * video.duration;
630
  });
631
 
632
- // Snapshot
633
- snapshotBtn.addEventListener('click', takeSnapshot);
634
-
635
- // Keyboard Shortcuts
636
- document.addEventListener('keydown', (e) => {
637
- if (e.code === 'Space') {
638
- e.preventDefault();
639
- togglePlay();
640
- }
641
  });
642
 
643
- // --- Functions ---
 
 
 
 
 
 
 
 
 
 
 
644
 
645
- function togglePlay() {
646
- if (!video.src) return;
647
  if (video.paused) {
648
  video.play();
649
- playPauseBtn.innerHTML = '<i class="fa-solid fa-pause"></i>';
650
  } else {
651
  video.pause();
652
- playPauseBtn.innerHTML = '<i class="fa-solid fa-play"></i>';
653
  }
654
- }
655
-
656
- function stopVideo() {
657
- video.pause();
658
- video.currentTime = 0;
659
- playPauseBtn.innerHTML = '<i class="fa-solid fa-play"></i>';
660
- }
661
-
662
- function updateTimeDisplay() {
663
- const current = formatTime(video.currentTime);
664
- const total = formatTime(video.duration || 0);
665
- timeDisplay.textContent = `${current} / ${total}`;
666
- }
667
-
668
- function formatTime(seconds) {
669
- const m = Math.floor(seconds / 60);
670
- const s = Math.floor(seconds % 60);
671
- return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
672
- }
673
-
674
- function updatePlayhead() {
675
- if (!video.duration) return;
676
- const percentage = (video.currentTime / video.duration) * 100;
677
- playhead.style.left = `${percentage}%`;
678
- }
679
 
680
- // --- Effects Logic ---
681
 
682
- function updateEffect(type, value) {
683
- effects[type] = value;
 
684
 
685
- // Update text display
686
- const unit = (type === 'blur') ? 'px' : (type === 'hue-rotate' ? 'deg' : '%');
687
- document.getElementById(`val-${type}`).textContent = `${value}${unit}`;
688
-
689
- renderEffects();
690
- }
691
-
692
- function renderEffects() {
693
- let filterString = `
694
- brightness(${effects.brightness}%)
695
- contrast(${effects.contrast}%)
696
- saturate(${effects.saturate}%)
697
- hue-rotate(${effects['hue-rotate']}deg)
698
- blur(${effects.blur}px)
699
- sepia(${effects.sepia}%)
700
- invert(${effects.invert}%)
701
- `;
702
-
703
- video.style.filter = filterString;
704
- }
705
 
706
- function resetEffect(type) {
707
- if (type === 'all') {
708
- // Reset sliders
709
- for (const key in effects) {
710
- effects[key] = (key === 'brightness' || key === 'contrast' || key === 'saturate') ? 100 : 0;
711
- const input = document.getElementById(key);
712
- if(input) input.value = effects[key];
713
- updateEffect(key, effects[key]);
714
- }
715
- isGlitching = false;
716
- video.classList.remove('glitch-active');
717
- showToast('All Effects Reset');
718
- }
719
  }
720
 
721
- function setPreset(name) {
722
- // First reset to base
723
- resetEffect('all');
724
-
725
- switch(name) {
726
- case 'bw':
727
- updateEffect('saturate', 0);
728
- document.getElementById('saturate').value = 0;
729
- break;
730
- case 'sepia':
731
- updateEffect('sepia', 100);
732
- document.getElementById('sepia').value = 100;
733
- break;
734
- case 'cyber':
735
- updateEffect('contrast', 130);
736
- updateEffect('saturate', 150);
737
- updateEffect('hue-rotate', 180);
738
- document.getElementById('contrast').value = 130;
739
- document.getElementById('saturate').value = 150;
740
- document.getElementById('hue-rotate').value = 180;
741
- break;
742
- case 'invert':
743
- updateEffect('invert', 100);
744
- document.getElementById('invert').value = 100;
745
- break;
746
- case 'vhs':
747
- updateEffect('blur', 0.5);
748
- updateEffect('contrast', 120);
749
- updateEffect('saturate', 80);
750
- document.getElementById('blur').value = 0.5;
751
- document.getElementById('contrast').value = 120;
752
- document.getElementById('saturate').value = 80;
753
- break;
754
  }
755
- showToast(`Preset Applied: ${name.charAt(0).toUpperCase() + name.slice(1)}`);
756
- }
757
 
758
- function toggleGlitch() {
759
- isGlitching = !isGlitching;
760
- if (isGlitching) {
761
- video.classList.add('glitch-active');
762
- showToast('Glitch Effect ON');
763
- } else {
764
- video.classList.remove('glitch-active');
765
- showToast('Glitch Effect OFF');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
  }
767
- }
768
-
769
- // --- Utilities ---
770
 
771
- function showToast(message) {
772
- toast.textContent = message;
773
- toast.classList.add('show');
774
- setTimeout(() => {
775
- toast.classList.remove('show');
776
- }, 3000);
777
- }
778
-
779
- function takeSnapshot() {
780
- if (!video.src || video.readyState < 2) {
781
- showToast('No video to capture');
782
- return;
783
  }
784
 
785
- // Create a canvas to draw the video frame with filters
786
- const canvas = document.createElement('canvas');
787
- canvas.width = video.videoWidth;
788
- canvas.height = video.videoHeight;
789
- const ctx = canvas.getContext('2d');
790
-
791
- // Apply filters to context (Note: ctx.filter matches CSS filter syntax)
792
- ctx.filter = video.style.filter;
 
 
 
 
 
 
 
 
793
 
794
- // Draw
795
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
796
-
797
- // Convert to image and download
798
- try {
799
- const dataURL = canvas.toDataURL('image/png');
800
- const link = document.createElement('a');
801
- link.download = `snapshot_${Date.now()}.png`;
802
- link.href = dataURL;
803
- document.body.appendChild(link);
804
- link.click();
805
- document.body.removeChild(link);
806
- showToast('Snapshot Downloaded!');
807
- } catch (e) {
808
- showToast('Error saving snapshot');
809
- console.error(e);
 
 
 
 
 
810
  }
 
 
 
 
 
 
 
811
  }
812
 
813
  </script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Klasky Cuspo Video Effects</title>
7
+ <!-- Import Google Fonts for that 90s/90s Cartoon aesthetic -->
8
+ <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
9
+ <!-- Import FontAwesome for icons -->
10
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
 
12
  <style>
13
  :root {
14
+ --primary-color: #6C5CE7;
15
+ --secondary-color: #A29BFE;
16
+ --accent-color: #FDCB6E;
17
+ --dark-bg: #1e1e2e;
18
+ --panel-bg: rgba(30, 30, 46, 0.85);
19
+ --text-color: #ffffff;
20
+ --border-radius: 16px;
21
+ --transition: all 0.3s ease;
 
 
 
22
  }
23
 
24
  * {
25
  box-sizing: border-box;
26
  margin: 0;
27
  padding: 0;
28
+ outline: none;
29
  }
30
 
31
  body {
32
+ font-family: 'Roboto Mono', monospace;
33
+ background-color: var(--dark-bg);
34
+ background-image:
35
+ radial-gradient(circle at 10% 20%, rgba(108, 92, 231, 0.2) 0%, transparent 20%),
36
+ radial-gradient(circle at 90% 80%, rgba(253, 203, 110, 0.1) 0%, transparent 20%);
37
+ color: var(--text-color);
38
+ min-height: 100vh;
39
  display: flex;
40
  flex-direction: column;
41
+ overflow-x: hidden;
42
  }
43
 
44
+ /* Header */
45
  header {
 
 
46
  display: flex;
 
 
47
  justify-content: space-between;
48
+ align-items: center;
49
+ padding: 1rem 2rem;
50
+ background: rgba(0, 0, 0, 0.5);
51
+ backdrop-filter: blur(10px);
52
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
53
+ position: sticky;
54
+ top: 0;
55
+ z-index: 100;
56
  }
57
 
58
  .logo {
59
+ font-family: 'Fredoka One', cursive;
60
+ font-size: 1.8rem;
61
+ color: var(--secondary-color);
62
+ text-shadow: 2px 2px 0px #000;
63
  display: flex;
64
  align-items: center;
65
  gap: 10px;
66
  }
67
 
68
+ .logo i {
69
+ color: var(--accent-color);
 
 
 
70
  }
71
 
72
+ .attribution {
 
 
 
 
 
 
73
  font-size: 0.9rem;
74
+ color: rgba(255, 255, 255, 0.6);
 
 
 
 
 
 
 
 
75
  }
76
 
77
+ .attribution a {
78
+ color: var(--primary-color);
79
+ text-decoration: none;
80
+ font-weight: bold;
81
+ transition: var(--transition);
82
  }
83
 
84
+ .attribution a:hover {
85
+ color: var(--accent-color);
86
+ text-decoration: underline;
87
  }
88
 
89
+ /* Main Container */
90
  main {
91
  flex: 1;
92
+ display: flex;
93
+ flex-direction: column;
94
+ align-items: center;
95
+ padding: 2rem;
96
+ gap: 2rem;
97
+ max-width: 1400px;
98
+ margin: 0 auto;
99
+ width: 100%;
100
+ }
101
+
102
+ /* Upload Section */
103
+ .upload-area {
104
+ width: 100%;
105
+ max-width: 800px;
106
+ height: 200px;
107
+ border: 2px dashed var(--secondary-color);
108
+ border-radius: var(--border-radius);
109
+ display: flex;
110
+ flex-direction: column;
111
+ justify-content: center;
112
+ align-items: center;
113
+ cursor: pointer;
114
+ transition: var(--transition);
115
+ background: rgba(108, 92, 231, 0.05);
116
+ position: relative;
117
  overflow: hidden;
118
  }
119
 
120
+ .upload-area:hover, .upload-area.drag-over {
121
+ background: rgba(108, 92, 231, 0.15);
122
+ border-color: var(--accent-color);
123
+ transform: scale(1.01);
124
+ }
125
 
126
+ .upload-content {
127
+ text-align: center;
128
+ pointer-events: none;
129
+ z-index: 2;
 
 
 
 
 
130
  }
131
 
132
+ .upload-content i {
133
+ font-size: 3rem;
134
+ color: var(--secondary-color);
135
+ margin-bottom: 1rem;
136
  }
137
 
138
+ .upload-content p {
139
+ font-size: 1.1rem;
140
+ font-weight: bold;
 
 
 
 
 
 
 
141
  }
142
 
143
+ .upload-content span {
144
+ font-size: 0.9rem;
145
+ color: rgba(255, 255, 255, 0.6);
146
  }
147
 
148
+ #fileInput {
149
+ display: none;
150
+ }
151
 
152
+ /* Video Editor Workspace */
153
+ .workspace {
154
+ display: none; /* Hidden until video uploaded */
155
  width: 100%;
156
+ display: grid;
157
+ grid-template-columns: 1fr 300px;
158
+ gap: 2rem;
159
+ opacity: 0;
160
+ transform: translateY(20px);
161
+ transition: var(--transition);
162
+ }
163
+
164
+ .workspace.active {
165
+ display: grid;
166
+ opacity: 1;
167
+ transform: translateY(0);
168
+ }
169
+
170
+ /* Video Preview Area */
171
+ .preview-container {
172
+ background: #000;
173
+ border-radius: var(--border-radius);
174
  overflow: hidden;
175
+ position: relative;
176
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
177
+ aspect-ratio: 16/9;
178
  display: flex;
 
179
  justify-content: center;
180
+ align-items: center;
181
  }
182
 
183
  video {
184
  width: 100%;
185
  height: 100%;
186
  object-fit: contain;
187
+ display: none; /* We will draw video to canvas */
188
  }
189
 
190
+ canvas {
191
+ width: 100%;
192
+ height: 100%;
193
+ object-fit: contain;
194
+ }
195
+
196
+ .overlay-message {
197
+ position: absolute;
198
+ top: 50%;
199
+ left: 50%;
200
+ transform: translate(-50%, -50%);
201
+ color: white;
202
+ font-family: 'Fredoka One', cursive;
203
+ font-size: 2rem;
204
  text-align: center;
205
+ display: none;
 
 
 
 
206
  z-index: 10;
207
+ text-shadow: 4px 4px 0 #000;
208
  }
209
 
210
+ /* Controls Panel */
211
+ .controls {
212
+ background: var(--panel-bg);
213
+ backdrop-filter: blur(12px);
214
+ padding: 1.5rem;
215
+ border-radius: var(--border-radius);
216
+ border: 1px solid rgba(255, 255, 255, 0.1);
217
+ display: flex;
218
+ flex-direction: column;
219
+ gap: 1.5rem;
220
+ height: fit-content;
221
+ }
222
 
 
223
  .control-group {
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 0.5rem;
 
 
227
  }
228
 
229
+ .control-group label {
230
+ font-size: 0.9rem;
231
+ color: var(--secondary-color);
232
+ font-weight: bold;
233
  display: flex;
234
  justify-content: space-between;
 
 
 
235
  }
236
 
237
  .value-display {
238
+ color: var(--accent-color);
 
239
  }
240
 
241
  /* Custom Range Slider */
242
+ input[type="range"] {
243
  -webkit-appearance: none;
244
  width: 100%;
245
+ height: 6px;
246
+ background: rgba(255, 255, 255, 0.1);
247
+ border-radius: 3px;
248
+ outline: none;
249
  }
250
 
251
+ input[type="range"]::-webkit-slider-thumb {
 
 
252
  -webkit-appearance: none;
253
+ width: 18px;
254
+ height: 18px;
255
+ background: var(--accent-color);
256
  border-radius: 50%;
 
257
  cursor: pointer;
258
+ transition: var(--transition);
 
259
  }
260
 
261
+ input[type="range"]::-webkit-slider-thumb:hover {
262
+ transform: scale(1.2);
263
+ box-shadow: 0 0 10px var(--accent-color);
 
 
 
264
  }
265
 
266
+ /* Buttons */
267
+ .btn-group {
 
268
  display: flex;
269
+ gap: 1rem;
270
+ margin-top: auto;
 
 
 
271
  }
272
 
273
+ button {
274
+ padding: 0.8rem 1.2rem;
275
+ border: none;
276
+ border-radius: 8px;
277
+ font-family: 'Roboto Mono', monospace;
278
+ font-weight: bold;
279
+ cursor: pointer;
280
+ transition: var(--transition);
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ gap: 0.5rem;
285
  flex: 1;
 
 
 
 
286
  }
287
 
288
+ .btn-primary {
289
+ background: var(--primary-color);
290
+ color: white;
291
+ box-shadow: 0 4px 15px rgba(108, 92, 231, 0.4);
 
 
292
  }
293
 
294
+ .btn-primary:hover {
295
+ background: #5b4cc4;
296
+ transform: translateY(-2px);
 
 
 
 
 
 
 
 
 
 
 
 
297
  }
298
 
299
+ .btn-secondary {
300
+ background: rgba(255, 255, 255, 0.1);
301
+ color: white;
302
  }
303
 
304
+ .btn-secondary:hover {
305
+ background: rgba(255, 255, 255, 0.2);
306
+ }
307
+
308
+ /* Effect Toggle Switch */
309
+ .toggle-switch {
310
+ position: relative;
311
+ display: inline-block;
312
+ width: 50px;
313
+ height: 24px;
314
+ }
315
+
316
+ .toggle-switch input {
317
+ opacity: 0;
318
+ width: 0;
319
+ height: 0;
320
  }
321
 
322
+ .slider {
323
  position: absolute;
324
+ cursor: pointer;
325
  top: 0;
326
+ left: 0;
327
+ right: 0;
328
  bottom: 0;
329
+ background-color: rgba(255, 255, 255, 0.1);
330
+ transition: .4s;
331
+ border-radius: 24px;
 
332
  }
333
 
334
+ .slider:before {
 
335
  position: absolute;
336
+ content: "";
337
+ height: 16px;
338
+ width: 16px;
339
+ left: 4px;
340
+ bottom: 4px;
341
+ background-color: white;
342
+ transition: .4s;
343
+ border-radius: 50%;
344
  }
345
 
346
+ input:checked + .slider {
347
+ background-color: var(--primary-color);
 
 
 
 
 
348
  }
349
 
350
+ input:checked + .slider:before {
351
+ transform: translateX(26px);
 
 
 
 
 
 
 
 
 
 
 
352
  }
353
 
354
+ .switch-container {
355
+ display: flex;
356
+ align-items: center;
357
+ justify-content: space-between;
358
+ margin-bottom: 1rem;
359
  }
360
 
361
+ /* Responsive */
362
  @media (max-width: 900px) {
363
+ .workspace {
364
+ grid-template-columns: 1fr;
 
365
  }
366
+
367
+ .controls {
368
+ order: 2;
369
  }
 
370
 
371
+ .preview-container {
372
+ order: 1;
 
 
 
373
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  }
 
 
 
 
 
 
 
375
  </style>
376
  </head>
377
  <body>
378
 
 
379
  <header>
380
  <div class="logo">
381
+ <i class="fas fa-robot"></i>
382
+ <span>KLAS<span style="color:var(--text-color)">KY</span> LABS</span>
383
  </div>
384
+ <div class="attribution">
385
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  </div>
387
  </header>
388
 
 
389
  <main>
390
+ <!-- Upload Area -->
391
+ <div class="upload-area" id="dropZone">
392
+ <div class="upload-content">
393
+ <i class="fas fa-cloud-upload-alt"></i>
394
+ <p>Drag & Drop Video Here</p>
395
+ <span>or click to browse (MP4, WebM)</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  </div>
397
+ <input type="file" id="fileInput" accept="video/*">
398
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
+ <!-- Workspace (Initially Hidden) -->
401
+ <div class="workspace" id="workspace">
402
+ <div class="preview-container">
403
+ <video id="sourceVideo" playsinline loop muted></video>
404
+ <canvas id="outputCanvas"></canvas>
405
+ <div class="overlay-message" id="overlayMessage">KLASKY CSUPO</div>
 
 
 
 
 
406
  </div>
407
 
408
+ <aside class="controls">
409
+ <div class="switch-container">
410
+ <label style="font-weight:bold;">Enable Effects</label>
411
+ <label class="toggle-switch">
412
+ <input type="checkbox" id="effectToggle" checked>
413
+ <span class="slider"></span>
414
+ </label>
415
  </div>
 
 
416
 
417
+ <div class="control-group">
418
+ <label>Effect Intensity <span class="value-display" id="intensityVal">100%</span></label>
419
+ <input type="range" id="intensityRange" min="0" max="100" value="100">
 
 
420
  </div>
 
 
421
 
422
+ <div class="control-group">
423
+ <label>Distortion Level <span class="value-display" id="distortionVal">50%</span></label>
424
+ <input type="range" id="distortionRange" min="0" max="100" value="50">
 
 
425
  </div>
 
 
426
 
427
+ <div class="control-group">
428
+ <label>Robot Face Size <span class="value-display" id="faceSizeVal">1.0x</span></label>
429
+ <input type="range" id="faceSizeRange" min="10" max="200" value="100">
 
 
430
  </div>
 
 
431
 
432
+ <div class="control-group">
433
+ <label>Color Shift <span class="value-display" id="colorVal">Red</span></label>
434
+ <input type="range" id="colorRange" min="0" max="360" value="0">
 
 
 
 
 
 
 
 
 
 
 
435
  </div>
 
 
436
 
437
+ <div class="btn-group">
438
+ <button class="btn-secondary" id="resetBtn"><i class="fas fa-undo"></i> Reset</button>
439
+ <button class="btn-primary" id="playBtn"><i class="fas fa-play"></i> Pause</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  </div>
441
+ </aside>
442
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
443
  </main>
444
 
 
 
 
445
  <script>
446
+ /**
447
+ * KLASKY CSUPO VIDEO EFFECTS ENGINE
448
+ *
449
+ * This script handles video uploading, canvas rendering, and the specific
450
+ * visual effects associated with the "Klasky Csupo" aesthetic:
451
+ * - Paint splat overlay
452
+ * - RGB Shift / Chromatic Aberration
453
+ * - Grain/Noise
454
+ * - Robot face overlay
455
+ */
456
+
457
+ const dropZone = document.getElementById('dropZone');
458
+ const fileInput = document.getElementById('fileInput');
459
+ const workspace = document.getElementById('workspace');
460
+ const video = document.getElementById('sourceVideo');
461
+ const canvas = document.getElementById('outputCanvas');
462
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
463
+ const playBtn = document.getElementById('playBtn');
464
+ const overlayMessage = document.getElementById('overlayMessage');
465
+
466
+ // Inputs
467
+ const effectToggle = document.getElementById('effectToggle');
468
+ const intensityRange = document.getElementById('intensityRange');
469
+ const distortionRange = document.getElementById('distortionRange');
470
+ const faceSizeRange = document.getElementById('faceSizeRange');
471
+ const colorRange = document.getElementById('colorRange');
472
+ const resetBtn = document.getElementById('resetBtn');
473
 
474
  // State
475
  let isPlaying = false;
476
+ let animationId = null;
477
+ let robotFaceImg = new Image();
478
+ let splatterImg = new Image();
479
+
480
+ // Generate a "Robot Face" programmatically using a data URI so we don't rely on external assets failing
481
+ // This is a simple SVG representation of a chaotic robot face
482
+ const svgRobot = `
483
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
484
+ <rect x="10" y="10" width="80" height="80" fill="#555" stroke="#000" stroke-width="4"/>
485
+ <rect x="20" y="20" width="25" height="25" fill="#FFF" />
486
+ <rect x="55" y="20" width="25" height="25" fill="#FFF" />
487
+ <circle cx="32" cy="32" r="8" fill="#000" />
488
+ <circle cx="67" cy="32" r="8" fill="#000" />
489
+ <rect x="30" y="60" width="40" height="10" fill="#F00" />
490
+ <rect x="25" y="70" width="10" height="10" fill="#000" />
491
+ <rect x="65" y="70" width="10" height="10" fill="#000" />
492
+ <line x1="5" y1="50" x2="20" y2="50" stroke="#888" stroke-width="5"/>
493
+ <line x1="80" y1="50" x2="95" y2="50" stroke="#888" stroke-width="5"/>
494
+ </svg>`;
495
+ robotFaceImg.src = 'data:image/svg+xml;base64,' + btoa(svgRobot);
496
+
497
+ // Generate a "Splatter" texture programmatically (Canvas API)
498
+ const splatterCanvas = document.createElement('canvas');
499
+ splatterCanvas.width = 500;
500
+ splatterCanvas.height = 500;
501
+ const sCtx = splatterCanvas.getContext('2d');
502
+
503
+ // Draw random blobs to simulate paint splatter
504
+ function generateSplatter() {
505
+ sCtx.clearRect(0, 0, 500, 500);
506
+ sCtx.fillStyle = '#000'; // Background is transparent-ish
507
+ // Draw random colored blobs
508
+ const colors = ['#6C5CE7', '#A29BFE', '#FDCB6E', '#FAB1A0', '#000000'];
509
+ for(let i=0; i<30; i++) {
510
+ sCtx.beginPath();
511
+ sCtx.fillStyle = colors[Math.floor(Math.random() * colors.length)];
512
+ const x = Math.random() * 500;
513
+ const y = Math.random() * 500;
514
+ const r = Math.random() * 80 + 20;
515
+ sCtx.arc(x, y, r, 0, Math.PI * 2);
516
+ sCtx.fill();
517
+ }
518
+ // Add some "scary" scribbles
519
+ sCtx.strokeStyle = '#fff';
520
+ sCtx.lineWidth = 5;
521
+ for(let i=0; i<10; i++) {
522
+ sCtx.beginPath();
523
+ sCtx.moveTo(Math.random() * 500, Math.random() * 500);
524
+ sCtx.lineTo(Math.random() * 500, Math.random() * 500);
525
+ sCtx.stroke();
526
+ }
527
+ }
528
+ generateSplatter();
529
+ splatterImg.src = splatterCanvas.toDataURL();
530
 
531
  // --- Event Listeners ---
532
 
533
+ // Drag and Drop
534
+ dropZone.addEventListener('dragover', (e) => {
535
+ e.preventDefault();
536
+ dropZone.classList.add('drag-over');
 
 
 
 
 
 
537
  });
538
 
539
+ dropZone.addEventListener('dragleave', () => {
540
+ dropZone.classList.remove('drag-over');
 
 
541
  });
542
 
543
+ dropZone.addEventListener('drop', (e) => {
544
+ e.preventDefault();
545
+ dropZone.classList.remove('drag-over');
546
+ const files = e.dataTransfer.files;
547
+ if (files.length > 0 && files[0].type.startsWith('video/')) {
548
+ loadVideo(files[0]);
549
+ } else {
550
+ alert("Please upload a valid video file.");
551
+ }
552
  });
553
 
554
+ dropZone.addEventListener('click', () => {
555
+ fileInput.click();
 
556
  });
557
 
558
+ fileInput.addEventListener('change', (e) => {
559
+ if (e.target.files.length > 0) {
560
+ loadVideo(e.target.files[0]);
561
+ }
 
 
 
 
 
 
 
562
  });
563
 
564
+ // UI Updates for sliders
565
+ intensityRange.addEventListener('input', (e) => document.getElementById('intensityVal').textContent = e.target.value + '%');
566
+ distortionRange.addEventListener('input', (e) => document.getElementById('distortionVal').textContent = e.target.value + '%');
567
+ faceSizeRange.addEventListener('input', (e) => document.getElementById('faceSizeVal').textContent = (e.target.value / 100).toFixed(1) + 'x');
568
+ colorRange.addEventListener('input', (e) => {
569
+ const hue = e.target.value;
570
+ document.getElementById('colorVal').textContent = `Hue(${hue})`;
571
+ document.getElementById('colorVal').style.color = `hsl(${hue}, 100%, 50%)`;
 
572
  });
573
 
574
+ resetBtn.addEventListener('click', () => {
575
+ intensityRange.value = 100;
576
+ distortionRange.value = 50;
577
+ faceSizeRange.value = 100;
578
+ colorRange.value = 0;
579
+ effectToggle.checked = true;
580
+ // Trigger input events to update labels
581
+ intensityRange.dispatchEvent(new Event('input'));
582
+ distortionRange.dispatchEvent(new Event('input'));
583
+ faceSizeRange.dispatchEvent(new Event('input'));
584
+ colorRange.dispatchEvent(new Event('input'));
585
+ });
586
 
587
+ playBtn.addEventListener('click', () => {
 
588
  if (video.paused) {
589
  video.play();
590
+ playBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
591
  } else {
592
  video.pause();
593
+ playBtn.innerHTML = '<i class="fas fa-play"></i> Play';
594
  }
595
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
+ // --- Core Functions ---
598
 
599
+ function loadVideo(file) {
600
+ const url = URL.createObjectURL(file);
601
+ video.src = url;
602
 
603
+ video.onloadedmetadata = () => {
604
+ // Set canvas dimensions to match video
605
+ canvas.width = video.videoWidth;
606
+ canvas.height = video.videoHeight;
607
+
608
+ // Show workspace, hide upload prompt
609
+ workspace.classList.add('active');
610
+ dropZone.style.display = 'none';
611
+
612
+ // Start playing and rendering
613
+ video.play();
614
+ playBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
615
+ renderLoop();
616
+ };
 
 
 
 
 
 
617
 
618
+ video.onerror = () => {
619
+ alert("Error loading video file.");
620
+ };
 
 
 
 
 
 
 
 
 
 
621
  }
622
 
623
+ function renderLoop() {
624
+ if (video.paused || video.ended) {
625
+ // Keep loop alive but don't process frames heavily if paused
626
+ // We still draw one frame so the canvas isn't empty
627
+ processFrame();
628
+ if(!video.ended) requestAnimationFrame(renderLoop);
629
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  }
 
 
631
 
632
+ processFrame();
633
+ requestAnimationFrame(renderLoop);
634
+ }
635
+
636
+ function processFrame() {
637
+ const width = canvas.width;
638
+ const height = canvas.height;
639
+
640
+ // 1. Draw original video frame
641
+ ctx.drawImage(video, 0, 0, width, height);
642
+
643
+ // If effects are disabled, stop here
644
+ if (!effectToggle.checked) return;
645
+
646
+ // Get settings
647
+ const intensity = intensityRange.value / 100; // 0.0 to 1.0
648
+ const distortion = distortionRange.value / 100;
649
+ const faceScale = faceSizeRange.value / 100;
650
+ const hueShift = parseInt(colorRange.value);
651
+
652
+ // 2. Apply Chromatic Aberration (RGB Shift)
653
+ // We do this by getting image data, shifting channels, and putting back
654
+ if (distortion > 0) {
655
+ const frameData = ctx.getImageData(0, 0, width, height);
656
+ const data = frameData.data;
657
+ const offset = Math.floor(distortion * 20 * intensity); // Max 20px shift
658
+
659
+ // Create a copy to read from while writing to original
660
+ const copy = new Uint8ClampedArray(data);
661
+
662
+ for (let y = 0; y < height; y++) {
663
+ for (let x = 0; x < width; x++) {
664
+ const index = (y * width + x) * 4;
665
+
666
+ // Red channel shift left
667
+ const rIndex = (y * width + (x - offset)) * 4;
668
+ // Blue channel shift right
669
+ const bIndex = (y * width + (x + offset)) * 4;
670
+
671
+ if (x - offset >= 0) {
672
+ data[index] = copy[rIndex]; // Red
673
+ }
674
+ // Green stays
675
+ if (x + offset < width) {
676
+ data[index + 2] = copy[bIndex + 2]; // Blue
677
+ }
678
+ }
679
+ }
680
+ ctx.putImageData(frameData, 0, 0);
681
  }
 
 
 
682
 
683
+ // 3. Apply Color Filter (Hue Rotate)
684
+ if (hueShift !== 0) {
685
+ ctx.save();
686
+ ctx.globalCompositeOperation = 'hue';
687
+ ctx.globalAlpha = intensity * 0.8;
688
+ ctx.fillStyle = `hsl(${hueShift}, 100%, 50%)`;
689
+ ctx.fillRect(0, 0, width, height);
690
+ ctx.restore();
 
 
 
 
691
  }
692
 
693
+ // 4. Paint Splatter Overlay (The "Klasky" look)
694
+ // We draw the generated splatter image with a specific blend mode
695
+ ctx.save();
696
+ ctx.globalCompositeOperation = 'overlay'; // or 'hard-light' for stronger effect
697
+ ctx.globalAlpha = 0.4 * intensity;
698
+ // Scale splatter to cover screen
699
+ const scale = Math.max(width / splatterCanvas.width, height / splatterCanvas.height);
700
+ ctx.drawImage(splatterImg, 0, 0, splatterCanvas.width * scale, splatterCanvas.height * scale);
701
+ ctx.restore();
702
+
703
+ // 5. Draw Robot Face Overlay (Scary element)
704
+ // Pulse the size based on time
705
+ const pulse = 1 + Math.sin(Date.now() / 200) * 0.1 * intensity;
706
+ const fSize = (width * 0.3) * faceScale * pulse;
707
+ const fx = (width - fSize) / 2;
708
+ const fy = (height - fSize) / 2;
709
 
710
+ ctx.save();
711
+ ctx.globalAlpha = 0.8 * intensity;
712
+ // Add a glitch offset to the robot face
713
+ const glitchX = (Math.random() - 0.5) * distortion * 20;
714
+ const glitchY = (Math.random() - 0.5) * distortion * 20;
715
+ ctx.drawImage(robotFaceImg, fx + glitchX, fy + glitchY, fSize, fSize);
716
+ ctx.restore();
717
+
718
+ // 6. Add Noise/Grain
719
+ if (intensity > 0.1) {
720
+ const imageData = ctx.getImageData(0, 0, width, height);
721
+ const data = imageData.data;
722
+ const factor = intensity * 50; // Noise strength
723
+
724
+ for (let i = 0; i < data.length; i += 4) {
725
+ const noise = (Math.random() - 0.5) * factor;
726
+ data[i] += noise; // r
727
+ data[i + 1] += noise; // g
728
+ data[i + 2] += noise; // b
729
+ }
730
+ ctx.putImageData(imageData, 0, 0);
731
  }
732
+
733
+ // 7. Vignette
734
+ const gradient = ctx.createRadialGradient(width/2, height/2, height/3, width/2, height/2, height);
735
+ gradient.addColorStop(0, "rgba(0,0,0,0)");
736
+ gradient.addColorStop(1, `rgba(0,0,0,${0.8 * intensity})`);
737
+ ctx.fillStyle = gradient;
738
+ ctx.fillRect(0, 0, width, height);
739
  }
740
 
741
  </script>