Ezequield commited on
Commit
fee2d8d
·
verified ·
1 Parent(s): 342e516

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +632 -570
index.html CHANGED
@@ -3,714 +3,776 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Mac OS Tahoe - Web Edition</title>
7
- <!-- Importing a pixel font to mimic the classic Chicago look -->
8
- <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
9
- <!-- Importing FontAwesome for UI 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: #808080;
15
- --white: #ffffff;
16
- --black: #000000;
17
- --gray-light: #e0e0e0;
18
- --gray-dark: #404040;
19
- --border-light: #ffffff;
20
- --border-shadow: #000000;
21
- --title-bar-bg: #ffffff;
22
- --desktop-pattern: #c0c0c0;
23
- --font-main: 'Press Start 2P', cursive;
 
24
  }
25
 
26
  * {
27
  box-sizing: border-box;
28
- user-select: none; /* Prevent text selection mostly for UI feel */
 
 
 
29
  }
30
 
31
  body {
32
- margin: 0;
33
- padding: 0;
34
- font-family: var(--font-main);
35
- background-color: var(--desktop-pattern);
36
- /* Creating the classic dotted desktop pattern */
37
- background-image: radial-gradient(var(--black) 1px, transparent 1px);
38
- background-size: 4px 4px;
39
  height: 100vh;
40
- width: 100vw;
41
- overflow: hidden;
42
  display: flex;
43
  flex-direction: column;
44
- color: var(--black);
 
 
 
45
  }
46
 
47
- /* --- MENU BAR --- */
48
- .menubar {
49
- background-color: var(--white);
50
- border-bottom: 2px solid var(--black);
51
- height: 36px;
52
  display: flex;
53
- align-items: center;
54
- padding: 0 10px;
55
- font-size: 12px;
56
- z-index: 10000;
57
  justify-content: space-between;
58
- }
59
-
60
- .menubar-left, .menubar-right {
61
- display: flex;
62
  align-items: center;
63
- gap: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  }
65
 
66
- .apple-logo {
67
- font-size: 18px;
68
- cursor: pointer;
 
69
  }
70
 
71
- .menu-item {
72
- cursor: pointer;
 
 
 
 
 
73
  position: relative;
 
74
  }
75
 
76
- .menu-item:hover::after {
77
- content: attr(data-dropdown);
78
- position: absolute;
79
- top: 100%;
80
- left: -10px;
81
- background: var(--white);
82
- border: 2px solid var(--black);
83
- border-top: none;
84
- width: 150px;
85
- padding: 5px 0;
86
  display: flex;
87
- flex-direction: column;
88
- z-index: 10001;
89
- box-shadow: 4px 4px 0px rgba(0,0,0,0.2);
 
 
90
  }
91
 
92
- .menu-item:hover::after span {
93
- padding: 5px 15px;
94
- cursor: pointer;
 
 
 
 
 
 
 
 
 
95
  }
96
 
97
- .menu-item:hover::after span:hover {
98
- background-color: var(--black);
99
- color: var(--white);
 
100
  }
101
 
102
- /* --- DESKTOP AREA --- */
103
- .desktop {
104
- flex-grow: 1;
105
- position: relative;
106
- padding: 20px;
107
- display: flex;
108
- flex-direction: column;
109
- align-items: flex-start;
110
- flex-wrap: wrap;
111
- gap: 20px;
112
- align-content: flex-start;
113
  }
114
 
115
- /* --- ICONS --- */
116
- .desktop-icon {
117
- width: 80px;
118
- text-align: center;
119
- cursor: pointer;
120
  display: flex;
121
  flex-direction: column;
122
  align-items: center;
123
- color: var(--white);
124
- text-shadow: 1px 1px 0 #000;
 
 
 
125
  }
126
 
127
- .desktop-icon:hover {
128
- opacity: 0.8;
 
 
 
 
 
 
129
  }
130
 
131
- .icon-img {
132
- width: 48px;
133
- height: 48px;
134
- margin-bottom: 8px;
135
- font-size: 40px;
136
- display: flex;
137
- justify-content: center;
138
- align-items: center;
139
- /* Simple SVG styling or FontAwesome */
 
 
140
  }
141
 
142
- .icon-label {
143
- font-size: 10px;
144
- line-height: 1.4;
145
- background-color: transparent;
146
- padding: 2px;
147
- border-radius: 2px;
 
 
 
 
 
 
 
 
 
148
  }
149
 
150
- .desktop-icon:active .icon-label {
151
- background-color: var(--black);
152
- color: var(--white);
153
- text-shadow: none;
 
 
 
154
  }
155
 
156
- /* --- WINDOW SYSTEM --- */
157
- .window {
158
  position: absolute;
159
- background-color: var(--white);
160
- border: 2px solid var(--black);
161
- box-shadow: 8px 8px 0px rgba(0,0,0,0.5);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  display: flex;
163
- flex-direction: column;
164
- min-width: 250px;
165
- min-height: 150px;
166
- /* Default position, will be overridden by JS */
167
- top: 100px;
168
- left: 100px;
169
- transition: transform 0.1s;
170
  }
171
 
172
- .window-header {
173
- background: repeating-linear-gradient(
174
- 0deg,
175
- #fff,
176
- #fff 2px,
177
- #ddd 2px,
178
- #ddd 4px
179
- );
180
- border-bottom: 2px solid var(--black);
181
- padding: 4px 8px;
182
  display: flex;
183
- justify-content: space-between;
184
  align-items: center;
185
  cursor: grab;
186
- height: 28px;
 
 
 
 
 
 
 
 
187
  }
188
 
189
- .window-header:active {
190
  cursor: grabbing;
191
  }
192
 
193
- .window-title {
194
- font-size: 12px;
195
- font-weight: bold;
196
- text-align: center;
197
- flex-grow: 1;
198
  }
199
 
200
- .window-controls {
201
- display: flex;
202
- gap: 6px;
203
  }
204
 
205
- .control-box {
206
- width: 14px;
207
- height: 14px;
208
- border: 2px solid var(--black);
209
- background-color: var(--gray-light);
210
- cursor: pointer;
 
 
 
 
 
 
 
 
211
  display: flex;
212
- justify-content: center;
213
  align-items: center;
214
- font-size: 8px;
215
  }
216
 
217
- .control-box.close {
218
- background-color: var(--black);
219
- color: var(--white);
 
 
 
 
 
 
 
 
 
220
  }
221
 
222
- .window-content {
223
- padding: 10px;
224
- flex-grow: 1;
225
- overflow: auto;
226
- position: relative;
227
  }
228
 
