DiamanteAmarelo commited on
Commit
eca1aee
·
verified ·
1 Parent(s): c1b8c1a

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +543 -577
index.html CHANGED
@@ -11,11 +11,21 @@
11
  :root {
12
  --bg: #0a0a0a;
13
  --surface: #111111;
 
14
  --border: #1a1a1a;
 
15
  --text: #e0e0e0;
16
  --text-dim: #666666;
17
- --accent: #888888;
18
- --accent-dim: #444444;
 
 
 
 
 
 
 
 
19
  }
20
 
21
  * {
@@ -24,63 +34,118 @@
24
  box-sizing: border-box;
25
  }
26
 
 
 
 
 
27
  body {
28
- font-family: 'Courier New', monospace;
29
  background: var(--bg);
30
  color: var(--text);
31
  min-height: 100vh;
32
  display: flex;
33
  flex-direction: column;
34
  overflow-x: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
 
37
  header {
38
- padding: 1rem;
39
  text-align: center;
40
  border-bottom: 1px solid var(--border);
 
41
  }
42
 
43
  header h1 {
44
- font-size: 0.9rem;
45
- font-weight: 300;
46
- letter-spacing: 0.3em;
47
  text-transform: uppercase;
48
  color: var(--text-dim);
 
49
  }
50
 
51
  header a {
52
- color: var(--text-dim);
53
  text-decoration: none;
54
- font-size: 0.7rem;
55
- letter-spacing: 0.1em;
56
- transition: color 0.3s;
 
 
 
57
  }
58
 
59
  header a:hover {
60
  color: var(--accent);
 
 
61
  }
62
 
63
- .stage-title {
 
64
  text-align: center;
65
- padding: 1.5rem 1rem;
66
- min-height: 80px;
 
 
 
 
67
  }
68
 
69
- .stage-title h2 {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  font-size: 1rem;
71
  font-weight: 400;
72
- letter-spacing: 0.2em;
73
- transition: opacity 0.5s;
 
 
74
  }
75
 
76
- .stage-title p {
77
  font-size: 0.7rem;
78
  color: var(--text-dim);
79
- margin-top: 0.5rem;
80
- max-width: 300px;
81
- margin-left: auto;
82
- margin-right: auto;
83
- line-height: 1.6;
84
  }
85
 
86
  main {
@@ -89,15 +154,30 @@
89
  flex-direction: column;
90
  padding: 1rem;
91
  gap: 1.5rem;
 
 
 
92
  }
93
 
 
94
  .visualizer-container {
95
  width: 100%;
96
- height: 100px;
97
  background: var(--surface);
98
  border: 1px solid var(--border);
99
  position: relative;
100
  overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
101
  }
102
 
103
  #visualizer {
@@ -106,20 +186,19 @@
106
  display: block;
107
  }
108
 
 
109
  .controls {
110
  display: flex;
111
  flex-direction: column;
112
  gap: 1rem;
113
  }
114
 
115
- .file-input-container {
116
  position: relative;
117
- overflow: hidden;
118
- display: inline-block;
119
  width: 100%;
120
  }
121
 
122
- .file-input-container input[type="file"] {
123
  position: absolute;
124
  left: 0;
125
  top: 0;
@@ -127,38 +206,63 @@
127
  width: 100%;
128
  height: 100%;
129
  cursor: pointer;
 
130
  }
131
 
