Ezequield commited on
Commit
35be40f
·
verified ·
1 Parent(s): fdbee22

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +542 -798
index.html CHANGED
@@ -3,983 +3,727 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Sprunki Custom Maker OC Phase 5</title>
7
- <!-- Import Google Font for a Creepy/Digital look -->
8
- <link href="https://fonts.googleapis.com/css2?family=Creepster&family=Roboto: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
- --bg-color: #0f0f11;
15
- --panel-bg: #1a1a1e;
16
- --accent-color: #76ff03; /* Toxic Green */
17
- --accent-hover: #64dd17;
18
- --text-color: #e0e0e0;
19
- --border-color: #333;
20
- --danger-color: #ff1744;
21
- --shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
22
- --font-display: 'Creepster', cursive;
23
- --font-body: 'Roboto', sans-serif;
 
24
  }
25
 
26
  * {
27
  box-sizing: border-box;
28
  margin: 0;
29
  padding: 0;
30
- -webkit-tap-highlight-color: transparent;
31
  }
32
 
33
  body {
34
- background-color: var(--bg-color);
35
- color: var(--text-color);
36
- font-family: var(--font-body);
 
 
 
37
  display: flex;
38
  flex-direction: column;
39
- min-height: 100vh;
40
- overflow-x: hidden;
41
  }
42
 
43
- /* --- Header --- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  header {
45
- background: linear-gradient(180deg, #000000 0%, #1a1a1e 100%);
46
  padding: 1rem 2rem;
 
47
  display: flex;
48
  justify-content: space-between;
49
  align-items: center;
50
- border-bottom: 2px solid var(--accent-color);
51
- box-shadow: 0 0 15px rgba(118, 255, 3, 0.2);
52
- position: relative;
53
- z-index: 10;
54
  }
55
 
56
  .brand {
57
- display: flex;
58
- flex-direction: column;
59
- }
60
-
61
- h1 {
62
  font-family: var(--font-display);
63
- font-size: 2.5rem;
64
- color: var(--accent-color);
65
- text-shadow: 2px 2px 0px #ff0000;
66
  letter-spacing: 2px;
67
- margin: 0;
68
- line-height: 1;
 
 
69
  }
70
 
71
- .subtitle {
72
- font-size: 0.9rem;
73
- color: #888;
74
- margin-top: 5px;
75
- text-transform: uppercase;
76
- letter-spacing: 1px;
77
  }
78
 
79
  .anycoder-link {
80
  font-size: 0.85rem;
81
- color: #fff;
82
  text-decoration: none;
83
- background: #333;
84
- padding: 5px 10px;
85
- border-radius: 4px;
86
  transition: all 0.3s ease;
87
- border: 1px solid #555;
88
  }
89
-
90
  .anycoder-link:hover {
91
- background: var(--accent-color);
92
- color: #000;
93
- border-color: var(--accent-color);
94
- box-shadow: 0 0 10px var(--accent-color);
95
  }
96
 
97
- /* --- Main Layout --- */
98
  main {
99
  flex: 1;
100
- display: grid;
101
- grid-template-columns: 1fr 400px;
102
- gap: 2rem;
103
- padding: 2rem;
104
- max-width: 1400px;
105
  margin: 0 auto;
106
  width: 100%;
 
 
 
 
107
  }
108
 
109
- @media (max-width: 900px) {
110
  main {
111
- grid-template-columns: 1fr;
112
- padding: 1rem;
113
  }
114
- h1 { font-size: 2rem; }
115
  }
116
 