229
- /* --- APP SPECIFIC STYLES --- */
230
-
231
- /* Calculator */
232
- .calc-grid {
233
- display: grid;
234
- grid-template-columns: repeat(4, 1fr);
235
- gap: 6px;
236
- }
237
- .calc-display {
238
- grid-column: span 4;
239
- background: #c3c3c3;
240
- border: 2px inset #fff;
241
- text-align: right;
242
- padding: 8px;
243
- font-size: 14px;
244
- margin-bottom: 8px;
245
- font-family: 'Courier New', monospace;
246
- min-height: 30px;
247
- }
248
- .calc-btn {
249
- border: 2px outset #fff;
250
- background: #c0c0c0;
251
- padding: 8px 0;
252
- text-align: center;
253
- cursor: pointer;
254
- font-size: 10px;
255
  }
256
- .calc-btn:active {
257
- border-style: inset;
258
- }
259
- .calc-btn.op {
260
- background: #a0a0a0;
261
  }
262
 
263
- /* Notepad */
264
- .notepad-area {
 
 
 
265
  width: 100%;
266
  height: 100%;
267
- border: none;
268
- resize: none;
269
- font-family: var(--font-main);
270
- font-size: 10px;
271
- outline: none;
272
- background: transparent;
 
273
  }
274
 
275
- /* --- ATTRIBUTION --- */
276
- .attribution-link {
277
- color: var(--black);
278
- text-decoration: none;
279
- font-weight: bold;
 
 
 
 
 
 
 
280
  }
281
- .attribution-link:hover {
282
- text-decoration: underline;
 
 
 
 
 
 
 
 
 
283
  }
284
 
285
- /* --- RESPONSIVE --- */
286
  @media (max-width: 600px) {
287
- .menubar {
288
- font-size: 10px;
289
- padding: 0 5px;
 
 
290
  }
291
- .menu-item:hover::after {
292
- left: -20px;
293
- width: 120px;
294
  }
295
- .window {
296
- width: 95% !important;
297
- height: 60% !important;
298
- left: 2.5% !important;
299
- top: 10% !important;
300
  }
301
- .desktop {
302
- flex-direction: row;
303
- justify-content: flex-start;
304
  }
305
  }
306
  </style>
307
  </head>
308
  <body>
309
 
310
- <!-- Header / Menu Bar -->
311
- <header class="menubar">
312
- <div class="menubar-left">
313
- <div class="apple-logo menu-item" data-dropdown="">
314
- <i class="fa-brands fa-apple"></i>
315
- <!-- Dropdown content simulation via CSS attr -->
316
- <div style="display:none;">
317
- <span>About This Mac</span>
318
- <span class="attribution-link" onclick="window.open('https://huggingface.co/spaces/akhaliq/anycoder', '_blank')">Built with anycoder</span>
319
- <hr style="border: 1px dashed #ccc; width: 80%;">
320
- <span>System Info...</span>
321
- </div>
322
- </div>
323
- <div class="menu-item" data-dropdown="File">
324
- File
325
- <span>New Folder</span>
326
- <span>Open</span>
327
- <span>Close</span>
328
- <span>Save</span>
329
- </div>
330
- <div class="menu-item" data-dropdown="Edit">
331
- Edit
332
- <span>Undo</span>
333
- <span>Cut</span>
334
- <span>Copy</span>
335
- <span>Paste</span>
336
- </div>
337
- <div class="menu-item" data-dropdown="View">
338
- View
339
- <span>as Icons</span>
340
- <span>as List</span>
341
- </div>
342
- <div class="menu-item" data-dropdown="Special">
343
- Special
344
- <span>Empty Trash...</span>
345
- <span>Clean Up</span>
346
- </div>
347
- </div>
348
- <div class="menubar-right">
349
- <div class="menu-item" id="clock">12:00 PM</div>
350
- <div class="menu-item"><i class="fa-solid fa-battery-three-quarters"></i></div>
351
- </div>
352
  </header>
353
 
354
- <!-- Main Desktop -->
355
- <main class="desktop" id="desktop">
356
-
357
- <!-- Desktop Icons -->
358
- <div class="desktop-icon" ondblclick="openWindow('window-mac-hd')">
359
- <div class="icon-img" style="color: #ddd;"><i class="fa-solid fa-hard-drive"></i></div>
360
- <div class="icon-label">Macintosh HD</div>
361
  </div>
362
 
363
- <div class="desktop-icon" ondblclick="openWindow('window-trash')">
364
- <div class="icon-img" style="color: #ddd;"><i class="fa-solid fa-trash-can"></i></div>
365
- <div class="icon-label">Trash</div>
 
366
  </div>
367
 
368
- <div class="desktop-icon" ondblclick="openWindow('window-calc')">
369
- <div class="icon-img" style="color: #ddd;"><i class="fa-solid fa-calculator"></i></div>
370
- <div class="icon-label">Calculator</div>
371
- </div>
372
-
373
- <div class="desktop-icon" ondblclick="openWindow('window-notepad')">
374
- <div class="icon-img" style="color: #ddd;"><i class="fa-solid fa-note-sticky"></i></div>
375
- <div class="icon-label">Note Pad</div>
376
- </div>
377
-
378
- <!-- Windows -->
379
-
380
- <!-- About / Intro Window -->
381
- <div class="window" id="window-mac-hd" style="width: 400px; height: 250px; top: 150px; left: 150px; z-index: 10;">
382
- <div class="window-header">
383
- <div class="window-controls">
384
- <div class="control-box close" onclick="closeWindow('window-mac-hd')"></div>
385
- <div class="control-box"></div>
386
- <div class="control-box"></div>
387
- </div>
388
- <div class="window-title">Macintosh HD</div>
389
- </div>
390
- <div class="window-content" style="display: flex; align-items: center; justify-content: center; flex-direction: column; text-align: center;">
391
- <i class="fa-brands fa-apple" style="font-size: 48px; margin-bottom: 20px;"></i>
392
- <h2 style="font-size: 16px; margin-bottom: 10px;">Welcome to Macintosh</h2>
393
- <p style="font-size: 10px; line-height: 1.6;">
394
- System 7.0.1<br>
395
- 4 MB RAM<br>
396
- Macintosh HD
397
- </p>
398
- <div style="margin-top: 20px; border: 2px solid #000; padding: 5px 10px; background: #fff; cursor: pointer;" onclick="closeWindow('window-mac-hd')">OK</div>
399
- </div>
400
- </div>
401
-
402
- <!-- Calculator Window -->
403
- <div class="window" id="window-calc" style="width: 220px; height: 280px; top: 100px; left: 300px; z-index: 5;">
404
- <div class="window-header">
405
- <div class="window-controls">
406
- <div class="control-box close" onclick="closeWindow('window-calc')"></div>
407
- <div class="control-box"></div>
408
- </div>
409
- <div class="window-title">Calculator</div>
410
- </div>
411
- <div class="window-content">
412
- <div class="calc-grid">
413
- <div class="calc-display" id="calc-display">0</div>
414
- <button class="calc-btn" onclick="calcInput('C')">C</button>
415
- <button class="calc-btn" onclick="calcInput('±')">±</button>
416
- <button class="calc-btn" onclick="calcInput('%')">%</button>
417
- <button class="calc-btn op" onclick="calcInput('/')">÷</button>
418
-
419
- <button class="calc-btn" onclick="calcInput('7')">7</button>
420
- <button class="calc-btn" onclick="calcInput('8')">8</button>
421
- <button class="calc-btn" onclick="calcInput('9')">9</button>
422
- <button class="calc-btn op" onclick="calcInput('*')">×</button>
423
-
424
- <button class="calc-btn" onclick="calcInput('4')">4</button>
425
- <button class="calc-btn" onclick="calcInput('5')">5</button>
426
- <button class="calc-btn" onclick="calcInput('6')">6</button>
427
- <button class="calc-btn op" onclick="calcInput('-')">−</button>
428
-
429
- <button class="calc-btn" onclick="calcInput('1')">1</button>
430
- <button class="calc-btn" onclick="calcInput('2')">2</button>
431
- <button class="calc-btn" onclick="calcInput('3')">3</button>
432
- <button class="calc-btn op" onclick="calcInput('+')">+</button>
433
-
434
- <button class="calc-btn" onclick="calcInput('0')" style="grid-column: span 2;">0</button>
435
- <button class="calc-btn" onclick="calcInput('.')">.</button>
436
- <button class="calc-btn op" onclick="calcInput('=')">=</button>
437
- </div>
438
- </div>
439
  </div>
 
