zeroed-some/sortmanteau / b3d55ee

Browse files

init sortmanteau

Authored by espadonne
SHA
b3d55eee2dc045fe1b2fe1757815630f3eb07a01
Tree
6f65f5b

4 changed files

StatusFile+-
A css/styles.css 330 0
A deploy.sh 28 0
A index.html 66 0
A js/app.js 777 0
css/styles.cssadded
@@ -0,0 +1,330 @@
1
+@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
2
+
3
+* {
4
+  margin: 0;
5
+  padding: 0;
6
+  box-sizing: border-box;
7
+}
8
+
9
+body {
10
+  font-family: 'VT323', monospace;
11
+  background: #000;
12
+  color: #0f0;
13
+  min-height: 100vh;
14
+  display: flex;
15
+  flex-direction: column;
16
+  text-shadow: 0 0 5px #0f0;
17
+  overflow-x: hidden;
18
+}
19
+
20
+body::before {
21
+  content: '';
22
+  position: fixed;
23
+  top: 0;
24
+  left: 0;
25
+  width: 100%;
26
+  height: 100%;
27
+  background: repeating-linear-gradient(
28
+    0deg,
29
+    rgba(0, 0, 0, 0.15),
30
+    rgba(0, 0, 0, 0.15) 1px,
31
+    transparent 1px,
32
+    transparent 2px
33
+  );
34
+  pointer-events: none;
35
+  z-index: 1;
36
+}
37
+
38
+.container {
39
+  position: relative;
40
+  z-index: 2;
41
+}
42
+
43
+header {
44
+  padding: 2rem;
45
+  text-align: center;
46
+  border-bottom: 2px solid #0f0;
47
+}
48
+
49
+.ascii-title {
50
+  font-size: 1.5rem;
51
+  line-height: 1.2;
52
+  white-space: pre;
53
+  color: #0f0;
54
+  margin-bottom: 1rem;
55
+  overflow-x: auto;
56
+}
57
+
58
+@media (max-width: 768px) {
59
+  .ascii-title {
60
+    font-size: 0.7rem;
61
+  }
62
+}
63
+
64
+.subtitle {
65
+  font-size: 1.5rem;
66
+  color: #0a0;
67
+  animation: blink 1s infinite;
68
+}
69
+
70
+@keyframes blink {
71
+  0%,
72
+  50% {
73
+    opacity: 1;
74
+  }
75
+  51%,
76
+  100% {
77
+    opacity: 0.3;
78
+  }
79
+}
80
+
81
+.controls {
82
+  display: flex;
83
+  justify-content: center;
84
+  gap: 1rem;
85
+  padding: 2rem;
86
+  flex-wrap: wrap;
87
+  font-size: 1.2rem;
88
+}
89
+
90
+@media (max-width: 768px) {
91
+  .controls {
92
+    padding: 1rem;
93
+    gap: 0.5rem;
94
+  }
95
+}
96
+
97
+button {
98
+  background: #000;
99
+  color: #0f0;
100
+  border: 2px solid #0f0;
101
+  padding: 0.5rem 1rem;
102
+  font-family: 'VT323', monospace;
103
+  font-size: 1.2rem;
104
+  cursor: pointer;
105
+  transition: all 0.1s ease;
106
+  text-transform: uppercase;
107
+  position: relative;
108
+  text-shadow: 0 0 5px #0f0;
109
+}
110
+
111
+@media (max-width: 768px) {
112
+  button {
113
+    padding: 0.4rem 0.6rem;
114
+    font-size: 1rem;
115
+    border-width: 1px;
116
+  }
117
+}
118
+
119
+button::before {
120
+  content: '[ ';
121
+}
122
+
123
+button::after {
124
+  content: ' ]';
125
+}
126
+
127
+button:hover {
128
+  background: #0f0;
129
+  color: #000;
130
+  text-shadow: none;
131
+  box-shadow: 0 0 10px #0f0;
132
+}
133
+
134
+button:active {
135
+  transform: translate(2px, 2px);
136
+}
137
+
138
+button:disabled {
139
+  color: #030;
140
+  border-color: #030;
141
+  cursor: not-allowed;
142
+  text-shadow: none;
143
+}
144
+
145
+button:disabled:hover {
146
+  background: #000;
147
+  color: #030;
148
+}
149
+
150
+.algorithm-btn.active {
151
+  background: #0f0;
152
+  color: #000;
153
+  text-shadow: none;
154
+  animation: pulse 0.5s infinite alternate;
155
+}
156
+
157
+@keyframes pulse {
158
+  from {
159
+    box-shadow: 0 0 5px #0f0;
160
+  }
161
+  to {
162
+    box-shadow: 0 0 20px #0f0;
163
+  }
164
+}
165
+
166
+.visualization-container {
167
+  flex: 1;
168
+  padding: 2rem;
169
+  font-family: monospace;
170
+  font-size: 14px;
171
+  line-height: 1.2;
172
+  white-space: pre;
173
+  display: flex;
174
+  flex-direction: column;
175
+  align-items: center;
176
+  justify-content: center;
177
+  overflow-x: auto;
178
+}
179
+
180
+@media (max-width: 768px) {
181
+  .visualization-container {
182
+    padding: 1rem;
183
+    font-size: 10px;
184
+  }
185
+}
186
+
187
+.ascii-display {
188
+  text-align: left;
189
+  padding: 1rem;
190
+  border: 2px solid #0f0;
191
+  background: #000;
192
+  box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
193
+  overflow-x: auto;
194
+  max-width: 100%;
195
+}
196
+
197
+@media (max-width: 768px) {
198
+  .ascii-display {
199
+    padding: 0.5rem;
200
+    border-width: 1px;
201
+    font-size: 0.8rem;
202
+  }
203
+}
204
+
205
+.bar-row {
206
+  color: #0f0;
207
+  transition: all 0.3s ease;
208
+}
209
+
210
+.bar-row.comparing {
211
+  color: #ff0;
212
+  text-shadow: 0 0 10px #ff0;
213
+}
214
+
215
+.bar-row.sorted {
216
+  color: #0ff;
217
+  text-shadow: 0 0 10px #0ff;
218
+}
219
+
220
+.stats {
221
+  display: flex;
222
+  justify-content: center;
223
+  gap: 3rem;
224
+  padding: 2rem;
225
+  border-top: 2px solid #0f0;
226
+  font-size: 1.2rem;
227
+}
228
+
229
+@media (max-width: 768px) {
230
+  .stats {
231
+    gap: 1rem;
232
+    padding: 1rem;
233
+    font-size: 1rem;
234
+    border-width: 1px;
235
+  }
236
+}
237
+
238
+.stat {
239
+  text-align: center;
240
+}
241
+
242
+.stat-label {
243
+  color: #0a0;
244
+}
245
+
246
+@media (max-width: 768px) {
247
+  .stat-label {
248
+    font-size: 0.9rem;
249
+  }
250
+}
251
+
252
+.stat-value {
253
+  font-size: 2rem;
254
+  color: #0f0;
255
+  text-shadow: 0 0 10px #0f0;
256
+}
257
+
258
+@media (max-width: 768px) {
259
+  .stat-value {
260
+    font-size: 1.5rem;
261
+  }
262
+}
263
+
264
+.speed-control {
265
+  display: flex;
266
+  align-items: center;
267
+  gap: 1rem;
268
+}
269
+
270
+input[type='range'] {
271
+  width: 150px;
272
+  height: 8px;
273
+  background: #030;
274
+  border: 1px solid #0f0;
275
+  outline: none;
276
+  cursor: pointer;
277
+}
278
+
279
+input[type='range']::-webkit-slider-thumb {
280
+  -webkit-appearance: none;
281
+  width: 16px;
282
+  height: 16px;
283
+  background: #0f0;
284
+  border: 2px solid #000;
285
+  cursor: pointer;
286
+  box-shadow: 0 0 10px #0f0;
287
+}
288
+
289
+.glitch {
290
+  animation: glitch 2s infinite;
291
+}
292
+
293
+@keyframes glitch {
294
+  0%,
295
+  90%,
296
+  100% {
297
+    transform: translate(0);
298
+  }
299
+  91% {
300
+    transform: translate(-2px, 2px);
301
+  }
302
+  92% {
303
+    transform: translate(2px, -1px);
304
+  }
305
+  93% {
306
+    transform: translate(-1px, 1px);
307
+  }
308
+}
309
+
310
+.ascii-title {
311
+    font-size: 1.5rem;
312
+    line-height: 1.2;
313
+    white-space: pre;
314
+    color: #0f0;
315
+    margin-bottom: 1rem;
316
+    overflow-x: auto;
317
+    text-align: center;
318
+    display: inline-block;
319
+    margin-left: auto;
320
+    margin-right: auto;
321
+}
322
+
323
+header {
324
+    padding: 2rem;
325
+    text-align: center;
326
+    border-bottom: 2px solid #0f0;
327
+    display: flex;
328
+    flex-direction: column;
329
+    align-items: center;
330
+}
deploy.shadded
@@ -0,0 +1,28 @@
1
+#!/usr/bin/env bash
2
+set -euo pipefail
3
+
4
+PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+STAMP=$(date +%Y-%m-%d-%H%M%S)
6
+OUT=~/builds/$STAMP
7
+mkdir -p "$OUT"
8
+
9
+echo "▶ Staging files"
10
+rsync -az --delete \
11
+      --exclude deploy.sh --exclude .git \
12
+      "$PROJECT_DIR"/ "$OUT"/
13
+
14
+echo "▶ Publishing release $STAMP"
15
+rsync -az --delete "$OUT"/ \
16
+      /var/www/sortmanteau.musicsian.com/releases/$STAMP/
17
+
18
+echo "▶ Flipping 'current' symlink"
19
+sudo ln -nfs /var/www/sortmanteau.musicsian.com/releases/$STAMP \
20
+            /var/www/sortmanteau.musicsian.com/current
21
+
22
+echo "▶ Restoring SELinux context"
23
+sudo restorecon -Rv /var/www/sortmanteau.musicsian.com/releases/$STAMP >/dev/null
24
+
25
+echo "▶ Reloading Nginx"
26
+sudo systemctl reload nginx
27
+
28
+echo "✓ Deployed $STAMP → sortmanteau.musicsian.com"
index.htmladded
@@ -0,0 +1,66 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+    <meta charset="UTF-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+    <link rel="stylesheet" href="css/styles.css">
7
+    <title>ASCII Sorting Visualizer</title>
8
+</head>
9
+<body>
10
+    <div class="container">
11
+        <header>
12
+            <div class="ascii-title">
13
+               ╔═╗ ╔═╗ ╔═╗ ╦╦  ╔═╗ ╔═╗ ╦═╗ ╔╦╗ ╔═╗ ╦═╗
14
+              ╠═╣ ╚═╗ ║   ║║  ╚═╗ ║ ║ ╠╦╝  ║  ║╣  ╠╦╝
15
+               ╩ ╩ ╚═╝ ╚═╝ ╩╩  ╚═╝ ╚═╝ ╩╚═  ╩  ╚═╝ ╩╚═
16
+            </div>
17
+            <p class="subtitle">&gt;&gt; TERMINAL SORTING VISUALIZATION &lt;&lt;</p>
18
+        </header>
19
+
20
+        <div class="controls">
21
+            <button onclick="generateArray()">NEW_ARRAY</button>
22
+            <button class="algorithm-btn" onclick="selectAlgorithm('bubble')">BUBBLE</button>
23
+            <button class="algorithm-btn" onclick="selectAlgorithm('selection')">SELECTION</button>
24
+            <button class="algorithm-btn" onclick="selectAlgorithm('insertion')">INSERTION</button>
25
+            <button class="algorithm-btn" onclick="selectAlgorithm('quick')">QUICK</button>
26
+            <button class="algorithm-btn" onclick="selectAlgorithm('merge')">MERGE</button>
27
+            <button class="algorithm-btn" onclick="selectAlgorithm('heap')">HEAP</button>
28
+            <button class="algorithm-btn" onclick="selectAlgorithm('shell')">SHELL</button>
29
+            <button class="algorithm-btn" onclick="selectAlgorithm('cocktail')">COCKTAIL</button>
30
+            <button class="algorithm-btn" onclick="selectAlgorithm('radix')">RADIX</button>
31
+            <button class="algorithm-btn" onclick="selectAlgorithm('gnome')">GNOME</button>
32
+            <button class="algorithm-btn" onclick="selectAlgorithm('bogo')">BOGO</button>
33
+            <button id="startBtn" onclick="startSort()">EXECUTE</button>
34
+            <button id="soundBtn" onclick="toggleSound()">SOUND: ON</button>
35
+            <div class="speed-control">
36
+                <span>SPEED:</span>
37
+                <input type="range" id="speedSlider" min="1" max="100" value="50">
38
+            </div>
39
+        </div>
40
+
41
+        <div class="visualization-container">
42
+            <div class="ascii-display" id="visualization"></div>
43
+        </div>
44
+
45
+        <div class="stats">
46
+            <div class="stat">
47
+                <div class="stat-label">:: COMPARISONS ::</div>
48
+                <div class="stat-value glitch" id="comparisons">0</div>
49
+            </div>
50
+            <div class="stat">
51
+                <div class="stat-label">:: SWAPS ::</div>
52
+                <div class="stat-value glitch" id="swaps">0</div>
53
+            </div>
54
+            <div class="stat">
55
+                <div class="stat-label">:: ARRAY_SIZE ::</div>
56
+                <div class="stat-value" id="arraySize">20</div>
57
+            </div>
58
+            <div class="stat" id="attemptsContainer" style="display: none;">
59
+                <div class="stat-label">:: ATTEMPTS ::</div>
60
+                <div class="stat-value" id="attempts">0</div>
61
+            </div>
62
+        </div>
63
+    </div>
64
+    <script src="js/app.js"></script>
65
+</body>
66
+</html>
js/app.jsadded
@@ -0,0 +1,777 @@
1
+let array = [];
2
+let arraySize = 20;
3
+let selectedAlgorithm = 'bubble';
4
+let sorting = false;
5
+let comparisons = 0;
6
+let swaps = 0;
7
+let attempts = 0;
8
+
9
+const asciiChars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
10
+
11
+// Audio context and sound system
12
+let audioContext;
13
+let isSoundEnabled = true;
14
+let slideWhistleOscillator;
15
+let slideWhistleGain;
16
+let startTime;
17
+let totalSortingTime = 10; // Will be dynamically adjusted
18
+
19
+function initAudio() {
20
+    if (!audioContext) {
21
+        audioContext = new (window.AudioContext || window.webkitAudioContext)();
22
+    }
23
+}
24
+
25
+function startSlideWhistle() {
26
+    if (!isSoundEnabled || !audioContext) return;
27
+    
28
+    // Create oscillator and gain for slide whistle
29
+    slideWhistleOscillator = audioContext.createOscillator();
30
+    slideWhistleGain = audioContext.createGain();
31
+    
32
+    slideWhistleOscillator.type = 'sine';
33
+    
34
+    // Start at low frequency
35
+    slideWhistleOscillator.frequency.setValueAtTime(200, audioContext.currentTime);
36
+    
37
+    // Estimate sorting time based on array size and algorithm
38
+    totalSortingTime = estimateSortingTime();
39
+    
40
+    // Schedule a smooth frequency ramp over the estimated time
41
+    slideWhistleOscillator.frequency.exponentialRampToValueAtTime(
42
+        800, 
43
+        audioContext.currentTime + totalSortingTime
44
+    );
45
+    
46
+    slideWhistleGain.gain.setValueAtTime(0.15, audioContext.currentTime);
47
+    
48
+    slideWhistleOscillator.connect(slideWhistleGain);
49
+    slideWhistleGain.connect(audioContext.destination);
50
+    
51
+    slideWhistleOscillator.start();
52
+    startTime = audioContext.currentTime;
53
+}
54
+
55
+function estimateSortingTime() {
56
+    // Estimate based on array size and algorithm complexity
57
+    const baseTime = array.length * getDelay() / 1000;
58
+    
59
+    switch(selectedAlgorithm) {
60
+        case 'bubble':
61
+        case 'selection':
62
+        case 'insertion':
63
+        case 'cocktail':
64
+            return baseTime * array.length * 0.5;
65
+        case 'quick':
66
+        case 'merge':
67
+        case 'heap':
68
+            return baseTime * Math.log2(array.length) * 2;
69
+        case 'shell':
70
+            return baseTime * array.length * 0.3;
71
+        case 'radix':
72
+            return baseTime * 3;
73
+        case 'gnome':
74
+            return baseTime * array.length * 0.7;
75
+        case 'bogo':
76
+            return baseTime * 10; // Who knows!
77
+        default:
78
+            return baseTime * array.length * 0.5;
79
+    }
80
+}
81
+
82
+function updateSlideWhistle(progress) {
83
+    // With exponential ramping, we don't need to update manually
84
+    // The Web Audio API handles the smooth transition
85
+}
86
+
87
+function stopSlideWhistle() {
88
+    if (!slideWhistleOscillator) return;
89
+    
90
+    // Cancel any scheduled frequency changes
91
+    slideWhistleOscillator.frequency.cancelScheduledValues(audioContext.currentTime);
92
+    
93
+    // Set current frequency and fade out
94
+    const currentFreq = slideWhistleOscillator.frequency.value;
95
+    slideWhistleOscillator.frequency.setValueAtTime(currentFreq, audioContext.currentTime);
96
+    
97
+    // Smooth fade out
98
+    slideWhistleGain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.2);
99
+    
100
+    setTimeout(() => {
101
+        if (slideWhistleOscillator) {
102
+            slideWhistleOscillator.stop();
103
+            slideWhistleOscillator = null;
104
+            slideWhistleGain = null;
105
+        }
106
+    }, 200);
107
+}
108
+
109
+function playZoot() {
110
+    if (!isSoundEnabled || !audioContext) return;
111
+    
112
+    // Create a fun "zoot" sound with multiple components
113
+    const oscillator1 = audioContext.createOscillator();
114
+    const oscillator2 = audioContext.createOscillator();
115
+    const oscillator3 = audioContext.createOscillator();
116
+    const gainNode = audioContext.createGain();
117
+    const filter = audioContext.createBiquadFilter();
118
+    
119
+    // Three oscillators for a richer, more satisfying sound
120
+    oscillator1.type = 'sawtooth';
121
+    oscillator2.type = 'square';
122
+    oscillator3.type = 'triangle';
123
+    
124
+    // Quick frequency sweep for "zoot" effect
125
+    const now = audioContext.currentTime;
126
+    
127
+    // Main sweep
128
+    oscillator1.frequency.setValueAtTime(800, now);
129
+    oscillator1.frequency.exponentialRampToValueAtTime(1600, now + 0.05);
130
+    oscillator1.frequency.exponentialRampToValueAtTime(400, now + 0.15);
131
+    oscillator1.frequency.exponentialRampToValueAtTime(800, now + 0.25);
132
+    
133
+    // Harmonic sweep
134
+    oscillator2.frequency.setValueAtTime(400, now);
135
+    oscillator2.frequency.exponentialRampToValueAtTime(800, now + 0.05);
136
+    oscillator2.frequency.exponentialRampToValueAtTime(200, now + 0.15);
137
+    oscillator2.frequency.exponentialRampToValueAtTime(400, now + 0.25);
138
+    
139
+    // Sub bass sweep
140
+    oscillator3.frequency.setValueAtTime(100, now);
141
+    oscillator3.frequency.exponentialRampToValueAtTime(200, now + 0.1);
142
+    oscillator3.frequency.exponentialRampToValueAtTime(50, now + 0.25);
143
+    
144
+    // Filter sweep for extra "zoot" character
145
+    filter.type = 'bandpass';
146
+    filter.frequency.setValueAtTime(400, now);
147
+    filter.frequency.exponentialRampToValueAtTime(2000, now + 0.1);
148
+    filter.frequency.exponentialRampToValueAtTime(800, now + 0.25);
149
+    filter.Q.value = 5;
150
+    
151
+    // Envelope
152
+    gainNode.gain.setValueAtTime(0, now);
153
+    gainNode.gain.linearRampToValueAtTime(0.4, now + 0.02);
154
+    gainNode.gain.linearRampToValueAtTime(0.3, now + 0.1);
155
+    gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
156
+    
157
+    // Connect
158
+    oscillator1.connect(filter);
159
+    oscillator2.connect(filter);
160
+    oscillator3.connect(filter);
161
+    filter.connect(gainNode);
162
+    gainNode.connect(audioContext.destination);
163
+    
164
+    // Play
165
+    oscillator1.start();
166
+    oscillator2.start();
167
+    oscillator3.start();
168
+    oscillator1.stop(now + 0.4);
169
+    oscillator2.stop(now + 0.4);
170
+    oscillator3.stop(now + 0.4);
171
+}
172
+
173
+// Add a function to calculate sorting progress
174
+function getSortingProgress() {
175
+    let sortedCount = 0;
176
+    const rows = document.querySelectorAll('.bar-row');
177
+    rows.forEach(row => {
178
+        if (row.classList.contains('sorted')) {
179
+            sortedCount++;
180
+        }
181
+    });
182
+    return sortedCount / array.length;
183
+}
184
+
185
+// Toggle sound on/off
186
+function toggleSound() {
187
+    isSoundEnabled = !isSoundEnabled;
188
+    const soundBtn = document.getElementById('soundBtn');
189
+    soundBtn.textContent = isSoundEnabled ? 'SOUND: ON' : 'SOUND: OFF';
190
+    
191
+    // If sound is turned off while playing, stop the whistle
192
+    if (!isSoundEnabled && slideWhistleOscillator) {
193
+        stopSlideWhistle();
194
+    }
195
+}
196
+
197
+function generateArray() {
198
+    // Use smaller array for Bogo Sort
199
+    if (selectedAlgorithm === 'bogo') {
200
+        arraySize = 5; // Much smaller for Bogo!
201
+        document.getElementById('arraySize').textContent = arraySize;
202
+    } else if (arraySize === 5 && selectedAlgorithm !== 'bogo') {
203
+        arraySize = 20; // Reset to normal size
204
+        document.getElementById('arraySize').textContent = arraySize;
205
+    }
206
+    
207
+    array = [];
208
+    for (let i = 0; i < arraySize; i++) {
209
+        array.push(Math.floor(Math.random() * 90) + 10);
210
+    }
211
+    renderArray();
212
+    resetStats();
213
+}
214
+
215
+function getAsciiBar(value, maxValue) {
216
+    const normalized = value / maxValue;
217
+    const maxBarLength = window.innerWidth < 768 ? 20 : 40;
218
+    const barLength = Math.floor(normalized * maxBarLength);
219
+    let bar = '';
220
+    
221
+    for (let i = 0; i < barLength; i++) {
222
+        bar += asciiChars[Math.min(7, Math.floor(i / 5))];
223
+    }
224
+    
225
+    return bar;
226
+}
227
+
228
+function renderArray() {
229
+    const container = document.getElementById('visualization');
230
+    const maxValue = Math.max(...array);
231
+    const isMobile = window.innerWidth < 768;
232
+    const borderLength = isMobile ? 30 : 50;
233
+    const padLength = isMobile ? 20 : 40;
234
+    let asciiDisplay = '';
235
+    
236
+    // Top border
237
+    asciiDisplay += '┌' + '─'.repeat(borderLength) + '┐\n';
238
+    
239
+    // Array visualization
240
+    array.forEach((value, index) => {
241
+        const bar = getAsciiBar(value, maxValue);
242
+        const paddedValue = String(value).padStart(3, '0');
243
+        const paddedIndex = String(index).padStart(2, '0');
244
+        if (isMobile) {
245
+            asciiDisplay += `│${paddedIndex}:[${paddedValue}]${bar.padEnd(padLength, ' ')}│\n`;
246
+        } else {
247
+            asciiDisplay += `│ ${paddedIndex}: [${paddedValue}] ${bar.padEnd(padLength, ' ')}│\n`;
248
+        }
249
+    });
250
+    
251
+    // Bottom border
252
+    asciiDisplay += '└' + '─'.repeat(borderLength) + '┘';
253
+    
254
+    container.innerHTML = asciiDisplay.split('\n').map((line, index) => {
255
+        if (index > 0 && index <= array.length) {
256
+            return `<span class="bar-row" data-index="${index-1}">${line}</span>`;
257
+        }
258
+        return `<span>${line}</span>`;
259
+    }).join('\n');
260
+}
261
+
262
+function resetStats() {
263
+    comparisons = 0;
264
+    swaps = 0;
265
+    attempts = 0;
266
+    updateStats();
267
+}
268
+
269
+function updateStats() {
270
+    document.getElementById('comparisons').textContent = String(comparisons).padStart(4, '0');
271
+    document.getElementById('swaps').textContent = String(swaps).padStart(4, '0');
272
+    document.getElementById('attempts').textContent = String(attempts).padStart(4, '0');
273
+}
274
+
275
+function selectAlgorithm(algo) {
276
+    selectedAlgorithm = algo;
277
+    document.querySelectorAll('.algorithm-btn').forEach(btn => {
278
+        btn.classList.remove('active');
279
+    });
280
+    event.target.classList.add('active');
281
+    
282
+    // Show/hide attempts counter for Bogo Sort
283
+    const attemptsContainer = document.getElementById('attemptsContainer');
284
+    if (algo === 'bogo') {
285
+        attemptsContainer.style.display = 'block';
286
+        generateArray(); // Auto-generate smaller array for Bogo
287
+    } else {
288
+        attemptsContainer.style.display = 'none';
289
+        if (arraySize === 5) {
290
+            generateArray(); // Reset to normal size
291
+        }
292
+    }
293
+}
294
+
295
+async function startSort() {
296
+    if (sorting) return;
297
+    
298
+    sorting = true;
299
+    document.getElementById('startBtn').disabled = true;
300
+    resetStats();
301
+    
302
+    // Initialize audio on user interaction
303
+    initAudio();
304
+    
305
+    // Start slide whistle
306
+    startSlideWhistle();
307
+    
308
+    switch (selectedAlgorithm) {
309
+        case 'bubble':
310
+            await bubbleSort();
311
+            break;
312
+        case 'selection':
313
+            await selectionSort();
314
+            break;
315
+        case 'insertion':
316
+            await insertionSort();
317
+            break;
318
+        case 'quick':
319
+            await quickSort(0, array.length - 1);
320
+            break;
321
+        case 'merge':
322
+            await mergeSort(0, array.length - 1);
323
+            break;
324
+        case 'heap':
325
+            await heapSort();
326
+            break;
327
+        case 'shell':
328
+            await shellSort();
329
+            break;
330
+        case 'cocktail':
331
+            await cocktailSort();
332
+            break;
333
+        case 'radix':
334
+            await radixSort();
335
+            break;
336
+        case 'gnome':
337
+            await gnomeSort();
338
+            break;
339
+        case 'bogo':
340
+            await bogoSort();
341
+            break;
342
+    }
343
+    
344
+    // Stop slide whistle and play zoot
345
+    stopSlideWhistle();
346
+    playZoot();
347
+    
348
+    markAllSorted();
349
+    sorting = false;
350
+    document.getElementById('startBtn').disabled = false;
351
+}
352
+
353
+function getDelay() {
354
+    return 101 - document.getElementById('speedSlider').value;
355
+}
356
+
357
+async function sleep(ms) {
358
+    return new Promise(resolve => setTimeout(resolve, ms));
359
+}
360
+
361
+async function compare(i, j) {
362
+    comparisons++;
363
+    updateStats();
364
+    const rows = document.querySelectorAll('.bar-row');
365
+    if (rows[i]) rows[i].classList.add('comparing');
366
+    if (rows[j]) rows[j].classList.add('comparing');
367
+    
368
+    // Subtle pitch update during comparisons for continuous glissando
369
+    const progress = getSortingProgress();
370
+    updateSlideWhistle(progress);
371
+    
372
+    await sleep(getDelay());
373
+    if (rows[i]) rows[i].classList.remove('comparing');
374
+    if (rows[j]) rows[j].classList.remove('comparing');
375
+}
376
+
377
+async function swap(i, j) {
378
+    swaps++;
379
+    updateStats();
380
+    
381
+    const temp = array[i];
382
+    array[i] = array[j];
383
+    array[j] = temp;
384
+    
385
+    renderArray();
386
+    
387
+    // Update slide whistle pitch based on progress
388
+    const progress = getSortingProgress();
389
+    updateSlideWhistle(progress);
390
+    
391
+    await sleep(getDelay());
392
+}
393
+
394
+async function bubbleSort() {
395
+    for (let i = 0; i < array.length - 1; i++) {
396
+        for (let j = 0; j < array.length - i - 1; j++) {
397
+            await compare(j, j + 1);
398
+            if (array[j] > array[j + 1]) {
399
+                await swap(j, j + 1);
400
+            }
401
+        }
402
+        document.querySelectorAll('.bar-row')[array.length - i - 1].classList.add('sorted');
403
+        
404
+        // Update slide whistle pitch
405
+        const progress = getSortingProgress();
406
+        updateSlideWhistle(progress);
407
+    }
408
+    document.querySelectorAll('.bar-row')[0].classList.add('sorted');
409
+}
410
+
411
+async function selectionSort() {
412
+    for (let i = 0; i < array.length - 1; i++) {
413
+        let minIdx = i;
414
+        for (let j = i + 1; j < array.length; j++) {
415
+            await compare(minIdx, j);
416
+            if (array[j] < array[minIdx]) {
417
+                minIdx = j;
418
+            }
419
+        }
420
+        if (minIdx !== i) {
421
+            await swap(i, minIdx);
422
+        }
423
+        document.querySelectorAll('.bar-row')[i].classList.add('sorted');
424
+        
425
+        // Update slide whistle pitch
426
+        const progress = getSortingProgress();
427
+        updateSlideWhistle(progress);
428
+    }
429
+    document.querySelectorAll('.bar-row')[array.length - 1].classList.add('sorted');
430
+}
431
+
432
+async function insertionSort() {
433
+    for (let i = 1; i < array.length; i++) {
434
+        let j = i - 1;
435
+        let key = array[i];
436
+        
437
+        while (j >= 0 && array[j] > key) {
438
+            await compare(j, j + 1);
439
+            array[j + 1] = array[j];
440
+            renderArray();
441
+            j--;
442
+            await sleep(getDelay());
443
+        }
444
+        
445
+        array[j + 1] = key;
446
+        renderArray();
447
+        document.querySelectorAll('.bar-row')[j + 1].classList.add('sorted');
448
+    }
449
+    markAllSorted();
450
+}
451
+
452
+async function quickSort(low, high) {
453
+    if (low < high) {
454
+        let pi = await partition(low, high);
455
+        await quickSort(low, pi - 1);
456
+        await quickSort(pi + 1, high);
457
+    }
458
+}
459
+
460
+async function partition(low, high) {
461
+    let pivot = array[high];
462
+    let i = low - 1;
463
+    
464
+    for (let j = low; j < high; j++) {
465
+        await compare(j, high);
466
+        if (array[j] < pivot) {
467
+            i++;
468
+            await swap(i, j);
469
+        }
470
+    }
471
+    
472
+    await swap(i + 1, high);
473
+    return i + 1;
474
+}
475
+
476
+async function mergeSort(left, right) {
477
+    if (left < right) {
478
+        let mid = Math.floor((left + right) / 2);
479
+        await mergeSort(left, mid);
480
+        await mergeSort(mid + 1, right);
481
+        await merge(left, mid, right);
482
+    }
483
+}
484
+
485
+async function merge(left, mid, right) {
486
+    let leftArr = array.slice(left, mid + 1);
487
+    let rightArr = array.slice(mid + 1, right + 1);
488
+    let i = 0, j = 0, k = left;
489
+    
490
+    while (i < leftArr.length && j < rightArr.length) {
491
+        await compare(left + i, mid + 1 + j);
492
+        if (leftArr[i] <= rightArr[j]) {
493
+            array[k] = leftArr[i];
494
+            i++;
495
+        } else {
496
+            array[k] = rightArr[j];
497
+            j++;
498
+        }
499
+        k++;
500
+        renderArray();
501
+        await sleep(getDelay());
502
+    }
503
+    
504
+    while (i < leftArr.length) {
505
+        array[k] = leftArr[i];
506
+        i++;
507
+        k++;
508
+        renderArray();
509
+        await sleep(getDelay());
510
+    }
511
+    
512
+    while (j < rightArr.length) {
513
+        array[k] = rightArr[j];
514
+        j++;
515
+        k++;
516
+        renderArray();
517
+        await sleep(getDelay());
518
+    }
519
+}
520
+
521
+// Heap Sort
522
+async function heapSort() {
523
+    // Build max heap
524
+    for (let i = Math.floor(array.length / 2) - 1; i >= 0; i--) {
525
+        await heapify(array.length, i);
526
+    }
527
+    
528
+    // Extract elements from heap one by one
529
+    for (let i = array.length - 1; i > 0; i--) {
530
+        await swap(0, i);
531
+        document.querySelectorAll('.bar-row')[i].classList.add('sorted');
532
+        await heapify(i, 0);
533
+        
534
+        // Update slide whistle pitch
535
+        const progress = getSortingProgress();
536
+        updateSlideWhistle(progress);
537
+    }
538
+    document.querySelectorAll('.bar-row')[0].classList.add('sorted');
539
+}
540
+
541
+async function heapify(n, i) {
542
+    let largest = i;
543
+    let left = 2 * i + 1;
544
+    let right = 2 * i + 2;
545
+    
546
+    if (left < n) {
547
+        await compare(left, largest);
548
+        if (array[left] > array[largest]) {
549
+            largest = left;
550
+        }
551
+    }
552
+    
553
+    if (right < n) {
554
+        await compare(right, largest);
555
+        if (array[right] > array[largest]) {
556
+            largest = right;
557
+        }
558
+    }
559
+    
560
+    if (largest !== i) {
561
+        await swap(i, largest);
562
+        await heapify(n, largest);
563
+    }
564
+}
565
+
566
+// Shell Sort
567
+async function shellSort() {
568
+    let gap = Math.floor(array.length / 2);
569
+    
570
+    while (gap > 0) {
571
+        for (let i = gap; i < array.length; i++) {
572
+            let temp = array[i];
573
+            let j = i;
574
+            
575
+            while (j >= gap) {
576
+                await compare(j - gap, j);
577
+                if (array[j - gap] > temp) {
578
+                    array[j] = array[j - gap];
579
+                    j -= gap;
580
+                    renderArray();
581
+                    await sleep(getDelay());
582
+                } else {
583
+                    break;
584
+                }
585
+            }
586
+            
587
+            array[j] = temp;
588
+            renderArray();
589
+            await sleep(getDelay());
590
+        }
591
+        
592
+        gap = Math.floor(gap / 2);
593
+    }
594
+    
595
+    // Mark all as sorted
596
+    for (let i = 0; i < array.length; i++) {
597
+        document.querySelectorAll('.bar-row')[i].classList.add('sorted');
598
+    }
599
+}
600
+
601
+// Cocktail Sort (Bidirectional Bubble Sort)
602
+async function cocktailSort() {
603
+    let swapped = true;
604
+    let start = 0;
605
+    let end = array.length - 1;
606
+    
607
+    while (swapped) {
608
+        swapped = false;
609
+        
610
+        // Forward pass (like bubble sort)
611
+        for (let i = start; i < end; i++) {
612
+            await compare(i, i + 1);
613
+            if (array[i] > array[i + 1]) {
614
+                await swap(i, i + 1);
615
+                swapped = true;
616
+            }
617
+        }
618
+        
619
+        if (!swapped) break;
620
+        
621
+        document.querySelectorAll('.bar-row')[end].classList.add('sorted');
622
+        end--;
623
+        swapped = false;
624
+        
625
+        // Backward pass
626
+        for (let i = end; i > start; i--) {
627
+            await compare(i, i - 1);
628
+            if (array[i] < array[i - 1]) {
629
+                await swap(i, i - 1);
630
+                swapped = true;
631
+            }
632
+        }
633
+        
634
+        document.querySelectorAll('.bar-row')[start].classList.add('sorted');
635
+        start++;
636
+    }
637
+    
638
+    // Mark remaining elements as sorted
639
+    for (let i = start; i <= end; i++) {
640
+        document.querySelectorAll('.bar-row')[i].classList.add('sorted');
641
+    }
642
+}
643
+
644
+// Radix Sort (LSD - Least Significant Digit)
645
+async function radixSort() {
646
+    // Find the maximum number to know number of digits
647
+    let max = Math.max(...array);
648
+    
649
+    // Do counting sort for every digit
650
+    for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) {
651
+        await countingSortByDigit(exp);
652
+    }
653
+    
654
+    // Mark all as sorted
655
+    for (let i = 0; i < array.length; i++) {
656
+        document.querySelectorAll('.bar-row')[i].classList.add('sorted');
657
+    }
658
+}
659
+
660
+async function countingSortByDigit(exp) {
661
+    let output = new Array(array.length);
662
+    let count = new Array(10).fill(0);
663
+    
664
+    // Store count of occurrences
665
+    for (let i = 0; i < array.length; i++) {
666
+        let digit = Math.floor(array[i] / exp) % 10;
667
+        count[digit]++;
668
+    }
669
+    
670
+    // Change count[i] so it contains actual position
671
+    for (let i = 1; i < 10; i++) {
672
+        count[i] += count[i - 1];
673
+    }
674
+    
675
+    // Build the output array
676
+    for (let i = array.length - 1; i >= 0; i--) {
677
+        let digit = Math.floor(array[i] / exp) % 10;
678
+        output[count[digit] - 1] = array[i];
679
+        count[digit]--;
680
+        
681
+        // Visual comparison for the digit
682
+        if (i < array.length - 1) {
683
+            await compare(i, i + 1);
684
+        }
685
+    }
686
+    
687
+    // Copy the output array to arr[]
688
+    for (let i = 0; i < array.length; i++) {
689
+        array[i] = output[i];
690
+        renderArray();
691
+        await sleep(getDelay());
692
+    }
693
+}
694
+
695
+// Gnome Sort (also called Stupid Sort)
696
+async function gnomeSort() {
697
+    let index = 0;
698
+    
699
+    while (index < array.length) {
700
+        if (index == 0) {
701
+            index++;
702
+        } else {
703
+            await compare(index, index - 1);
704
+            if (array[index] >= array[index - 1]) {
705
+                document.querySelectorAll('.bar-row')[index - 1].classList.add('sorted');
706
+                index++;
707
+            } else {
708
+                await swap(index, index - 1);
709
+                index--;
710
+            }
711
+        }
712
+    }
713
+    
714
+    // Mark the last element as sorted
715
+    if (array.length > 0) {
716
+        document.querySelectorAll('.bar-row')[array.length - 1].classList.add('sorted');
717
+    }
718
+}
719
+
720
+// Bogo Sort (Warning: This is intentionally terrible!)
721
+async function bogoSort() {
722
+    attempts = 0;
723
+    const maxAttempts = 10000; // Safety limit to prevent infinite loops
724
+    
725
+    while (!isSorted() && attempts < maxAttempts) {
726
+        // Shuffle the array randomly
727
+        await shuffleArray();
728
+        attempts++;
729
+        updateStats();
730
+    }
731
+    
732
+    if (attempts >= maxAttempts) {
733
+        alert("Bogo Sort gave up after " + maxAttempts + " attempts! Try a smaller array.");
734
+    }
735
+    
736
+    // Mark all as sorted
737
+    for (let i = 0; i < array.length; i++) {
738
+        document.querySelectorAll('.bar-row')[i].classList.add('sorted');
739
+    }
740
+}
741
+
742
+function isSorted() {
743
+    for (let i = 0; i < array.length - 1; i++) {
744
+        if (array[i] > array[i + 1]) {
745
+            return false;
746
+        }
747
+    }
748
+    return true;
749
+}
750
+
751
+async function shuffleArray() {
752
+    // Fisher-Yates shuffle with visualization
753
+    for (let i = array.length - 1; i > 0; i--) {
754
+        let j = Math.floor(Math.random() * (i + 1));
755
+        await compare(i, j);
756
+        await swap(i, j);
757
+    }
758
+}
759
+
760
+function markAllSorted() {
761
+    document.querySelectorAll('.bar-row').forEach(row => {
762
+        row.classList.add('sorted');
763
+    });
764
+}
765
+
766
+// Initialize
767
+generateArray();
768
+document.querySelector('.algorithm-btn').click();
769
+
770
+// Re-render on window resize
771
+let resizeTimeout;
772
+window.addEventListener('resize', () => {
773
+    clearTimeout(resizeTimeout);
774
+    resizeTimeout = setTimeout(() => {
775
+        renderArray();
776
+    }, 250);
777
+});