/** * PK10 Sound Engine (SoundFX) * Depends on: (none - first to load) * Provides: SoundFX, toggleRaceSound * * Sound only plays when the race modal is open (raceModalOpen === true). * betSuccess/error are exempt — they are UI feedback, not race sounds. */ var SoundFX={ enabled:localStorage.getItem('raceSoundOn')!=='0', _cache:{}, // Core play — checks both enabled AND raceModalOpen _play:function(name,vol,startAt){if(!this.enabled||!window.raceModalOpen)return null;var a=new Audio('/Static/sounds/'+name+'.mp3');a.volume=Math.min(vol||0.6,1);if(startAt)a.currentTime=startAt;a.play().catch(function(){});return a;}, _stop:function(key){if(this._cache[key]){try{this._cache[key].pause();this._cache[key].currentTime=0;}catch(e){}delete this._cache[key];}}, engineIdle:function(){this._cache.idle=this._play('engine',0.3);}, trafficBeep:function(n){this._play('beep',n>0?0.4:0.6);}, engineStart:function(){this._stop('idle');this._cache.eng=this._play('engine',0.7);}, accelerate:function(){this._stop('eng');this._cache.race=this._play('racing',0.5);}, gearShift:function(){this._play('engine',0.25,1.5);}, whoosh:function(){this._play('whoosh',0.5);}, brakeSqueal:function(){this._stop('race');this._play('brake',0.6);}, finish:function(){this._play('finish',0.7);}, podium:function(){this._play('finish',0.5);}, // tick — race countdown sound, only when modal open tick:function(){if(!this.enabled||!window.raceModalOpen)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();var o=ctx.createOscillator(),g=ctx.createGain();o.type='sine';o.frequency.value=800;g.gain.setValueAtTime(0.1,ctx.currentTime);g.gain.linearRampToValueAtTime(0,ctx.currentTime+0.05);o.connect(g).connect(ctx.destination);o.start();o.stop(ctx.currentTime+0.05);}catch(e){}}, // betSuccess/error — UI feedback, always plays when enabled (no modal check) betSuccess:function(){if(!this.enabled)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();[880,1175].forEach(function(f,i){var o=ctx.createOscillator(),g=ctx.createGain();o.type='sine';o.frequency.value=f;g.gain.setValueAtTime(0.08,ctx.currentTime+i*0.08);g.gain.linearRampToValueAtTime(0,ctx.currentTime+i*0.08+0.12);o.connect(g).connect(ctx.destination);o.start(ctx.currentTime+i*0.08);o.stop(ctx.currentTime+i*0.08+0.12);});}catch(e){}}, error:function(){if(!this.enabled)return;try{var ctx=new(window.AudioContext||window.webkitAudioContext)();var o=ctx.createOscillator(),g=ctx.createGain();o.type='square';o.frequency.setValueAtTime(200,ctx.currentTime);o.frequency.setValueAtTime(150,ctx.currentTime+0.15);g.gain.setValueAtTime(0.06,ctx.currentTime);g.gain.linearRampToValueAtTime(0,ctx.currentTime+0.3);o.connect(g).connect(ctx.destination);o.start();o.stop(ctx.currentTime+0.3);}catch(e){}}, // stopAll — kill all cached sounds (called when modal closes) stopAll:function(){for(var k in this._cache){this._stop(k);}}, toggle:function(){this.enabled=!this.enabled;localStorage.setItem('raceSoundOn',this.enabled?'1':'0');return this.enabled;} }; function toggleRaceSound(){var on=SoundFX.toggle();var btn=document.querySelector('.rs-sound');if(btn)btn.textContent=on?'🔊':'🔇';if(on)SoundFX.tick();}