440
 
441
- <!-- Notepad Window -->
442
- <div class="window" id="window-notepad" style="width: 300px; height: 300px; top: 120px; left: 400px; z-index: 6;">
443
- <div class="window-header">
444
- <div class="window-controls">
445
- <div class="control-box close" onclick="closeWindow('window-notepad')"></div>
446
- <div class="control-box"></div>
447
- <div class="control-box"></div>
448
- </div>
449
- <div class="window-title">Note Pad</div>
450
- </div>
451
- <div class="window-content" style="padding: 0;">
452
- <textarea class="notepad-area" placeholder="Type here..."></textarea>
453
- </div>
454
- </div>
455
 
456
- <!-- Trash Window -->
457
- <div class="window" id="window-trash" style="width: 300px; height: 200px; top: 200px; left: 100px; z-index: 4; display: none;">
458
- <div class="window-header">
459
- <div class="window-controls">
460
- <div class="control-box close" onclick="closeWindow('window-trash')"></div>
461
- <div class="control-box"></div>
462
- <div class="control-box"></div>
463
- </div>
464
- <div class="window-title">Trash</div>
465
- </div>
466
- <div class="window-content">
467
- <p style="text-align: center; color: #666; margin-top: 50px;">0 items</p>
468
- </div>
469
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
 
471
- </main>
 
 
 
 
 
 
472
 
473
- <script>
474
- // --- CLOCK FUNCTIONALITY ---
475
- function updateClock() {
476
- const now = new Date();
477
- let hours = now.getHours();
478
- const minutes = String(now.getMinutes()).padStart(2, '0');
479
- const ampm = hours >= 12 ? 'PM' : 'AM';
480
- hours = hours % 12;
481
- hours = hours ? hours : 12;
482
- const timeString = `${hours}:${minutes} ${ampm}`;
483
- document.getElementById('clock').textContent = timeString;
484
- }
485
- setInterval(updateClock, 1000);
486
- updateClock();
487
-
488
- // --- WINDOW MANAGEMENT ---
489
- let zIndexCounter = 100;
490
-
491
- function bringToFront(element) {
492
- zIndexCounter++;
493
- element.style.zIndex = zIndexCounter;
494
- }
495
-
496
- function openWindow(id) {
497
- const win = document.getElementById(id);
498
- if (win) {
499
- win.style.display = 'flex';
500
- bringToFront(win);
501
 
502
- // Simple animation effect
503
- win.style.transform = 'scale(0.95)';
504
- setTimeout(() => {
505
- win.style.transform = 'scale(1)';
506
- }, 50);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  }
508
  }
509
 
510
- function closeWindow(id) {
511
- const win = document.getElementById(id);
512
- if (win) {
513
- win.style.display = 'none';
 
 
 
 
 
514
  }
515
  }
516
 
517
- // Add click listener to all windows to bring to front
518
- document.querySelectorAll('.window').forEach(win => {
519
- win.addEventListener('mousedown', () => bringToFront(win));
520
- });
521
-
522
- // --- DRAGGABLE WINDOWS ---
523
- let isDragging = false;
524
- let currentWindow = null;
525
- let initialX;
526
- let initialY;
527
- let xOffset = 0;
528
- let yOffset = 0;
529
 
530
- document.querySelectorAll('.window-header').forEach(header => {
531
- header.addEventListener('mousedown', dragStart);
532
- });
533
 
534
- document.addEventListener('mouseup', dragEnd);
535
- document.addEventListener('mousemove', drag);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
- function dragStart(e) {
538
- // Identify the parent window
539
- currentWindow = e.target.closest('.window');
540
-
541
- // Only dragging if it's not on mobile (simple check) or if desired
542
- if(currentWindow) {
543
- bringToFront(currentWindow);
544
-
545
- // Get current transform values if any, but here we use top/left
546
- const rect = currentWindow.getBoundingClientRect();
547
-
548
- // Calculate offset from mouse to top-left corner of window
549
- // We need to account for the window's current position relative to viewport
550
- // Since we use absolute positioning relative to the body (desktop)
551
- // We need to calculate the mouse offset within the header
552
-
553
- initialX = e.clientX - currentWindow.offsetLeft;
554
- initialY = e.clientY - currentWindow.offsetTop;
555
 
556
- isDragging = true;
 
 
 
 
 
557
  }
558
  }
559
 
560
- function dragEnd(e) {
561
- initialX = 0;
562
- initialY = 0;
563
- isDragging = false;
564
- currentWindow = null;
565
  }
566
 
567
- function drag(e) {
568
- if (isDragging && currentWindow) {
569
- e.preventDefault();
 
 
 
 
 
 
 
 
 
570
 
571
- const currentX = e.clientX - initialX;
572
- const currentY = e.clientY - initialY;
 
 
 
 
 
 
 
 
 
 
573
 
574
- // Boundary checks (optional but good for UX)
575
- const headerHeight = 28; // keep title bar visible
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
 
577
- // Simple boundary constraint
578
- let newY = currentY;
579
- if (newY < 0) newY = 0; // Don't go above screen
580
 
581
- currentWindow.style.left = `${currentX}px`;
582
- currentWindow.style.top = `${newY}px`;
583
- }
 
 
 
 
584
  }