117
- /* --- Preview Section (Left) --- */
118
- .preview-area {
119
- background: var(--panel-bg);
120
- border-radius: 12px;
121
- border: 1px solid var(--border-color);
 
 
 
 
122
  display: flex;
123
- flex-direction: column;
124
  align-items: center;
125
  justify-content: center;
126
- position: relative;
127
- padding: 2rem;
128
- box-shadow: var(--shadow);
129
- min-height: 500px;
130
- background-image: radial-gradient(circle at center, #2a2a30 0%, #1a1a1e 70%);
131
  }
132
 
133
- canvas {
134
- max-width: 100%;
135
- max-height: 500px;
136
- filter: drop-shadow(0 0 20px rgba(0,0,0,0.5));
137
- transition: transform 0.2s ease;
138
  }
139
 
140
- .canvas-controls {
141
- margin-top: 2rem;
 
 
 
 
 
 
 
 
 
142
  display: flex;
143
  gap: 1rem;
144
- flex-wrap: wrap;
145
- justify-content: center;
146
  }
147
 
148
- .action-btn {
149
- background: #333;
150
- color: white;
151
- border: 1px solid var(--border-color);
152
- padding: 10px 20px;
153
- border-radius: 6px;
 
154
  cursor: pointer;
155
- font-family: var(--font-body);
156
- font-weight: bold;
157
  display: flex;
158
  align-items: center;
159
- gap: 8px;
160
  transition: all 0.2s;
161
  }
162
 
163
- .action-btn:hover {
164
- background: #444;
165
- transform: translateY(-2px);
166
  }
167
 
168
- .action-btn.primary {
169
- background: var(--accent-color);
170
- color: #000;
171
- border-color: var(--accent-color);
172
- }
173
-
174
- .action-btn.primary:hover {
175
- background: var(--accent-hover);
176
- box-shadow: 0 0 15px rgba(118, 255, 3, 0.4);
177
  }
178
 
179
- /* --- Customizer Section (Right) --- */
180
- .customizer-panel {
181
- background: var(--panel-bg);
182
- border-radius: 12px;
183
- border: 1px solid var(--border-color);
184
  display: flex;
185
  flex-direction: column;
186
- overflow: hidden;
187
- box-shadow: var(--shadow);
188
- height: fit-content;
189
  }
190
 
191
- /* Tabs */
192
- .tabs {
193
- display: flex;
194
- background: #111;
195
- overflow-x: auto;
196
- scrollbar-width: none;
 
 
 
197
  }
198
- .tabs::-webkit-scrollbar { display: none; }
199
 
200
- .tab-btn {
201
- background: transparent;
202
- border: none;
203
- color: #888;
204
- padding: 1rem;
205
- cursor: pointer;
206
- font-weight: bold;
207
- text-transform: uppercase;
208
- font-size: 0.85rem;
209
- border-bottom: 3px solid transparent;
210
- transition: all 0.3s;
211
- white-space: nowrap;
212
  }
213
 
214
- .tab-btn:hover {
215
- color: #fff;
216
- background: rgba(255,255,255,0.05);
 
217
  }
218
 
219
- .tab-btn.active {
220
- color: var(--accent-color);
221
- border-bottom-color: var(--accent-color);
222
- background: rgba(118, 255, 3, 0.05);
223
  }
224
 
225
- /* Items Grid */
226
- .items-container {
227
- padding: 1.5rem;
228
- flex: 1;
229
  }
230
 
231
- .category-title {
232
- margin-bottom: 1rem;
233
- font-size: 1.1rem;
234
- color: #fff;
235
- border-left: 4px solid var(--accent-color);
236
- padding-left: 10px;
237
  }
238
 
239
- .grid {
240
- display: grid;
241
- grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
242
- gap: 10px;
243
  }
244
 
245
- .grid-item {
246
- aspect-ratio: 1;
247
- background: #25252a;
248
- border: 2px solid #333;
249
- border-radius: 8px;
250
- cursor: pointer;
251
  display: flex;
 
252
  align-items: center;
253
- justify-content: center;
254
- transition: all 0.2s;
255
- position: relative;
256
- overflow: hidden;
257
  }
258
 
259
- .grid-item:hover {
260
- border-color: #666;
261
- transform: scale(1.05);
 
262
  }
263
 
264
- .grid-item.selected {
265
- border-color: var(--accent-color);
266
- box-shadow: 0 0 10px rgba(118, 255, 3, 0.2);
267
- background: #2a2a30;
268
  }
269
 
270
- .grid-item canvas {
271
- max-width: 90%;
272
- max-height: 90%;
273
- filter: none; /* Remove drop shadow for thumbnails */
 
 
 
 
274
  }
275
 
276
- /* Color Picker */
277
- .color-picker-container {
278
- margin-top: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
279
  padding: 1rem;
280
- background: #25252a;
281
- border-radius: 8px;
282
  }
283
-
284
- .color-label {
285
- display: block;
286
- margin-bottom: 0.5rem;
287
  font-size: 0.9rem;
288
- color: #aaa;
 
 
 
289
  }
290
 
291
- .color-options {
 
 
 
292
  display: flex;
293
- gap: 10px;
294
- flex-wrap: wrap;
295
  }
296
 
297
- .color-swatch {
298
- width: 30px;
299
- height: 30px;
300
- border-radius: 50%;
301
- cursor: pointer;
302
- border: 2px solid transparent;
303
- transition: transform 0.2s;
304
  }
 
 
 
305
 
306
- .color-swatch:hover {
307
- transform: scale(1.2);
 
 
 
 
 
 
 
308
  }
309
 
310
- .color-swatch.active {
311
- border-color: #fff;
312
- box-shadow: 0 0 0 2px var(--accent-color);
 
 
 
 
 
 
 
 
 
 
313
  }
314
 
315
- /* --- Footer --- */
316
  footer {
317
  text-align: center;
318
- padding: 1.5rem;
319
- color: #555;
320
  font-size: 0.8rem;
 
321
  margin-top: auto;
322
  }
323
 
324
- /* --- Animations --- */
325
- @keyframes float {
326
- 0% { transform: translateY(0px); }
327
- 50% { transform: translateY(-5px); }
328
- 100% { transform: translateY(0px); }
329
  }
330
 
331
- @keyframes glitch {
332
- 0% { text-shadow: 2px 2px 0px #ff0000; }
333
- 25% { text-shadow: -2px -2px 0px #00ffff; }
334
- 50% { text-shadow: 2px -2px 0px #ff0000; }
335
- 75% { text-shadow: -2px 2px 0px #00ffff; }
336
- 100% { text-shadow: 2px 2px 0px #ff0000; }
337
  }
338
 
339
- .glitch-hover:hover {
340
- animation: glitch 0.3s infinite;
341
  }
342
 
343
- /* Notification Toast */
344
- .toast {
345
- position: fixed;
346
- bottom: 20px;
347
- right: 20px;
348
- background: var(--accent-color);
349
- color: #000;
350
- padding: 10px 20px;
351
- border-radius: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  font-weight: bold;
353
- transform: translateY(100px);
354
- opacity: 0;
355
- transition: all 0.3s ease;
356
- z-index: 100;
357
- box-shadow: 0 5px 15px rgba(0,0,0,0.5);
358
  }
359
- .toast.show {
360
- transform: translateY(0);
361
- opacity: 1;
362
  }
 
 
 
363
 
364
  </style>
365
  </head>
366
  <body>
367
 
 
 
 
 
368
  <header>
369
  <div class="brand">
370
- <h1 class="glitch-hover">SPRUNKI MAKER</h1>
371
- <span class="subtitle">OC Phase 5 Edition</span>
 
 
 
 
 
372
  </div>
 
373
  <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
374
- Built with anycoder <i class="fas fa-external-link-alt"></i>
375
  </a>
376
  </header>
377
 
378
  <main>
379
- <!-- Left Column: Preview -->
380
- <section class="preview-area">
381
- <canvas id="mainCanvas" width="500" height="500"></canvas>
 
 
 
 
 
 
 
 
382
 
383
- <div class="canvas-controls">
384
- <button class="action-btn" id="randomizeBtn">
385
- <i class="fas fa-dice"></i> Randomize
386
- </button>
387
- <button class="action-btn" id="clearBtn">
388
- <i class="fas fa-trash"></i> Clear
389
  </button>
390
- <button class="action-btn primary" id="downloadBtn">
391
- <i class="fas fa-download"></i> Save OC
 
392
  </button>
 
393
  </div>
394
  </section>
395
 
396
- <!-- Right Column: Customizer -->
397
- <section class="customizer-panel">
398
- <div class="tabs" id="tabsContainer">
399
- <!-- Tabs injected by JS -->
 
 
 
 
 
 
 
400
  </div>
401
 
402
- <div class="items-container">
403
- <h3 class="category-title" id="categoryTitle">Select a Category</h3>
404
- <div class="grid" id="itemsGrid">
405
- <!-- Items injected by JS -->
 
406
  </div>
407
-
408
- <!-- Color Picker (Only shows for specific categories) -->
409
- <div class="color-picker-container" id="colorPickerContainer" style="display:none;">
410
- <span class="color-label" id="colorLabel">Skin Tone</span>
411
- <div class="color-options" id="colorOptions">
412
- <!-- Colors injected by JS -->
413
- </div>
414
  </div>
415
  </div>
 
 
 
 
 
 
 
 
 
 
 
416
  </section>
417
  </main>
418
 
419
  <footer>
420
- <p>&copy; 2023 Sprunki Fan Tool. Not affiliated with Incredibox original creators.</p>
 
421
  </footer>
422
 
423
- <div class="toast" id="toast">Image Saved!</div>
424
 
425
  <script>
426
- /**
427
- * SPRUNKI MAKER ENGINE
428
- * Procedural drawing logic to create Sprunki-style characters without external assets.
429
- */
430
-
431
- const canvas = document.getElementById('mainCanvas');
432
- const ctx = canvas.getContext('2d');
433
- const dpr = window.devicePixelRatio || 1;
434
-
435
- // Fix for high DPI screens
436
- canvas.width = 500 * dpr;
437
- canvas.height = 500 * dpr;
438
- ctx.scale(dpr, dpr);
439
- canvas.style.width = '500px';
440
- canvas.style.height = '500px';
441
-
442
- // --- Configuration & State ---
443
- const CATEGORIES = ['Base', 'Eyes', 'Mouth', 'Hat', 'Effect', 'Background'];
444
-
445
- // Colors palette
446
- const COLORS = {
447
- skin: ['#e0ac69', '#8d5524', '#c68642', '#3d2314', '#9e9e9e', '#2c3e50', '#4a235a', '#212121'],
448
- eyes: ['#ffffff', '#ff0000', '#00ff00', '#ffff00', '#000000', '#76ff03'],
449
- misc: ['#ffffff', '#ff0000', '#000000', '#333333', '#76ff03']
450
- };
451
-
452
- // Character State
453
- let currentCharacter = {
454
- Base: { id: 0, color: COLORS.skin[0] },
455
- Eyes: { id: 0, color: COLORS.eyes[0] },
456
- Mouth: { id: 0, color: '#000000' },
457
- Hat: { id: -1, color: '#333333' }, // -1 means none
458
- Effect: { id: -1, color: '#ffffff' },
459
- Background: { id: 0, color: '#1a1a1e' }
460
- };
461
-
462
- let activeTab = 'Base';
463
-
464
- // --- Drawing Functions (Procedural Assets) ---
465
-
466
- const Helpers = {
467
- drawCircle: (x, y, r, color) => {
468
- ctx.fillStyle = color;
469
- ctx.beginPath();
470
- ctx.arc(x, y, r, 0, Math.PI * 2);
471
- ctx.fill();
472
  },
473
- drawRect: (x, y, w, h, color, rotation = 0) => {
474
- ctx.save();
475
- ctx.translate(x, y);
476
- ctx.rotate(rotation);
477
- ctx.fillStyle = color;
478
- ctx.fillRect(-w / 2, -h / 2, w, h);
479
- ctx.restore();
480
  },
481
- drawTriangle: (x, y, size, color, direction = 'up') => {
482
- ctx.fillStyle = color;
483
- ctx.beginPath();
484
- if (direction === 'up') {
485
- ctx.moveTo(x, y - size);
486
- ctx.lineTo(x + size, y + size);
487
- ctx.lineTo(x - size, y + size);
488
- } else {
489
- ctx.moveTo(x, y + size);
490
- ctx.lineTo(x + size, y - size);
491
- ctx.lineTo(x - size, y - size);
492
- }
493
- ctx.closePath();
494
- ctx.fill();
495
  }
496
  };
497
 
498
- const Drawers = {
499
- Base: {
500
- count: 5,
501
- draw: (id, color) => {
502
- // Standard Sprunki shape is usually a pear/blob
503
- ctx.fillStyle = color;
504
- ctx.beginPath();
505
-
506
- if (id === 0) { // Normal
507
- ctx.moveTo(250, 100); // Top head
508
- ctx.bezierCurveTo(350, 100, 380, 200, 380, 250); // Right shoulder
509
- ctx.bezierCurveTo(380, 350, 300, 420, 250, 420); // Bottom right
510
- ctx.bezierCurveTo(200, 420, 120, 350, 120, 250); // Bottom left
511
- ctx.bezierCurveTo(120, 200, 150, 100, 250, 100); // Left shoulder
512
- } else if (id === 1) { // Round
513
- ctx.arc(250, 250, 130, 0, Math.PI * 2);
514
- } else if (id === 2) { // Tall
515
- ctx.moveTo(250, 80);
516
- ctx.bezierCurveTo(340, 80, 360, 200, 360, 250);
517
- ctx.bezierCurveTo(360, 380, 300, 430, 250, 430);
518
- ctx.bezierCurveTo(200, 430, 140, 380, 140, 250);
519
- ctx.bezierCurveTo(140, 200, 160, 80, 250, 80);
520
- } else if (id === 3) { // Wide
521
- ctx.moveTo(250, 120);
522
- ctx.bezierCurveTo(400, 120, 420, 220, 420, 250);
523
- ctx.bezierCurveTo(420, 320, 300, 380, 250, 380);
524
- ctx.bezierCurveTo(200, 380, 80, 320, 80, 250);
525
- ctx.bezierCurveTo(80, 220, 100, 120, 250, 120);
526
- } else if (id === 4) { // Sharp/Pointy
527
- ctx.moveTo(250, 60);
528
- ctx.lineTo(350, 250);
529
- ctx.bezierCurveTo(350, 350, 300, 420, 250, 420);
530
- ctx.bezierCurveTo(200, 420, 150, 350, 150, 250);
531
- ctx.lineTo(250, 60);
532
- }
533
-
534
- ctx.fill();
535
- // Add subtle shading
536
- ctx.fillStyle = 'rgba(0,0,0,0.1)';
537
- ctx.beginPath();
538
- ctx.arc(280, 250, 100, 0, Math.PI * 2);
539
- ctx.fill();
540
- }
541
- },
542
- Eyes: {
543
- count: 8,
544
- draw: (id, color) => {
545
- ctx.fillStyle = color;
546
- ctx.strokeStyle = '#000';
547
- ctx.lineWidth = 2;
548
-
549
- const leftX = 210;
550
- const rightX = 290;
551
- const y = 220;
552
-
553
- if (id === 0) { // Dots
554
- Helpers.drawCircle(leftX, y, 10, color);
555
- Helpers.drawCircle(rightX, y, 10, color);
556
- } else if (id === 1) { // Hollow
557
- ctx.beginPath(); ctx.arc(leftX, y, 12, 0, Math.PI*2); ctx.stroke();
558
- ctx.beginPath(); ctx.arc(rightX, y, 12, 0, Math.PI*2); ctx.stroke();
559
- } else if (id === 2) { // X eyes (Horror)
560
- ctx.lineWidth = 4;
561
- ctx.beginPath(); ctx.moveTo(leftX-10, y-10); ctx.lineTo(leftX+10, y+10); ctx.stroke();
562
- ctx.beginPath(); ctx.moveTo(leftX+10, y-10); ctx.lineTo(leftX-10, y+10); ctx.stroke();
563
- ctx.beginPath(); ctx.moveTo(rightX-10, y-10); ctx.lineTo(rightX+10, y+10); ctx.stroke();
564
- ctx.beginPath(); ctx.moveTo(rightX+10, y-10); ctx.lineTo(rightX-10, y+10); ctx.stroke();
565
- } else if (id === 3) { // Wide/Crazy
566
- ctx.beginPath();
567
- ctx.ellipse(leftX, y, 18, 8, 0, 0, Math.PI*2);
568
- ctx.ellipse(rightX, y, 18, 8, 0, 0, Math.PI*2);
569
- ctx.fill();
570
- // Pupils
571
- ctx.fillStyle = 'black';
572
- Helpers.drawCircle(leftX, y, 3, 'black');
573
- Helpers.drawCircle(rightX, y, 3, 'black');
574
- } else if (id === 4) { // One Eye (Cyclops)
575
- Helpers.drawCircle(250, y, 20, color);
576
- ctx.fillStyle = 'black';
577
- Helpers.drawCircle(250, y, 5, 'black');
578
- } else if (id === 5) { // Visor
579
- ctx.fillStyle = color;
580
- ctx.fillRect(190, y-10, 120, 20);
581
- // Glint
582
- ctx.fillStyle = 'rgba(255,255,255,0.8)';
583
- ctx.fillRect(200, y-5, 40, 5);
584
- } else if (id === 6) { // Spiral
585
- // Simple spiral approximation
586
- ctx.strokeStyle = color;
587
- [leftX, rightX].forEach(x => {
588
- ctx.beginPath();
589
- ctx.arc(x, y, 15, 0, Math.PI*2);
590
- ctx.stroke();
591
- ctx.beginPath();
592
- ctx.arc(x, y, 8, 0, Math.PI*2);
593
- ctx.stroke();
594
- ctx.fillStyle = color;
595
- Helpers.drawCircle(x, y, 2, color);
596
- });
597
- } else if (id === 7) { // Happy Arcs
598
- ctx.strokeStyle = color;
599
- ctx.lineWidth = 4;
600
- ctx.beginPath(); ctx.arc(leftX, y, 10, Math.PI, 0); ctx.stroke();
601
- ctx.beginPath(); ctx.arc(rightX, y, 10, Math.PI, 0); ctx.stroke();
602
- }
603
- }
604
- },
605
- Mouth: {
606
- count: 7,
607
- draw: (id, color) => {
608
- ctx.fillStyle = color;
609
- ctx.strokeStyle = color;
610
- const x = 250;
611
- const y = 290;
612
-
613
- if (id === 0) { // Smile
614
- ctx.beginPath();
615
- ctx.arc(x, y, 20, 0, Math.PI);
616
- ctx.fill();
617
- } else if (id === 1) // Straight line
618
- {
619
- ctx.fillRect(220, y, 60, 5);
620
- } else if (id === 2) // Open O
621
- {
622
- ctx.beginPath();
623
- ctx.arc(x, y, 15, 0, Math.PI*2);
624
- ctx.fill();
625
- ctx.fillStyle = '#fff'; // Teeth hint
626
- ctx.fillRect(240, y-2, 20, 4);
627
- } else if (id === 3) // ZigZag (Horror)
628
- {
629
- ctx.beginPath();
630
- ctx.moveTo(220, y);
631
- ctx.lineTo(230, y+10);
632
- ctx.lineTo(240, y-5);
633
- ctx.lineTo(250, y+10);
634
- ctx.lineTo(260, y-5);
635
- ctx.lineTo(270, y+10);
636
- ctx.lineTo(280, y);
637
- ctx.lineWidth = 3;
638
- ctx.stroke();
639
- } else if (id === 4) // Frown
640
- {
641
- ctx.beginPath();
642
- ctx.arc(x, y+20, 20, Math.PI, 0);
643
- ctx.fill();
644
- } else if (id === 5) // Stitches
645
- {
646
- ctx.strokeStyle = color;
647
- ctx.lineWidth = 2;
648
- ctx.beginPath();
649
- ctx.moveTo(220, y+10); ctx.lineTo(280, y+10);
650
- ctx.stroke();
651
- // Vertical stitches
652
- for(let i=0; i<5; i++) {
653
- ctx.beginPath();
654
- ctx.moveTo(230 + (i*12), y+5);
655
- ctx.lineTo(230 + (i*12), y+15);
656
- ctx.stroke();
657
- }
658
- } else if (id === 6) // Tongue out
659
- {
660
- ctx.beginPath();
661
- ctx.arc(x, y, 10, 0, Math.PI);
662
- ctx.fill();
663
- ctx.fillStyle = '#d32f2f'; // Red tongue
664
- ctx.beginPath();
665
- ctx.ellipse(x, y+15, 8, 12, 0, 0, Math.PI*2);
666
- ctx.fill();
667
- }
668
- }
669
- },
670
- Hat: {
671
- count: 5,
672
- draw: (id, color) => {
673
- ctx.fillStyle = color;
674
- const cx = 250;
675
-
676
- if (id === 0) { // Top Hat
677
- ctx.fillRect(cx-40, 60, 80, 80);
678
- ctx.fillRect(cx-60, 140, 120, 15);
679
- } else if (id === 1) { // Horns
680
- ctx.beginPath();
681
- ctx.moveTo(180, 120);
682
- ctx.lineTo(150, 40);
683
- ctx.lineTo(130, 120);
684
- ctx.fill();
685
- ctx.beginPath();
686
- ctx.moveTo(320, 120);
687
- ctx.lineTo(350, 40);
688
- ctx.lineTo(370, 120);
689
- ctx.fill();
690
- } else if (id === 2) { // Crown
691
- ctx.beginPath();
692
- ctx.moveTo(cx-50, 150);
693
- ctx.lineTo(cx-50, 80);
694
- ctx.lineTo(cx-20, 110);
695
- ctx.lineTo(cx, 60);
696
- ctx.lineTo(cx+20, 110);
697
- ctx.lineTo(cx+50, 80);
698
- ctx.lineTo(cx+50, 150);
699
- ctx.fill();
700
- } else if (id === 3) { // Headphones
701
- ctx.strokeStyle = color;
702
- ctx.lineWidth = 15;
703
- ctx.beginPath(); ctx.arc(cx, 220, 90, Math.PI, 0); ctx.stroke();
704
- ctx.fillStyle = color;
705
- ctx.fillRect(cx-105, 180, 20, 60); // Left cup
706
- ctx.fillRect(cx+85, 180, 20, 60); // Right cup
707
- } else if (id === 4) { // Halo
708
- ctx.strokeStyle = '#ffff00';
709
- ctx.lineWidth = 5;
710
- ctx.beginPath();
711
- ctx.arc(cx, 80, 40, 0.5, Math.PI - 0.5);
712
- ctx.stroke();
713
- }
714
- }
715
- },
716
- Effect: {
717
- count: 4,
718
- draw: (id, color) => {
719
- ctx.globalCompositeOperation = 'source-over';
720
- ctx.fillStyle = color;
721
- ctx.strokeStyle = color;
722
-
723
- if (id === 0) { // Goop (Drips)
724
- ctx.globalAlpha = 0.8;
725
- ctx.beginPath();
726
- ctx.moveTo(150, 400);
727
- ctx.lineTo(140, 480); ctx.lineTo(150, 480); ctx.lineTo(160, 400);
728
- ctx.moveTo(350, 400);
729
- ctx.lineTo(340, 460); ctx.lineTo(350, 460); ctx.lineTo(360, 400);
730
- ctx.fill();
731
- } else if (id === 1) { // Glow/Aura
732
- ctx.globalCompositeOperation = 'screen';
733
- ctx.globalAlpha = 0.3;
734
- const grad = ctx.createRadialGradient(250, 250, 100, 250, 250, 250);
735
- grad.addColorStop(0, color);
736
- grad.addColorStop(1, 'transparent');
737
- ctx.fillStyle = grad;
738
- ctx.fillRect(0,0,500,500);
739
- } else if (id === 2) { // Static/Noise (Simulated with dots)
740
- ctx.globalAlpha = 0.5;
741
- for(let i=0; i<50; i++) {
742
- const rx = Math.random() * 500;
743
- const ry = Math.random() * 500;
744
- ctx.fillRect(rx, ry, 2, 2);
745
- }
746
- } else if (id === 3) { // Scar
747
- ctx.strokeStyle = '#a00';
748
- ctx.lineWidth = 3;
749
- ctx.beginPath();
750
- ctx.moveTo(300, 200);
751
- ctx.lineTo(330, 250);
752
- ctx.lineTo(310, 280);
753
- ctx.stroke();
754
- }
755
- ctx.globalAlpha = 1.0;
756
- }
757
- },
758
- Background: {
759
- count: 3,
760
- draw: (id, color) => {
761
- // Background is drawn first in render loop, but this defines the pattern
762
- if (id === 0) { // Solid
763
- // Handled by clearRect/fillRect in main render
764
- } else if (id === 1) { // Radial Gradient
765
- const grad = ctx.createRadialGradient(250, 250, 50, 250, 250, 400);
766
- grad.addColorStop(0, '#2a2a30');
767
- grad.addColorStop(1, '#000000');
768
- ctx.fillStyle = grad;
769
- ctx.fillRect(0,0,500,500);
770
- } else if (id === 2) { // Grid
771
- ctx.fillStyle = color;
772
- ctx.fillRect(0,0,500,500);
773
- ctx.strokeStyle = 'rgba(255,255,255,0.05)';
774
- ctx.lineWidth = 1;
775
- for(let i=0; i<500; i+=40) {
776
- ctx.beginPath(); ctx.moveTo(i,0); ctx.lineTo(i,500); ctx.stroke();
777
- ctx.beginPath(); ctx.moveTo(0,i); ctx.lineTo(500,i); ctx.stroke();
778
- }
779
- }
780
- }
781
- }
782
- };
783
 
784
- // --- Core Application Logic ---
785
-
786
- function render() {
787
- // 1. Draw Background
788
- const bgState = currentCharacter.Background;
789
- if (bgState.id === 0) {
790
- ctx.fillStyle = bgState.color;
791
- ctx.fillRect(0, 0, 500, 500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
  } else {
793
- Drawers.Background.draw(bgState.id, bgState.color);
794
  }
 
795
 
796
- // 2. Draw Base
797
- Drawers.Base.draw(currentCharacter.Base.id, currentCharacter.Base.color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
 
799
- // 3. Draw Eyes
800
- Drawers.Eyes.draw(currentCharacter.Eyes.id, currentCharacter.Eyes.color);
 
 
801
 
802
- // 4. Draw Mouth
803
- Drawers.Mouth.draw(currentCharacter.Mouth.id, currentCharacter.Mouth.color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
 
805
- // 5. Draw Hat
806
- if (currentCharacter.Hat.id !== -1) {
807
- Drawers.Hat.draw(currentCharacter.Hat.id, currentCharacter.Hat.color);
 
 
 
 
 
 
 
 
 
 
808
  }
 
809
 
810
- // 6. Draw Effect
811
- if (currentCharacter.Effect.id !== -1) {
812
- Drawers.Effect.draw(currentCharacter.Effect.id, currentCharacter.Effect.color);
 
 
 
 
 
 
 
 
813
  }
814
  }
815
 
816
- function init() {
817
- render();
818
- setupUI();
819
- }
820
-
821
- function setupUI() {
822
- const tabsContainer = document.getElementById('tabsContainer');
823
- const grid = document.getElementById('itemsGrid');
824
- const title = document.getElementById('categoryTitle');
825
- const colorContainer = document.getElementById('colorPickerContainer');
826
- const colorOptions = document.getElementById('colorOptions');
827
- const colorLabel = document.getElementById('colorLabel');
828
-
829
- // 1. Create Tabs
830
- CATEGORIES.forEach(cat => {
831
- const btn = document.createElement('button');
832
- btn.className = `tab-btn ${cat === activeTab ? 'active' : ''}`;
833
- btn.textContent = cat;
834
- btn.onclick = () => {
835
- activeTab = cat;
836
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
837
- btn.classList.add('active');
838
- refreshGrid();
839
- };
840
- tabsContainer.appendChild(btn);
841
- });
842
 
843
- // 2. Refresh Grid Logic
844
- function refreshGrid() {
845
- grid.innerHTML = '';
846
- title.textContent = `Customize ${activeTab}`;
847
-
848
- // Determine Drawer and Count
849
- const drawer = Drawers[activeTab];
850
- const count = drawer ? drawer.count : 0;
851
- const currentId = currentCharacter[activeTab].id;
852
-
853
- // Show Color Picker if applicable
854
- if (activeTab === 'Base') {
855
- colorContainer.style.display = 'block';
856
- colorLabel.textContent = "Skin Tone";
857
- renderColors(COLORS.skin, 'Base');
858
- } else if (activeTab === 'Eyes') {
859
- colorContainer.style.display = 'block';
860
- colorLabel.textContent = "Eye Color";
861
- renderColors(COLORS.eyes, 'Eyes');
862
- } else if (activeTab === 'Hat') {
863
- colorContainer.style.display = 'block';
864
- colorLabel.textContent = "Hat Color";
865
- renderColors(COLORS.misc, 'Hat');
866
- } else {
867
- colorContainer.style.display = 'none';
868
- }
869
 
870
- // Generate Grid Items
871
- for (let i = 0; i < count; i++) {
872
- const itemDiv = document.createElement('div');
873
- itemDiv.className = `grid-item ${currentId === i ? 'selected' : ''}`;
874
-
875
- // Create mini canvas for thumbnail
876
- const miniCanvas = document.createElement('canvas');
877
- miniCanvas.width = 80;
878
- miniCanvas.height = 80;
879
- const mCtx = miniCanvas.getContext('2d');
880
-
881
- // Draw preview on mini canvas
882
- mCtx.save();
883
- mCtx.translate(40, 40); // Center
884
- mCtx.scale(0.15, 0.15); // Scale down to fit thumbnail
885
- mCtx.translate(-250, -250); // Offset back to center of main canvas coords
886
-
887
- // For thumbnails, we usually only draw the part itself, not the whole body
888
- // But to keep it simple, we just render the specific drawer
889
- // However, hats need to be positioned correctly.
890
- // Simplified: We just draw the item relative to center 0,0 in local space
891
- // Actually, let's just reuse the drawer but with a simplified background
892
-
893
- // Reset transform for simpler drawing in thumbnail
894
- mCtx.restore();
895
- mCtx.clearRect(0,0,80,80);
896
- mCtx.save();
897
- mCtx.scale(0.2, 0.2);
898
-
899
- // Draw a dummy background for visibility
900
- if(activeTab === 'Eyes' || activeTab === 'Mouth') {
901
- mCtx.fillStyle = '#ccc';
902
- mCtx.fillRect(0,0,400,400);
903
- }
904
-
905
- // We need to adjust drawer calls to work in small canvas.
906
- // Since drawers are hardcoded to 500x500 coords, we scale context
907
- mCtx.translate(0,0);
908
-
909
- // Re-implement simplified drawing for thumbnails to avoid offset issues
910
- // Or just use the main drawer but shift context
911
- mCtx.scale(1,1);
912
- // Hack: Just draw a simple shape representation for thumbnail
913
- drawThumbnailIcon(mCtx, activeTab, i);
914
-
915
- itemDiv.appendChild(miniCanvas);
916
-
917
- itemDiv.onclick = () => {
918
- currentCharacter[activeTab].id = i;
919
- render();
920
- refreshGrid(); // Update selection state
921
- };
922
 
923
- grid.appendChild(itemDiv);
924
- }
925
- }
 
 
 
 
 
 
 
 
926
 
927
- function renderColors(palette, category) {
928
- colorOptions.innerHTML = '';
929
- palette.forEach(color => {
930
- const swatch = document.createElement('div');
931
- swatch.className = `color-swatch ${currentCharacter[category].color === color ? 'active' : ''}`;
932
- swatch.style.backgroundColor = color;
933
- swatch.onclick = () => {
934
- currentCharacter[category].color = color;
935
- render();
936
- renderColors(palette, category); // Update active state
937
- };
938
- colorOptions.appendChild(swatch);
939
- });
940
- }
941
 
942
- // Helper for thumbnail icons
943
- function drawThumbnailIcon(ctx, cat, id) {
944
- const cx = 100; // Center of 200x200 space (since scale is 0.2 of 500)
945
- const cy = 100;
946
- ctx.fillStyle = '#fff';
947
- ctx.strokeStyle = '#fff';
948
-
949
- if (cat === 'Base') {
950
- ctx.fillStyle = '#555';
951
- ctx.beginPath(); ctx.arc(cx, cy, 50, 0, Math.PI*2); ctx.fill();
952
- } else if (cat === 'Eyes') {
953
- if (id===0) { ctx.beginPath(); ctx.arc(cx-15, cy, 5, 0, Math.PI*2); ctx.arc(cx+15, cy, 5, 0, Math.PI*2); ctx.fill(); }
954
- else if (id===2) { // X
955
- ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(cx-20,cy-10); ctx.lineTo(cx-10,cy+10); ctx.stroke();
956
- ctx.beginPath(); ctx.moveTo(cx-10,cy-10); ctx.lineTo(cx-20,cy+10); ctx.stroke();
957
- ctx.beginPath(); ctx.moveTo(cx+20,cy-10); ctx.lineTo(cx+10,cy+10); ctx.stroke();
958
- ctx.beginPath(); ctx.moveTo(cx+10,cy-10); ctx.lineTo(cx+20,cy+10); ctx.stroke();
959
- } else {
960
- ctx.beginPath(); ctx.arc(cx-15, cy, 8, 0, Math.PI*2); ctx.arc(cx+15, cy, 8, 0, Math.PI*2); ctx.stroke();
961
- }
962
- } else if (cat === 'Mouth') {
963
- ctx.beginPath(); ctx.arc(cx, cy+10, 15, 0, Math.PI); ctx.fill();
964
- } else if (cat === 'Hat') {
965
- ctx.fillRect(cx-20, cy-30, 40, 30);
966
- } else if (cat === 'Effect') {
967
- ctx.fillStyle = 'red'; ctx.globalAlpha=0.5; ctx.fillRect(cx-10, cy, 20, 20);
968
- } else if (cat === 'Background') {
969
- ctx.fillStyle = id === 0 ? '#000' : '#222';
970
- ctx.fillRect(0,0,200,200);
971
- }
972
- }
973
 
974
- // Initial call
975
- refreshGrid();
 
 
 
976
  }
977
 
978
- // --- Event Listeners for Buttons ---
979
-
980
- document.getElementById('randomizeBtn').addEventListener('click', () => {
981
- CATEGORIES.forEach(cat => {
982
- const count = Drawers[cat].count;
983
- // Random ID
984
- const randId = Math.floor(Math.random() * count);
985
- currentCharacter[cat].id
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RetroArc: 1953 - VHS - DVD Archive</title>
 
 
 
 
 
7
  <style>
8
+ /* --- CSS VARIABLES & RESET --- */
9
  :root {
10
+ --bg-dark: #0a0a0c;
11
+ --bg-panel: #141418;
12
+ --accent-vhs: #ff0055; /* Hot Pink for VHS */
13
+ --accent-film: #ffcc00; /* Amber for Film */
14
+ --accent-dvd: #00ccff; /* Cyan for DVD */
15
+ --text-main: #e0e0e0;
16
+ --text-muted: #888888;
17
+ --font-display: 'Courier New', Courier, monospace;
18
+ --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
19
+ --border-radius: 8px;
20
+ --shadow-intensity: 0 4px 20px rgba(0,0,0,0.6);
21
  }
22
 
23
  * {
24
  box-sizing: border-box;
25
  margin: 0;
26
  padding: 0;
 
27
  }
28
 
29
  body {
30
+ background-color: var(--bg-dark);
31
+ color: var(--text-main);
32
+ font-family: var(--font-ui);
33
+ line-height: 1.6;
34
+ overflow-x: hidden;
35
+ min-height: 100vh;
36
  display: flex;
37
  flex-direction: column;
 
 
38
  }
39
 
40
+ /* --- BACKGROUND FX --- */
41
+ .scanlines {
42
+ position: fixed;
43
+ top: 0; left: 0; width: 100%; height: 100%;
44
+ background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1));
45
+ background-size: 100% 4px;
46
+ pointer-events: none;
47
+ z-index: 9999;
48
+ opacity: 0.3;
49
+ }
50
+
51
+ .grain {
52
+ position: fixed;
53
+ top: 0; left: 0; width: 100%; height: 100%;
54
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.05'/%3E%3C/svg%3E");
55
+ pointer-events: none;
56
+ z-index: 9998;
57
+ }
58
+
59
+ /* --- HEADER --- */
60
  header {
61
+ background-color: var(--bg-panel);
62
  padding: 1rem 2rem;
63
+ border-bottom: 2px solid #333;
64
  display: flex;
65
  justify-content: space-between;
66
  align-items: center;
67
+ position: sticky;
68
+ top: 0;
69
+ z-index: 100;
70
+ box-shadow: 0 4px 10px rgba(0,0,0,0.5);
71
  }
72
 
73
  .brand {
 
 
 
 
 
74
  font-family: var(--font-display);
75
+ font-size: 1.5rem;
76
+ font-weight: bold;
 
77
  letter-spacing: 2px;
78
+ text-transform: uppercase;
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 10px;
82
  }
83
 
84
+ .brand span {
85
+ color: var(--accent-film);
86
+ text-shadow: 0 0 10px rgba(255, 204, 0, 0.5);
 
 
 
87
  }
88
 
89
  .anycoder-link {
90
  font-size: 0.85rem;
91
+ color: var(--accent-dvd);
92
  text-decoration: none;
93
+ border: 1px solid var(--accent-dvd);
94
+ padding: 5px 12px;
95
+ border-radius: 20px;
96
  transition: all 0.3s ease;
 
97
  }
 
98
  .anycoder-link:hover {
99
+ background: var(--accent-dvd);
100
+ color: var(--bg-dark);
101
+ box-shadow: 0 0 15px var(--accent-dvd);
 
102
  }
103
 
104
+ /* --- MAIN LAYOUT --- */
105
  main {
106
  flex: 1;
107
+ max-width: 1200px;
 
 
 
 
108
  margin: 0 auto;
109
  width: 100%;
110
+ padding: 2rem;
111
+ display: grid;
112
+ grid-template-columns: 1fr;
113
+ gap: 2rem;
114
  }
115
 
116
+ @media (min-width: 900px) {
117
  main {
118
+ grid-template-columns: 2fr 1fr;
 
119
  }
 
120
  }
121
 
122
+ /* --- VIDEO PLAYER SECTION --- */
123
+ .player-container {
124
+ background: #000;
125
+ border: 4px solid #333;
126
+ border-radius: var(--border-radius);
127
+ position: relative;
128
+ overflow: hidden;
129
+ box-shadow: var(--shadow-intensity);
130
+ aspect-ratio: 16/9;
131
  display: flex;
 
132
  align-items: center;
133
  justify-content: center;
 
 
 
 
 
134
  }
135
 
136
+ video {
137
+ width: 100%;
138
+ height: 100%;
139
+ object-fit: contain;
 
140
  }
141
 
142
+ .placeholder-text {
143
+ position: absolute;
144
+ color: var(--text-muted);
145
+ font-family: var(--font-display);
146
+ text-align: center;
147
+ animation: pulse 2s infinite;
148
+ }
149
+
150
+ .player-controls {
151
+ background: #222;
152
+ padding: 1rem;
153
  display: flex;
154
  gap: 1rem;
155
+ align-items: center;
156
+ border-top: 1px solid #333;
157
  }
158
 
159
+ .control-btn {
160
+ background: none;
161
+ border: 1px solid #444;
162
+ color: #fff;
163
+ width: 40px;
164
+ height: 40px;
165
+ border-radius: 50%;
166
  cursor: pointer;
 
 
167
  display: flex;
168
  align-items: center;
169
+ justify-content: center;
170
  transition: all 0.2s;
171
  }
172
 
173
+ .control-btn:hover {
174
+ border-color: var(--accent-film);
175
+ color: var(--accent-film);
176
  }
177
 
178
+ .time-display {
179
+ font-family: var(--font-display);
180
+ font-size: 0.9rem;
181
+ color: var(--accent-film);
182
+ margin-left: auto;
 
 
 
 
183
  }
184
 
185
+ /* --- UPLOAD & SIDEBAR --- */
186
+ .sidebar {
 
 
 
187
  display: flex;
188
  flex-direction: column;
189
+ gap: 2rem;
 
 
190
  }
191
 
192
+ .upload-zone {
193
+ border: 2px dashed #444;
194
+ border-radius: var(--border-radius);
195
+ padding: 2rem;
196
+ text-align: center;
197
+ transition: all 0.3s ease;
198
+ background: rgba(255,255,255,0.02);
199
+ cursor: pointer;
200
+ position: relative;
201
  }
 
202
 
203
+ .upload-zone:hover, .upload-zone.dragover {
204
+ border-color: var(--accent-dvd);
205
+ background: rgba(0, 204, 255, 0.05);
 
 
 
 
 
 
 
 
 
206
  }
207
 
208
+ .upload-icon {
209
+ font-size: 3rem;
210
+ margin-bottom: 1rem;
211
+ color: var(--text-muted);
212
  }
213
 
214
+ .upload-zone h3 {
215
+ margin-bottom: 0.5rem;
216
+ font-family: var(--font-display);
 
217
  }
218
 
219
+ .upload-zone p {
220
+ font-size: 0.9rem;
221
+ color: var(--text-muted);
 
222
  }
223
 
224
+ #file-input {
225
+ display: none;
 
 
 
 
226
  }
227
 
228
+ /* --- MEDIA LIBRARY (Grid) --- */
229
+ .library-section {
230
+ grid-column: 1 / -1;
 
231
  }
232
 
233
+ .section-header {
 
 
 
 
 
234
  display: flex;
235
+ justify-content: space-between;
236
  align-items: center;
237
+ margin-bottom: 1.5rem;
238
+ border-bottom: 1px solid #333;
239
+ padding-bottom: 0.5rem;
 
240
  }
241
 
242
+ .section-title {
243
+ font-family: var(--font-display);
244
+ font-size: 1.2rem;
245
+ color: var(--text-main);
246
  }
247
 
248
+ .media-grid {
249
+ display: grid;
250
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
251
+ gap: 1.5rem;
252
  }
253
 
254
+ .media-card {
255
+ background: var(--bg-panel);
256
+ border: 1px solid #333;
257
+ border-radius: var(--border-radius);
258
+ overflow: hidden;
259
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
260
+ cursor: pointer;
261
+ position: relative;
262
  }
263
 
264
+ .media-card:hover {
265
+ transform: translateY(-5px);
266
+ box-shadow: 0 5px 15px rgba(0,0,0,0.5);
267
+ border-color: var(--accent-vhs);
268
+ }
269
+
270
+ .media-thumb {
271
+ width: 100%;
272
+ height: 120px;
273
+ background: #000;
274
+ object-fit: cover;
275
+ opacity: 0.8;
276
+ }
277
+
278
+ .media-info {
279
  padding: 1rem;
 
 
280
  }
281
+
282
+ .media-name {
 
 
283
  font-size: 0.9rem;
284
+ font-weight: bold;
285
+ white-space: nowrap;
286
+ overflow: hidden;
287
+ text-overflow: ellipsis;
288
  }
289
 
290
+ .media-meta {
291
+ font-size: 0.75rem;
292
+ color: var(--text-muted);
293
+ margin-top: 5px;
294
  display: flex;
295
+ justify-content: space-between;
 
296
  }
297
 
298
+ .tag {
299
+ font-size: 0.6rem;
300
+ padding: 2px 6px;
301
+ border-radius: 4px;
302
+ text-transform: uppercase;
303
+ font-weight: bold;
 
304
  }
305
+ .tag.film { background: rgba(255, 204, 0, 0.2); color: var(--accent-film); }
306
+ .tag.vhs { background: rgba(255, 0, 85, 0.2); color: var(--accent-vhs); }
307
+ .tag.dvd { background: rgba(0, 204, 255, 0.2); color: var(--accent-dvd); }
308
 
309
+ /* --- TOAST NOTIFICATIONS --- */
310
+ #toast-container {
311
+ position: fixed;
312
+ bottom: 20px;
313
+ right: 20px;
314
+ z-index: 10000;
315
+ display: flex;
316
+ flex-direction: column;
317
+ gap: 10px;
318
  }
319
 
320
+ .toast {
321
+ background: var(--bg-panel);
322
+ border-left: 4px solid var(--accent-film);
323
+ color: white;
324
+ padding: 1rem 1.5rem;
325
+ border-radius: 4px;
326
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
327
+ font-family: var(--font-display);
328
+ font-size: 0.9rem;
329
+ animation: slideIn 0.3s ease;
330
+ display: flex;
331
+ align-items: center;
332
+ min-width: 250px;
333
  }
334
 
335
+ /* --- FOOTER --- */
336
  footer {
337
  text-align: center;
338
+ padding: 2rem;
339
+ color: var(--text-muted);
340
  font-size: 0.8rem;
341
+ border-top: 1px solid #333;
342
  margin-top: auto;
343
  }
344
 
345
+ /* --- ANIMATIONS --- */
346
+ @keyframes pulse {
347
+ 0% { opacity: 0.4; }
348
+ 50% { opacity: 1; }
349
+ 100% { opacity: 0.4; }
350
  }
351
 
352
+ @keyframes slideIn {
353
+ from { transform: translateX(100%); opacity: 0; }
354
+ to { transform: translateX(0); opacity: 1; }
 
 
 
355
  }
356
 
357
+ @keyframes spin {
358
+ to { transform: rotate(360deg); }
359
  }
360
 
361
+ /* Utility */
362
+ .hidden { display: none; }
363
+
364
+ /* Loading Spinner */
365
+ .spinner {
366
+ width: 20px;
367
+ height: 20px;
368
+ border: 2px solid rgba(255,255,255,0.3);
369
+ border-radius: 50%;
370
+ border-top-color: #fff;
371
+ animation: spin 1s ease-in-out infinite;
372
+ margin-right: 10px;
373
+ }
374
+
375
+ /* Era Toggle Switch */
376
+ .era-switch {
377
+ display: flex;
378
+ background: #222;
379
+ padding: 4px;
380
+ border-radius: 20px;
381
+ }
382
+ .era-btn {
383
+ background: none;
384
+ border: none;
385
+ color: var(--text-muted);
386
+ padding: 5px 15px;
387
+ border-radius: 15px;
388
+ cursor: pointer;
389
+ font-size: 0.75rem;
390
  font-weight: bold;
391
+ transition: all 0.3s;
 
 
 
 
392
  }
393
+ .era-btn.active {
394
+ color: #000;
 
395
  }
396
+ .era-btn[data-era="film"].active { background: var(--accent-film); }
397
+ .era-btn[data-era="vhs"].active { background: var(--accent-vhs); }
398
+ .era-btn[data-era="dvd"].active { background: var(--accent-dvd); }
399
 
400
  </style>
401
  </head>
402
  <body>
403
 
404
+ <!-- Visual Overlays -->
405
+ <div class="scanlines"></div>
406
+ <div class="grain"></div>
407
+
408
  <header>
409
  <div class="brand">
410
+ <span>REC</span> // ARCHIVE_53
411
+ </div>
412
+
413
+ <div class="era-switch">
414
+ <button class="era-btn active" data-era="film">1953</button>
415
+ <button class="era-btn" data-era="vhs">VHS</button>
416
+ <button class="era-btn" data-era="dvd">DVD</button>
417
  </div>
418
+
419
  <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
420
+ Built with anycoder
421
  </a>
422
  </header>
423
 
424
  <main>
425
+ <!-- Player Section -->
426
+ <section class="stage">
427
+ <div class="player-container" id="player-wrapper">
428
+ <video id="main-video" controls>
429
+ Your browser does not support the video tag.
430
+ </video>
431
+ <div class="placeholder-text" id="placeholder-msg">
432
+ [ NO SIGNAL ]<br>
433
+ INSERT MEDIA TO PLAY
434
+ </div>
435
+ </div>
436
 
437
+ <div class="player-controls">
438
+ <button class="control-btn" id="btn-play-pause" aria-label="Play/Pause">
439
+ <!-- Play Icon -->
440
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
 
 
441
  </button>
442
+ <button class="control-btn" id="btn-stop" aria-label="Stop">
443
+ <!-- Stop Icon -->
444
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M6 6h12v12H6z"/></svg>
445
  </button>
446
+ <div class="time-display" id="time-display">00:00 / 00:00</div>
447
  </div>
448
  </section>
449
 
450
+ <!-- Sidebar / Upload -->
451
+ <aside class="sidebar">
452
+ <div class="upload-zone" id="drop-zone">
453
+ <div class="upload-icon">
454
+ <!-- Film Reel Icon -->
455
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/><path d="M12 2a15 15 0 0 1 10 10"/><path d="M2 12a15 15 0 0 0 10 10"/><path d="M12 2a15 15 0 0 0-10 10"/><path d="M22 12a15 15 0 0 1-10 10"/></svg>
456
+ </div>
457
+ <h3>Upload Media</h3>
458
+ <p>Drag & Drop or Click</p>
459
+ <p style="font-size: 0.7rem; margin-top: 10px; opacity: 0.6;">Supports MP4, WebM, Ogg</p>
460
+ <input type="file" id="file-input" accept="video/*">
461
  </div>
462
 
463
+ <div style="background: var(--bg-panel); padding: 1.5rem; border-radius: var(--border-radius); border: 1px solid #333;">
464
+ <h4 style="font-family: var(--font-display); margin-bottom: 1rem; color: var(--text-muted);">SYSTEM STATUS</h4>
465
+ <div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.85rem;">
466
+ <span>STORAGE</span>
467
+ <span style="color: var(--accent-dvd)">85% FREE</span>
468
  </div>
469
+ <div style="width: 100%; height: 4px; background: #333; border-radius: 2px; margin-bottom: 1rem;">
470
+ <div style="width: 15%; height: 100%; background: var(--accent-dvd);"></div>
471
+ </div>
472
+ <div style="display: flex; justify-content: space-between; font-size: 0.85rem;">
473
+ <span>FORMAT</span>
474
+ <span id="current-format">NTSC</span>
 
475
  </div>
476
  </div>
477
+ </aside>
478
+
479
+ <!-- Library Section -->
480
+ <section class="library-section">
481
+ <div class="section-header">
482
+ <span class="section-title">ARCHIVE LIBRARY</span>
483
+ <span style="font-family: var(--font-display); font-size: 0.8rem; color: var(--text-muted);">TOTAL: <span id="video-count">0</span></span>
484
+ </div>
485
+ <div class="media-grid" id="media-grid">
486
+ <!-- Javascript will populate this -->
487
+ </div>
488
  </section>
489
  </main>
490
 
491
  <footer>
492
+ &copy; 2023 RETRO ARCHIVE SYSTEMS. ALL RIGHTS RESERVED.<br>
493
+ DESIGNED FOR ARCHIVAL PURPOSES.
494
  </footer>
495
 
496
+ <div id="toast-container"></div>
497
 
498
  <script>
499
+ // --- DOM ELEMENTS ---
500
+ const fileInput = document.getElementById('file-input');
501
+ const dropZone = document.getElementById('drop-zone');
502
+ const mediaGrid = document.getElementById('media-grid');
503
+ const mainVideo = document.getElementById('main-video');
504
+ const placeholderMsg = document.getElementById('placeholder-msg');
505
+ const btnPlayPause = document.getElementById('btn-play-pause');
506
+ const btnStop = document.getElementById('btn-stop');
507
+ const timeDisplay = document.getElementById('time-display');
508
+ const videoCount = document.getElementById('video-count');
509
+ const formatDisplay = document.getElementById('current-format');
510
+ const eraBtns = document.querySelectorAll('.era-btn');
511
+
512
+ // --- STATE ---
513
+ let currentEra = 'film'; // film, vhs, dvd
514
+ let videos = []; // Stores objects: { id, name, url, size, type }
515
+
516
+ // --- ERA CONFIGURATION ---
517
+ const eraConfig = {
518
+ film: {
519
+ accent: '#ffcc00',
520
+ label: 'REEL',
521
+ filter: 'sepia(0.8) contrast(1.2) brightness(0.9)',
522
+ font: 'Courier New'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  },
524
+ vhs: {
525
+ accent: '#ff0055',
526
+ label: 'TAPE',
527
+ filter: 'saturate(1.5) contrast(1.1) blur(0.5px)',
528
+ font: 'Courier New'
 
 
529
  },
530
+ dvd: {
531
+ accent: '#00ccff',
532
+ label: 'DISC',
533
+ filter: 'none',
534
+ font: 'Arial'
 
 
 
 
 
 
 
 
 
535
  }
536
  };
537
 
538
+ // --- EVENT LISTENERS ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
 
540
+ // Era Switching
541
+ eraBtns.forEach(btn => {
542
+ btn.addEventListener('click', () => {
543
+ eraBtns.forEach(b => b.classList.remove('active'));
544
+ btn.classList.add('active');
545
+ currentEra = btn.dataset.era;
546
+ applyEraStyles();
547
+ });
548
+ });
549
+
550
+ // File Input
551
+ dropZone.addEventListener('click', () => fileInput.click());
552
+ fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
553
+
554
+ // Drag & Drop
555
+ dropZone.addEventListener('dragover', (e) => {
556
+ e.preventDefault();
557
+ dropZone.classList.add('dragover');
558
+ });
559
+ dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));
560
+ dropZone.addEventListener('drop', (e) => {
561
+ e.preventDefault();
562
+ dropZone.classList.remove('dragover');
563
+ handleFiles(e.dataTransfer.files);
564
+ });
565
+
566
+ // Player Controls
567
+ btnPlayPause.addEventListener('click', togglePlay);
568
+ btnStop.addEventListener('click', stopVideo);
569
+ mainVideo.addEventListener('timeupdate', updateTimeDisplay);
570
+ mainVideo.addEventListener('loadedmetadata', updateTimeDisplay);
571
+ mainVideo.addEventListener('ended', () => {
572
+ btnPlayPause.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>'; // Play icon
573
+ });
574
+
575
+ // --- FUNCTIONS ---
576
+
577
+ function applyEraStyles() {
578
+ const config = eraConfig[currentEra];
579
+ document.documentElement.style.setProperty('--accent-primary', config.accent);
580
+ formatDisplay.textContent = config.label;
581
+
582
+ // Apply filter to video for effect
583
+ mainVideo.style.filter = config.filter;
584
+
585
+ // Visual feedback on body
586
+ if(currentEra === 'vhs') {
587
+ document.querySelector('.scanlines').style.opacity = '0.6';
588
+ document.querySelector('.scanlines').style.backgroundSize = '100% 2px';
589
+ } else if (currentEra === 'film') {
590
+ document.querySelector('.scanlines').style.opacity = '0.2';
591
+ document.querySelector('.scanlines').style.backgroundSize = '100% 4px';
592
  } else {
593
+ document.querySelector('.scanlines').style.opacity = '0.1';
594
  }
595
+ }
596
 
597
+ function handleFiles(files) {
598
+ if (files.length === 0) return;
599
+
600
+ Array.from(files).forEach(file => {
601
+ if (!file.type.startsWith('video/')) {
602
+ showToast(`Error: ${file.name} is not a video file`, 'error');
603
+ return;
604
+ }
605
+
606
+ // Simulate processing delay for retro feel
607
+ showToast(`Importing ${file.name}...`, 'info');
608
+
609
+ const reader = new FileReader();
610
+ reader.onload = (e) => {
611
+ const newVideo = {
612
+ id: Date.now() + Math.random(),
613
+ name: file.name,
614
+ url: e.target.result,
615
+ size: (file.size / (1024*1024)).toFixed(2) + ' MB',
616
+ type: currentEra
617
+ };
618
+
619
+ videos.push(newVideo);
620
+ addToLibrary(newVideo);
621
+ videoCount.textContent = videos.length;
622
+ showToast(`Import Complete: ${file.name}`, 'success');
623
+ };
624
+ reader.readAsDataURL(file);
625
+ });
626
+ }
627
 
628
+ function addToLibrary(video) {
629
+ const card = document.createElement('div');
630
+ card.className = 'media-card';
631
+ card.onclick = () => loadVideo(video);
632
 
633
+ // Random gradient placeholder for thumbnail since we can't easily generate thumbnails from local video without canvas complexity
634
+ const hue = Math.floor(Math.random() * 360);
635
+
636
+ card.innerHTML = `
637
+ <div class="media-thumb" style="background: linear-gradient(45deg, hsl(${hue}, 60%, 20%), hsl(${hue+40}, 60%, 10%)); display:flex; align-items:center; justify-content:center;">
638
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="white" opacity="0.7"><path d="M8 5v14l11-7z"/></svg>
639
+ </div>
640
+ <div class="media-info">
641
+ <div class="media-name">${video.name}</div>
642
+ <div class="media-meta">
643
+ <span>${video.size}</span>
644
+ <span class="tag ${video.type}">${video.type.toUpperCase()}</span>
645
+ </div>
646
+ </div>
647
+ `;
648
+ mediaGrid.prepend(card);
649
+ }
650
 
651
+ function loadVideo(video) {
652
+ mainVideo.src = video.url;
653
+ mainVideo.type = 'video/mp4'; // Assuming generic
654
+ placeholderMsg.classList.add('hidden');
655
+ mainVideo.play();
656
+
657
+ // Update play button icon
658
+ btnPlayPause.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>'; // Pause icon
659
+
660
+ // Switch era to match video if desired, or keep current. Let's switch to match for UX.
661
+ const targetBtn = document.querySelector(`.era-btn[data-era="${video.type}"]`);
662
+ if(targetBtn) {
663
+ targetBtn.click();
664
  }
665
+ }
666
 
667
+ function togglePlay() {
668
+ if (mainVideo.paused) {
669
+ if (!mainVideo.src) {
670
+ showToast('No media loaded', 'error');
671
+ return;
672
+ }
673
+ mainVideo.play();
674
+ btnPlayPause.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';
675
+ } else {
676
+ mainVideo.pause();
677
+ btnPlayPause.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>';
678
  }
679
  }
680
 
681
+ function stopVideo() {
682
+ mainVideo.pause();
683
+ mainVideo.currentTime = 0;
684
+ btnPlayPause.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>';
685
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
 
687
+ function updateTimeDisplay() {
688
+ const current = formatTime(mainVideo.currentTime);
689
+ const duration = formatTime(mainVideo.duration || 0);
690
+ timeDisplay.textContent = `${current} / ${duration}`;
691
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
 
693
+ function formatTime(seconds) {
694
+ const m = Math.floor(seconds / 60);
695
+ const s = Math.floor(seconds % 60);
696
+ return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
697
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
 
699
+ // --- TOAST SYSTEM ---
700
+ function showToast(message, type = 'info') {
701
+ const container = document.getElementById('toast-container');
702
+ const toast = document.createElement('div');
703
+ toast.className = 'toast';
704
+
705
+ // Icon based on type
706
+ let icon = '';
707
+ if(type === 'success') icon = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>';
708
+ else if(type === 'error') icon = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
709
+ else icon = '<div class="spinner"></div>';
710
 
711
+ toast.innerHTML = `${icon} <span>${message}</span>`;
712
+
713
+ if(type === 'error') toast.style.borderLeftColor = '#ff4444';
714
+ if(type === 'success') toast.style.borderLeftColor = '#00C851';
 
 
 
 
 
 
 
 
 
 
715
 
716
+ container.appendChild(toast);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
 
718
+ // Remove after 3 seconds
719
+ setTimeout(() => {
720
+ toast.style.animation = 'slideIn 0.3s ease reverse forwards';
721
+ setTimeout(() => toast.remove(), 300);
722
+ }, 3000);
723
  }
724
 
725
+ // Initialize
726
+ applyEraStyles();
727
+ </script>
728
+ </body>
729
+ </html>