1(function () { 2 if (window.startradSfx) return; 3 4 var ctx = null; 5 var enabled = true; 6 var unlocked = false; 7 8 try { 9 var stored = localStorage.getItem('startrad_sound'); 10 if (stored !== null) { 11 enabled = !(stored === '0' || stored === 'off' || stored === 'false'); 12 } 13 } catch (e) {} 14 15 function getCtx() { 16 if (!ctx) { 17 var Ctx = window.AudioContext || window.webkitAudioContext; 18 if (!Ctx) return null; 19 ctx = new Ctx(); 20 } 21 return ctx; 22 } 23 24 function emit(c, freq, dur, type, gain) { 25 var now = c.currentTime; 26 var duration = Number(dur || 0.06); 27 var volume = Math.max(0.0002, Number(gain || 0.04)); 28 var osc = c.createOscillator(); 29 var amp = c.createGain(); 30 osc.type = type || 'sine'; 31 osc.frequency.value = Number(freq || 440); 32 amp.gain.setValueAtTime(0.0001, now); 33 amp.gain.exponentialRampToValueAtTime(volume, now + 0.01); 34 amp.gain.exponentialRampToValueAtTime(0.0001, now + duration); 35 osc.connect(amp); 36 amp.connect(c.destination); 37 osc.start(now); 38 osc.stop(now + duration); 39 } 40 41 function unlockAudio() { 42 try { 43 var c = getCtx(); 44 if (!c) return; 45 if (c.state === 'running') { 46 unlocked = true; 47 return; 48 } 49 var resumed = c.resume(); 50 if (resumed && typeof resumed.then === 'function') { 51 resumed.then(function () { unlocked = true; }).catch(function () {}); 52 } else { 53 unlocked = true; 54 } 55 } catch (e) {} 56 } 57 58 function play(freq, dur, type, gain) { 59 if (!enabled) return; 60 try { 61 var c = getCtx(); 62 if (!c) return; 63 var run = function () { 64 emit(c, freq, dur, type, gain); 65 }; 66 if (c.state === 'suspended') { 67 var resumed = c.resume(); 68 if (resumed && typeof resumed.then === 'function') { 69 resumed.then(function () { 70 unlocked = true; 71 run(); 72 }).catch(function () {}); 73 } else { 74 run(); 75 } 76 return; 77 } 78 run(); 79 } catch (e) {} 80 } 81 82 var api = { 83 hover: function () { play(880, 0.045, 'sine', 0.022); }, 84 click: function () { play(520, 0.09, 'triangle', 0.08); }, 85 tick: function () { play(1200, 0.035, 'square', 0.02); }, 86 toggle: function () { 87 play(420, 0.055, 'sine', 0.045); 88 setTimeout(function () { play(720, 0.055, 'sine', 0.038); }, 40); 89 }, 90 setEnabled: function (value) { 91 enabled = !!value; 92 try { localStorage.setItem('startrad_sound', enabled ? 'on' : 'off'); } catch (e) {} 93 }, 94 isEnabled: function () { 95 return enabled; 96 } 97 }; 98 99 window.startradSfx = api; 100 101 var hoverSelector = 'a, button, [role="button"], .faq-item, .install-step'; 102 var clickSelector = 'a.btn, button.btn, a.cta, [data-sfx-click]'; 103 var lastHoverTarget = null; 104 var lastHoverTs = 0; 105 var unlockOptions = { passive: true, capture: true }; 106 107 document.addEventListener('pointerdown', unlockAudio, unlockOptions); 108 document.addEventListener('touchstart', unlockAudio, unlockOptions); 109 document.addEventListener('keydown', unlockAudio, true); 110 document.addEventListener('mousedown', unlockAudio, unlockOptions); 111 112 document.addEventListener('mouseover', function (event) { 113 var target = event.target; 114 if (!target || typeof target.closest !== 'function') return; 115 var interactive = target.closest(hoverSelector); 116 if (!interactive) return; 117 var rel = event.relatedTarget; 118 if (rel && typeof interactive.contains === 'function' && interactive.contains(rel)) return; 119 120 var now = (window.performance && typeof window.performance.now === 'function') 121 ? window.performance.now() 122 : Date.now(); 123 if (interactive === lastHoverTarget && now - lastHoverTs < 70) return; 124 125 lastHoverTarget = interactive; 126 lastHoverTs = now; 127 api.hover(); 128 }, true); 129 130 document.addEventListener('click', function (event) { 131 var target = event.target; 132 if (!target || typeof target.closest !== 'function') return; 133 if (target.closest(clickSelector)) { 134 api.click(); 135 } 136 }, true); 137 138 if (!unlocked) { 139 unlockAudio(); 140 } 141})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.