585
 
586
- // --- CALCULATOR LOGIC ---
587
- let calcValue = '0';
588
- let calcPendingOp = null;
589
- let calcFirstOperand = null;
590
- let calcWaitingForSecondOperand = false;
591
 
592
- function updateDisplay() {
593
- const display = document.getElementById('calc-display');
594
- // Limit length
595
- if (calcValue.length > 9) {
596
- display.textContent = calcValue.substring(0, 9);
597
- } else {
598
- display.textContent = calcValue;
599
- }
600
  }
601
 
602
- function calcInput(val) {
603
- const display = document.getElementById('calc-display');
 
604
 
605
- if (val >= '0' && val <= '9') {
606
- if (calcWaitingForSecondOperand) {
607
- calcValue = val;
608
- calcWaitingForSecondOperand = false;
609
- } else {
610
- calcValue = calcValue === '0' ? val : calcValue + val;
611
- }
612
- } else if (val === '.') {
613
- if (!calcValue.includes('.')) {
614
- calcValue += '.';
615
- }
616
- } else if (val === 'C') {
617
- calcValue = '0';
618
- calcPendingOp = null;
619
- calcFirstOperand = null;
620
- calcWaitingForSecondOperand = false;
621
- } else if (val === '±') {
622
- calcValue = String(parseFloat(calcValue) * -1);
623
- } else if (val === '%') {
624
- calcValue = String(parseFloat(calcValue) / 100);
625
- } else if (['+', '-', '*', '/'].includes(val)) {
626
- const first = parseFloat(calcValue);
627
- if (calcFirstOperand === null) {
628
- calcFirstOperand = first;
629
- } else if (calcPendingOp) {
630
- const result = performCalc(calcPendingOp, calcFirstOperand, first);
631
- calcValue = String(result);
632
- calcFirstOperand = result;
633
- }
634
- calcPendingOp = val;
635
- calcWaitingForSecondOperand = true;
636
- } else if (val === '=') {
637
- if (calcPendingOp && calcFirstOperand !== null) {
638
- const result = performCalc(calcPendingOp, calcFirstOperand, parseFloat(calcValue));
639
- calcValue = String(result);
640
- calcFirstOperand = null;
641
- calcPendingOp = null;
642
- calcWaitingForSecondOperand = true;
643
- }
644
- }
645
- updateDisplay();
646
- }
647
 
648
- function performCalc(op, a, b) {
649
- switch(op) {
650
- case '+': return a + b;
651
- case '-': return a - b;
652
- case '*': return a * b;
653
- case '/': return b !== 0 ? a / b : 'Error';
654
- default: return b;
655
  }
656
  }
657
 
658
- // --- MENU BAR DROPDOWN CONTENT HANDLING ---
659
- // Since we used CSS attr for dropdowns to keep it simple,
660
- // we need to handle the clicks inside those pseudo-elements.
661
- // However, standard DOM events don't work well on pseudo-elements.
662
- // We will use a simple trick: The menu items have `data-dropdown` attribute.
663
- // The CSS renders the text. To actually click "Built with anycoder",
664
- // we must implement a real dropdown or use the Apple menu logic.
665
-
666
- // Let's make the Apple Menu functional by replacing the CSS hover with JS logic for that specific item
667
- const appleMenu = document.querySelector('.apple-logo');
668
- const appleDropdownContent = appleMenu.querySelector('div').innerHTML;
669
-
670
- // We will create a real hidden div for the apple menu to make links clickable
671
- const realDropdown = document.createElement('div');
672
- realDropdown.id = 'apple-menu-dropdown';
673
- realDropdown.style.position = 'absolute';
674
- realDropdown.style.top = '36px';
675
- realDropdown.style.left = '10px';
676
- realDropdown.style.background = '#fff';
677
- realDropdown.style.border = '2px solid #000';
678
- realDropdown.style.borderTop = 'none';
679
- realDropdown.style.display = 'none';
680
- realDropdown.style.flexDirection = 'column';
681
- realDropdown.style.zIndex = '10001';
682
- realDropdown.style.width = '150px';
683
- realDropdown.innerHTML = appleDropdownContent;
684
-
685
- // Add styles to dropdown items
686
- const items = realDropdown.querySelectorAll('span');
687
- items.forEach(item => {
688
- item.style.padding = '5px 15px';
689
- item.style.cursor = 'pointer';
690
- item.style.fontSize = '10px';
691
- item.addEventListener('mouseenter', function() { this.style.background = '#000'; this.style.color = '#fff'; });
692
- item.addEventListener('mouseleave', function() { this.style.background = 'transparent'; this.style.color = '#000'; });
 
 
 
 
 
 
 
 
693
  });
694
 
695
- document.body.appendChild(realDropdown);
696
-
697
- appleMenu.addEventListener('mouseenter', () => {
698
- realDropdown.style.display = 'flex';
 
 
 
 
 
 
699
  });
700
-
701
- appleMenu.addEventListener('mouseleave', () => {
702
- // Small delay to allow moving mouse to dropdown
703
- setTimeout(() => {
704
- if (!realDropdown.matches(':hover')) {
705
- realDropdown.style.display = 'none';
706
- }
707
- }, 100);
708
  });
709
 
710
- realDropdown.addEventListener('mouseleave', () => {
711
- realDropdown.style.display = 'none';
 
 
712
  });
713
 
 
 
 
714
  </script>
715
  </body>
716
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Sprunki GGTP Studio</title>
 
 
 
 
 
7
  <style>
8
  :root {
9
+ --bg-color: #0d0d12;
10
+ --stage-bg: #16161e;
11
+ --slot-empty: #2a2a35;
12
+ --primary: #00ff9d;
13
+ --secondary: #bd00ff;
14
+ --accent: #ff0055;
15
+ --text-main: #ffffff;
16
+ --text-muted: #8b8b9b;
17
+ --font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
18
+ --glass: rgba(255, 255, 255, 0.05);
19
+ --glass-border: rgba(255, 255, 255, 0.1);
20
  }
21
 
22
  * {
23
  box-sizing: border-box;
24
+ margin: 0;
25
+ padding: 0;
26
+ user-select: none;
27
+ -webkit-user-drag: none;
28
  }
29
 
30
  body {
31
+ background-color: var(--bg-color);
32
+ color: var(--text-main);
33
+ font-family: var(--font-family);
 
 
 
 
34
  height: 100vh;
 
 
35
  display: flex;
36
  flex-direction: column;
37
+ overflow: hidden;
38
+ background-image:
39
+ radial-gradient(circle at 10% 20%, rgba(0, 255, 157, 0.05) 0%, transparent 40%),
40
+ radial-gradient(circle at 90% 80%, rgba(189, 0, 255, 0.05) 0%, transparent 40%);
41
  }
