// Легкий звук без файлів: усе синтезується WebAudio. // Дощ у польового, гудіння термінала в аналітика, кліки, штамп, фінальний стінг. "use strict"; window.Snd = (() => { let ctx = null, master = null, started = false, muted = localStorage.getItem("ch3snd") === "off"; function ensure() { if (ctx) return; ctx = new (window.AudioContext || window.webkitAudioContext)(); master = ctx.createGain(); master.gain.value = muted ? 0 : 1; master.connect(ctx.destination); } function noiseBuf(secs) { const b = ctx.createBuffer(1, ctx.sampleRate * secs, ctx.sampleRate); const d = b.getChannelData(0); for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1; return b; } // фоновий шар: дощ (поле) або гудіння ЕПТ (аналітик) function start(role) { ensure(); if (started) return; started = true; const src = ctx.createBufferSource(); src.buffer = noiseBuf(3); src.loop = true; const filt = ctx.createBiquadFilter(); const amb = ctx.createGain(); if (role === "field") { filt.type = "bandpass"; filt.frequency.value = 1100; filt.Q.value = 0.4; amb.gain.value = 0.045; // пориви дощу const lfo = ctx.createOscillator(), lg = ctx.createGain(); lfo.frequency.value = 0.11; lg.gain.value = 0.018; lfo.connect(lg).connect(amb.gain); lfo.start(); } else { filt.type = "lowpass"; filt.frequency.value = 130; amb.gain.value = 0.02; const hum = ctx.createOscillator(), hg = ctx.createGain(); hum.frequency.value = 50; hg.gain.value = 0.006; hum.connect(hg).connect(master); hum.start(); } src.connect(filt).connect(amb).connect(master); src.start(); } function blip(freq, dur, vol, type) { if (!ctx) return; const o = ctx.createOscillator(), gn = ctx.createGain(); o.type = type || "square"; o.frequency.value = freq; gn.gain.setValueAtTime(vol, ctx.currentTime); gn.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + dur); o.connect(gn).connect(master); o.start(); o.stop(ctx.currentTime + dur); } const click = () => blip(1300, 0.03, 0.05); function stamp() { if (!ctx) return; blip(85, 0.16, 0.22, "sine"); const n = ctx.createBufferSource(); n.buffer = noiseBuf(0.08); const gn = ctx.createGain(); gn.gain.setValueAtTime(0.12, ctx.currentTime); gn.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.09); n.connect(gn).connect(master); n.start(); } function sting(solved) { if (!ctx) return; const seq = solved ? [220, 277, 330, 440] : [196, 185, 147, 98]; seq.forEach((f, i) => setTimeout(() => blip(f, 0.5, 0.09, "triangle"), i * 190), ); } function toggle() { muted = !muted; localStorage.setItem("ch3snd", muted ? "off" : "on"); if (master) master.gain.value = muted ? 0 : 1; return muted; } // клік на будь-якій кнопці — тихий тик document.addEventListener( "click", (e) => { if (e.target.closest("button")) click(); }, true, ); return { start, click, stamp, sting, toggle, get muted() { return muted; } }; })();