132
  .btn {
133
  background: var(--surface);
134
  border: 1px solid var(--border);
135
  color: var(--text);
136
- padding: 1rem;
137
  font-family: inherit;
138
- font-size: 0.8rem;
139
- letter-spacing: 0.15em;
140
  text-transform: uppercase;
141
  cursor: pointer;
142
- transition: all 0.3s;
143
  width: 100%;
144
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
 
147
  .btn:hover:not(:disabled) {
148
- border-color: var(--accent);
149
- background: var(--border);
 
 
 
 
 
150
  }
151
 
152
  .btn:disabled {
153
- opacity: 0.3;
154
  cursor: not-allowed;
155
  }
156
 
157
  .btn.active {
158
  background: var(--accent-dim);
159
  border-color: var(--accent);
 
160
  }
161
 
 
162
  .playback-controls {
163
  display: flex;
164
  gap: 0.5rem;
@@ -168,16 +272,34 @@
168
  flex: 1;
169
  }
170
 
171
- .progress-container {
 
172
  width: 100%;
173
  }
174
 
175
  .progress-bar {
176
  width: 100%;
177
- height: 4px;
178
  background: var(--border);
179
  cursor: pointer;
180
  position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  }
182
 
183
  .progress-fill {
@@ -185,36 +307,125 @@
185
  background: var(--accent);
186
  width: 0%;
187
  transition: width 0.1s linear;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  }
189
 
190
  .time-display {
191
  display: flex;
192
  justify-content: space-between;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  font-size: 0.65rem;
194
  color: var(--text-dim);
195
- margin-top: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  font-variant-numeric: tabular-nums;
197
  }
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  .stages-container {
200
  display: grid;
201
  grid-template-columns: repeat(7, 1fr);
202
- gap: 0.25rem;
203
  }
204
 
205
  .stage-btn {
206
- padding: 0.75rem 0.25rem;
207
- font-size: 0.65rem;
208
  background: var(--surface);
209
  border: 1px solid var(--border);
210
  color: var(--text-dim);
211
  cursor: pointer;
212
- transition: all 0.3s;
213
  text-align: center;
 
 
214
  }
215
 
216
- .stage-btn:hover {
217
  border-color: var(--accent-dim);
 
218
  }
219
 
220
  .stage-btn.active {
@@ -223,73 +434,86 @@
223
  color: var(--text);
224
  }
225
 
226
- .stage-btn[data-stage="0"] { border-left: 2px solid var(--accent); }
227
- .stage-btn[data-stage="6"] { border-right: 2px solid var(--accent); }
228
 
229
- .chaos-control {
230
- display: flex;
231
- align-items: center;
232
- gap: 1rem;
233
- padding: 0.5rem;
234
- background: var(--surface);
 
235
  border: 1px solid var(--border);
236
- }
237
-
238
- .chaos-control label {
239
- font-size: 0.7rem;
240
- color: var(--text-dim);
241
  white-space: nowrap;
 
 
 
 
 
242
  }
243
 
244
- .chaos-control input[type="range"] {
245
- flex: 1;
246
- -webkit-appearance: none;
247
- background: var(--border);
248
- height: 2px;
249
- outline: none;
250
- }
251
-
252
- .chaos-control input[type="range"]::-webkit-slider-thumb {
253
- -webkit-appearance: none;
254
- width: 12px;
255
- height: 12px;
256
- background: var(--accent);
257
- cursor: pointer;
258
  }
259
 
 
260
  .status {
261
  text-align: center;
262
- font-size: 0.65rem;
263
- color: var(--text-dim);
264
- padding: 0.5rem;
 
 
 
265
  }
266
 
267
  .status.error {
268
- color: #884444;
 
269
  }
270
 
271
  .status.success {
272
- color: #448844;
 
 
 
 
 
273
  }
274
 
 
275
  .offline-badge {
276
  position: fixed;
277
- bottom: 1rem;
278
- right: 1rem;
279
  background: var(--surface);
280
  border: 1px solid var(--border);
281
- padding: 0.5rem;
282
- font-size: 0.6rem;
283
- color: var(--text-dim);
284
  opacity: 0;
285
- transition: opacity 0.3s;
 
286
  }
287
 
288
  .offline-badge.visible {
289
  opacity: 1;
290
  }
291
 
292
- @media (max-width: 400px) {
 
 
 
 
 
 
 
 
 
 
293
  .stages-container {
294
  grid-template-columns: repeat(4, 1fr);
295
  }
@@ -299,6 +523,38 @@
299
  .stages-container .stage-btn:nth-child(7) {
300
  grid-column: span 1;
301
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  }
303
  </style>
304
  </head>
@@ -308,9 +564,10 @@
308
  <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
309
  </header>
310
 
311
- <div class="stage-title">
312
- <h2 id="stageName">Estágio 0</h2>
313
- <p id="stageDesc">Áudio puro</p>
 
314
  </div>
315
 
316
  <main>
@@ -319,18 +576,20 @@
319
  </div>
320
 
321
  <div class="controls">
322
- <div class="file-input-container">
323
- <button class="btn" id="loadBtn">Carregar Áudio</button>
324
- <input type="file" id="fileInput" accept=".flac,.mp3,.wav,.ogg,.m4a,.aac">
 
 
325
  </div>
326
 
327
  <div class="playback-controls">
328
- <button class="btn" id="playBtn" disabled>Play</button>
329
- <button class="btn" id="pauseBtn" disabled>Pausa</button>
330
- <button class="btn" id="restartBtn" disabled>Reiniciar</button>
331
  </div>
332
 
333
- <div class="progress-container">
334
  <div class="progress-bar" id="progressBar">
335
  <div class="progress-fill" id="progressFill"></div>
336
  </div>
@@ -341,18 +600,26 @@
341
  </div>
342
 
343
  <div class="chaos-control">
344
- <label>Entrada/Saída</label>
345
  <input type="range" id="chaosSlider" min="0" max="100" value="0">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  </div>
347
 
348
- <div class="stages-container">
349
- <button class="stage-btn active" data-stage="0">0</button>
350
- <button class="stage-btn" data-stage="1">1</button>
351
- <button class="stage-btn" data-stage="2">2</button>
352
- <button class="stage-btn" data-stage="3">3</button>
353
- <button class="stage-btn" data-stage="4">4</button>
354
- <button class="stage-btn" data-stage="5">5</button>
355
- <button class="stage-btn" data-stage="6">6</button>
356
  </div>
357
  </div>
358
 
@@ -362,9 +629,19 @@
362
  <div class="offline-badge" id="offlineBadge">Modo Offline</div>
363
 
364
  <script>
365
- // ==================== SERVICE WORKER ====================
 
 
 
 
 
 
 
 
 
 
366
  const swCode = `
367
- const CACHE_NAME = 'everywhere-v1';
368
  const urlsToCache = ['./'];
369
 
370
  self.addEventListener('install', event => {
@@ -394,18 +671,14 @@
394
  event.respondWith(
395
  caches.match(event.request)
396
  .then(response => {
397
- if (response) {
398
- return response;
399
- }
400
  return fetch(event.request).then(response => {
401
  if (!response || response.status !== 200 || response.type !== 'basic') {
402
  return response;
403
  }
404
  const responseToCache = response.clone();
405
  caches.open(CACHE_NAME)
406
- .then(cache => {
407
- cache.put(event.request, responseToCache);
408
- });
409
  return response;
410
  });
411
  })
@@ -415,7 +688,7 @@
415
 
416
  // ==================== INDEXEDDB ====================
417
  const DB_NAME = 'EverywhereAudioDB';
418
- const DB_VERSION = 1;
419
  const STORE_NAME = 'audioFiles';
420
 
421
  let db = null;
@@ -460,17 +733,14 @@
460
  });
461
  }
462
 
463
- // ==================== CACHE API ====================
464
- async function cacheAudioFile(file) {
465
- try {
466
- const cache = await caches.open('audio-cache-v1');
467
- const response = new Response(file, {
468
- headers: { 'Content-Type': file.type }
469
- });
470
- await cache.put('/cached-audio', response);
471
- } catch (e) {
472
- console.log('Cache API not available');
473
- }
474
  }
475
 
476
  // ==================== AUDIO ENGINE ====================
@@ -489,10 +759,12 @@
489
  // Auxiliary source pool for Stage 4+
490
  let auxiliarySources = [];
491
  let auxiliaryPool = [];
492
- const POOL_SIZE = 3;
493
 
494
- // LFO oscillators
495
  let lfos = [];
 
 
496
  let noiseSource = null;
497
  let noiseGain = null;
498
  let droneOscillator = null;
@@ -506,17 +778,18 @@
506
  let pauseTime = 0;
507
  let chaosLevel = 0;
508
  let lastStageTime = 0;
509
- let stageTransitionDuration = 3;
 
510
 
511
  // Stage descriptions
512
  const stageData = [
513
- { name: 'Estágio 0', desc: 'Áudio puro' },
514
- { name: 'Estágio 1', desc: 'Uma nostalgia doce e borrada que, sob uma superfície calma e repetitiva, esconde os primeiros sinais sutis da demência.' },
515
- { name: 'Estágio 2', desc: 'Uma melancólica recusa em aceitar o esquecimento, onde memórias claras começam a se fragmentar sob estática crescente.' },
516
- { name: 'Estágio 3', desc: 'A última tentativa desesperada da memória em manter a lucidez antes de sucumbir totalmente ao caos e à desorientação.' },
517
- { name: 'Estágio 4', desc: 'A ruptura final da consciência, transformando memórias em ruídos caóticos e desoladores.' },
518
- { name: 'Estágio 5', desc: 'O estágio 5 é a agonia da mente em ruínas, onde fragmentos musicais distorcidos dão lugar a um vazio de puro horror e desorientação.' },
519
- { name: 'Estágio 6', desc: 'A dissolução completa do self em silêncio e caos ambientais, marcando o fim definitivo da memória.' }
520
  ];
521
 
522
  function initAudioContext() {
@@ -527,12 +800,12 @@
527
  }
528
 
529
  // Create impulse response for reverb
530
- function createImpulseResponse(duration, decay) {
531
  const sampleRate = audioContext.sampleRate;
532
- const length = sampleRate * duration;
533
- const impulse = audioContext.createBuffer(2, length, sampleRate);
534
 
535
- for (let channel = 0; channel < 2; channel++) {
536
  const channelData = impulse.getChannelData(channel);
537
  for (let i = 0; i < length; i++) {
538
  channelData[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, decay);
@@ -578,6 +851,23 @@
578
  return noiseBuffer;
579
  }
580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  function createAudioGraph() {
582
  const ctx = audioContext;
583
 
@@ -591,7 +881,7 @@
591
  filterNode = ctx.createBiquadFilter();
592
  filterNode.type = 'lowpass';
593
  filterNode.frequency.value = 20000;
594
- filterNode.Q.value = 1;
595
 
596
  convolverNode = ctx.createConvolver();
597
  convolverNode.buffer = createImpulseResponse(2, 2);
@@ -609,6 +899,7 @@
609
  // Analyser
610
  analyserNode = ctx.createAnalyser();
611
  analyserNode.fftSize = 2048;
 
612
 
613
  // Master gain
614
  masterGain = ctx.createGain();
@@ -631,6 +922,8 @@
631
  for (let i = 0; i < POOL_SIZE; i++) {
632
  auxiliaryPool.push({
633
  source: null,
 
 
634
  inUse: false
635
  });
636
  }
@@ -642,7 +935,13 @@
642
  noiseSource.loop = true;
643
  noiseGain = ctx.createGain();
644
  noiseGain.gain.value = 0;
645
- noiseSource.connect(noiseGain);
 
 
 
 
 
 
646
  noiseGain.connect(masterGain);
647
  noiseSource.start();
648
 
@@ -652,7 +951,13 @@
652
  droneOscillator.frequency.value = 55;
653
  droneGain = ctx.createGain();
654
  droneGain.gain.value = 0;
655
- droneOscillator.connect(droneGain);
 
 
 
 
 
 
656
  droneGain.connect(masterGain);
657
  droneOscillator.start();
658
  }
@@ -668,129 +973,164 @@
668
  const range = max - min;
669
  lfoGain.gain.value = range / 2;
670
 
 
 
 
671
  lfo.connect(lfoGain);
672
  lfoGain.connect(targetParam);
673
 
674
  // Set initial value
675
- targetParam.setValueAtTime((min + max) / 2, audioContext.currentTime);
676
 
677
  lfo.start();
678
 
679
- return { lfo, lfoGain, targetParam, min, max };
680
  }
681
 
682
- function updateLFOsForStage(stage, chaos) {
683
- // Clear existing LFOs
684
- lfos.forEach(lfo => {
685
- try { lfo.lfo.stop(); } catch(e) {}
 
686
  });
687
  lfos = [];
 
688
 
689
- if (!audioBuffer) return;
 
 
 
690
 
691
- const bufferDuration = audioBuffer.duration;
692
  const chaosFactor = chaos / 100;
 
 
693
 
694
  switch(stage) {
695
  case 0:
696
  // Pure audio - minimal processing
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  break;
698
 
699
  case 1:
700
  // Stage 1: Subtle warmth, slight blur
701
- gainNode.gain.setTargetAtTime(0.95, audioContext.currentTime, 1);
702
- filterNode.frequency.setTargetAtTime(8000, audioContext.currentTime, 2);
703
- convolverNode.buffer = createImpulseResponse(1.5, 3);
 
 
 
 
 
 
 
704
  break;
705
 
706
  case 2:
707
  // Stage 2: Fragmentation begins
708
- gainNode.gain.setTargetAtTime(0.9, audioContext.currentTime, 1);
709
- filterNode.frequency.setTargetAtTime(4000 + Math.random() * 2000, audioContext.currentTime, 1);
710
- filterNode.Q.setTargetAtTime(2, audioContext.currentTime, 1);
711
- delayNode.delayTime.setTargetAtTime(0.4 + Math.random() * 0.2, audioContext.currentTime, 1);
712
- feedbackGain.gain.setTargetAtTime(0.4, audioContext.currentTime, 1);
 
 
713
 
714
- // Add subtle LFOs
715
- lfos.push(createLFO(gainNode.gain, 0.1 + Math.random() * 0.1, 0.85, 0.95, 'sine'));
716
- lfos.push(createLFO(filterNode.frequency, 0.05, 3000, 6000, 'triangle'));
717
  break;
718
 
719
  case 3:
720
  // Stage 3: Desperate lucidity
721
- gainNode.gain.setTargetAtTime(0.85, audioContext.currentTime, 0.5);
722
- filterNode.frequency.setTargetAtTime(2500 + Math.random() * 1500, audioContext.currentTime, 0.5);
723
- filterNode.Q.setTargetAtTime(4, audioContext.currentTime, 0.5);
724
- waveShaperNode.curve = makeDistortionCurve(10 + chaosFactor * 20);
725
  convolverNode.buffer = createImpulseResponse(2.5, 1.5);
 
 
726
 
727
- lfos.push(createLFO(gainNode.gain, 0.2, 0.7, 0.9, 'sine'));
 
728
  lfos.push(createLFO(filterNode.frequency, 0.15, 2000, 4000, 'sawtooth'));
729
- lfos.push(createLFO(waveShaperNode.curve ? waveShaperNode : { setValueAtTime: () => {} }, 0.08, 5, 30, 'square'));
730
  break;
731
 
732
  case 4:
733
- // Stage 4: Collapse of linearity - chaos injection
734
- gainNode.gain.setTargetAtTime(0.7 - chaosFactor * 0.3, audioContext.currentTime, 0.3);
735
- filterNode.frequency.setTargetAtTime(1500 * (1 - chaosFactor * 0.5), audioContext.currentTime, 0.3);
736
- filterNode.Q.setTargetAtTime(6 + chaosFactor * 4, audioContext.currentTime, 0.3);
737
- waveShaperNode.curve = makeDistortionCurve(30 + chaosFactor * 50);
738
- delayNode.delayTime.setTargetAtTime(0.5 + chaosFactor * 0.3, audioContext.currentTime, 0.2);
739
- feedbackGain.gain.setTargetAtTime(0.5 + chaosFactor * 0.3, audioContext.currentTime, 0.2);
 
740
 
741
- // Activate auxiliary sources for overlapping fragments
742
  activateAuxiliarySources(1 + Math.floor(chaosFactor * 2), chaosFactor);
743
 
744
- lfos.push(createLFO(gainNode.gain, 0.3 + Math.random() * 0.3, 0.5, 0.8, 'sine'));
745
- lfos.push(createLFO(filterNode.frequency, 0.2, 800, 2000, 'triangle'));
746
  break;
747
 
748
  case 5:
749
  // Stage 5: Agony, horror, disorientation
750
- gainNode.gain.setTargetAtTime(0.5 - chaosFactor * 0.2, audioContext.currentTime, 0.2);
751
- filterNode.frequency.setTargetAtTime(800 * (1 - chaosFactor * 0.3), audioContext.currentTime, 0.2);
752
- filterNode.Q.setTargetAtTime(8, audioContext.currentTime, 0.2);
753
  waveShaperNode.curve = makeDistortionCurve(80 + chaosFactor * 70);
754
- delayNode.delayTime.setTargetAtTime(0.6 + chaosFactor * 0.4, audioContext.currentTime, 0.15);
755
- feedbackGain.gain.setTargetAtTime(0.7, audioContext.currentTime, 0.15);
 
756
 
757
  // More auxiliary sources
758
- activateAuxiliarySources(2, chaosFactor);
759
 
760
  // Increase noise
761
- noiseGain.gain.setTargetAtTime(0.05 + chaosFactor * 0.1, audioContext.currentTime, 0.5);
762
 
763
- lfos.push(createLFO(gainNode.gain, 0.5, 0.3, 0.6, 'sawtooth'));
764
- lfos.push(createLFO(filterNode.frequency, 0.3, 400, 1200, 'square'));
765
  break;
766
 
767
  case 6:
768
- // Stage 6: Dissolution - drone, pink noise, ghost notes
769
- gainNode.gain.setTargetAtTime(0.3 - chaosFactor * 0.2, audioContext.currentTime, 1);
770
- filterNode.frequency.setTargetAtTime(110, audioContext.currentTime, 2);
771
- filterNode.Q.setTargetAtTime(10, audioContext.currentTime, 2);
772
  waveShaperNode.curve = makeDistortionCurve(150);
773
- masterGain.gain.setTargetAtTime(0.5, audioContext.currentTime, 2); // -6dB
774
 
775
  // Drone
776
- droneGain.gain.setTargetAtTime(0.15, audioContext.currentTime, 3);
777
- droneOscillator.frequency.setTargetAtTime(55 * (1 - chaosFactor * 0.1), audioContext.currentTime, 1);
778
 
779
- // Pink noise as floor
780
- noiseGain.gain.setTargetAtTime(0.15, audioContext.currentTime, 2);
781
 
782
- // Slow playback for main source
783
- if (sourceNode.playbackRate) {
784
- sourceNode.playbackRate.setTargetAtTime(0.12, audioContext.currentTime, 3);
785
  }
786
 
787
- // Ghost notes - sporadic triggering
788
  clearInterval(ghostNotesInterval);
789
  ghostNotesInterval = setInterval(() => {
790
- if (Math.random() < 0.1) {
791
  triggerGhostNote(chaosFactor);
792
  }
793
- }, 2000);
794
 
795
  // Reset auxiliary
796
  deactivateAuxiliarySources();
@@ -803,397 +1143,23 @@
803
 
804
  function activateAuxiliarySources(count, chaos) {
805
  for (let i = 0; i < count && i < auxiliaryPool.length; i++) {
806
- if (!auxiliaryPool[i].inUse && audioBuffer) {
807
  const auxSource = audioContext.createBufferSource();
808
  auxSource.buffer = audioBuffer;
809
 
810
- // Random start position
811
  const startOffset = Math.random() * audioBuffer.duration;
812
  auxSource.start(0, startOffset);
813
 
814
- // Modulated playback rate
815
- const pitchMod = 0.65 + Math.random() * 0.7; // 0.65 to 1.35
816
  auxSource.playbackRate.value = pitchMod;
817
 
818
- // Individual gain
819
  const auxGain = audioContext.createGain();
820
- auxGain.gain.value = 0.3 + Math.random() * 0.2;
821
 
822
- // Connect through filter
823
  const auxFilter = audioContext.createBiquadFilter();
824
  auxFilter.type = 'lowpass';
825
- auxFilter.frequency.value = 2000 + Math.random() * 3000;
 
826
 
827
  auxSource.connect(auxGain);
828
- auxGain.connect(auxFilter);
829
- auxFilter.connect(analyserNode);
830
-
831
- auxiliaryPool[i] = {
832
- source: auxSource,
833
- gain: auxGain,
834
- filter: auxFilter,
835
- inUse: true
836
- };
837
-
838
- // Store in active array
839
- auxiliarySources.push(auxiliaryPool[i]);
840
- }
841
- }
842
- }
843
-
844
- function deactivateAuxiliarySources() {
845
- auxiliarySources.forEach(aux => {
846
- if (aux && aux.gain) {
847
- aux.gain.gain.setTargetAtTime(0, audioContext.currentTime, 0.5);
848
- setTimeout(() => {
849
- try { aux.source.stop(); } catch(e) {}
850
- }, 2000);
851
- }
852
- });
853
- auxiliarySources = [];
854
-
855
- auxiliaryPool.forEach(poolItem => {
856
- poolItem.inUse = false;
857
- });
858
- }
859
-
860
- function triggerGhostNote(chaos) {
861
- if (!audioBuffer) return;
862
-
863
- const ghostSource = audioContext.createBufferSource();
864
- ghostSource.buffer = audioBuffer;
865
-
866
- const ghostGain = audioContext.createGain();
867
- ghostGain.gain.value = 0;
868
-
869
- const ghostFilter = audioContext.createBiquadFilter();
870
- ghostFilter.type = 'bandpass';
871
- ghostFilter.frequency.value = 300 + Math.random() * 500;
872
- ghostFilter.Q.value = 10;
873
-
874
- ghostSource.connect(ghostGain);
875
- ghostGain.connect(ghostFilter);
876
- ghostFilter.connect(masterGain);
877
-
878
- const startTime = Math.random() * (audioBuffer.duration - 2);
879
- ghostSource.start(0, startTime, 4);
880
-
881
- // Extremely slow fade in/out
882
- const now = audioContext.currentTime;
883
- ghostGain.gain.setValueAtTime(0, now);
884
- ghostGain.gain.linearRampToValueAtTime(0.2, now + 2);
885
- ghostGain.gain.linearRampToValueAtTime(0, now + 4);
886
-
887
- setTimeout(() => {
888
- try { ghostSource.stop(); } catch(e) {}
889
- }, 4500);
890
- }
891
-
892
- let stage6EndScheduled = false;
893
-
894
- function scheduleStage6End() {
895
- if (stage6EndScheduled) return;
896
- stage6EndScheduled = true;
897
-
898
- // 6 minutes fade
899
- const fadeDuration = 360; // seconds
900
- const now = audioContext.currentTime;
901
-
902
- masterGain.gain.setValueAtTime(masterGain.gain.value, now);
903
- masterGain.gain.linearRampToValueAtTime(0, now + fadeDuration);
904
-
905
- // After fade, 1 minute silence then restart
906
- setTimeout(() => {
907
- // Stop everything
908
- try { sourceNode.stop(); } catch(e) {}
909
- deactivateAuxiliarySources();
910
- clearInterval(ghostNotesInterval);
911
-
912
- // 1 minute silence
913
- setTimeout(() => {
914
- // Soft restart
915
- stage6EndScheduled = false;
916
- if (audioBuffer) {
917
- playAudio(0);
918
- changeStage(0);
919
- }
920
- }, 60000);
921
-
922
- }, fadeDuration * 1000);
923
- }
924
-
925
- function changeStage(stage) {
926
- currentStage = stage;
927
- lastStageTime = Date.now();
928
-
929
- // Update UI
930
- document.getElementById('stageName').textContent = stageData[stage].name;
931
- document.getElementById('stageDesc').textContent = stageData[stage].desc;
932
-
933
- document.querySelectorAll('.stage-btn').forEach(btn => {
934
- btn.classList.toggle('active', parseInt(btn.dataset.stage) === stage);
935
- });
936
-
937
- // Update audio processing
938
- if (audioContext && audioBuffer) {
939
- updateLFOsForStage(stage, chaosLevel);
940
- }
941
- }
942
-
943
- async function loadAudioFile(file) {
944
- const ctx = initAudioContext();
945
-
946
- try {
947
- document.getElementById('status').textContent = 'Carregando áudio...';
948
- document.getElementById('status').className = 'status';
949
-
950
- const arrayBuffer = await file.arrayBuffer();
951
- audioBuffer = await ctx.decodeAudioData(arrayBuffer);
952
-
953
- // Save to IndexedDB
954
- await saveAudioToDB('current-audio', arrayBuffer, file.name);
955
-
956
- // Cache it
957
- await cacheAudioFile(file);
958
-
959
- document.getElementById('status').textContent = `Carregado: ${file.name}`;
960
- document.getElementById('status').className = 'status success';
961
-
962
- document.getElementById('duration').textContent = formatTime(audioBuffer.duration);
963
-
964
- // Enable controls
965
- document.getElementById('playBtn').disabled = false;
966
- document.getElementById('pauseBtn').disabled = false;
967
- document.getElementById('restartBtn').disabled = false;
968
-
969
- // Create audio graph
970
- createAudioGraph();
971
-
972
- // Reset stage
973
- changeStage(0);
974
-
975
- } catch (error) {
976
- console.error('Error loading audio:', error);
977
- document.getElementById('status').textContent = 'Erro ao carregar áudio: ' + error.message;
978
- document.getElementById('status').className = 'status error';
979
- }
980
- }
981
-
982
- function playAudio(offset = 0) {
983
- if (!audioBuffer) return;
984
-
985
- const ctx = audioContext;
986
-
987
- if (ctx.state === 'suspended') {
988
- ctx.resume();
989
- }
990
-
991
- // Stop existing source
992
- if (sourceNode) {
993
- try { sourceNode.stop(); } catch(e) {}
994
- }
995
-
996
- // Create new source
997
- sourceNode = ctx.createBufferSource();
998
- sourceNode.buffer = audioBuffer;
999
- sourceNode.loop = true;
1000
-
1001
- // Reconnect
1002
- sourceNode.connect(gainNode);
1003
-
1004
- // Start from offset
1005
- sourceNode.start(0, offset);
1006
- startTime = ctx.currentTime - offset;
1007
- isPlaying = true;
1008
-
1009
- // Update processing
1010
- updateLFOsForStage(currentStage, chaosLevel);
1011
-
1012
- // Start visualization
1013
- drawVisualization();
1014
- }
1015
-
1016
- function pauseAudio() {
1017
- if (sourceNode && isPlaying) {
1018
- try { sourceNode.stop(); } catch(e) {}
1019
- pauseTime = audioContext.currentTime - startTime;
1020
- isPlaying = false;
1021
- }
1022
- }
1023
-
1024
- function stopAudio() {
1025
- if (sourceNode) {
1026
- try { sourceNode.stop(); } catch(e) {}
1027
- }
1028
- deactivateAuxiliarySources();
1029
- isPlaying = false;
1030
- pauseTime = 0;
1031
- updateProgress(0);
1032
- }
1033
-
1034
- function getCurrentTime() {
1035
- if (!audioContext || !isPlaying) return pauseTime;
1036
- return audioContext.currentTime - startTime;
1037
- }
1038
-
1039
- function formatTime(seconds) {
1040
- const mins = Math.floor(seconds / 60);
1041
- const secs = Math.floor(seconds % 60);
1042
- return `${mins}:${secs.toString().padStart(2, '0')}`;
1043
- }
1044
-
1045
- function updateProgress(time) {
1046
- if (!audioBuffer) return;
1047
-
1048
- const percent = (time / audioBuffer.duration) * 100;
1049
- document.getElementById('progressFill').style.width = `${percent}%`;
1050
- document.getElementById('currentTime').textContent = formatTime(time);
1051
- }
1052
-
1053
- // ==================== VISUALIZATION ====================
1054
- const canvas = document.getElementById('visualizer');
1055
- const ctx = canvas.getContext('2d');
1056
-
1057
- function resizeCanvas() {
1058
- const rect = canvas.parentElement.getBoundingClientRect();
1059
- canvas.width = rect.width * window.devicePixelRatio;
1060
- canvas.height = rect.height * window.devicePixelRatio;
1061
- ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
1062
- }
1063
-
1064
- resizeCanvas();
1065
- window.addEventListener('resize', resizeCanvas);
1066
-
1067
- function drawVisualization() {
1068
- if (!analyserNode) return;
1069
-
1070
- requestAnimationFrame(drawVisualization);
1071
-
1072
- const bufferLength = analyserNode.frequencyBinCount;
1073
- const dataArray = new Uint8Array(bufferLength);
1074
- analyserNode.getByteTimeDomainData(dataArray);
1075
-
1076
- const width = canvas.width / window.devicePixelRatio;
1077
- const height = canvas.height / window.devicePixelRatio;
1078
-
1079
- // Clear with background
1080
- ctx.fillStyle = '#111111';
1081
- ctx.fillRect(0, 0, width, height);
1082
-
1083
- // Draw waveform
1084
- ctx.lineWidth = 1.5;
1085
- ctx.strokeStyle = '#888888';
1086
- ctx.beginPath();
1087
-
1088
- const sliceWidth = width / bufferLength;
1089
- let x = 0;
1090
-
1091
- for (let i = 0; i < bufferLength; i++) {
1092
- const v = dataArray[i] / 128.0;
1093
- const y = (v * height) / 2;
1094
-
1095
- if (i === 0) {
1096
- ctx.moveTo(x, y);
1097
- } else {
1098
- ctx.lineTo(x, y);
1099
- }
1100
-
1101
- x += sliceWidth;
1102
- }
1103
-
1104
- ctx.lineTo(width, height / 2);
1105
- ctx.stroke();
1106
-
1107
- // Update progress
1108
- if (isPlaying && audioBuffer) {
1109
- updateProgress(getCurrentTime());
1110
- }
1111
- }
1112
-
1113
- // ==================== EVENT HANDLERS ====================
1114
- document.getElementById('fileInput').addEventListener('change', async (e) => {
1115
- const file = e.target.files[0];
1116
- if (file) {
1117
- await loadAudioFile(file);
1118
- }
1119
- });
1120
-
1121
- document.getElementById('playBtn').addEventListener('click', () => {
1122
- if (!audioContext) initAudioContext();
1123
- playAudio(pauseTime);
1124
- });
1125
-
1126
- document.getElementById('pauseBtn').addEventListener('click', pauseAudio);
1127
-
1128
- document.getElementById('restartBtn').addEventListener('click', () => {
1129
- stopAudio();
1130
- if (audioBuffer) playAudio(0);
1131
- changeStage(0);
1132
- });
1133
-
1134
- document.getElementById('progressBar').addEventListener('click', (e) => {
1135
- if (!audioBuffer) return;
1136
-
1137
- const rect = e.target.getBoundingClientRect();
1138
- const percent = (e.clientX - rect.left) / rect.width;
1139
- const newTime = percent * audioBuffer.duration;
1140
-
1141
- if (isPlaying) {
1142
- pauseTime = newTime;
1143
- playAudio(newTime);
1144
- } else {
1145
- pauseTime = newTime;
1146
- updateProgress(newTime);
1147
- }
1148
- });
1149
-
1150
- document.getElementById('chaosSlider').addEventListener('input', (e) => {
1151
- chaosLevel = parseInt(e.target.value);
1152
- if (audioBuffer && isPlaying) {
1153
- updateLFOsForStage(currentStage, chaosLevel);
1154
- }
1155
- });
1156
-
1157
- document.querySelectorAll('.stage-btn').forEach(btn => {
1158
- btn.addEventListener('click', () => {
1159
- const stage = parseInt(btn.dataset.stage);
1160
- changeStage(stage);
1161
- });
1162
- });
1163
-
1164
- // ==================== OFFLINE DETECTION ====================
1165
- function updateOnlineStatus() {
1166
- const badge = document.getElementById('offlineBadge');
1167
- if (!navigator.onLine) {
1168
- badge.classList.add('visible');
1169
- } else {
1170
- badge.classList.remove('visible');
1171
- }
1172
- }
1173
-
1174
- window.addEventListener('online', updateOnlineStatus);
1175
- window.addEventListener('offline', updateOnlineStatus);
1176
- updateOnlineStatus();
1177
-
1178
- // ==================== INITIALIZATION ====================
1179
- async function init() {
1180
- try {
1181
- // Open database
1182
- await openDatabase();
1183
-
1184
- // Try to load cached audio
1185
- const cached = await getAudioFromDB('current-audio');
1186
- if (cached && cached.data) {
1187
- document.getElementById('status').textContent = 'Carregando áudio salvo...';
1188
- document.getElementById('status').className = 'status';
1189
-
1190
- const ctx = initAudioContext();
1191
- audioBuffer = await ctx.decodeAudioData(cached.data);
1192
-
1193
- document.getElementById('status').textContent = `Carregado: ${cached.name}`;
1194
- document.getElementById('status').className = 'status success';
1195
- document.getElementById('duration').textContent = formatTime(audioBuffer.duration);
1196
-
1197
- document.getElementById('playBtn').disabled = false;
1198
- document.getElementById('pauseBtn').disabled = false;
1199
- document.getElementById('
 
11
  :root {
12
  --bg: #0a0a0a;
13
  --surface: #111111;
14
+ --surface-elevated: #161616;
15
  --border: #1a1a1a;
16
+ --border-hover: #2a2a2a;
17
  --text: #e0e0e0;
18
  --text-dim: #666666;
19
+ --text-dimmer: #444444;
20
+ --accent: #999999;
21
+ --accent-dim: #555555;
22
+ --stage-0: #888888;
23
+ --stage-1: #777777;
24
+ --stage-2: #666666;
25
+ --stage-3: #555555;
26
+ --stage-4: #444444;
27
+ --stage-5: #333333;
28
+ --stage-6: #222222;
29
  }
30
 
31
  * {
 
34
  box-sizing: border-box;
35
  }
36
 
37
+ html {
38
+ scroll-behavior: smooth;
39
+ }
40
+
41
  body {
42
+ font-family: 'SF Mono', 'Fira Code', 'Courier New', monospace;
43
  background: var(--bg);
44
  color: var(--text);
45
  min-height: 100vh;
46
  display: flex;
47
  flex-direction: column;
48
  overflow-x: hidden;
49
+ line-height: 1.5;
50
+ }
51
+
52
+ /* Animated background */
53
+ body::before {
54
+ content: '';
55
+ position: fixed;
56
+ top: 0;
57
+ left: 0;
58
+ right: 0;
59
+ bottom: 0;
60
+ background:
61
+ radial-gradient(ellipse at 20% 20%, rgba(30, 30, 30, 0.3) 0%, transparent 50%),
62
+ radial-gradient(ellipse at 80% 80%, rgba(20, 20, 20, 0.3) 0%, transparent 50%);
63
+ pointer-events: none;
64
+ z-index: -1;
65
  }
66
 
67
  header {
68
+ padding: 1.5rem 1rem;
69
  text-align: center;
70
  border-bottom: 1px solid var(--border);
71
+ background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, transparent 100%);
72
  }
73
 
74
  header h1 {
75
+ font-size: 0.85rem;
76
+ font-weight: 400;
77
+ letter-spacing: 0.4em;
78
  text-transform: uppercase;
79
  color: var(--text-dim);
80
+ margin-bottom: 0.5rem;
81
  }
82
 
83
  header a {
84
+ color: var(--text-dimmer);
85
  text-decoration: none;
86
+ font-size: 0.65rem;
87
+ letter-spacing: 0.15em;
88
+ transition: color 0.3s ease;
89
+ display: inline-block;
90
+ padding: 0.25rem 0.5rem;
91
+ border: 1px solid transparent;
92
  }
93
 
94
  header a:hover {
95
  color: var(--accent);
96
+ border-color: var(--border);
97
+ background: var(--surface);
98
  }
99
 
100
+ .stage-display {
101
+ padding: 2rem 1rem;
102
  text-align: center;
103
+ min-height: 140px;
104
+ display: flex;
105
+ flex-direction: column;
106
+ justify-content: center;
107
+ align-items: center;
108
+ position: relative;
109
  }
110
 
111
+ .stage-display::after {
112
+ content: '';
113
+ position: absolute;
114
+ bottom: 0;
115
+ left: 50%;
116
+ transform: translateX(-50%);
117
+ width: 60%;
118
+ height: 1px;
119
+ background: linear-gradient(90deg, transparent, var(--border), transparent);
120
+ }
121
+
122
+ .stage-number {
123
+ font-size: 3rem;
124
+ font-weight: 200;
125
+ color: var(--text-dim);
126
+ opacity: 0.3;
127
+ position: absolute;
128
+ top: 1rem;
129
+ left: 50%;
130
+ transform: translateX(-50%);
131
+ letter-spacing: 0.2em;
132
+ }
133
+
134
+ .stage-name {
135
  font-size: 1rem;
136
  font-weight: 400;
137
+ letter-spacing: 0.25em;
138
+ text-transform: uppercase;
139
+ margin-bottom: 0.75rem;
140
+ transition: all 0.5s ease;
141
  }
142
 
143
+ .stage-desc {
144
  font-size: 0.7rem;
145
  color: var(--text-dim);
146
+ max-width: 320px;
147
+ line-height: 1.7;
148
+ font-style: italic;
 
 
149
  }
150
 
151
  main {
 
154
  flex-direction: column;
155
  padding: 1rem;
156
  gap: 1.5rem;
157
+ max-width: 600px;
158
+ margin: 0 auto;
159
+ width: 100%;
160
  }
161
 
162
+ /* Visualizer */
163
  .visualizer-container {
164
  width: 100%;
165
+ height: 120px;
166
  background: var(--surface);
167
  border: 1px solid var(--border);
168
  position: relative;
169
  overflow: hidden;
170
+ border-radius: 2px;
171
+ }
172
+
173
+ .visualizer-container::before {
174
+ content: '';
175
+ position: absolute;
176
+ top: 0;
177
+ left: 0;
178
+ right: 0;
179
+ height: 1px;
180
+ background: linear-gradient(90deg, transparent, var(--accent-dim), transparent);
181
  }
182
 
183
  #visualizer {
 
186
  display: block;
187
  }
188
 
189
+ /* Controls */
190
  .controls {
191
  display: flex;
192
  flex-direction: column;
193
  gap: 1rem;
194
  }
195
 
196
+ .file-input-wrapper {
197
  position: relative;
 
 
198
  width: 100%;
199
  }
200
 
201
+ .file-input-wrapper input[type="file"] {
202
  position: absolute;
203
  left: 0;
204
  top: 0;
 
206
  width: 100%;
207
  height: 100%;
208
  cursor: pointer;
209
+ z-index: 2;
210
  }
211
 
212
  .btn {
213
  background: var(--surface);
214
  border: 1px solid var(--border);
215
  color: var(--text);
216
+ padding: 0.9rem 1.2rem;
217
  font-family: inherit;
218
+ font-size: 0.7rem;
219
+ letter-spacing: 0.2em;
220
  text-transform: uppercase;
221
  cursor: pointer;
222
+ transition: all 0.25s ease;
223
  width: 100%;
224
  text-align: center;
225
+ position: relative;
226
+ overflow: hidden;
227
+ }
228
+
229
+ .btn::before {
230
+ content: '';
231
+ position: absolute;
232
+ top: 0;
233
+ left: -100%;
234
+ width: 100%;
235
+ height: 100%;
236
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.05), transparent);
237
+ transition: left 0.5s ease;
238
+ }
239
+
240
+ .btn:hover::before {
241
+ left: 100%;
242
  }
243
 
244
  .btn:hover:not(:disabled) {
245
+ border-color: var(--accent-dim);
246
+ background: var(--surface-elevated);
247
+ transform: translateY(-1px);
248
+ }
249
+
250
+ .btn:active:not(:disabled) {
251
+ transform: translateY(0);
252
  }
253
 
254
  .btn:disabled {
255
+ opacity: 0.25;
256
  cursor: not-allowed;
257
  }
258
 
259
  .btn.active {
260
  background: var(--accent-dim);
261
  border-color: var(--accent);
262
+ color: #fff;
263
  }
264
 
265
+ /* Playback controls */
266
  .playback-controls {
267
  display: flex;
268
  gap: 0.5rem;
 
272
  flex: 1;
273
  }
274
 
275
+ /* Progress */
276
+ .progress-section {
277
  width: 100%;
278
  }
279
 
280
  .progress-bar {
281
  width: 100%;
282
+ height: 3px;
283
  background: var(--border);
284
  cursor: pointer;
285
  position: relative;
286
+ border-radius: 1px;
287
+ overflow: hidden;
288
+ }
289
+
290
+ .progress-bar::after {
291
+ content: '';
292
+ position: absolute;
293
+ top: 0;
294
+ left: 0;
295
+ right: 0;
296
+ bottom: 0;
297
+ background: transparent;
298
+ transition: background 0.3s;
299
+ }
300
+
301
+ .progress-bar:hover::after {
302
+ background: rgba(255,255,255,0.02);
303
  }
304
 
305
  .progress-fill {
 
307
  background: var(--accent);
308
  width: 0%;
309
  transition: width 0.1s linear;
310
+ position: relative;
311
+ }
312
+
313
+ .progress-fill::after {
314
+ content: '';
315
+ position: absolute;
316
+ right: 0;
317
+ top: 50%;
318
+ transform: translateY(-50%);
319
+ width: 8px;
320
+ height: 8px;
321
+ background: var(--accent);
322
+ border-radius: 50%;
323
+ opacity: 0;
324
+ transition: opacity 0.2s;
325
+ }
326
+
327
+ .progress-bar:hover .progress-fill::after {
328
+ opacity: 1;
329
  }
330
 
331
  .time-display {
332
  display: flex;
333
  justify-content: space-between;
334
+ font-size: 0.6rem;
335
+ color: var(--text-dimmer);
336
+ margin-top: 0.6rem;
337
+ font-variant-numeric: tabular-nums;
338
+ letter-spacing: 0.05em;
339
+ }
340
+
341
+ /* Chaos slider */
342
+ .chaos-control {
343
+ display: flex;
344
+ align-items: center;
345
+ gap: 1rem;
346
+ padding: 0.75rem 1rem;
347
+ background: var(--surface);
348
+ border: 1px solid var(--border);
349
+ border-radius: 2px;
350
+ }
351
+
352
+ .chaos-control label {
353
  font-size: 0.65rem;
354
  color: var(--text-dim);
355
+ white-space: nowrap;
356
+ letter-spacing: 0.1em;
357
+ text-transform: uppercase;
358
+ }
359
+
360
+ .chaos-control input[type="range"] {
361
+ flex: 1;
362
+ -webkit-appearance: none;
363
+ appearance: none;
364
+ background: var(--border);
365
+ height: 2px;
366
+ outline: none;
367
+ border-radius: 1px;
368
+ }
369
+
370
+ .chaos-control input[type="range"]::-webkit-slider-thumb {
371
+ -webkit-appearance: none;
372
+ width: 14px;
373
+ height: 14px;
374
+ background: var(--accent);
375
+ cursor: pointer;
376
+ border-radius: 50%;
377
+ transition: transform 0.2s, background 0.2s;
378
+ }
379
+
380
+ .chaos-control input[type="range"]::-webkit-slider-thumb:hover {
381
+ transform: scale(1.2);
382
+ background: var(--text);
383
+ }
384
+
385
+ .chaos-value {
386
+ font-size: 0.6rem;
387
+ color: var(--text-dimmer);
388
+ min-width: 2.5em;
389
+ text-align: right;
390
  font-variant-numeric: tabular-nums;
391
  }
392
 
393
+ /* Stage buttons */
394
+ .stages-section {
395
+ margin-top: 0.5rem;
396
+ }
397
+
398
+ .stages-label {
399
+ font-size: 0.6rem;
400
+ color: var(--text-dimmer);
401
+ letter-spacing: 0.15em;
402
+ text-transform: uppercase;
403
+ text-align: center;
404
+ margin-bottom: 0.75rem;
405
+ }
406
+
407
  .stages-container {
408
  display: grid;
409
  grid-template-columns: repeat(7, 1fr);
410
+ gap: 0.3rem;
411
  }
412
 
413
  .stage-btn {
414
+ padding: 0.8rem 0.25rem;
415
+ font-size: 0.7rem;
416
  background: var(--surface);
417
  border: 1px solid var(--border);
418
  color: var(--text-dim);
419
  cursor: pointer;
420
+ transition: all 0.3s ease;
421
  text-align: center;
422
+ font-family: inherit;
423
+ position: relative;
424
  }
425
 
426
+ .stage-btn:hover:not(.active) {
427
  border-color: var(--accent-dim);
428
+ background: var(--surface-elevated);
429
  }
430
 
431
  .stage-btn.active {
 
434
  color: var(--text);
435
  }
436
 
437
+ .stage-btn[data-stage="0"].active { border-left: 2px solid var(--stage-0); }
438
+ .stage-btn[data-stage="6"].active { border-right: 2px solid var(--stage-6); }
439
 
440
+ .stage-btn::after {
441
+ content: attr(data-desc);
442
+ position: absolute;
443
+ bottom: 100%;
444
+ left: 50%;
445
+ transform: translateX(-50%);
446
+ background: var(--surface-elevated);
447
  border: 1px solid var(--border);
448
+ padding: 0.4rem 0.6rem;
449
+ font-size: 0.55rem;
 
 
 
450
  white-space: nowrap;
451
+ opacity: 0;
452
+ pointer-events: none;
453
+ transition: opacity 0.2s, transform 0.2s;
454
+ z-index: 10;
455
+ margin-bottom: 0.5rem;
456
  }
457
 
458
+ .stage-btn:hover::after {
459
+ opacity: 1;
 
 
 
 
 
 
 
 
 
 
 
 
460
  }
461
 
462
+ /* Status */
463
  .status {
464
  text-align: center;
465
+ font-size: 0.6rem;
466
+ color: var(--text-dimmer);
467
+ padding: 0.75rem;
468
+ letter-spacing: 0.05em;
469
+ border-top: 1px solid var(--border);
470
+ background: var(--surface);
471
  }
472
 
473
  .status.error {
474
+ color: #996666;
475
+ background: rgba(153, 102, 102, 0.1);
476
  }
477
 
478
  .status.success {
479
+ color: #669966;
480
+ background: rgba(102, 153, 102, 0.1);
481
+ }
482
+
483
+ .status.processing {
484
+ color: var(--accent);
485
  }
486
 
487
+ /* Offline badge */
488
  .offline-badge {
489
  position: fixed;
490
+ bottom: 1.5rem;
491
+ right: 1.5rem;
492
  background: var(--surface);
493
  border: 1px solid var(--border);
494
+ padding: 0.5rem 0.75rem;
495
+ font-size: 0.55rem;
496
+ color: var(--text-dimmer);
497
  opacity: 0;
498
+ transition: opacity 0.4s ease;
499
+ letter-spacing: 0.1em;
500
  }
501
 
502
  .offline-badge.visible {
503
  opacity: 1;
504
  }
505
 
506
+ /* Tips */
507
+ .tips {
508
+ font-size: 0.55rem;
509
+ color: var(--text-dimmer);
510
+ text-align: center;
511
+ padding: 0.5rem;
512
+ font-style: italic;
513
+ }
514
+
515
+ /* Responsive */
516
+ @media (max-width: 480px) {
517
  .stages-container {
518
  grid-template-columns: repeat(4, 1fr);
519
  }
 
523
  .stages-container .stage-btn:nth-child(7) {
524
  grid-column: span 1;
525
  }
526
+
527
+ .stage-display {
528
+ padding: 1.5rem 1rem;
529
+ min-height: 120px;
530
+ }
531
+
532
+ .stage-number {
533
+ font-size: 2.5rem;
534
+ }
535
+
536
+ .stage-name {
537
+ font-size: 0.9rem;
538
+ }
539
+
540
+ .stage-desc {
541
+ font-size: 0.65rem;
542
+ }
543
+
544
+ .visualizer-container {
545
+ height: 100px;
546
+ }
547
+ }
548
+
549
+ /* Animations */
550
+ @keyframes pulse {
551
+ 0%, 100% { opacity: 0.3; }
552
+ 50% { opacity: 0.5; }
553
+ }
554
+
555
+ .status.processing::before {
556
+ content: '● ';
557
+ animation: pulse 1.5s infinite;
558
  }
559
  </style>
560
  </head>
 
564
  <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
565
  </header>
566
 
567
+ <div class="stage-display">
568
+ <span class="stage-number" id="stageNumber">0</span>
569
+ <h2 class="stage-name" id="stageName">Estágio 0</h2>
570
+ <p class="stage-desc" id="stageDesc">Áudio puro e inalterado</p>
571
  </div>
572
 
573
  <main>
 
576
  </div>
577
 
578
  <div class="controls">
579
+ <div class="file-input-wrapper">
580
+ <button class="btn" id="loadBtn">
581
+ <span>Carregar Áudio</span>
582
+ </button>
583
+ <input type="file" id="fileInput" accept=".flac,.mp3,.wav,.ogg,.m4a,.aac,.wma,.aiff">
584
  </div>
585
 
586
  <div class="playback-controls">
587
+ <button class="btn" id="playBtn" disabled>▶ Reproduzir</button>
588
+ <button class="btn" id="pauseBtn" disabled>⏸ Pausar</button>
589
+ <button class="btn" id="restartBtn" disabled>Reiniciar</button>
590
  </div>
591
 
592
+ <div class="progress-section">
593
  <div class="progress-bar" id="progressBar">
594
  <div class="progress-fill" id="progressFill"></div>
595
  </div>
 
600
  </div>
601
 
602
  <div class="chaos-control">
603
+ <label>Caos</label>
604
  <input type="range" id="chaosSlider" min="0" max="100" value="0">
605
+ <span class="chaos-value" id="chaosValue">0%</span>
606
+ </div>
607
+
608
+ <div class="stages-section">
609
+ <div class="stages-label">Selecione o estágio de degradação</div>
610
+ <div class="stages-container">
611
+ <button class="stage-btn active" data-stage="0" data-desc="Áudio puro">0</button>
612
+ <button class="stage-btn" data-stage="1" data-desc="Nostalgia doce">1</button>
613
+ <button class="stage-btn" data-stage="2" data-desc="Fragmentação">2</button>
614
+ <button class="stage-btn" data-stage="3" data-desc="Última lucidez">3</button>
615
+ <button class="stage-btn" data-stage="4" data-desc="Colapso">4</button>
616
+ <button class="stage-btn" data-stage="5" data-desc="Agonia">5</button>
617
+ <button class="stage-btn" data-stage="6" data-desc="Dissolução">6</button>
618
+ </div>
619
  </div>
620
 
621
+ <div class="tips">
622
+ Dica: Use fones de ouvido para melhor experiência
 
 
 
 
 
 
623
  </div>
624
  </div>
625
 
 
629
  <div class="offline-badge" id="offlineBadge">Modo Offline</div>
630
 
631
  <script>
632
+ // ==================== SERVICE WORKER REGISTRATION ====================
633
+ if ('serviceWorker' in navigator) {
634
+ const swBlob = new Blob([`${swCode}`], { type: 'application/javascript' });
635
+ const swUrl = URL.createObjectURL(swBlob);
636
+
637
+ navigator.serviceWorker.register(swUrl)
638
+ .then(reg => console.log('SW registered'))
639
+ .catch(err => console.log('SW registration failed:', err));
640
+ }
641
+
642
+ // ==================== SERVICE WORKER CODE ====================
643
  const swCode = `
644
+ const CACHE_NAME = 'everywhere-v2';
645
  const urlsToCache = ['./'];
646
 
647
  self.addEventListener('install', event => {
 
671
  event.respondWith(
672
  caches.match(event.request)
673
  .then(response => {
674
+ if (response) return response;
 
 
675
  return fetch(event.request).then(response => {
676
  if (!response || response.status !== 200 || response.type !== 'basic') {
677
  return response;
678
  }
679
  const responseToCache = response.clone();
680
  caches.open(CACHE_NAME)
681
+ .then(cache => cache.put(event.request, responseToCache));
 
 
682
  return response;
683
  });
684
  })
 
688
 
689
  // ==================== INDEXEDDB ====================
690
  const DB_NAME = 'EverywhereAudioDB';
691
+ const DB_VERSION = 2;
692
  const STORE_NAME = 'audioFiles';
693
 
694
  let db = null;
 
733
  });
734
  }
735
 
736
+ async function clearAudioFromDB(id) {
737
+ return new Promise((resolve, reject) => {
738
+ const transaction = db.transaction([STORE_NAME], 'readwrite');
739
+ const store = transaction.objectStore(STORE_NAME);
740
+ const request = store.delete(id);
741
+ request.onsuccess = () => resolve();
742
+ request.onerror = () => reject(request.error);
743
+ });
 
 
 
744
  }
745
 
746
  // ==================== AUDIO ENGINE ====================
 
759
  // Auxiliary source pool for Stage 4+
760
  let auxiliarySources = [];
761
  let auxiliaryPool = [];
762
+ const POOL_SIZE = 4;
763
 
764
+ // LFO oscillators array
765
  let lfos = [];
766
+
767
+ // Noise and drone for later stages
768
  let noiseSource = null;
769
  let noiseGain = null;
770
  let droneOscillator = null;
 
778
  let pauseTime = 0;
779
  let chaosLevel = 0;
780
  let lastStageTime = 0;
781
+ let stage6EndScheduled = false;
782
+ let animationFrameId = null;
783
 
784
  // Stage descriptions
785
  const stageData = [
786
+ { name: 'Estágio 0', desc: 'Áudio puro e inalterado', number: '0' },
787
+ { name: 'Estágio 1', desc: 'Uma nostalgia doce e borrada que, sob uma superfície calma e repetitiva, esconde os primeiros sinais sutis da demência.', number: '1' },
788
+ { name: 'Estágio 2', desc: 'Uma melancólica recusa em aceitar o esquecimento, onde memórias claras começam a se fragmentar sob estática crescente.', number: '2' },
789
+ { name: 'Estágio 3', desc: 'A última tentativa desesperada da memória em manter a lucidez antes de sucumbir totalmente ao caos e à desorientação.', number: '3' },
790
+ { name: 'Estágio 4', desc: 'A ruptura final da consciência, transformando memórias em ruídos caóticos e desoladores.', number: '4' },
791
+ { name: 'Estágio 5', desc: 'O estágio 5 é a agonia da mente em ruínas, onde fragmentos musicais distorcidos dão lugar a um vazio de puro horror e desorientação.', number: '5' },
792
+ { name: 'Estágio 6', desc: 'A dissolução completa do self em silêncio e caos ambientais, marcando o fim definitivo da memória.', number: '6' }
793
  ];
794
 
795
  function initAudioContext() {
 
800
  }
801
 
802
  // Create impulse response for reverb
803
+ function createImpulseResponse(duration, decay, channels = 2) {
804
  const sampleRate = audioContext.sampleRate;
805
+ const length = Math.floor(sampleRate * duration);
806
+ const impulse = audioContext.createBuffer(channels, length, sampleRate);
807
 
808
+ for (let channel = 0; channel < channels; channel++) {
809
  const channelData = impulse.getChannelData(channel);
810
  for (let i = 0; i < length; i++) {
811
  channelData[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, decay);
 
851
  return noiseBuffer;
852
  }
853
 
854
+ // Create brown noise
855
+ function createBrownNoise() {
856
+ const bufferSize = 2 * audioContext.sampleRate;
857
+ const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
858
+ const output = noiseBuffer.getChannelData(0);
859
+
860
+ let lastOut = 0;
861
+ for (let i = 0; i < bufferSize; i++) {
862
+ const white = Math.random() * 2 - 1;
863
+ output[i] = (lastOut + (0.02 * white)) / 1.02;
864
+ lastOut = output[i];
865
+ output[i] *= 3.5;
866
+ }
867
+
868
+ return noiseBuffer;
869
+ }
870
+
871
  function createAudioGraph() {
872
  const ctx = audioContext;
873
 
 
881
  filterNode = ctx.createBiquadFilter();
882
  filterNode.type = 'lowpass';
883
  filterNode.frequency.value = 20000;
884
+ filterNode.Q.value = 0.7;
885
 
886
  convolverNode = ctx.createConvolver();
887
  convolverNode.buffer = createImpulseResponse(2, 2);
 
899
  // Analyser
900
  analyserNode = ctx.createAnalyser();
901
  analyserNode.fftSize = 2048;
902
+ analyserNode.smoothingTimeConstant = 0.8;
903
 
904
  // Master gain
905
  masterGain = ctx.createGain();
 
922
  for (let i = 0; i < POOL_SIZE; i++) {
923
  auxiliaryPool.push({
924
  source: null,
925
+ gain: null,
926
+ filter: null,
927
  inUse: false
928
  });
929
  }
 
935
  noiseSource.loop = true;
936
  noiseGain = ctx.createGain();
937
  noiseGain.gain.value = 0;
938
+
939
+ const noiseFilter = ctx.createBiquadFilter();
940
+ noiseFilter.type = 'lowpass';
941
+ noiseFilter.frequency.value = 1000;
942
+
943
+ noiseSource.connect(noiseFilter);
944
+ noiseFilter.connect(noiseGain);
945
  noiseGain.connect(masterGain);
946
  noiseSource.start();
947
 
 
951
  droneOscillator.frequency.value = 55;
952
  droneGain = ctx.createGain();
953
  droneGain.gain.value = 0;
954
+
955
+ const droneFilter = ctx.createBiquadFilter();
956
+ droneFilter.type = 'lowpass';
957
+ droneFilter.frequency.value = 200;
958
+
959
+ droneOscillator.connect(droneFilter);
960
+ droneFilter.connect(droneGain);
961
  droneGain.connect(masterGain);
962
  droneOscillator.start();
963
  }
 
973
  const range = max - min;
974
  lfoGain.gain.value = range / 2;
975
 
976
+ // Center value
977
+ const centerValue = (min + max) / 2;
978
+
979
  lfo.connect(lfoGain);
980
  lfoGain.connect(targetParam);
981
 
982
  // Set initial value
983
+ targetParam.setValueAtTime(centerValue, audioContext.currentTime);
984
 
985
  lfo.start();
986
 
987
+ return { lfo, lfoGain, targetParam, min, max, centerValue };
988
  }
989
 
990
+ function clearLFOs() {
991
+ lfos.forEach(lfoObj => {
992
+ try {
993
+ if (lfoObj.lfo) lfoObj.lfo.stop();
994
+ } catch(e) {}
995
  });
996
  lfos = [];
997
+ }
998
 
999
+ function updateLFOsForStage(stage, chaos) {
1000
+ clearLFOs();
1001
+
1002
+ if (!audioBuffer || !gainNode) return;
1003
 
 
1004
  const chaosFactor = chaos / 100;
1005
+ const ctx = audioContext;
1006
+ const now = ctx.currentTime;
1007
 
1008
  switch(stage) {
1009
  case 0:
1010
  // Pure audio - minimal processing
1011
+ gainNode.gain.setTargetAtTime(1, now, 0.5);
1012
+ filterNode.frequency.setTargetAtTime(20000, now, 0.5);
1013
+ filterNode.Q.setTargetAtTime(0.7, now, 0.5);
1014
+ waveShaperNode.curve = makeDistortionCurve(0);
1015
+ convolverNode.buffer = createImpulseResponse(1, 4);
1016
+ delayNode.delayTime.setTargetAtTime(0.3, now, 0.5);
1017
+ feedbackGain.gain.setTargetAtTime(0.2, now, 0.5);
1018
+ masterGain.gain.setTargetAtTime(1, now, 0.5);
1019
+ noiseGain.gain.setTargetAtTime(0, now, 0.5);
1020
+ droneGain.gain.setTargetAtTime(0, now, 0.5);
1021
+ if (sourceNode && sourceNode.playbackRate) {
1022
+ sourceNode.playbackRate.setTargetAtTime(1, now, 0.5);
1023
+ }
1024
  break;
1025
 
1026
  case 1:
1027
  // Stage 1: Subtle warmth, slight blur
1028
+ gainNode.gain.setTargetAtTime(0.95, now, 1);
1029
+ filterNode.frequency.setTargetAtTime(8000 + chaosFactor * 2000, now, 2);
1030
+ filterNode.Q.setTargetAtTime(1, now, 1);
1031
+ convolverNode.buffer = createImpulseResponse(1.5 + chaosFactor, 3 - chaosFactor);
1032
+ delayNode.delayTime.setTargetAtTime(0.35, now, 1);
1033
+ feedbackGain.gain.setTargetAtTime(0.25 + chaosFactor * 0.1, now, 1);
1034
+ waveShaperNode.curve = makeDistortionCurve(chaosFactor * 5);
1035
+
1036
+ // Subtle LFO for warmth
1037
+ lfos.push(createLFO(gainNode.gain, 0.08, 0.92, 0.98, 'sine'));
1038
  break;
1039
 
1040
  case 2:
1041
  // Stage 2: Fragmentation begins
1042
+ gainNode.gain.setTargetAtTime(0.88 - chaosFactor * 0.05, now, 0.8);
1043
+ filterNode.frequency.setTargetAtTime(5000 - chaosFactor * 1500, now, 0.8);
1044
+ filterNode.Q.setTargetAtTime(2 + chaosFactor * 2, now, 0.8);
1045
+ delayNode.delayTime.setTargetAtTime(0.4 + chaosFactor * 0.15, now, 0.8);
1046
+ feedbackGain.gain.setTargetAtTime(0.35 + chaosFactor * 0.15, now, 0.8);
1047
+ waveShaperNode.curve = makeDistortionCurve(5 + chaosFactor * 15);
1048
+ convolverNode.buffer = createImpulseResponse(2 + chaosFactor, 2);
1049
 
1050
+ // LFOs for instability
1051
+ lfos.push(createLFO(gainNode.gain, 0.12, 0.82, 0.92, 'sine'));
1052
+ lfos.push(createLFO(filterNode.frequency, 0.06, 3500, 6000, 'triangle'));
1053
  break;
1054
 
1055
  case 3:
1056
  // Stage 3: Desperate lucidity
1057
+ gainNode.gain.setTargetAtTime(0.8 - chaosFactor * 0.1, now, 0.5);
1058
+ filterNode.frequency.setTargetAtTime(3000 - chaosFactor * 1000, now, 0.5);
1059
+ filterNode.Q.setTargetAtTime(4 + chaosFactor * 3, now, 0.5);
1060
+ waveShaperNode.curve = makeDistortionCurve(20 + chaosFactor * 30);
1061
  convolverNode.buffer = createImpulseResponse(2.5, 1.5);
1062
+ delayNode.delayTime.setTargetAtTime(0.45 + chaosFactor * 0.15, now, 0.5);
1063
+ feedbackGain.gain.setTargetAtTime(0.45 + chaosFactor * 0.15, now, 0.5);
1064
 
1065
+ // More aggressive LFOs
1066
+ lfos.push(createLFO(gainNode.gain, 0.2, 0.65, 0.85, 'sine'));
1067
  lfos.push(createLFO(filterNode.frequency, 0.15, 2000, 4000, 'sawtooth'));
 
1068
  break;
1069
 
1070
  case 4:
1071
+ // Stage 4: Collapse of linearity
1072
+ gainNode.gain.setTargetAtTime(0.7 - chaosFactor * 0.25, now, 0.3);
1073
+ filterNode.frequency.setTargetAtTime(1800 - chaosFactor * 800, now, 0.3);
1074
+ filterNode.Q.setTargetAtTime(6 + chaosFactor * 4, now, 0.3);
1075
+ waveShaperNode.curve = makeDistortionCurve(40 + chaosFactor * 40);
1076
+ delayNode.delayTime.setTargetAtTime(0.5 + chaosFactor * 0.25, now, 0.3);
1077
+ feedbackGain.gain.setTargetAtTime(0.5 + chaosFactor * 0.25, now, 0.3);
1078
+ convolverNode.buffer = createImpulseResponse(3, 1);
1079
 
1080
+ // Activate auxiliary sources
1081
  activateAuxiliarySources(1 + Math.floor(chaosFactor * 2), chaosFactor);
1082
 
1083
+ lfos.push(createLFO(gainNode.gain, 0.3, 0.45, 0.75, 'sine'));
1084
+ lfos.push(createLFO(filterNode.frequency, 0.2, 800, 2200, 'triangle'));
1085
  break;
1086
 
1087
  case 5:
1088
  // Stage 5: Agony, horror, disorientation
1089
+ gainNode.gain.setTargetAtTime(0.5 - chaosFactor * 0.15, now, 0.2);
1090
+ filterNode.frequency.setTargetAtTime(900 - chaosFactor * 300, now, 0.2);
1091
+ filterNode.Q.setTargetAtTime(8 + chaosFactor * 2, now, 0.2);
1092
  waveShaperNode.curve = makeDistortionCurve(80 + chaosFactor * 70);
1093
+ delayNode.delayTime.setTargetAtTime(0.55 + chaosFactor * 0.35, now, 0.2);
1094
+ feedbackGain.gain.setTargetAtTime(0.65, now, 0.2);
1095
+ convolverNode.buffer = createImpulseResponse(3.5, 0.8);
1096
 
1097
  // More auxiliary sources
1098
+ activateAuxiliarySources(2 + Math.floor(chaosFactor), chaosFactor);
1099
 
1100
  // Increase noise
1101
+ noiseGain.gain.setTargetAtTime(0.04 + chaosFactor * 0.08, now, 0.5);
1102
 
1103
+ lfos.push(createLFO(gainNode.gain, 0.5, 0.3, 0.55, 'sawtooth'));
1104
+ lfos.push(createLFO(filterNode.frequency, 0.3, 400, 1100, 'square'));
1105
  break;
1106
 
1107
  case 6:
1108
+ // Stage 6: Dissolution
1109
+ gainNode.gain.setTargetAtTime(0.3 - chaosFactor * 0.15, now, 1);
1110
+ filterNode.frequency.setTargetAtTime(100, now, 2);
1111
+ filterNode.Q.setTargetAtTime(10, now, 2);
1112
  waveShaperNode.curve = makeDistortionCurve(150);
1113
+ masterGain.gain.setTargetAtTime(0.6 - chaosFactor * 0.2, now, 2);
1114
 
1115
  // Drone
1116
+ droneGain.gain.setTargetAtTime(0.12 + chaosFactor * 0.08, now, 3);
1117
+ droneOscillator.frequency.setTargetAtTime(55 * (1 - chaosFactor * 0.15), now, 1);
1118
 
1119
+ // Brown noise floor
1120
+ noiseGain.gain.setTargetAtTime(0.12, now, 2);
1121
 
1122
+ // Extremely slow playback
1123
+ if (sourceNode && sourceNode.playbackRate) {
1124
+ sourceNode.playbackRate.setTargetAtTime(0.08 + Math.random() * 0.08, now, 2);
1125
  }
1126
 
1127
+ // Ghost notes
1128
  clearInterval(ghostNotesInterval);
1129
  ghostNotesInterval = setInterval(() => {
1130
+ if (Math.random() < 0.08) {
1131
  triggerGhostNote(chaosFactor);
1132
  }
1133
+ }, 2500);
1134
 
1135
  // Reset auxiliary
1136
  deactivateAuxiliarySources();
 
1143
 
1144
  function activateAuxiliarySources(count, chaos) {
1145
  for (let i = 0; i < count && i < auxiliaryPool.length; i++) {
1146
+ if (!auxiliaryPool[i].inUse && audioBuffer && audioContext) {
1147
  const auxSource = audioContext.createBufferSource();
1148
  auxSource.buffer = audioBuffer;
1149
 
 
1150
  const startOffset = Math.random() * audioBuffer.duration;
1151
  auxSource.start(0, startOffset);
1152
 
1153
+ const pitchMod = 0.5 + Math.random() * 0.8;
 
1154
  auxSource.playbackRate.value = pitchMod;
1155
 
 
1156
  const auxGain = audioContext.createGain();
1157
+ auxGain.gain.value = 0.25 + Math.random() * 0.2;
1158
 
 
1159
  const auxFilter = audioContext.createBiquadFilter();
1160
  auxFilter.type = 'lowpass';
1161
+ auxFilter.frequency.value = 1500 + Math.random() * 2500;
1162
+ auxFilter.Q.value = 2 + Math.random() * 3;
1163
 
1164
  auxSource.connect(auxGain);
1165
+ aux