const SoundManager = { _ctx: null, _master: null, _analyser: null, _enabled: true, _volume: 0.3, _ambient: null, _ambientGain: null, _initCalled: false, init() { if (this._initCalled) return; this._initCalled = true; try { this._ctx = new (window.AudioContext || window.webkitAudioContext)(); this._master = this._ctx.createGain(); this._master.gain.value = this._volume; this._master.connect(this._ctx.destination); } 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'; const vol = localStorage.getItem('sound_volume'); if (vol !== null) this.setVolume(parseFloat(vol)); document.addEventListener('click', () => { if (this._ctx && this._ctx.state === 'suspended') this._ctx.resume(); }, { once: true }); }, setEnabled(on) { this._enabled = on; localStorage.setItem('sound_enabled', on); if (!on && this._ambientGain) { this._ambientGain.gain.setTargetAtTime(0, this._ctx.currentTime, 0.5); } }, toggle() { this.setEnabled(!this._enabled); return this._enabled; }, setVolume(v) { this._volume = Math.max(0, Math.min(1, v)); if (this._master) this._master.gain.value = this._volume; localStorage.setItem('sound_volume', this._volume); }, _t() { return this._ctx ? this._ctx.currentTime : 0; }, _osc(freq, duration, type = 'sine', vol = 1, dest) { if (!this._enabled || !this._ctx) return; if (this._ctx.state === 'suspended') this._ctx.resume(); const now = this._t(); const o = this._ctx.createOscillator(); const g = this._ctx.createGain(); o.type = type; o.frequency.setValueAtTime(freq, now); g.gain.setValueAtTime(vol, now); g.gain.exponentialRampToValueAtTime(0.001, now + duration); o.connect(g); g.connect(dest || this._master); o.start(now); o.stop(now + duration); }, _noise(duration, vol = 1, filterFreq, dest) { if (!this._enabled || !this._ctx) return; if (this._ctx.state === 'suspended') this._ctx.resume(); const now = this._t(); const sr = this._ctx.sampleRate; const len = sr * duration; const buf = this._ctx.createBuffer(1, len, sr); const data = buf.getChannelData(0); for (let i = 0; i < len; i++) data[i] = Math.random() * 2 - 1; const src = this._ctx.createBufferSource(); src.buffer = buf; let node = src; if (filterFreq) { const f = this._ctx.createBiquadFilter(); f.type = 'lowpass'; f.frequency.value = filterFreq; src.connect(f); node = f; } const g = this._ctx.createGain(); g.gain.setValueAtTime(vol, now); g.gain.exponentialRampToValueAtTime(0.001, now + duration); node.connect(g); g.connect(dest || this._master); src.start(now); }, _chord(freqs, duration, type = 'sine', vol = 0.15) { freqs.forEach(f => this._osc(f, duration, type, vol / freqs.length)); }, // ── Rarity color helpers ── _rarityParams(rarity) { const map = { 'Consumer Grade': { baseFreq: 300, arp: [300, 350, 400], noiseVol: 0.2, dur: 0.6 }, 'Industrial Grade': { baseFreq: 400, arp: [400, 500, 600], noiseVol: 0.2, dur: 0.7 }, 'Mil-Spec': { baseFreq: 500, arp: [500, 630, 750], noiseVol: 0.25, dur: 0.8 }, 'Mil-Spec Grade': { baseFreq: 500, arp: [500, 630, 750], noiseVol: 0.25, dur: 0.8 }, 'Restricted': { baseFreq: 600, arp: [600, 750, 900], noiseVol: 0.3, dur: 1.0 }, 'Classified': { baseFreq: 700, arp: [700, 880, 1050], noiseVol: 0.35, dur: 1.2 }, 'Covert': { baseFreq: 800, arp: [800, 1000, 1200], noiseVol: 0.4, dur: 1.5 }, 'Rare Special Item': { baseFreq: 900, arp: [900, 1100, 1400], noiseVol: 0.45, dur: 1.8 }, 'Extraordinary': { baseFreq: 900, arp: [900, 1100, 1400], noiseVol: 0.45, dur: 1.8 }, }; return map[rarity] || map['Consumer Grade']; }, // ── Ambient ── startAmbient() { if (!this._ctx || this._ambient) return; const now = this._t(); this._ambientGain = this._ctx.createGain(); this._ambientGain.gain.value = 0; const bufLen = this._ctx.sampleRate * 2; const buf = this._ctx.createBuffer(1, bufLen, this._ctx.sampleRate); const data = buf.getChannelData(0); for (let i = 0; i < bufLen; i++) { const t = i / this._ctx.sampleRate; data[i] = (Math.random() - 0.5) * 0.5 + Math.sin(2 * Math.PI * 55 * t) * 0.08 + Math.sin(2 * Math.PI * 82.5 * t) * 0.05; } const src = this._ctx.createBufferSource(); src.buffer = buf; src.loop = true; const lp = this._ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 300; const hp = this._ctx.createBiquadFilter(); hp.type = 'highpass'; hp.frequency.value = 40; src.connect(lp); lp.connect(hp); hp.connect(this._ambientGain); this._ambientGain.connect(this._master); src.start(); this._ambient = src; this._ambientGain.gain.setTargetAtTime(0.06, now, 1); }, stopAmbient() { if (this._ambientGain) { this._ambientGain.gain.setTargetAtTime(0, this._t(), 0.5); setTimeout(() => { if (this._ambient) { try { this._ambient.stop(); } catch (e) {} this._ambient = null; } }, 1500); } }, // ── Case Opening ── caseOpen() { this._noise(0.15, 0.4, 2000); this._osc(700, 0.06, 'square', 0.25); setTimeout(() => this._osc(500, 0.08, 'square', 0.2), 50); setTimeout(() => this._osc(350, 0.1, 'sawtooth', 0.18), 110); setTimeout(() => this._osc(250, 0.12, 'sine', 0.12), 180); setTimeout(() => this._noise(0.3, 0.15, 4000), 120); }, caseReveal(rarity) { const p = this._rarityParams(rarity); this._noise(0.08, 0.3, 3000); p.arp.forEach((f, i) => { setTimeout(() => { this._osc(f, 0.15, 'sine', 0.25); this._osc(f * 2, 0.1, 'sine', 0.08); }, i * 100); }); setTimeout(() => { this._osc(p.baseFreq * 2, 0.4, 'sine', 0.35); this._noise(0.15, p.noiseVol, 5000); this._osc(p.baseFreq, 0.6, 'sawtooth', 0.15); }, p.arp.length * 100); }, caseCovert() { this._noise(0.1, 0.5, 4000); [392, 523, 659, 784, 1047].forEach((f, i) => { setTimeout(() => { this._osc(f, 0.2, 'sawtooth', 0.3); this._osc(f * 1.5, 0.15, 'sine', 0.1); }, i * 120); }); setTimeout(() => { this._osc(1047, 0.8, 'sine', 0.6); this._osc(784, 0.6, 'triangle', 0.25); this._noise(0.6, 0.4, 6000); }, 600); }, caseRare() { [523, 659, 784, 1047].forEach((f, i) => { setTimeout(() => { this._osc(f, 0.12, 'sine', 0.3); this._osc(f * 1.5, 0.08, 'sine', 0.06); }, i * 80); }); setTimeout(() => this._osc(1319, 0.5, 'sine', 0.5), 320); }, // ── Slots ── slotSpin() { for (let i = 0; i < 10; i++) { setTimeout(() => { this._osc(200 + Math.random() * 600, 0.04, 'square', 0.12); }, i * 60); } }, slotMatch() { [523, 659, 784, 1047].forEach((f, i) => { setTimeout(() => this._osc(f, 0.15, 'sine', 0.35), i * 100); }); setTimeout(() => this._noise(0.2, 0.3, 8000), 300); }, // ── Contract ── contractSubmit() { this._osc(150, 0.25, 'sawtooth', 0.2); setTimeout(() => this._osc(200, 0.15, 'square', 0.18), 150); setTimeout(() => { this._osc(250, 0.08, 'square', 0.15); this._noise(0.05, 0.15); }, 280); }, contractResult() { [400, 600, 800].forEach((f, i) => { setTimeout(() => this._osc(f, 0.15, 'sine', 0.3), i * 100); }); setTimeout(() => { this._osc(1200, 0.4, 'sine', 0.45); this._noise(0.2, 0.2, 5000); }, 300); }, // ── Wheel ── wheelTick() { this._osc(200, 0.03, 'square', 0.08); }, wheelWin() { [523, 659, 784, 1047].forEach((f, i) => { setTimeout(() => this._osc(f, 0.18, 'sine', 0.35), i * 100); }); setTimeout(() => this._noise(0.4, 0.3, 8000), 400); }, wheelLose() { [400, 350, 300].forEach((f, i) => { setTimeout(() => this._osc(f, 0.25, 'sine', 0.2), i * 150); }); }, // ── Achievements ── achievement() { [659, 523, 784, 659, 1047].forEach((f, i) => { setTimeout(() => this._osc(f, 0.12, 'sine', 0.3), i * 80); }); setTimeout(() => this._noise(0.3, 0.25, 6000), 400); }, // ── Crash ── crashBet() { this._osc(300, 0.08, 'square', 0.15); setTimeout(() => this._osc(350, 0.06, 'square', 0.1), 60); }, crashTick(multiplier) { const freq = 300 + Math.min(multiplier * 50, 800); this._osc(freq, 0.03, 'sine', 0.05 + Math.min(multiplier * 0.01, 0.15)); }, crashCrashed() { this._noise(0.5, 0.6, 2000); this._osc(80, 0.6, 'sawtooth', 0.4); setTimeout(() => this._osc(50, 1, 'sine', 0.3), 100); setTimeout(() => this._osc(40, 0.8, 'triangle', 0.2), 200); }, crashCashout(multiplier) { const vol = 0.2 + Math.min(multiplier * 0.02, 0.5); [600, 800, 1000, 1200].forEach((f, i) => { setTimeout(() => this._osc(f, 0.12, 'sine', vol), i * 80); }); setTimeout(() => this._noise(0.3, 0.3, 8000), 320); }, // ── Upgrade ── upgradeSubmit() { this._osc(250, 0.1, 'square', 0.15); this._noise(0.05, 0.2); }, upgradeResult() { [523, 784, 1047].forEach((f, i) => { setTimeout(() => this._osc(f, 0.2, 'sine', 0.3), i * 120); }); setTimeout(() => this._noise(0.25, 0.25, 6000), 360); }, upgradeFail() { [300, 250, 200].forEach((f, i) => { setTimeout(() => this._osc(f, 0.2, 'sawtooth', 0.2), i * 120); }); }, // ── UI ── click() { this._osc(800, 0.03, 'sine', 0.08); }, hover() { this._osc(1200, 0.015, 'sine', 0.03); }, error() { this._osc(200, 0.15, 'square', 0.2); setTimeout(() => this._osc(180, 0.12, 'square', 0.15), 100); }, success() { [600, 800, 1000].forEach((f, i) => { setTimeout(() => this._osc(f, 0.08, 'sine', 0.2), i * 60); }); }, balanceUpdate() { this._osc(1000, 0.04, 'sine', 0.08); setTimeout(() => this._osc(1200, 0.04, 'sine', 0.06), 50); }, }; document.addEventListener('DOMContentLoaded', () => SoundManager.init());