Soundscape - Atmosphere Creator

Rain, fireplace, nature, ocean, night sounds while browsing – interaction‑aware atmosphere

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Soundscape - Atmosphere Creator
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Rain, fireplace, nature, ocean, night sounds while browsing – interaction‑aware atmosphere
// @author       Mustafa Hakan
// @match        *://*/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    const AC = window.AudioContext || window.webkitAudioContext;
    let ctx, master, playing = false, currentProfile = 'rain', volume = 0.4;
    let oscillators = [], gainNodes = [], noiseNodes = [];
    let rainNodes = [], fireNodes = [], windNodes = [], birdNodes = [];

    const PROFILES = {
        rain: {
            name: '🌧️ Rainstorm',
            desc: 'Gentle rain and distant thunder',
            color: '#4a90d9',
            icon: '🌧️'
        },
        fireplace: {
            name: '🔥 Fireplace',
            desc: 'Crackling logs and warm crackle',
            color: '#d94a1a',
            icon: '🔥'
        },
        forest: {
            name: '🌲 Deep Forest',
            desc: 'Birdsong, wind and rustling leaves',
            color: '#3d7a3d',
            icon: '🌲'
        },
        ocean: {
            name: '🌊 Ocean Waves',
            desc: 'Waves on the shore and seagulls',
            color: '#1a6b8a',
            icon: '🌊'
        },
        night: {
            name: '🦗 Night Symphony',
            desc: 'Crickets, owl and distant wind',
            color: '#2d1b69',
            icon: '🦗'
        },
        thunder: {
            name: '⛈️ Thunderstorm',
            desc: 'Heavy rain and close lightning',
            color: '#5a5a8a',
            icon: '⛈️'
        },
        cafe: {
            name: '☕ Café Ambience',
            desc: 'Distant chatter, clinking cups, soft music',
            color: '#8b6914',
            icon: '☕'
        }
    };

    function createNoise(duration) {
        const size = ctx.sampleRate * duration;
        const buffer = ctx.createBuffer(1, size, ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < size; i++) {
            data[i] = Math.random() * 2 - 1;
        }
        return buffer;
    }

    function createRain() {
        const nodes = [];
        for (let i = 0; i < 15; i++) {
            const noise = createNoise(0.1);
            const source = ctx.createBufferSource();
            source.buffer = noise;
            source.loop = true;

            const filter = ctx.createBiquadFilter();
            filter.type = 'bandpass';
            filter.frequency.value = 2000 + Math.random() * 5000;
            filter.Q.value = 0.5;

            const gain = ctx.createGain();
            gain.gain.value = 0.01 + Math.random() * 0.03;

            source.connect(filter);
            filter.connect(gain);
            gain.connect(master);
            source.start();
            nodes.push({ source, filter, gain });
        }
        return nodes;
    }

    function createFire() {
        const nodes = [];
        for (let i = 0; i < 10; i++) {
            const noise = createNoise(0.05);
            const source = ctx.createBufferSource();
            source.buffer = noise;
            source.loop = true;

            const filter = ctx.createBiquadFilter();
            filter.type = 'lowpass';
            filter.frequency.value = 200 + Math.random() * 400;
            filter.Q.value = 1;

            const gain = ctx.createGain();
            gain.gain.value = 0.02 + Math.random() * 0.04;
            gain.gain.setValueAtTime(gain.gain.value, ctx.currentTime);
            gain.gain.exponentialRampToValueAtTime(gain.gain.value * (0.3 + Math.random() * 0.7), ctx.currentTime + 0.1 + Math.random() * 0.3);

            source.connect(filter);
            filter.connect(gain);
            gain.connect(master);
            source.start();
            nodes.push({ source, filter, gain });
        }
        return nodes;
    }

    function createWind() {
        const nodes = [];
        for (let i = 0; i < 3; i++) {
            const osc = ctx.createOscillator();
            osc.type = 'sine';
            osc.frequency.value = 100 + Math.random() * 300;

            const gain = ctx.createGain();
            gain.gain.value = 0.02;
            gain.gain.setValueAtTime(0.01, ctx.currentTime);
            gain.gain.linearRampToValueAtTime(0.04, ctx.currentTime + 2 + Math.random() * 3);

            osc.connect(gain);
            gain.connect(master);
            osc.start();
            nodes.push({ osc, gain });
        }
        return nodes;
    }

    function createBirds() {
        const nodes = [];
        for (let i = 0; i < 5; i++) {
            const osc = ctx.createOscillator();
            osc.type = 'sine';
            osc.frequency.value = 2000 + Math.random() * 4000;

            const gain = ctx.createGain();
            gain.gain.value = 0;
            const schedule = () => {
                const now = ctx.currentTime;
                const start = now + Math.random() * 5;
                const dur = 0.05 + Math.random() * 0.2;
                gain.gain.setValueAtTime(0, start);
                gain.gain.linearRampToValueAtTime(0.015, start + dur * 0.3);
                gain.gain.linearRampToValueAtTime(0, start + dur);
                setTimeout(schedule, (start - now + dur) * 1000 + Math.random() * 3000);
            };
            schedule();

            osc.connect(gain);
            gain.connect(master);
            osc.start();
            nodes.push({ osc, gain });
        }
        return nodes;
    }

    function createThunder() {
        const nodes = [];
        const schedule = () => {
            const now = ctx.currentTime;
            const delay = 3 + Math.random() * 12;
            const start = now + delay;

            const noise = createNoise(1.5);
            const source = ctx.createBufferSource();
            source.buffer = noise;

            const filter = ctx.createBiquadFilter();
            filter.type = 'lowpass';
            filter.frequency.setValueAtTime(80, start);
            filter.frequency.linearRampToValueAtTime(3000, start + 0.1);
            filter.frequency.linearRampToValueAtTime(60, start + 1.5);

            const gain = ctx.createGain();
            gain.gain.setValueAtTime(0, start);
            gain.gain.linearRampToValueAtTime(0.15, start + 0.1);
            gain.gain.exponentialRampToValueAtTime(0.001, start + 2);

            source.connect(filter);
            filter.connect(gain);
            gain.connect(master);
            source.start(start);
            source.stop(start + 2);

            setTimeout(schedule, delay * 1000 + 2000);
        };
        schedule();
        return nodes;
    }

    function createCafe() {
        const nodes = [];
        const murmur = createNoise(2);
        for (let i = 0; i < 3; i++) {
            const source = ctx.createBufferSource();
            source.buffer = murmur;
            source.loop = true;

            const filter = ctx.createBiquadFilter();
            filter.type = 'bandpass';
            filter.frequency.value = 300 + Math.random() * 700;
            filter.Q.value = 0.3;

            const gain = ctx.createGain();
            gain.gain.value = 0.01;

            source.connect(filter);
            filter.connect(gain);
            gain.connect(master);
            source.start();
            nodes.push({ source, filter, gain });
        }
        return nodes;
    }

    function clearAll() {
        const allNodes = [...rainNodes, ...fireNodes, ...windNodes, ...birdNodes, ...noiseNodes];
        allNodes.forEach(n => {
            try { n.source?.stop(); } catch(e) {}
            try { n.osc?.stop(); } catch(e) {}
            try { n.gain?.disconnect(); } catch(e) {}
        });
        rainNodes = []; fireNodes = []; windNodes = []; birdNodes = []; noiseNodes = [];
        oscillators.forEach(o => { try { o.stop(); } catch(e) {} });
        oscillators = [];
        gainNodes.forEach(g => { try { g.disconnect(); } catch(e) {} });
        gainNodes = [];
    }

    function startProfile(profile) {
        clearAll();
        currentProfile = profile;
        switch(profile) {
            case 'rain':
                rainNodes = createRain();
                windNodes = createWind();
                noiseNodes = createThunder();
                break;
            case 'fireplace':
                fireNodes = createFire();
                windNodes = createWind();
                break;
            case 'forest':
                birdNodes = createBirds();
                windNodes = createWind();
                break;
            case 'ocean':
                noiseNodes = createThunder();
                windNodes = createWind();
                break;
            case 'night':
                birdNodes = createBirds();
                windNodes = createWind();
                break;
            case 'thunder':
                rainNodes = createRain();
                noiseNodes = createThunder();
                windNodes = createWind();
                break;
            case 'cafe':
                noiseNodes = createCafe();
                break;
        }
    }

    function initAudio() {
        if (ctx) return;
        ctx = new AC();
        master = ctx.createGain();
        master.gain.value = volume;
        master.connect(ctx.destination);
        startProfile(currentProfile);
        playing = true;
    }

    function setVolume(v) {
        volume = v;
        if (master) {
            master.gain.setValueAtTime(v, ctx.currentTime);
            master.gain.linearRampToValueAtTime(v, ctx.currentTime + 0.1);
        }
    }

    // Scroll sound
    let lastScroll = 0;
    window.addEventListener('scroll', () => {
        if (!playing) return;
        const now = Date.now();
        if (now - lastScroll < 50) return;
        lastScroll = now;

        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.type = 'sine';
        osc.frequency.value = 200 + Math.random() * 600;
        gain.gain.setValueAtTime(0.003, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.15);
        osc.connect(gain);
        gain.connect(master);
        osc.start();
        osc.stop(ctx.currentTime + 0.15);
    });

    // Click sound
    document.addEventListener('click', (e) => {
        if (!playing || e.target.closest('#soundscape-panel')) return;
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.type = 'sine';
        osc.frequency.value = 800 + Math.random() * 400;
        gain.gain.setValueAtTime(0.01, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.05);
        osc.connect(gain);
        gain.connect(master);
        osc.start();
        osc.stop(ctx.currentTime + 0.05);
    });

    // Typing sound
    document.addEventListener('keydown', () => {
        if (!playing) return;
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.type = 'sine';
        osc.frequency.value = 400 + Math.random() * 300;
        gain.gain.setValueAtTime(0.005, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.03);
        osc.connect(gain);
        gain.connect(master);
        osc.start();
        osc.stop(ctx.currentTime + 0.03);
    });

    function createPanel() {
        if (document.getElementById('soundscape-panel')) return;

        const panel = document.createElement('div');
        panel.id = 'soundscape-panel';
        panel.style.cssText = `
            position:fixed;bottom:30px;left:30px;background:rgba(15,15,15,0.95);
            border-radius:20px;padding:20px;z-index:2147483646;min-width:260px;
            border:1px solid rgba(255,255,255,0.1);font-family:system-ui;
            box-shadow:0 20px 60px rgba(0,0,0,0.8);backdrop-filter:blur(20px);
            transition:all 0.3s;animation:soundscapeIn 0.5s ease-out;
        `;

        panel.innerHTML = `
            <style>
                @keyframes soundscapeIn { from{opacity:0;transform:translateY(30px)} to{opacity:1;transform:translateY(0)} }
                @keyframes soundscapePulse { 0%,100%{box-shadow:0 0 10px currentColor} 50%{box-shadow:0 0 25px currentColor} }
            </style>
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;">
                <div style="color:#fff;font-weight:700;font-size:15px;">🎧 Soundscape</div>
                <div style="display:flex;gap:5px;">
                    <button id="ss-min" style="background:none;border:none;color:#888;font-size:18px;cursor:pointer;">─</button>
                    <button id="ss-close" style="background:none;border:none;color:#888;font-size:18px;cursor:pointer;">✕</button>
                </div>
            </div>
            <div id="ss-profiles" style="display:flex;flex-direction:column;gap:6px;"></div>
            <div style="margin-top:12px;display:flex;align-items:center;gap:10px;">
                <span style="color:#888;font-size:12px;">🔈</span>
                <input type="range" id="ss-volume" min="0" max="100" value="${volume*100}" style="flex:1;accent-color:#4a90d9;">
                <span style="color:#888;font-size:12px;">🔊</span>
            </div>
            <div id="ss-status" style="text-align:center;color:#666;font-size:10px;margin-top:8px;"></div>
        `;

        document.body.appendChild(panel);

        const profileContainer = document.getElementById('ss-profiles');
        Object.entries(PROFILES).forEach(([key, prof]) => {
            const btn = document.createElement('div');
            btn.style.cssText = `
                padding:12px;border-radius:12px;cursor:pointer;display:flex;align-items:center;gap:10px;
                background:${key===currentProfile?prof.color+'33':'rgba(255,255,255,0.03)'};
                border:1px solid ${key===currentProfile?prof.color:'transparent'};
                transition:all 0.2s;color:#ddd;
            `;
            btn.innerHTML = `
                <span style="font-size:22px;">${prof.icon}</span>
                <div>
                    <div style="font-weight:600;font-size:13px;">${prof.name}</div>
                    <div style="font-size:10px;color:#888;">${prof.desc}</div>
                </div>
            `;
            btn.onmouseover = () => {
                if (key !== currentProfile) btn.style.background = 'rgba(255,255,255,0.06)';
            };
            btn.onmouseout = () => {
                if (key !== currentProfile) btn.style.background = 'rgba(255,255,255,0.03)';
            };
            btn.onclick = () => {
                if (!ctx) initAudio();
                startProfile(key);
                document.querySelectorAll('#ss-profiles > div').forEach(b => {
                    b.style.background = 'rgba(255,255,255,0.03)';
                    b.style.borderColor = 'transparent';
                });
                btn.style.background = prof.color+'33';
                btn.style.borderColor = prof.color;
                document.getElementById('ss-status').textContent = '▶ ' + prof.name;
            };
            profileContainer.appendChild(btn);
        });

        document.getElementById('ss-volume').oninput = function() {
            setVolume(this.value / 100);
        };

        document.getElementById('ss-close').onclick = () => {
            panel.remove();
            if (ctx) { ctx.close(); ctx = null; playing = false; clearAll(); }
        };

        document.getElementById('ss-min').onclick = () => {
            const content = panel.querySelector('#ss-profiles');
            const vol = panel.querySelector('div:last-child');
            const status = document.getElementById('ss-status');
            if (content.style.display === 'none') {
                content.style.display = 'flex';
                vol.style.display = 'flex';
                status.style.display = 'block';
                panel.style.minWidth = '260px';
            } else {
                content.style.display = 'none';
                vol.style.display = 'none';
                status.style.display = 'none';
                panel.style.minWidth = 'auto';
                panel.style.padding = '12px';
            }
        };

        document.getElementById('ss-status').textContent = PROFILES[currentProfile] ? '▶ ' + PROFILES[currentProfile].name : '';
    }

    function addTrigger() {
        const btn = document.createElement('div');
        btn.id = 'soundscape-trigger';
        btn.style.cssText = `
            position:fixed;bottom:30px;left:30px;width:50px;height:50px;
            background:rgba(15,15,15,0.9);border-radius:50%;display:flex;
            align-items:center;justify-content:center;font-size:24px;
            cursor:pointer;z-index:2147483645;border:1px solid rgba(255,255,255,0.2);
            transition:all 0.3s;box-shadow:0 10px 30px rgba(0,0,0,0.5);
        `;
        btn.textContent = '🎧';
        btn.title = 'Soundscape';
        btn.onmouseover = () => { btn.style.transform = 'scale(1.1)'; btn.style.boxShadow = '0 15px 40px rgba(0,0,0,0.7)'; };
        btn.onmouseout = () => { btn.style.transform = 'scale(1)'; btn.style.boxShadow = '0 10px 30px rgba(0,0,0,0.5)'; };
        btn.onclick = () => {
            if (document.getElementById('soundscape-panel')) {
                document.getElementById('soundscape-panel').remove();
            } else {
                createPanel();
                if (!ctx) initAudio();
            }
        };
        document.body.appendChild(btn);
    }

    setTimeout(addTrigger, 1500);
    console.log('🎧 Soundscape ready | Click the button at bottom left');
})();