Ezequield commited on
Commit
7f480ea
·
verified ·
1 Parent(s): c3efb13

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +699 -19
index.html CHANGED
@@ -1,19 +1,699 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>VIPID ABC 1977 | Retro Video Maker</title>
7
+ <!-- Import Retro Fonts -->
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Monoton&family=VT323&display=swap" rel="stylesheet">
11
+ <!-- Import Icons -->
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
+
14
+ <style>
15
+ :root {
16
+ --primary-neon: #ff00ff;
17
+ --secondary-neon: #00ffff;
18
+ --accent-yellow: #ffff00;
19
+ --bg-dark: #0f0f1a;
20
+ --panel-bg: #1a1a2e;
21
+ --text-color: #e0e0e0;
22
+ --border-radius: 8px;
23
+ --crt-scanline: rgba(18, 16, 16, 0.3);
24
+ --font-display: 'Monoton', cursive;
25
+ --font-ui: 'VT323', monospace;
26
+ }
27
+
28
+ * {
29
+ box-sizing: border-box;
30
+ margin: 0;
31
+ padding: 0;
32
+ }
33
+
34
+ body {
35
+ font-family: var(--font-ui);
36
+ background-color: var(--bg-dark);
37
+ color: var(--text-color);
38
+ display: flex;
39
+ flex-direction: column;
40
+ min-height: 100vh;
41
+ overflow-x: hidden;
42
+ }
43
+
44
+ /* --- Header --- */
45
+ header {
46
+ background: linear-gradient(90deg, #2b0042, #4a0069);
47
+ padding: 1rem 2rem;
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: center;
51
+ border-bottom: 2px solid var(--primary-neon);
52
+ box-shadow: 0 0 15px var(--primary-neon);
53
+ z-index: 10;
54
+ }
55
+
56
+ .logo {
57
+ font-family: var(--font-display);
58
+ font-size: 2rem;
59
+ color: var(--secondary-neon);
60
+ text-shadow: 2px 2px var(--primary-neon);
61
+ letter-spacing: 2px;
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 10px;
65
+ }
66
+
67
+ .anycoder-link {
68
+ font-family: sans-serif;
69
+ font-size: 0.8rem;
70
+ color: var(--accent-yellow);
71
+ text-decoration: none;
72
+ background: rgba(0,0,0,0.3);
73
+ padding: 5px 10px;
74
+ border-radius: 4px;
75
+ border: 1px solid var(--accent-yellow);
76
+ transition: all 0.3s ease;
77
+ }
78
+
79
+ .anycoder-link:hover {
80
+ background: var(--accent-yellow);
81
+ color: #000;
82
+ box-shadow: 0 0 10px var(--accent-yellow);
83
+ }
84
+
85
+ /* --- Main Layout --- */
86
+ main {
87
+ display: flex;
88
+ flex: 1;
89
+ flex-wrap: wrap;
90
+ padding: 2rem;
91
+ gap: 2rem;
92
+ max-width: 1600px;
93
+ margin: 0 auto;
94
+ width: 100%;
95
+ }
96
+
97
+ /* --- Controls Section --- */
98
+ aside.controls {
99
+ flex: 1;
100
+ min-width: 300px;
101
+ max-width: 400px;
102
+ background: var(--panel-bg);
103
+ border: 1px solid var(--secondary-neon);
104
+ border-radius: var(--border-radius);
105
+ padding: 1.5rem;
106
+ box-shadow: 0 0 10px rgba(0, 255, 255, 0.1);
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 1.5rem;
110
+ height: fit-content;
111
+ }
112
+
113
+ .control-group {
114
+ display: flex;
115
+ flex-direction: column;
116
+ gap: 0.5rem;
117
+ }
118
+
119
+ label {
120
+ font-size: 1.2rem;
121
+ color: var(--secondary-neon);
122
+ text-transform: uppercase;
123
+ }
124
+
125
+ input[type="text"],
126
+ select {
127
+ background: #000;
128
+ border: 1px solid var(--primary-neon);
129
+ color: #fff;
130
+ padding: 10px;
131
+ font-family: var(--font-ui);
132
+ font-size: 1.1rem;
133
+ outline: none;
134
+ transition: 0.3s;
135
+ }
136
+
137
+ input[type="text"]:focus {
138
+ box-shadow: 0 0 8px var(--primary-neon);
139
+ }
140
+
141
+ input[type="range"] {
142
+ width: 100%;
143
+ accent-color: var(--primary-neon);
144
+ height: 6px;
145
+ background: #000;
146
+ outline: none;
147
+ -webkit-appearance: none;
148
+ border-radius: 3px;
149
+ }
150
+
151
+ input[type="range"]::-webkit-slider-thumb {
152
+ -webkit-appearance: none;
153
+ width: 20px;
154
+ height: 20px;
155
+ background: var(--secondary-neon);
156
+ border-radius: 50%;
157
+ cursor: pointer;
158
+ box-shadow: 0 0 10px var(--secondary-neon);
159
+ }
160
+
161
+ input[type="color"] {
162
+ width: 100%;
163
+ height: 40px;
164
+ border: none;
165
+ background: none;
166
+ cursor: pointer;
167
+ }
168
+
169
+ .btn {
170
+ background: linear-gradient(45deg, var(--primary-neon), #b000b0);
171
+ color: #fff;
172
+ border: none;
173
+ padding: 12px 20px;
174
+ font-family: var(--font-display);
175
+ font-size: 1.2rem;
176
+ cursor: pointer;
177
+ transition: transform 0.1s, box-shadow 0.3s;
178
+ text-transform: uppercase;
179
+ letter-spacing: 1px;
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ gap: 10px;
184
+ border-radius: 4px;
185
+ }
186
+
187
+ .btn:hover {
188
+ box-shadow: 0 0 15px var(--primary-neon);
189
+ transform: scale(1.02);
190
+ }
191
+
192
+ .btn:active {
193
+ transform: scale(0.98);
194
+ }
195
+
196
+ .btn.secondary {
197
+ background: transparent;
198
+ border: 2px solid var(--secondary-neon);
199
+ color: var(--secondary-neon);
200
+ }
201
+
202
+ .btn.secondary:hover {
203
+ background: rgba(0, 255, 255, 0.1);
204
+ box-shadow: 0 0 10px var(--secondary-neon);
205
+ }
206
+
207
+ /* --- Preview Section --- */
208
+ section.preview-area {
209
+ flex: 2;
210
+ min-width: 300px;
211
+ display: flex;
212
+ flex-direction: column;
213
+ align-items: center;
214
+ justify-content: center;
215
+ background: #000;
216
+ border: 4px solid #333;
217
+ border-radius: 20px; /* CRT Shape */
218
+ position: relative;
219
+ overflow: hidden;
220
+ box-shadow: inset 0 0 50px rgba(0,0,0,0.8);
221
+ }
222
+
223
+ canvas {
224
+ width: 100%;
225
+ height: auto;
226
+ max-height: 70vh;
227
+ display: block;
228
+ /* CRT Curvature Effect */
229
+ filter: contrast(1.2) brightness(1.1) saturate(1.2);
230
+ }
231
+
232
+ /* CRT Overlay Effects */
233
+ .scanlines {
234
+ position: absolute;
235
+ top: 0;
236
+ left: 0;
237
+ width: 100%;
238
+ height: 100%;
239
+ background: linear-gradient(
240
+ to bottom,
241
+ rgba(255,255,255,0),
242
+ rgba(255,255,255,0) 50%,
243
+ rgba(0,0,0,0.2) 50%,
244
+ rgba(0,0,0,0.2)
245
+ );
246
+ background-size: 100% 4px;
247
+ pointer-events: none;
248
+ z-index: 5;
249
+ }
250
+
251
+ .screen-glow {
252
+ position: absolute;
253
+ top: 0;
254
+ left: 0;
255
+ width: 100%;
256
+ height: 100%;
257
+ box-shadow: inset 0 0 80px rgba(0,0,0,0.7);
258
+ pointer-events: none;
259
+ z-index: 6;
260
+ border-radius: 16px;
261
+ }
262
+
263
+ /* --- Toast Notification --- */
264
+ .toast {
265
+ position: fixed;
266
+ bottom: 20px;
267
+ left: 50%;
268
+ transform: translateX(-50%) translateY(100px);
269
+ background: var(--secondary-neon);
270
+ color: #000;
271
+ padding: 10px 20px;
272
+ border-radius: 4px;
273
+ font-weight: bold;
274
+ font-family: var(--font-ui);
275
+ opacity: 0;
276
+ transition: all 0.4s ease;
277
+ z-index: 100;
278
+ box-shadow: 0 0 15px var(--secondary-neon);
279
+ }
280
+
281
+ .toast.show {
282
+ transform: translateX(-50%) translateY(0);
283
+ opacity: 1;
284
+ }
285
+
286
+ /* --- Responsive Design --- */
287
+ @media (max-width: 768px) {
288
+ main {
289
+ flex-direction: column-reverse; /* Controls below preview on mobile */
290
+ padding: 1rem;
291
+ }
292
+
293
+ .logo {
294
+ font-size: 1.5rem;
295
+ }
296
+
297
+ header {
298
+ padding: 1rem;
299
+ }
300
+ }
301
+ </style>
302
+ </head>
303
+ <body>
304
+
305
+ <header>
306
+ <div class="logo">
307
+ <i class="fas fa-video"></i>
308
+ VIPID ABC 1977
309
+ </div>
310
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
311
+ Built with anycoder
312
+ </a>
313
+ </header>
314
+
315
+ <main>
316
+ <!-- Controls Panel -->
317
+ <aside class="controls">
318
+ <h3><i class="fas fa-sliders-h"></i> Template Settings</h3>
319
+
320
+ <div class="control-group">
321
+ <label for="textInput">Main Text</label>
322
+ <input type="text" id="textInput" value="VIPID" maxlength="15">
323
+ </div>
324
+
325
+ <div class="control-group">
326
+ <label for="subTextInput">Subtitle</label>
327
+ <input type="text" id="subTextInput" value="EST. 1977" maxlength="20">
328
+ </div>
329
+
330
+ <div class="control-group">
331
+ <label for="effectSelect">Animation Style</label>
332
+ <select id="effectSelect">
333
+ <option value="pulse">Neon Pulse</option>
334
+ <option value="scroll">Retro Scroll</option>
335
+ <option value="bounce">Disco Bounce</option>
336
+ <option value="glitch">Cyber Glitch</option>
337
+ </select>
338
+ </div>
339
+
340
+ <div class="control-group">
341
+ <label>Colors</label>
342
+ <div style="display: flex; gap: 10px;">
343
+ <div>
344
+ <label style="font-size: 0.9rem;">Primary</label>
345
+ <input type="color" id="primaryColor" value="#ff00ff">
346
+ </div>
347
+ <div>
348
+ <label style="font-size: 0.9rem;">Secondary</label>
349
+ <input type="color" id="secondaryColor" value="#00ffff">
350
+ </div>
351
+ </div>
352
+ </div>
353
+
354
+ <div class="control-group">
355
+ <label for="speedRange">Animation Speed</label>
356
+ <input type="range" id="speedRange" min="1" max="10" value="5">
357
+ </div>
358
+
359
+ <div class="control-group">
360
+ <label for="distortionRange">VHS Distortion</label>
361
+ <input type="range" id="distortionRange" min="0" max="100" value="30">
362
+ </div>
363
+
364
+ <div style="margin-top: 1rem; display: flex; gap: 10px;">
365
+ <button class="btn secondary" id="randomBtn" style="flex: 1;">
366
+ <i class="fas fa-random"></i> Randomize
367
+ </button>
368
+ </div>
369
+
370
+ <div style="margin-top: auto; border-top: 1px solid #333; padding-top: 1rem;">
371
+ <button class="btn" id="exportBtn">
372
+ <i class="fas fa-download"></i> Export Video (5s)
373
+ </button>
374
+ </div>
375
+ </aside>
376
+
377
+ <!-- Preview Area -->
378
+ <section class="preview-area">
379
+ <canvas id="videoCanvas" width="800" height="450"></canvas>
380
+ <div class="scanlines"></div>
381
+ <div class="screen-glow"></div>
382
+ </section>
383
+ </main>
384
+
385
+ <div id="toast" class="toast">Video Exported Successfully!</div>
386
+
387
+ <script>
388
+ /**
389
+ * VIPID ABC 1977 - Retro Video Maker Logic
390
+ * Uses HTML5 Canvas for real-time rendering and MediaRecorder for export.
391
+ */
392
+
393
+ const canvas = document.getElementById('videoCanvas');
394
+ const ctx = canvas.getContext('2d');
395
+
396
+ // UI Elements
397
+ const textInput = document.getElementById('textInput');
398
+ const subTextInput = document.getElementById('subTextInput');
399
+ const effectSelect = document.getElementById('effectSelect');
400
+ const primaryColor = document.getElementById('primaryColor');
401
+ const secondaryColor = document.getElementById('secondaryColor');
402
+ const speedRange = document.getElementById('speedRange');
403
+ const distortionRange = document.getElementById('distortionRange');
404
+ const randomBtn = document.getElementById('randomBtn');
405
+ const exportBtn = document.getElementById('exportBtn');
406
+ const toast = document.getElementById('toast');
407
+
408
+ // State
409
+ let state = {
410
+ text: "VIPID",
411
+ subText: "EST. 1977",
412
+ effect: "pulse",
413
+ color1: "#ff00ff",
414
+ color2: "#00ffff",
415
+ speed: 5,
416
+ distortion: 30,
417
+ frame: 0
418
+ };
419
+
420
+ // Animation Loop ID
421
+ let animationId;
422
+ let isRecording = false;
423
+
424
+ // --- Initialization ---
425
+ function init() {
426
+ updateStateFromDOM();
427
+ addEventListeners();
428
+ animate();
429
+ }
430
+
431
+ function addEventListeners() {
432
+ const inputs = [textInput, subTextInput, effectSelect, primaryColor, secondaryColor, speedRange, distortionRange];
433
+ inputs.forEach(input => {
434
+ input.addEventListener('input', updateStateFromDOM);
435
+ });
436
+
437
+ randomBtn.addEventListener('click', randomizeSettings);
438
+ exportBtn.addEventListener('click', exportVideo);
439
+ }
440
+
441
+ function updateStateFromDOM() {
442
+ state.text = textInput.value.toUpperCase();
443
+ state.subText = subTextInput.value.toUpperCase();
444
+ state.effect = effectSelect.value;
445
+ state.color1 = primaryColor.value;
446
+ state.color2 = secondaryColor.value;
447
+ state.speed = parseInt(speedRange.value);
448
+ state.distortion = parseInt(distortionRange.value);
449
+ }
450
+
451
+ function randomizeSettings() {
452
+ const colors = ['#ff00ff', '#00ffff', '#ffff00', '#ff0000', '#00ff00', '#ffffff'];
453
+ state.color1 = colors[Math.floor(Math.random() * colors.length)];
454
+ state.color2 = colors[Math.floor(Math.random() * colors.length)];
455
+ state.effect = ['pulse', 'scroll', 'bounce', 'glitch'][Math.floor(Math.random() * 4)];
456
+ state.speed = Math.floor(Math.random() * 10) + 1;
457
+
458
+ // Update DOM
459
+ primaryColor.value = state.color1;
460
+ secondaryColor.value = state.color2;
461
+ effectSelect.value = state.effect;
462
+ speedRange.value = state.speed;
463
+ }
464
+
465
+ // --- Drawing Engine ---
466
+
467
+ function drawGrid(t) {
468
+ const w = canvas.width;
469
+ const h = canvas.height;
470
+ const horizon = h * 0.4;
471
+
472
+ // Background Gradient
473
+ const bgGrad = ctx.createLinearGradient(0, 0, 0, h);
474
+ bgGrad.addColorStop(0, '#050011');
475
+ bgGrad.addColorStop(0.5, '#2b0042');
476
+ bgGrad.addColorStop(1, '#000000');
477
+ ctx.fillStyle = bgGrad;
478
+ ctx.fillRect(0, 0, w, h);
479
+
480
+ // Draw Sun
481
+ ctx.save();
482
+ ctx.translate(w/2, horizon);
483
+ ctx.beginPath();
484
+ const sunGrad = ctx.createLinearGradient(0, -50, 0, 50);
485
+ sunGrad.addColorStop(0, state.color1);
486
+ sunGrad.addColorStop(1, '#ffff00');
487
+ ctx.fillStyle = sunGrad;
488
+ ctx.arc(0, 0, 80, 0, Math.PI * 2);
489
+ ctx.fill();
490
+
491
+ // Sun Stripes
492
+ ctx.fillStyle = bgGrad; // Mask with bg
493
+ for(let i=0; i<10; i++) {
494
+ let y = -60 + (i * 12);
495
+ let height = 4 + (i * 0.8);
496
+ ctx.fillRect(-90, y, 180, height);
497
+ }
498
+ ctx.restore();
499
+
500
+ // Perspective Grid
501
+ ctx.strokeStyle = state.color2;
502
+ ctx.lineWidth = 1;
503
+ ctx.globalAlpha = 0.5;
504
+
505
+ // Vertical Lines (Perspective)
506
+ const centerX = w / 2;
507
+ for (let i = -10; i <= 10; i++) {
508
+ ctx.beginPath();
509
+ ctx.moveTo(centerX + i * 40, horizon);
510
+ ctx.lineTo(centerX + i * 150, h);
511
+ ctx.stroke();
512
+ }
513
+
514
+ // Horizontal Lines (Moving)
515
+ const speed = state.speed * 0.5;
516
+ const offset = (t * speed) % 40;
517
+ for (let i = 0; i < (h - horizon) / 20 + 5; i++) {
518
+ let y = horizon + Math.pow(i * 4, 1.5) + offset;
519
+ if (y < h) {
520
+ ctx.beginPath();
521
+ ctx.moveTo(0, y);
522
+ ctx.lineTo(w, y);
523
+ ctx.stroke();
524
+ }
525
+ }
526
+ ctx.globalAlpha = 1.0;
527
+ }
528
+
529
+ function drawText(t) {
530
+ const w = canvas.width;
531
+ const h = canvas.height;
532
+ const centerX = w / 2;
533
+ const centerY = h / 2;
534
+
535
+ ctx.textAlign = 'center';
536
+ ctx.textBaseline = 'middle';
537
+
538
+ // Text Shadow / Glow
539
+ ctx.shadowColor = state.color1;
540
+ ctx.shadowBlur = 20;
541
+ ctx.fillStyle = '#fff';
542
+
543
+ // Font settings
544
+ ctx.font = `100px 'Monoton'`;
545
+
546
+ let x = centerX;
547
+ let y = centerY;
548
+ let scale = 1;
549
+
550
+ // Apply Animation Logic
551
+ if (state.effect === 'pulse') {
552
+ scale = 1 + Math.sin(t * 0.05 * state.speed) * 0.05;
553
+ } else if (state.effect === 'bounce') {
554
+ y = centerY + Math.abs(Math.sin(t * 0.1 * state.speed)) * -50;
555
+ } else if (state.effect === 'scroll') {
556
+ // Simple sinusoid movement
557
+ x = centerX + Math.sin(t * 0.05 * state.speed) * 100;
558
+ } else if (state.effect === 'glitch') {
559
+ if (Math.random() > 0.9) {
560
+ x = centerX + (Math.random() - 0.5) * 20;
561
+ scale = 1 + (Math.random() - 0.5) * 0.1;
562
+ }
563
+ }
564
+
565
+ ctx.save();
566
+ ctx.translate(x, y);
567
+ ctx.scale(scale, scale);
568
+
569
+ // Main Text
570
+ ctx.fillText(state.text, 0, 0);
571
+
572
+ // Subtitle
573
+ ctx.font = `30px 'VT323'`;
574
+ ctx.shadowBlur = 10;
575
+ ctx.shadowColor = state.color2;
576
+ ctx.fillStyle = state.color2;
577
+ ctx.fillText(state.subText, 0, 60);
578
+
579
+ ctx.restore();
580
+
581
+ // Reset Shadow
582
+ ctx.shadowBlur = 0;
583
+ }
584
+
585
+ function applyVHSEffects(t) {
586
+ const w = canvas.width;
587
+ const h = canvas.height;
588
+
589
+ // Scanlines
590
+ ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
591
+ for (let i = 0; i < h; i += 4) {
592
+ ctx.fillRect(0, i, w, 1);
593
+ }
594
+
595
+ // Noise / Grain
596
+ if (state.distortion > 0) {
597
+ const imageData = ctx.getImageData(0, 0, w, h);
598
+ const data = imageData.data;
599
+ const noiseStrength = state.distortion * 0.5;
600
+
601
+ for (let i = 0; i < data.length; i += 4) {
602
+ const grain = (Math.random() - 0.5) * noiseStrength;
603
+ data[i] += grain; // r
604
+ data[i+1] += grain; // g
605
+ data[i+2] += grain; // b
606
+ }
607
+ ctx.putImageData(imageData, 0, 0);
608
+
609
+ // RGB Split (Chromatic Aberration) based on distortion
610
+ if (state.distortion > 20) {
611
+ const offset = Math.floor(state.distortion / 10);
612
+ // Draw a slice of the canvas offset
613
+ try {
614
+ const slice = ctx.getImageData(offset, 0, w - offset, h);
615
+ ctx.putImageData(slice, 0, 0);
616
+
617
+ // Occasional horizontal tear
618
+ if(Math.random() > 0.95) {
619
+ const tearY = Math.random() * h;
620
+ const tearH = Math.random() * 20;
621
+ const tearSlice = ctx.getImageData(0, tearY, w, tearH);
622
+ ctx.putImageData(tearSlice, Math.random() * 10 - 5, tearY);
623
+ }
624
+ } catch(e) {
625
+ // Prevent crashing on edge cases
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ function animate() {
632
+ state.frame++;
633
+
634
+ // Clear Canvas
635
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
636
+
637
+ // 1. Draw Background
638
+ drawGrid(state.frame);
639
+
640
+ // 2. Draw Text
641
+ drawText(state.frame);
642
+
643
+ // 3. Post-Processing (VHS)
644
+ applyVHSEffects(state.frame);
645
+
646
+ animationId = requestAnimationFrame(animate);
647
+ }
648
+
649
+ // --- Export Logic ---
650
+ function showToast(message) {
651
+ toast.textContent = message;
652
+ toast.classList.add('show');
653
+ setTimeout(() => {
654
+ toast.classList.remove('show');
655
+ }, 3000);
656
+ }
657
+
658
+ function exportVideo() {
659
+ if (isRecording) return;
660
+ isRecording = true;
661
+ exportBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Recording...';
662
+
663
+ const stream = canvas.captureStream(30); // 30 FPS
664
+ const mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
665
+ const chunks = [];
666
+
667
+ mediaRecorder.ondataavailable = (e) => {
668
+ if (e.data.size > 0) chunks.push(e.data);
669
+ };
670
+
671
+ mediaRecorder.onstop = () => {
672
+ const blob = new Blob(chunks, { type: 'video/webm' });
673
+ const url = URL.createObjectURL(blob);
674
+ const a = document.createElement('a');
675
+ a.href = url;
676
+ a.download = `vipid-1977-${Date.now()}.webm`;
677
+ document.body.appendChild(a);
678
+ a.click();
679
+ document.body.removeChild(a);
680
+
681
+ showToast("Video Exported Successfully!");
682
+ isRecording = false;
683
+ exportBtn.innerHTML = '<i class="fas fa-download"></i> Export Video (5s)';
684
+ };
685
+
686
+ mediaRecorder.start();
687
+
688
+ // Record for 5 seconds
689
+ setTimeout(() => {
690
+ mediaRecorder.stop();
691
+ }, 5000);
692
+ }
693
+
694
+ // Start
695
+ init();
696
+
697
+ </script>
698
+ </body>
699
+ </html>