42
 
43
+ /* --- Header --- */
44
+ header {
 
 
 
45
  display: flex;
 
 
 
 
46
  justify-content: space-between;
 
 
 
 
47
  align-items: center;
48
+ padding: 1rem 2rem;
49
+ background: rgba(13, 13, 18, 0.8);
50
+ backdrop-filter: blur(10px);
51
+ border-bottom: 1px solid var(--glass-border);
52
+ z-index: 100;
53
+ }
54
+
55
+ .logo {
56
+ font-size: 1.5rem;
57
+ font-weight: 800;
58
+ letter-spacing: 2px;
59
+ text-transform: uppercase;
60
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
61
+ -webkit-background-clip: text;
62
+ -webkit-text-fill-color: transparent;
63
+ text-shadow: 0 0 20px rgba(0, 255, 157, 0.3);
64
+ }
65
+
66
+ .anycoder-link {
67
+ font-size: 0.8rem;
68
+ color: var(--text-muted);
69
+ text-decoration: none;
70
+ padding: 0.5rem 1rem;
71
+ border: 1px solid var(--glass-border);
72
+ border-radius: 20px;
73
+ transition: all 0.3s ease;
74
  }
75
 
76
+ .anycoder-link:hover {
77
+ color: var(--primary);
78
+ border-color: var(--primary);
79
+ box-shadow: 0 0 15px rgba(0, 255, 157, 0.2);
80
  }
81
 
82
+ /* --- Main Layout --- */
83
+ main {
84
+ flex: 1;
85
+ display: flex;
86
+ flex-direction: column;
87
+ align-items: center;
88
+ justify-content: center;
89
  position: relative;
90
+ padding: 1rem;
91
  }
92
 
93
+ /* --- Stage Area --- */
94
+ .stage-container {
 
 
 
 
 
 
 
 
95
  display: flex;
96
+ gap: 10px;
97
+ margin-bottom: 2rem;
98
+ perspective: 1000px;
99
+ flex-wrap: wrap;
100
+ justify-content: center;
101
  }
102
 
103
+ .slot {
104
+ width: 80px;
105
+ height: 220px;
106
+ background: var(--slot-empty);
107
+ border-radius: 12px;
108
+ border: 2px dashed rgba(255, 255, 255, 0.1);
109
+ position: relative;
110
+ display: flex;
111
+ justify-content: center;
112
+ align-items: flex-end;
113
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
114
+ overflow: hidden;
115
  }
116
 
117
+ .slot.drag-over {
118
+ border-color: var(--primary);
119
+ background: rgba(0, 255, 157, 0.1);
120
+ transform: scale(1.05);
121
  }
122
 
123
+ .slot.active {
124
+ border-style: solid;
125
+ box-shadow: 0 0 30px rgba(0,0,0,0.5);
 
 
 
 
 
 
 
 
126
  }
127
 
128
+ /* Character Visuals inside Slots */
129
+ .character {
130
+ width: 100%;
131
+ height: 100%;
 
132
  display: flex;
133
  flex-direction: column;
134
  align-items: center;
135
+ justify-content: flex-end;
136
+ padding-bottom: 20px;
137
+ position: relative;
138
+ cursor: pointer;
139
+ animation: breathe 3s infinite ease-in-out;
140
  }
141
 
142
+ .char-visual {
143
+ width: 60px;
144
+ height: 140px;
145
+ background: #333;
146
+ border-radius: 30px;
147
+ position: relative;
148
+ transition: transform 0.1s;
149
+ box-shadow: inset 0 -10px 20px rgba(0,0,0,0.5);
150
  }
151
 
152
+ /* Face/Eyes */
153
+ .char-visual::after {
154
+ content: '';
155
+ position: absolute;
156
+ top: 30px;
157
+ left: 15px;
158
+ width: 30px;
159
+ height: 10px;
160
+ background: #000;
161
+ border-radius: 5px;
162
+ box-shadow: 0 15px 0 #000; /* Mouth */
163
  }
164
 
165
+ .char-visual .eyes {
166
+ position: absolute;
167
+ top: 35px;
168
+ left: 10px;
169
+ width: 40px;
170
+ height: 20px;
171
+ display: flex;
172
+ justify-content: space-between;
173
+ }
174
+ .eye {
175
+ width: 12px;
176
+ height: 12px;
177
+ background: var(--primary);
178
+ border-radius: 50%;
179
+ box-shadow: 0 0 10px var(--primary);
180
  }
181
 
182
+ /* Animation States */
183
+ .slot.active .char-visual {
184
+ transform: scaleY(1.05);
185
+ }
186
+
187
+ .slot.pulse .char-visual {
188
+ transform: scaleY(1.2) scaleX(0.95);
189
  }
190
 
191
+ .remove-btn {
 
192
  position: absolute;
193
+ top: 5px;
194
+ right: 5px;
195
+ background: rgba(255, 0, 85, 0.8);
196
+ color: white;
197
+ border: none;
198
+ width: 20px;
199
+ height: 20px;
200
+ border-radius: 50%;
201
+ font-size: 12px;
202
+ cursor: pointer;
203
+ opacity: 0;
204
+ transition: opacity 0.2s;
205
+ z-index: 10;
206
+ }
207
+
208
+ .slot:hover .remove-btn {
209
+ opacity: 1;
210
+ }
211
+
212
+ /* --- Dock / Palette --- */
213
+ .dock-container {
214
+ width: 100%;
215
+ max-width: 800px;
216
+ background: var(--glass);
217
+ backdrop-filter: blur(15px);
218
+ border: 1px solid var(--glass-border);
219
+ border-radius: 20px;
220
+ padding: 1rem;
221
  display: flex;
222
+ justify-content: center;
223
+ gap: 15px;
224
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
 
 
 
 
225
  }
226
 
227
+ .sound-item {
228
+ width: 60px;
229
+ height: 60px;
230
+ border-radius: 12px;
231
+ background: #25252e;
 
 
 
 
 
232
  display: flex;
233
+ justify-content: center;
234
  align-items: center;
235
  cursor: grab;
236
+ transition: all 0.2s;
237
+ position: relative;
238
+ border: 1px solid transparent;
239
+ }
240
+
241
+ .sound-item:hover {
242
+ transform: translateY(-5px);
243
+ background: #2f2f3a;
244
+ border-color: rgba(255,255,255,0.2);
245
  }
246
 
247
+ .sound-item:active {
248
  cursor: grabbing;
249
  }
250
 
251
+ .sound-icon {
252
+ width: 30px;
253
+ height: 30px;
254
+ fill: var(--text-muted);
255
+ transition: fill 0.2s;
256
  }
257
 
