SwordMasters.io下雨動畫

Added distinct, louder audio for cold rain (with thunder). Warm rain audio. Darker cold rain, fixed rain fall distance. Intensity control, UI Fix, Fog, Sunlight, Presets, Water Ambience. Fog intensity adjusted.

// ==UserScript==
// @name         SwordMasters.io下雨動畫
// @namespace    http://tampermonkey.net/
// @license      MIT
// @version      1.0
// @description  Added distinct, louder audio for cold rain (with thunder). Warm rain audio. Darker cold rain, fixed rain fall distance. Intensity control, UI Fix, Fog, Sunlight, Presets, Water Ambience. Fog intensity adjusted.
// @match        https://swordmasters.io/*
// @author       HUANG WEI-LUN HUANG WEI-LUN
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  console.log('[Shader FX Suite v6.7.8 - Modified Fog - Cold Rain Audio & Thunder] Script starting...');

  const getCanvas = () => document.querySelector('canvas');

  const filters = {
    shader: "brightness(1.3) contrast(1.2) saturate(1.5) drop-shadow(0 0 10px #6df) blur(0.5px)",
    sunlight: "brightness(1.2) sepia(0.3) hue-rotate(-10deg)",
    coldRainAtmosphere: "brightness(0.45) saturate(0.50) contrast(1.0)",
  };

  const WATER_TINT_COLOR = "rgba(70, 100, 180, 0.10)";
  const WATER_AMBIENCE_SOUND_URL = "https://raw.githubusercontent.com/Myrther1/Audios/main/bubbles-159281.mp3";
  const WARM_RAIN_SOUND_URL = "https://raw.githubusercontent.com/Myrther1/Audios/main/rain-sound-188158.mp3";
  // --- NEW: Audio URL for cold rain ---
  const COLD_RAIN_SOUND_URL = "https://raw.githubusercontent.com/Myrther1/Audios/main/night-rain-with-distant-thunder-321446%20(1).mp3";


  const presets = {
    swamp: { getColor1: i => `rgba(120,255,120,${Math.min(0.05 + i * 1.2, 1)})`, getColor2: i => `rgba(60,255,60,${Math.min(0.02 + i * 1.0, 1)})`, label: 'Swamp Fog'},
    desert: { getColor1: i => `rgba(255,210,100,${Math.min(0.03 + i * 0.9, 1)})`, getColor2: i => `rgba(255,240,180,${Math.min(0.02 + i * 0.7, 1)})`, heatWobble: true, label: 'Desert Heat'},
    end: { getColor1: i => `rgba(180,130,255,${Math.min(0.04 + i * 0.9, 1)})`, getColor2: i => `rgba(150,100,255,${Math.min(0.02 + i * 0.6, 1)})`, label: 'End Mist'},
    nether: { getColor1: i => `rgba(255,60,60,${Math.min(0.06 + i * 1.0, 1)})`, getColor2: i => `rgba(100,0,0,${Math.min(0.04 + i * 0.8, 1)})`, label: 'Nether Smoke'}
  };

  let settings = JSON.parse(localStorage.getItem("shaderFXSettings_v6_7_8")) || { // Updated version
    shader: false,
    fog: false,
    sunlight: false,
    wavy: false,
    waterAmbience: false,
    rain: false,
    rainIntensity: 150,
    rainType: 'warm',
    fogIntensity: 30,
    preset: null,
    collapsed: false,
    fogPresetsCollapsed: true,
    ambienceCollapsed: false
  };

  const style = document.createElement('style');
  style.textContent = `
    @keyframes heatWobble {0%{transform:scale(1) skewX(0deg) skewY(0deg)}25%{transform:scale(1.01) skewX(.4deg) skewY(-.3deg)}50%{transform:scale(1.02) skewX(-.3deg) skewY(.4deg)}75%{transform:scale(1.01) skewX(.5deg) skewY(0deg)}100%{transform:scale(1) skewX(0deg) skewY(0deg)}}
    .shader-button{width:100%;margin:4px 0;padding:6px;background-color:#1a1a1a;color:#0ff;border:2px solid #555;border-radius:4px;font-weight:700;font-family:'Courier New',monospace;cursor:pointer;text-shadow:0 0 5px #0ff}
    .shader-slider{width:90%;max-width:160px;margin:4px auto 0;display:block}
    .shader-label{text-align:center;color:#0ff;font-size:12px;font-family:'Courier New',monospace;margin-top:2px}
    .shader-collapse{position:absolute;top:-12px;right:0;color:#0ff;font-weight:700;font-family:monospace;font-size:14px;cursor:pointer}
    .collapsible-header{color:#0ff;font-family:'Courier New',monospace;font-weight:700;padding:5px 0;margin-top:5px;border-top:1px solid #0aa;border-bottom:1px solid #0aa;cursor:pointer;display:flex;justify-content:space-between;align-items:center}
    .collapsible-content{padding-left:10px;overflow:hidden; margin-top: 5px;}
    hr{border:0;height:1px;background-image:linear-gradient(to right,transparent,#0ff,transparent);margin:8px 0}
    .shader-radio-group label { margin-right: 10px; color: #0ff; font-family:'Courier New',monospace; font-size: 12px; }
    .shader-radio-group input[type="radio"] { margin-right: 3px; vertical-align: middle; }
    .rain-controls-container { margin-top: 8px; padding-top: 8px; border-top: 1px dashed #0aa; }
  `;
  document.head.appendChild(style);

    // MODIFIED FUNCTION: applyFogLayer
    function applyFogLayer(intensity = 30, preset = null) {
        // intensity is the value from the UI slider (0-100)
        const uiSliderValue = Math.max(0, Math.min(100, intensity));

        // Normalize the UI slider value to a 0-1 range
        const normalizedUiInput = uiSliderValue / 100;

        // Re-scale: the maximum effect (slider at 100%) should now be equivalent to
        // the old 40% effect. So, the effective range for fog calculation is 0-0.4.
        const effectiveNormalizedIntensity = normalizedUiInput * 0.4;

        // Now use effectiveNormalizedIntensity for all subsequent alpha calculations
        let fog = document.getElementById("shader-fog-layer");
        let color1 = `rgba(255,255,255,${Math.min(0.2 + effectiveNormalizedIntensity * 1.3, 1)})`,
            color2 = `rgba(255,255,255,${Math.min(0.1 + effectiveNormalizedIntensity * 1.1, 1)})`;

        if (preset && presets[preset]) {
            color1 = presets[preset].getColor1(effectiveNormalizedIntensity);
            color2 = presets[preset].getColor2(effectiveNormalizedIntensity);
        }

        const background = `linear-gradient(to bottom right, ${color2} 10%, transparent 90%), linear-gradient(to top left, ${color1} 10%, transparent 90%), radial-gradient(circle at 0% 0%, ${color1} 0%, transparent 85%), radial-gradient(circle at 100% 0%, ${color2} 0%, transparent 85%), radial-gradient(circle at 100% 100%, ${color1} 0%, transparent 85%), radial-gradient(circle at 0% 100%, ${color2} 0%, transparent 85%), radial-gradient(ellipse at 50% 50%, ${color1} 0%, transparent 90%)`;

        if (!fog) {
            fog = document.createElement("div");
            fog.id = "shader-fog-layer";
            fog.style.cssText = "position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9000;";
            document.body.appendChild(fog);
        }
        fog.style.background = background;
        fog.style.opacity = 1;
        fog.style.animation = (preset === 'desert' && presets[preset].heatWobble) ? 'heatWobble 6s ease-in-out infinite' : 'none';
        fog.style.backgroundSize = 'cover';
    }
    // END OF MODIFIED FUNCTION

    function removeFogLayer() { const fog = document.getElementById("shader-fog-layer"); if (fog) fog.remove(); }
    function applyWaterTintOverlay() { let tintOverlay = document.getElementById("water-tint-overlay"); if (!tintOverlay) { tintOverlay = document.createElement("div"); tintOverlay.id = "water-tint-overlay"; tintOverlay.style.cssText = "position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:"+WATER_TINT_COLOR+";pointer-events:none;z-index:8998;"; document.body.appendChild(tintOverlay); } tintOverlay.style.display = 'block'; }
    function removeWaterTintOverlay() { const tintOverlay = document.getElementById("water-tint-overlay"); if (tintOverlay) tintOverlay.style.display = 'none'; }
    const waterEffectState = { canvas: null, ctx: null, bubbles: [], animationId: null, isActive: false, maxBubbles: 60, bubbleSpawnChance: 0.2, audioElement: null, audioInitialized: false, resizeHandler: null };
    class Bubble { constructor(W, H) { this.canvasWidth=W; this.canvasHeight=H; this.baseOpacity=Math.random()*.4+.25; this.radius=Math.random()*10+4; this.x=Math.random()*W; this.y=H+this.radius+Math.random()*H*.2; this.vy=-(Math.random()*1.2+.5); this.vx_amplitude=Math.random()*1.3+.4; this.vx_frequency=Math.random()*.018+.008; this.initialX=this.x; } update() { this.y+=this.vy; this.x=this.initialX+Math.sin(this.y*this.vx_frequency)*this.vx_amplitude; return this.y >= -this.radius*2; } draw(ctx) { ctx.beginPath(); const gr=ctx.createRadialGradient(this.x,this.y,this.radius*.1,this.x,this.y,this.radius); gr.addColorStop(0,`rgba(230,240,255,${this.baseOpacity*.8})`); gr.addColorStop(.7,`rgba(180,210,255,${this.baseOpacity*.6})`); gr.addColorStop(1,`rgba(100,150,220,${this.baseOpacity*.4})`); ctx.fillStyle=gr; ctx.arc(this.x,this.y,this.radius,0,Math.PI*2); ctx.fill(); ctx.strokeStyle=`rgba(80,130,230,${this.baseOpacity*.9})`; ctx.lineWidth=Math.max(1,this.radius*.08); ctx.stroke(); ctx.beginPath(); ctx.fillStyle=`rgba(255,255,255,${this.baseOpacity*.85})`; ctx.arc(this.x-this.radius*.35,this.y-this.radius*.4,this.radius*.3,0,Math.PI*2); ctx.fill(); ctx.beginPath(); const isg=ctx.createRadialGradient(this.x,this.y,this.radius*.7,this.x,this.y,this.radius); isg.addColorStop(0,`rgba(50,80,150,0)`); isg.addColorStop(1,`rgba(50,80,150,${this.baseOpacity*.15})`); ctx.fillStyle=isg; ctx.arc(this.x,this.y,this.radius,0,Math.PI*2); ctx.globalCompositeOperation='source-atop'; ctx.fill(); ctx.globalCompositeOperation='source-over'; } }
    function animateWaterBubbles() { if (!waterEffectState.isActive) return; const {canvas:C,ctx,bubbles:B}=waterEffectState; if(!C||!ctx)return; ctx.clearRect(0,0,C.width,C.height); if(B.length<waterEffectState.maxBubbles&&Math.random()<waterEffectState.bubbleSpawnChance)B.push(new Bubble(C.width,C.height)); for(let i=B.length-1;i>=0;i--){const b=B[i];if(b.update())b.draw(ctx);else B.splice(i,1);} waterEffectState.animationId=requestAnimationFrame(animateWaterBubbles); }
    function initWaterAmbienceAudio() { if(!waterEffectState.audioElement){waterEffectState.audioElement=new Audio();waterEffectState.audioElement.loop=true;waterEffectState.audioElement.volume=.8;} }
    function playWaterAmbienceAudio() { if(!waterEffectState.audioElement)initWaterAmbienceAudio(); if(waterEffectState.audioElement){if(waterEffectState.audioElement.src!==WATER_AMBIENCE_SOUND_URL){waterEffectState.audioElement.src=WATER_AMBIENCE_SOUND_URL;waterEffectState.audioElement.load();} if(waterEffectState.audioElement.paused){const pP=waterEffectState.audioElement.play();if(pP!==undefined)pP.catch(e=>console.error("Shader FX Suite: Water audio playback failed.",e));}} }
    function stopWaterAmbienceAudio() { if(waterEffectState.audioElement&&!waterEffectState.audioElement.paused)waterEffectState.audioElement.pause(); }
    function startWaterBubbleEffect() { if(waterEffectState.isActive)return; waterEffectState.isActive=true; let C=document.getElementById("water-bubbles-layer"); if(!C){C=document.createElement("canvas");C.id="water-bubbles-layer";C.style.cssText="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:8999;";document.body.appendChild(C);} C.style.display='block'; C.width=C.offsetWidth; C.height=C.offsetHeight; waterEffectState.canvas=C; waterEffectState.ctx=C.getContext('2d'); waterEffectState.bubbles=[]; if(!waterEffectState.resizeHandler){waterEffectState.resizeHandler=()=>{if(waterEffectState.canvas&&waterEffectState.isActive){waterEffectState.canvas.width=waterEffectState.canvas.offsetWidth;waterEffectState.canvas.height=waterEffectState.canvas.offsetHeight;}};window.addEventListener('resize',waterEffectState.resizeHandler);} if(waterEffectState.animationId)cancelAnimationFrame(waterEffectState.animationId); animateWaterBubbles();playWaterAmbienceAudio(); }
    function stopWaterBubbleEffect() { if(!waterEffectState.isActive)return; waterEffectState.isActive=false; if(waterEffectState.animationId){cancelAnimationFrame(waterEffectState.animationId);waterEffectState.animationId=null;} if(waterEffectState.canvas){const c=waterEffectState.canvas.getContext('2d');if(c)c.clearRect(0,0,waterEffectState.canvas.width,waterEffectState.canvas.height);waterEffectState.canvas.style.display='none';} waterEffectState.bubbles=[]; stopWaterAmbienceAudio(); if(waterEffectState.resizeHandler){window.removeEventListener('resize',waterEffectState.resizeHandler);waterEffectState.resizeHandler=null;} }

  let warmRainAudioElement = null;
  // --- NEW: Cold Rain Audio Element ---
  let coldRainAudioElement = null;

  function initWarmRainAudio() {
    if (!warmRainAudioElement) {
      warmRainAudioElement = new Audio();
      warmRainAudioElement.loop = true;
      warmRainAudioElement.volume = 0.5; // Adjusted volume for balance
      console.log("[Shader FX Suite] Warm rain audio element initialized.");
    }
  }
  function playWarmRainAudio() {
    if (!warmRainAudioElement) initWarmRainAudio();
    if (warmRainAudioElement) {
      if (warmRainAudioElement.src !== WARM_RAIN_SOUND_URL) {
        warmRainAudioElement.src = WARM_RAIN_SOUND_URL;
        warmRainAudioElement.load();
      }
      if (warmRainAudioElement.paused) {
        const playPromise = warmRainAudioElement.play();
        if (playPromise !== undefined) {
          playPromise.catch(error => console.error("Shader FX Suite: Warm rain audio playback failed.", error));
        }
      }
    }
  }
  function stopWarmRainAudio() {
    if (warmRainAudioElement && !warmRainAudioElement.paused) {
      warmRainAudioElement.pause();
    }
  }

  // --- NEW: Cold Rain Audio Functions ---
  function initColdRainAudio() {
    if (!coldRainAudioElement) {
      coldRainAudioElement = new Audio();
      coldRainAudioElement.loop = true;
      coldRainAudioElement.volume = 0.8; // Louder volume for cold rain
      console.log("[Shader FX Suite] Cold rain audio element initialized.");
    }
  }
  function playColdRainAudio() {
    if (!coldRainAudioElement) initColdRainAudio();
    if (coldRainAudioElement) {
      if (coldRainAudioElement.src !== COLD_RAIN_SOUND_URL) {
        coldRainAudioElement.src = COLD_RAIN_SOUND_URL;
        coldRainAudioElement.load();
      }
      if (coldRainAudioElement.paused) {
        const playPromise = coldRainAudioElement.play();
        if (playPromise !== undefined) {
          playPromise.catch(error => console.error("Shader FX Suite: Cold rain audio playback failed.", error));
        }
      }
    }
  }
  function stopColdRainAudio() {
    if (coldRainAudioElement && !coldRainAudioElement.paused) {
      coldRainAudioElement.pause();
    }
  }


  let domRainActive = false;
  let domRainContainer;

  function createDomRain() {
    domRainActive = true;

    domRainContainer = document.createElement('div');
    domRainContainer.id = 'dom-rain-effect-container';
    domRainContainer.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:8997;overflow:hidden;';
    document.body.appendChild(domRainContainer);

    const particleCount = settings.rainIntensity;
    let particleColor, particleHeightMin, particleHeightMax, animName, animDurationMin, animDurationMax, particleWidthMin, particleWidthMax, animDelayMax;

    if (settings.rainType === 'cold') {
        particleColor = 'rgba(130, 170, 210, 0.6)';
        particleHeightMin = 25; particleHeightMax = 45;
        particleWidthMin = 1.5; particleWidthMax = 3;
        animName = 'fallCold';
        animDurationMin = 0.3; animDurationMax = 0.8;
        animDelayMax = 2.5;
        stopWarmRainAudio(); // Stop warm rain
        playColdRainAudio(); // Play cold rain
    } else { // Warm rain
        particleColor = 'rgba(180, 170, 150, 0.45)';
        particleHeightMin = 20; particleHeightMax = 35;
        particleWidthMin = 1.5; particleWidthMax = 2.5;
        animName = 'fallWarm';
        animDurationMin = 0.6; animDurationMax = 1.5;
        animDelayMax = 3.0;
        stopColdRainAudio(); // Stop cold rain
        playWarmRainAudio(); // Play warm rain
    }

    for (let i = 0; i < particleCount; i++) {
      const drop = document.createElement('div');
      const height = Math.random() * (particleHeightMax - particleHeightMin) + particleHeightMin;
      const width = Math.random() * (particleWidthMax - particleWidthMin) + particleWidthMin;
      const duration = Math.random() * (animDurationMax - animDurationMin) + animDurationMin;
      const delay = Math.random() * animDelayMax;

      drop.style.cssText = `
        position:absolute;
        width:${width}px;
        height:${height}px;
        background:${particleColor};
        left:${Math.random() * 100}vw;
        top:${Math.random() * -200 - 100}vh;
        animation-name: ${animName};
        animation-duration: ${duration}s;
        animation-timing-function: linear;
        animation-iteration-count: infinite;
        animation-delay:${delay}s;
      `;
      domRainContainer.appendChild(drop);
    }

    let rainStyleElement = document.getElementById('dom-rain-animation-style');
    if (!rainStyleElement) {
        rainStyleElement = document.createElement('style');
        rainStyleElement.id = 'dom-rain-animation-style';
        document.head.appendChild(rainStyleElement);
    }
    const fallDistance = '420vh';
    rainStyleElement.textContent = `
      @keyframes fallWarm {
        0% { transform: translateY(0) translateX(0); opacity: 1; }
        90% { opacity: 1; }
        100% { transform: translateY(${fallDistance}) translateX(${Math.random()*20-10}px); opacity: 0; }
      }
      @keyframes fallCold {
        0% { transform: translateY(0) translateX(0); opacity: 1; }
        90% { opacity: 1; }
        100% { transform: translateY(${fallDistance}) translateX(${Math.random()*40-20}px); opacity: 0; }
      }
    `;
  }

  function removeDomRain() {
    if (!domRainActive && !domRainContainer) return;
    domRainActive = false;
    if (domRainContainer) {
        domRainContainer.remove();
        domRainContainer = null;
    }
    stopWarmRainAudio(); // Stop warm rain audio
    stopColdRainAudio(); // Stop cold rain audio
  }

  function updateCanvasEffects() {
    const gameCanvas = getCanvas();
    if (!gameCanvas) { console.warn("[Shader FX Suite] Game canvas not found."); return; }

    const appliedFilters = [];
    if (settings.shader) appliedFilters.push(filters.shader);
    if (settings.sunlight) appliedFilters.push(filters.sunlight);
    if (settings.rain && settings.rainType === 'cold') {
        appliedFilters.push(filters.coldRainAtmosphere);
    }

    gameCanvas.style.filter = appliedFilters.join(" ");
    gameCanvas.style.transition = "filter 0.4s ease-in-out";
    gameCanvas.style.animation = settings.wavy ? "heatWobble 3s infinite alternate" : "none";

    if (settings.fog || settings.preset) applyFogLayer(settings.fogIntensity, settings.preset); else removeFogLayer();
    if (settings.waterAmbience) { applyWaterTintOverlay(); startWaterBubbleEffect(); } else { removeWaterTintOverlay(); stopWaterBubbleEffect(); }

    if (settings.rain) {
        removeDomRain();
        createDomRain();
    } else {
        removeDomRain();
    }

    localStorage.setItem("shaderFXSettings_v6_7_8", JSON.stringify(settings));
  }

  function createCollapsibleSection(title, settingsKeyForCollapsed, parentElementToAttachTo, populateContentCallback) {
    const sectionWrapperDiv = document.createElement('div');
    const headerDiv = document.createElement('div'); headerDiv.className = 'collapsible-header';
    const titleSpan = document.createElement('span'); titleSpan.innerText = title;
    const toggleSpan = document.createElement('span'); toggleSpan.className = 'section-toggle';
    toggleSpan.style.fontFamily = 'monospace'; toggleSpan.style.fontSize = '14px';
    headerDiv.appendChild(titleSpan); headerDiv.appendChild(toggleSpan);
    const contentDiv = document.createElement('div'); contentDiv.className = 'collapsible-content';
    const updateVisibility = () => {
      contentDiv.style.display = settings[settingsKeyForCollapsed] ? 'none' : 'block';
      toggleSpan.innerText = settings[settingsKeyForCollapsed] ? '[+]' : '[–]';
    };
    headerDiv.onclick = (event) => {
      event.stopPropagation();
      settings[settingsKeyForCollapsed] = !settings[settingsKeyForCollapsed];
      updateVisibility();
      localStorage.setItem("shaderFXSettings_v6_7_8", JSON.stringify(settings));
    };
    populateContentCallback(contentDiv);
    sectionWrapperDiv.appendChild(headerDiv); sectionWrapperDiv.appendChild(contentDiv);
    parentElementToAttachTo.appendChild(sectionWrapperDiv);
    updateVisibility();
  }

  function createUI() {
    console.log('[Shader FX Suite v6.7.8] createUI function started.');
    try {
        const ui = document.createElement('div'); ui.id = "shader-fx-suite-ui";
        ui.style.cssText = "position:fixed;top:60px;right:10px;width:200px;padding:10px;background:rgba(0,0,0,0.85);border:2px solid #0ff;border-radius:12px;z-index:9999;user-select:none;";
        const mainCollapseToggle = document.createElement('div'); mainCollapseToggle.className = 'shader-collapse';
        mainCollapseToggle.innerText = settings.collapsed ? '[+]' : '[–]';
        mainCollapseToggle.onclick = () => { settings.collapsed = !settings.collapsed; mainCollapseToggle.innerText = settings.collapsed ? '[+]' : '[–]'; innerBox.style.display = settings.collapsed ? 'none' : ''; localStorage.setItem("shaderFXSettings_v6_7_8", JSON.stringify(settings)); };
        ui.appendChild(mainCollapseToggle);

        const innerBox = document.createElement('div'); innerBox.style.marginTop = '8px';
        innerBox.id = 'sfx-inner-box'; ui.appendChild(innerBox);

        const fogSlider = document.createElement('input'); fogSlider.type = 'range'; fogSlider.min = '0'; fogSlider.max = '100'; fogSlider.value = settings.fogIntensity; fogSlider.className = 'shader-slider';
        fogSlider.oninput = () => { settings.fogIntensity = parseInt(fogSlider.value); fogLabel.innerText = `Fog Intensity: ${settings.fogIntensity}%`; if (settings.fog || settings.preset) updateCanvasEffects(); };
        const fogLabel = document.createElement('div'); fogLabel.className = 'shader-label'; fogLabel.innerText = `Fog Intensity: ${settings.fogIntensity}%`;

        const createToggle = (label, key, callback) => {
          const btn = document.createElement('button'); btn.className = 'shader-button';
          btn.innerText = settings[key] ? `${label} (On)` : label;
          btn.onclick = () => {
            settings[key] = !settings[key];
            btn.innerText = settings[key] ? `${label} (On)` : label;
            if (key === 'fog' || key === 'preset') { fogSlider.disabled = !settings.fog && !settings.preset; fogLabel.style.opacity = fogSlider.disabled ? 0.5 : 1; }
            if (callback) callback(settings[key]);
            updateCanvasEffects();
          };
          if (key === 'fog') { fogSlider.disabled = !settings.fog && !settings.preset; fogLabel.style.opacity = fogSlider.disabled ? 0.5 : 1; }
          return btn;
        };
        const createPresetBtn = (key, presetButtonsContainer) => {
          const btn = document.createElement('button'); btn.className = 'shader-button'; btn.innerText = presets[key].label; if (settings.preset === key) { btn.style.borderColor = '#0ff'; btn.style.boxShadow = '0 0 8px #0ff'; }
          btn.onclick = () => {
            settings.preset = settings.preset === key ? null : key;
            Array.from(presetButtonsContainer.querySelectorAll('.shader-button')).forEach(b => { b.style.borderColor = '#555'; b.style.boxShadow = 'none'; });
            if (settings.preset === key) { btn.style.borderColor = '#0ff'; btn.style.boxShadow = '0 0 8px #0ff'; }
            fogSlider.disabled = !settings.fog && !settings.preset; fogLabel.style.opacity = fogSlider.disabled ? 0.5 : 1;
            updateCanvasEffects();
          }; return btn;
        };

        innerBox.appendChild(createToggle("Shaders", "shader"));
        innerBox.appendChild(createToggle("Sunlight", "sunlight"));
        innerBox.appendChild(createToggle("Wavy Canvas", "wavy"));
        innerBox.appendChild(document.createElement('hr'));

        createCollapsibleSection("Ambience", "ambienceCollapsed", innerBox, (contentDiv) => {
            contentDiv.appendChild(createToggle("Water Ambience", "waterAmbience"));
            contentDiv.appendChild(document.createElement('hr'));

            const rainControlsContainer = document.createElement('div');
            rainControlsContainer.className = 'rain-controls-container';
            rainControlsContainer.style.display = settings.rain ? 'block' : 'none';

            const rainIntensitySlider = document.createElement('input');
            rainIntensitySlider.type = 'range'; rainIntensitySlider.min = '0'; rainIntensitySlider.max = '300';
            rainIntensitySlider.value = settings.rainIntensity;
            rainIntensitySlider.className = 'shader-slider';
            const rainIntensityLabel = document.createElement('div');
            rainIntensityLabel.className = 'shader-label';
            rainIntensityLabel.innerText = `Rain Intensity: ${settings.rainIntensity}`;
            rainIntensitySlider.oninput = () => {
                settings.rainIntensity = parseInt(rainIntensitySlider.value);
                rainIntensityLabel.innerText = `Rain Intensity: ${settings.rainIntensity}`;
                updateCanvasEffects();
            };

            const rainTypeGroup = document.createElement('div');
            rainTypeGroup.className = 'shader-radio-group';
            rainTypeGroup.style.textAlign = 'center'; rainTypeGroup.style.marginTop = '5px';

            ['warm', 'cold'].forEach(type => {
                const id = `rain-type-${type}`;
                const radioLabel = document.createElement('label');
                radioLabel.setAttribute('for', id);
                radioLabel.innerText = type.charAt(0).toUpperCase() + type.slice(1);

                const radio = document.createElement('input');
                radio.type = 'radio'; radio.name = 'rainType'; radio.id = id; radio.value = type;
                radio.checked = settings.rainType === type;
                radio.onchange = () => {
                    settings.rainType = radio.value;
                    updateCanvasEffects();
                };
                rainTypeGroup.appendChild(radio);
                rainTypeGroup.appendChild(radioLabel);
            });

            rainControlsContainer.appendChild(rainIntensityLabel);
            rainControlsContainer.appendChild(rainIntensitySlider);
            rainControlsContainer.appendChild(rainTypeGroup);

            contentDiv.appendChild(createToggle("Rain", "rain", (isRainActive) => {
                rainControlsContainer.style.display = isRainActive ? 'block' : 'none';
            }));
            contentDiv.appendChild(rainControlsContainer);
        });

        const fogControlsGroup = document.createElement('div');
        innerBox.appendChild(fogControlsGroup);
        fogControlsGroup.appendChild(document.createElement('hr'));
        fogControlsGroup.appendChild(createToggle("Generic Fog", "fog"));
        fogControlsGroup.appendChild(fogSlider);
        fogControlsGroup.appendChild(fogLabel);

        createCollapsibleSection("Fog Presets", "fogPresetsCollapsed", fogControlsGroup, (contentDiv) => {
            Object.keys(presets).forEach(key => contentDiv.appendChild(createPresetBtn(key, contentDiv)));
        });

        if (settings.collapsed) innerBox.style.display = 'none';
        if (!document.body) { console.error("[Shader FX Suite] CRITICAL: document.body not available when trying to append UI!"); return; }
        document.body.appendChild(ui);

        let dragging = false, offsetX, offsetY;
        ui.addEventListener('mousedown', e => {
          if (e.target.closest('button, input, .shader-collapse, .collapsible-header, .shader-radio-group')) return;
          dragging = true; offsetX = e.clientX - ui.offsetLeft; offsetY = e.clientY - ui.offsetTop;
          ui.style.cursor = 'grabbing'; document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDrag);
        });
        function drag(e) { if (!dragging) return; ui.style.left = `${e.clientX - offsetX}px`; ui.style.top = `${e.clientY - offsetY}px`; }
        function stopDrag() { if (!dragging) return; dragging = false; ui.style.cursor = 'default'; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDrag); }

    } catch (error) {
        console.error("[Shader FX Suite v6.7.8] Error during createUI:", error);
    }
    console.log('[Shader FX Suite v6.7.8] createUI function finished.');
  }

  function initializeScript() {
    console.log("[Shader FX Suite v6.7.8] initializeScript called.");
    if (!document.body) {
        console.warn("[Shader FX Suite v6.7.8] document.body not ready in initializeScript. Waiting...");
        let retries = 0;
        const checkBody = () => {
            if (document.body) {
                 console.log("[Shader FX Suite v6.7.8] document.body is now ready for init.");
                 initializeScript();
            } else if (retries < 50) { retries++; setTimeout(checkBody, 100); }
            else { console.error("[Shader FX Suite v6.7.8] document.body not found after retries."); }
        };
        if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeScript); }
        else { setTimeout(checkBody, 100); }
        return;
    }
    try {
        initWaterAmbienceAudio();
        initWarmRainAudio();
        initColdRainAudio(); // Initialize the cold rain audio element
        createUI();
        updateCanvasEffects();
        console.log("[Shader FX Suite v6.7.8 - Modified Fog] Script initialized successfully.");
    } catch (error) { console.error("[Shader FX Suite v6.7.8 - Modified Fog] Error during initializeScript (after body check):", error); }
  }

  if (document.body) { setTimeout(initializeScript, 0); }
  else if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeScript); }
  else { setTimeout(initializeScript, 50); }

})();