const SoundManager = { _ctx: null, _enabled: true, _volume: 0.3, _activeNodes: new Set(), _timeouts: [], init() { try { this._ctx = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { console.warn('Web Audio API not supported'); this._enabled = false; } const saved = localStorage.getItem('sound_enabled'); if (saved !== null) this._enabled = saved === 'true'; }, setEnabled(on) { this._enabled = on; localStorage.setItem('sound_enabled', on); }, toggle() { this.setEnabled(!this._enabled); return this._enabled; }, stopAll() { this._timeouts.forEach(clearTimeout); this._timeouts = []; this._activeNodes.forEach(node => { try { node.stop(); } catch (e) {} try { node.disconnect(); } catch (e) {} }); this._activeNodes.clear(); }, _trackNode(node, duration) { this._activeNodes.add(node); node.addEventListener('ended', () => this._activeNodes.delete(node)); setTimeout(() => { this._activeNodes.delete(node); try { node.disconnect(); } catch (e) {} }, (duration + 0.1) * 1000); }, _play(freq, duration, type = 'sine', volume = 1) { if (!this._enabled || !this._ctx) return; if (this._ctx.state === 'suspended') this._ctx.resume(); const osc = this._ctx.createOscillator(); const gain = this._ctx.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, this._ctx.currentTime); gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration); osc.connect(gain); gain.connect(this._ctx.destination); osc.start(); osc.stop(this._ctx.currentTime + duration); this._trackNode(osc, duration); this._trackNode(gain, duration); }, _noise(duration, volume = 1) { if (!this._enabled || !this._ctx) return; if (this._ctx.state === 'suspended') this._ctx.resume(); const bufferSize = this._ctx.sampleRate * duration; const buffer = this._ctx.createBuffer(1, bufferSize, this._ctx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) { data[i] = Math.random() * 2 - 1; } const source = this._ctx.createBufferSource(); source.buffer = buffer; const gain = this._ctx.createGain(); gain.gain.setValueAtTime(this._volume * volume, this._ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, this._ctx.currentTime + duration); source.connect(gain); gain.connect(this._ctx.destination); source.start(); this._trackNode(source, duration); this._trackNode(gain, duration); }, _delay(fn, ms) { const id = setTimeout(() => { this._timeouts = this._timeouts.filter(t => t !== id); fn(); }, ms); this._timeouts.push(id); return id; }, caseOpen() { this._play(800, 0.1, 'square', 0.3); this._delay(() => this._play(400, 0.15, 'sawtooth', 0.2), 80); this._delay(() => this._play(200, 0.2, 'sine', 0.15), 180); this._noise(0.05, 0.4); }, caseRare() { this._play(523, 0.15, 'sine', 0.3); this._delay(() => this._play(659, 0.15, 'sine', 0.3), 100); this._delay(() => this._play(784, 0.15, 'sine', 0.3), 200); this._delay(() => this._play(1047, 0.3, 'sine', 0.4), 300); }, caseCovert() { this._play(392, 0.2, 'sawtooth', 0.3); this._delay(() => this._play(523, 0.2, 'sawtooth', 0.3), 150); this._delay(() => this._play(659, 0.2, 'sawtooth', 0.3), 300); this._delay(() => this._play(784, 0.4, 'sine', 0.5), 450); this._delay(() => this._play(1047, 0.6, 'sine', 0.6), 600); }, slotSpin() { for (let i = 0; i < 8; i++) { this._delay(() => { this._play(300 + Math.random() * 400, 0.05, 'square', 0.15); }, i * 80); } }, slotMatch() { this._play(523, 0.1, 'sine', 0.3); this._delay(() => this._play(659, 0.1, 'sine', 0.3), 100); this._delay(() => this._play(784, 0.1, 'sine', 0.3), 200); this._delay(() => this._play(1047, 0.4, 'sine', 0.5), 300); }, contractSubmit() { this._play(150, 0.3, 'sawtooth', 0.2); this._delay(() => this._play(200, 0.2, 'square', 0.2), 200); this._delay(() => this._play(250, 0.1, 'square', 0.15), 350); }, contractResult() { this._play(400, 0.15, 'sine', 0.3); this._delay(() => this._play(600, 0.15, 'sine', 0.3), 120); this._delay(() => this._play(800, 0.3, 'sine', 0.4), 240); }, wheelSpin() { this._play(600 + Math.random() * 400, 0.04, 'triangle', 0.12); this._play(200, 0.02, 'square', 0.06); }, wheelWin() { const t = this._ctx.currentTime; if (!this._enabled || !this._ctx) return; if (this._ctx.state === 'suspended') this._ctx.resume(); [523, 659, 784, 1047].forEach((freq, i) => { const osc = this._ctx.createOscillator(); const gain = this._ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(freq, t + i * 0.1); gain.gain.setValueAtTime(0, t + i * 0.1); gain.gain.linearRampToValueAtTime(this._volume * 0.35, t + i * 0.1 + 0.05); gain.gain.exponentialRampToValueAtTime(0.001, t + i * 0.1 + 0.5); osc.connect(gain); gain.connect(this._ctx.destination); osc.start(t + i * 0.1); osc.stop(t + i * 0.1 + 0.5); this._trackNode(osc, 0.6); this._trackNode(gain, 0.6); }); this._delay(() => this._play(1568, 0.4, 'sine', 0.25), 350); this._delay(() => this._play(2093, 0.6, 'sine', 0.2), 500); }, wheelLose() { this._play(300, 0.15, 'sawtooth', 0.25); this._delay(() => this._play(200, 0.2, 'sawtooth', 0.2), 100); this._delay(() => this._play(120, 0.3, 'sawtooth', 0.15), 220); this._noise(0.15, 0.3); this._delay(() => { this._play(60, 0.5, 'sine', 0.2); this._play(45, 0.6, 'sine', 0.15); }, 350); }, achievement() { this._play(659, 0.1, 'sine', 0.3); this._delay(() => this._play(523, 0.1, 'sine', 0.3), 100); this._delay(() => this._play(784, 0.1, 'sine', 0.3), 200); this._delay(() => this._play(659, 0.1, 'sine', 0.3), 300); this._delay(() => this._play(1047, 0.4, 'sine', 0.5), 400); }, crashBet() { this._play(300, 0.1, 'square', 0.15); }, crashTick() { this._play(500 + Math.random() * 500, 0.03, 'sine', 0.08); }, crashCrashed() { this._noise(0.3, 0.6); this._play(80, 0.5, 'sawtooth', 0.4); this._delay(() => this._play(50, 0.8, 'sine', 0.3), 100); }, crashCashout() { this._play(600, 0.1, 'sine', 0.3); this._delay(() => this._play(800, 0.1, 'sine', 0.3), 80); this._delay(() => this._play(1000, 0.2, 'sine', 0.4), 160); }, click() { this._play(800, 0.03, 'sine', 0.1); }, error() { this._play(200, 0.15, 'square', 0.2); }, balanceUpdate() { this._play(1000, 0.05, 'sine', 0.1); } }; document.addEventListener('DOMContentLoaded', () => SoundManager.init());