258
+ .sound-item:hover .sound-icon {
259
+ fill: var(--text-main);
 
260
  }
261
 
262
+ /* Color variations for sound types */
263
+ .type-beat { border-bottom: 3px solid var(--primary); }
264
+ .type-bass { border-bottom: 3px solid var(--secondary); }
265
+ .type-melody { border-bottom: 3px solid var(--accent); }
266
+ .type-fx { border-bottom: 3px solid #ffcc00; }
267
+
268
+ .type-beat .sound-icon { fill: var(--primary); }
269
+ .type-bass .sound-icon { fill: var(--secondary); }
270
+ .type-melody .sound-icon { fill: var(--accent); }
271
+ .type-fx .sound-icon { fill: #ffcc00; }
272
+
273
+ /* --- Controls --- */
274
+ .controls {
275
+ margin-top: 1.5rem;
276
  display: flex;
277
+ gap: 1rem;
278
  align-items: center;
 
279
  }
280
 
281
+ .btn {
282
+ background: var(--glass);
283
+ border: 1px solid var(--glass-border);
284
+ color: var(--text-main);
285
+ padding: 0.8rem 2rem;
286
+ border-radius: 30px;
287
+ font-weight: 600;
288
+ cursor: pointer;
289
+ text-transform: uppercase;
290
+ letter-spacing: 1px;
291
+ transition: all 0.2s;
292
+ font-size: 0.9rem;
293
  }
294
 
295
+ .btn:hover {
296
+ background: rgba(255,255,255,0.1);
297
+ transform: scale(1.05);
 
 
298
  }
299
 
300
+ .btn.primary {
301
+ background: var(--primary);
302
+ color: #000;
303
+ box-shadow: 0 0 20px rgba(0, 255, 157, 0.4);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  }
305
+
306
+ .btn.primary:hover {
307
+ background: #00e08b;
308
+ box-shadow: 0 0 30px rgba(0, 255, 157, 0.6);
 
309
  }
310
 
311
+ /* --- Overlay --- */
312
+ #start-overlay {
313
+ position: fixed;
314
+ top: 0;
315
+ left: 0;
316
  width: 100%;
317
  height: 100%;
318
+ background: rgba(13, 13, 18, 0.95);
319
+ display: flex;
320
+ flex-direction: column;
321
+ justify-content: center;
322
+ align-items: center;
323
+ z-index: 200;
324
+ transition: opacity 0.5s;
325
  }
326
 
327
+ #start-overlay h1 {
328
+ font-size: 3rem;
329
+ margin-bottom: 1rem;
330
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
331
+ -webkit-background-clip: text;
332
+ -webkit-text-fill-color: transparent;
333
+ }
334
+
335
+ #start-overlay p {
336
+ color: var(--text-muted);
337
+ margin-bottom: 2rem;
338
+ font-size: 1.1rem;
339
  }
340
+
341
+ /* --- Animations --- */
342
+ @keyframes breathe {
343
+ 0%, 100% { transform: translateY(0); }
344
+ 50% { transform: translateY(-3px); }
345
+ }
346
+
347
+ @keyframes bg-pulse {
348
+ 0% { opacity: 0.3; }
349
+ 50% { opacity: 0.6; }
350
+ 100% { opacity: 0.3; }
351
  }
352
 
353
+ /* Responsive */
354
  @media (max-width: 600px) {
355
+ .slot {
356
+ width: 13vw;
357
+ height: 35vw;
358
+ max-width: 60px;
359
+ max-height: 160px;
360
  }
361
+ .dock-container {
362
+ gap: 8px;
363
+ padding: 0.5rem;
364
  }
365
+ .sound-item {
366
+ width: 45px;
367
+ height: 45px;
 
 
368
  }
369
+ .sound-icon {
370
+ width: 20px;
371
+ height: 20px;
372
  }
373
  }
374
  </style>
375
  </head>
376
  <body>
377
 
378
+ <header>
379
+ <div class="logo">Sprunki <span style="color:white; font-weight:300;">GGTP</span></div>
380
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  </header>
382
 
383
+ <main>
384
+ <div class="stage-container" id="stage">
385
+ <!-- Slots generated by JS -->
 
 
 
 
386
  </div>
387
 
388
+ <div class="controls">
389
+ <button class="btn" id="btn-stop">Stop</button>
390
+ <button class="btn primary" id="btn-play">Play Mix</button>
391
+ <button class="btn" id="btn-reset">Reset</button>
392
  </div>
393
 
394
+ <div class="dock-container" id="dock">
395
+ <!-- Sound items generated by JS -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  </div>
397
+ </main>
398
 
399
+ <!-- Start Overlay for Audio Context -->
400
+ <div id="start-overlay">
401
+ <h1>SPRUNKI GGTP</h1>
402
+ <p>Drag sounds to the characters to create music.</p>
403
+ <button class="btn primary" id="btn-init">ENTER STUDIO</button>
404
+ </div>
 
 
 
 
 
 
 
 
405
 
406
+ <script>
407
+ /**
408
+ * Sprunki GGTP - Logic
409
+ * Handles Audio Synthesis (Web Audio API), State Management, and UI Interaction.
410
+ */
411
+
412
+ // --- Configuration ---
413
+ const CONFIG = {
414
+ totalSlots: 7,
415
+ bpm: 110,
416
+ sounds: [
417
+ { id: 'beat1', type: 'beat', color: '#00ff9d', name: 'Kick Heavy' },
418
+ { id: 'beat2', type: 'beat', color: '#00cc7d', name: 'Snare Tech' },
419
+ { id: 'bass1', type: 'bass', color: '#bd00ff', name: 'Deep Bass' },
420
+ { id: 'bass2', type: 'bass', color: '#9d00df', name: 'Funky Bass' },
421
+ { id: 'melody1', type: 'melody', color: '#ff0055', name: 'Lead Synth' },
422
+ { id: 'melody2', type: 'melody', color: '#ff4444', name: 'Arp Pluck' },
423
+ { id: 'fx1', type: 'fx', color: '#ffcc00', name: 'Space FX' }
424
+ ]
425
+ };
426
+
427
+ // --- State ---
428
+ let state = {
429
+ isPlaying: false,
430
+ slots: Array(CONFIG.totalSlots).fill(null), // null or soundId
431
+ audioContext: null,
432
+ nextNoteTime: 0,
433
+ timerID: null,
434
+ current16thNote: 0,
435
+ lookahead: 25.0,
436
+ scheduleAheadTime: 0.1
437
+ };
438
+
439
+ // --- Audio Engine (Synthesizer) ---
440
+ class AudioEngine {
441
+ constructor() {
442
+ this.ctx = null;
443
+ this.masterGain = null;
444
+ }
445
 
446
+ init() {
447
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
448
+ this.ctx = new AudioContext();
449
+ this.masterGain = this.ctx.createGain();
450
+ this.masterGain.gain.value = 0.5;
451
+ this.masterGain.connect(this.ctx.destination);
452
+ }
453
 
454
+ // Synthesize sounds based on ID
455
+ playSound(id, time) {
456
+ if (!this.ctx) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
 
458
+ // Oscillator setup helper
459
+ const createOsc = (type, freq, duration, vol = 1, detune = 0) => {
460
+ const osc = this.ctx.createOscillator();
461
+ const gain = this.ctx.createGain();
462
+ osc.type = type;
463
+ osc.frequency.setValueAtTime(freq, time);
464
+ osc.detune.value = detune;
465
+
466
+ gain.gain.setValueAtTime(vol, time);
467
+ gain.gain.exponentialRampToValueAtTime(0.01, time + duration);
468
+
469
+ osc.connect(gain);
470
+ gain.connect(this.masterGain);
471
+ osc.start(time);
472
+ osc.stop(time + duration);
473
+ };
474
+
475
+ // Noise helper
476
+ const createNoise = (duration, vol = 1) => {
477
+ const bufferSize = this.ctx.sampleRate * duration;
478
+ const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
479
+ const data = buffer.getChannelData(0);
480
+ for (let i = 0; i < bufferSize; i++) {
481
+ data[i] = Math.random() * 2 - 1;
482
+ }
483
+ const noise = this.ctx.createBufferSource();
484
+ noise.buffer = buffer;
485
+ const gain = this.ctx.createGain();
486
+ gain.gain.setValueAtTime(vol, time);
487
+ gain.gain.exponentialRampToValueAtTime(0.01, time + duration);
488
+
489
+ // Filter for snare/hihat
490
+ const filter = this.ctx.createBiquadFilter();
491
+ filter.type = 'highpass';
492
+ filter.frequency.value = 1000;
493
+
494
+ noise.connect(filter);
495
+ filter.connect(gain);
496
+ gain.connect(this.masterGain);
497
+ noise.start(time);
498
+ };
499
+
500
+ switch (id) {
501
+ case 'beat1': // Kick
502
+ createOsc('sine', 150, 0.5, 1, 0);
503
+ createOsc('sine', 60, 0.5, 1, 0); // Sub
504
+ // Pitch drop
505
+ const osc = this.ctx.createOscillator();
506
+ const g = this.ctx.createGain();
507
+ osc.frequency.setValueAtTime(150, time);
508
+ osc.frequency.exponentialRampToValueAtTime(0.01, time + 0.5);
509
+ g.gain.setValueAtTime(1, time);
510
+ g.gain.exponentialRampToValueAtTime(0.01, time + 0.5);
511
+ osc.connect(g);
512
+ g.connect(this.masterGain);
513
+ osc.start(time);
514
+ osc.stop(time + 0.5);
515
+ break;
516
+ case 'beat2': // Snare
517
+ createNoise(0.2, 0.8);
518
+ createOsc('triangle', 200, 0.1, 0.5);
519
+ break;
520
+ case 'bass1': // Deep Bass
521
+ // Simple pattern logic handled in sequencer, this is raw sound
522
+ createOsc('sawtooth', 55, 0.4, 0.6);
523
+ break;
524
+ case 'bass2': // Funky Bass
525
+ createOsc('square', 65, 0.2, 0.5);
526
+ break;
527
+ case 'melody1': // Lead
528
+ createOsc('sawtooth', 440, 0.3, 0.4);
529
+ break;
530
+ case 'melody2': // Arp
531
+ createOsc('sine', 880, 0.1, 0.3);
532
+ break;
533
+ case 'fx1': // FX
534
+ createNoise(0.5, 0.2);
535
+ break;
536
+ }
537
  }
538
  }
539
 
540
+ const audioSys = new AudioEngine();
541
+
542
+ // --- Scheduler Logic ---
543
+ function nextNote() {
544
+ const secondsPerBeat = 60.0 / CONFIG.bpm;
545
+ state.nextNoteTime += 0.25 * secondsPerBeat; // 16th notes
546
+ state.current16thNote++;
547
+ if (state.current16thNote === 16) {
548
+ state.current16thNote = 0;
549
  }
550
  }
551
 
552
+ function scheduleNote(beatNumber, time) {
553
+ // Visual Sync Queue
554
+ requestAnimationFrame(() => {
555
+ updateVisuals(beatNumber);
556
+ });
 
 
 
 
 
 
 
557
 
558
+ // Play active sounds based on logic
559
+ state.slots.forEach((soundId, index) => {
560
+ if (!soundId) return;
561
 
562
+ // Sequencer Logic (Simplified)
563
+ // Beat 1: Kick (1, 5, 9, 13)
564
+ if (soundId === 'beat1' && (beatNumber % 4 === 0)) {
565
+ audioSys.playSound(soundId, time);
566
+ triggerSlotPulse(index);
567
+ }
568
+ // Beat 2: Snare (5, 13)
569
+ if (soundId === 'beat2' && (beatNumber === 4 || beatNumber === 12)) {
570
+ audioSys.playSound(soundId, time);
571
+ triggerSlotPulse(index);
572
+ }
573
+ // Bass: Random pattern
574
+ if (soundId === 'bass1' && (beatNumber % 4 === 0)) {
575
+ audioSys.playSound(soundId, time);
576
+ triggerSlotPulse(index);
577
+ }
578
+ if (soundId === 'bass2' && (beatNumber === 2 || beatNumber === 6 || beatNumber === 10 || beatNumber === 14)) {
579
+ audioSys.playSound(soundId, time);
580
+ triggerSlotPulse(index);
581
+ }
582
+ // Melody 1: Every 3
583
+ if (soundId === 'melody1' && (beatNumber % 3 === 0)) {
584
+ audioSys.playSound(soundId, time);
585
+ triggerSlotPulse(index);
586
+ }
587
+ // Melody 2: Fast arps
588
+ if (soundId === 'melody2') {
589
+ audioSys.playSound(soundId, time);
590
+ triggerSlotPulse(index);
591
+ }
592
+ // FX: Random
593
+ if (soundId === 'fx1' && Math.random() > 0.8) {
594
+ audioSys.playSound(soundId, time);
595
+ triggerSlotPulse(index);
596
+ }
597
+ });
598
+ }
599
 
600
+ function scheduler() {
601
+ while (state.nextNoteTime < audioSys.ctx.currentTime + state.scheduleAheadTime) {
602
+ scheduleNote(state.current16thNote, state.nextNoteTime);
603
+ nextNote();
604
+ }
605
+ state.timerID = setTimeout(scheduler, state.lookahead);
606
+ }
 
 
 
 
 
 
 
 
 
 
 
607
 
608
+ function triggerSlotPulse(index) {
609
+ const slot = document.getElementById(`slot-${index}`);
610
+ if (slot) {
611
+ slot.classList.remove('pulse');
612
+ void slot.offsetWidth; // trigger reflow
613
+ slot.classList.add('pulse');
614
  }
615
  }
616
 
617
+ function updateVisuals(beat) {
618
+ // Global beat effects could go here
 
 
 
619
  }
620
 
621
+ // --- UI Generation & Interaction ---
622
+
623
+ function initUI() {
624
+ const stage = document.getElementById('stage');
625
+ const dock = document.getElementById('dock');
626
+
627
+ // Generate Slots
628
+ for (let i = 0; i < CONFIG.totalSlots; i++) {
629
+ const slot = document.createElement('div');
630
+ slot.className = 'slot';
631
+ slot.id = `slot-${i}`;
632
+ slot.dataset.index = i;
633
 
634
+ // Drag Events
635
+ slot.addEventListener('dragover', handleDragOver);
636
+ slot.addEventListener('dragleave', handleDragLeave);
637
+ slot.addEventListener('drop', handleDrop);
638
+ // Click to empty
639
+ slot.addEventListener('click', (e) => {
640
+ if(e.target.classList.contains('remove-btn')) return;
641
+ // If has sound, maybe toggle mute? For now just visual feedback
642
+ });
643
+
644
+ stage.appendChild(slot);
645
+ }
646
 
647
+ // Generate Dock Items
648
+ CONFIG.sounds.forEach(sound => {
649
+ const item = document.createElement('div');
650
+ item.className = `sound-item type-${sound.type}`;
651
+ item.draggable = true;
652
+ item.dataset.soundId = sound.id;
653
+ item.title = sound.name;
654
+
655
+ // SVG Icon based on type
656
+ let svgPath = '';
657
+ if (sound.type === 'beat') svgPath = 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z'; // Pause-like
658
+ if (sound.type === 'bass') svgPath = 'M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z'; // Note
659
+ if (sound.type === 'melody') svgPath = 'M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z'; // Music Note
660
+ if (sound.type === 'fx') svgPath = 'M12 2L2 22h20L12 2zm0 3.5L18.5 20H5.5L12 5.5z'; // Triangle/Warning
661
+
662
+ item.innerHTML = `<svg class="sound-icon" viewBox="0 0 24 24"><path d="${svgPath}"/></svg>`;
663
+
664
+ item.addEventListener('dragstart', handleDragStart);
665
 
666
+ dock.appendChild(item);
667
+ });
668
+ }
669
 
670
+ // --- Drag and Drop Handlers ---
671
+ let draggedSoundId = null;
672
+
673
+ function handleDragStart(e) {
674
+ draggedSoundId = this.dataset.soundId;
675
+ e.dataTransfer.effectAllowed = 'copy';
676
+ this.style.opacity = '0.5';
677
  }
678
 
679
+ function handleDragOver(e) {
680
+ e.preventDefault();
681
+ e.dataTransfer.dropEffect = 'copy';
682
+ this.classList.add('drag-over');
683
+ }
684
 
685
+ function handleDragLeave(e) {
686
+ this.classList.remove('drag-over');
 
 
 
 
 
 
687
  }
688
 
689
+ function handleDrop(e) {
690
+ e.preventDefault();
691
+ this.classList.remove('drag-over');
692
 
693
+ // Reset opacity of dock items
694
+ document.querySelectorAll('.sound-item').forEach(el => el.style.opacity = '1');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
695
 
696
+ const slotIndex = parseInt(this.dataset.index);
697
+
698
+ if (draggedSoundId) {
699
+ assignSoundToSlot(slotIndex, draggedSoundId);
 
 
 
700
  }
701
  }
702
 
703
+ function assignSoundToSlot(index, soundId) {
704
+ const slot = document.getElementById(`slot-${index}`);
705
+ const soundConfig = CONFIG.sounds.find(s => s.id === soundId);
706
+
707
+ // Update State
708
+ state.slots[index] = soundId;
709
+
710
+ // Update Visuals
711
+ slot.classList.add('active');
712
+ slot.style.borderColor = soundConfig.color;
713
+ slot.style.boxShadow = `0 0 15px ${soundConfig.color}40`;
714
+
715
+ // Create Character HTML
716
+ const charHtml = `
717
+ <button class="remove-btn" onclick="removeSound(${index})">×</button>
718
+ <div class="character">
719
+ <div class="char-visual" style="background: linear-gradient(180deg, ${soundConfig.color} 0%, #333 100%);">
720
+ <div class="eyes">
721
+ <div class="eye" style="background: white; box-shadow: 0 0 5px white;"></div>
722
+ <div class="eye" style="background: white; box-shadow: 0 0 5px white;"></div>
723
+ </div>
724
+ </div>
725
+ </div>
726
+ `;
727
+ slot.innerHTML = charHtml;
728
+ }
729
+
730
+ window.removeSound = function(index) {
731
+ const slot = document.getElementById(`slot-${index}`);
732
+ state.slots[index] = null;
733
+ slot.classList.remove('active');
734
+ slot.style.borderColor = '';
735
+ slot.style.boxShadow = '';
736
+ slot.innerHTML = '';
737
+ };
738
+
739
+ // --- Controls ---
740
+ document.getElementById('btn-init').addEventListener('click', () => {
741
+ audioSys.init();
742
+ document.getElementById('start-overlay').style.opacity = '0';
743
+ setTimeout(() => {
744
+ document.getElementById('start-overlay').style.display = 'none';
745
+ }, 500);
746
  });
747
 
748
+ document.getElementById('btn-play').addEventListener('click', () => {
749
+ if (state.isPlaying) return;
750
+ if (audioSys.ctx.state === 'suspended') audioSys.ctx.resume();
751
+
752
+ state.isPlaying = true;
753
+ state.current16thNote = 0;
754
+ state.nextNoteTime = audioSys.ctx.currentTime + 0.05;
755
+ scheduler();
756
+ document.getElementById('btn-play').classList.add('primary');
757
+ document.getElementById('btn-stop').classList.remove('primary');
758
  });
759
+
760
+ document.getElementById('btn-stop').addEventListener('click', () => {
761
+ state.isPlaying = false;
762
+ clearTimeout(state.timerID);
763
+ document.getElementById('btn-stop').classList.add('primary');
764
+ document.getElementById('btn-play').classList.remove('primary');
 
 
765
  });
766
 
767
+ document.getElementById('btn-reset').addEventListener('click', () => {
768
+ for(let i=0; i<CONFIG.totalSlots; i++) {
769
+ removeSound(i);
770
+ }
771
  });
772
 
773
+ // Initialize App
774
+ initUI();
775
+
776
  </script>
777
  </body>
778
  </html>