From c9a01f8ed6d8fac03b5f14c3be196d61fa8fc162 Mon Sep 17 00:00:00 2001 From: dilgenfritz Date: Wed, 3 Dec 2025 00:57:41 -0600 Subject: [PATCH] refined level 6-8 --- campaign.html | 6 + src/data/modes/trainingGameData.js | 102 ++++-- src/features/tasks/interactiveTaskManager.js | 358 ++++++++++--------- 3 files changed, 272 insertions(+), 194 deletions(-) diff --git a/campaign.html b/campaign.html index 66ad447..2686b44 100644 --- a/campaign.html +++ b/campaign.html @@ -4814,6 +4814,12 @@ function completeScenarioAction(nextStep) { console.log('✅ Scenario action completed, moving to:', nextStep); + // Call cleanup on current interactive task if it exists + if (currentInteractiveTask && typeof currentInteractiveTask.cleanup === 'function') { + console.log('🧹 Calling interactive task cleanup'); + currentInteractiveTask.cleanup(); + } + // Clean up any active webcam streams const webcamVideo = document.getElementById('webcam-video'); if (webcamVideo && webcamVideo.srcObject) { diff --git a/src/data/modes/trainingGameData.js b/src/data/modes/trainingGameData.js index 047c8fc..24b6720 100644 --- a/src/data/modes/trainingGameData.js +++ b/src/data/modes/trainingGameData.js @@ -747,9 +747,14 @@ const trainingGameData = { mirror_edges: { type: 'action', mood: 'intense', - story: 'Now edge 25 times while watching yourself stroke to porn. See your face flush. See your hand pumping. Watch yourself being consumed by the video. This is your reality. This is who you\'re becoming. And it feels good, doesn\'t it?', - interactiveType: 'edge', - params: { count: 25, instruction: 'Edge 25 times while watching yourself', keepVideoPlaying: true }, + story: 'Now we flip it. The porn becomes your focus, and you watch yourself in the corner. 15 minutes of stroking to porn while glimpsing yourself in the mirror of the PiP. See how natural it looks now - you, stroking, watching. This is what a gooner does.', + interactiveType: 'dual-video', + params: { + mainVideo: 'focus', + pipVideo: 'webcam', + pipPosition: 'bottom-right', + minDuration: 900 + }, nextStep: 'completion' }, completion: { @@ -767,7 +772,7 @@ const trainingGameData = { name: 'Dual Focus', arc: 'Feature Discovery', level: 7, - duration: 2400, // 40 minutes + duration: 3600, // 60 minutes interactiveType: 'scenario-adventure', interactiveData: { title: 'Level 7: Dual Focus', @@ -783,21 +788,33 @@ const trainingGameData = { mood: 'overwhelming', story: 'Starting dual video mode. Main screen + picture-in-picture. TWO feeds of porn. Your eyes won\'t know where to look. Your brain won\'t know what to process. That\'s the point. Sensory overload. Pure gooning.', interactiveType: 'dual-video', - params: { mainVideo: 'focus', pipVideo: 'overlay', pipPosition: 'bottom-right' }, - nextStep: 'dual_edges' + params: { mainVideo: 'focus', pipVideo: 'overlay', pipPosition: 'bottom-right', minDuration: 120 }, + nextStep: 'media_linking' }, - dual_edges: { + media_linking: { type: 'action', - mood: 'demanding', - story: 'Edge 30 times while both videos play. Try to watch both at once. Let your mind split between the two streams. This is advanced gooner technique - fractured attention, unified pleasure.', - interactiveType: 'edge', - params: { count: 30, instruction: 'Edge 30 times to dual video streams' }, + mood: 'instructional', + story: 'Before we continue, we need to expand your library. Link 10 more videos to your collection. The more content you have, the deeper you can goon. Feed the machine.', + interactiveType: 'link-media', + params: { + minFiles: 10, + mediaType: 'video', + suggestedTags: ['pov', 'blowjob', 'riding', 'amateur', 'solo'] + }, + nextStep: 'dual_video_extended' + }, + dual_video_extended: { + type: 'action', + mood: 'overwhelming', + story: 'Now the real test. 20 minutes of dual video mode. Two streams. Constant stimulation. Your eyes will dart between them. Your mind will fracture and reform around the porn. This is what it means to truly goon.', + interactiveType: 'dual-video', + params: { mainVideo: 'focus', pipVideo: 'overlay', pipPosition: 'bottom-right', minDuration: 1200 }, nextStep: 'completion' }, completion: { type: 'completion', mood: 'intense', - story: '✅ Level 7 Complete - DUAL VIDEO UNLOCKED. More screens = more stimulation.', + story: '✅ Level 7 Complete - DUAL VIDEO MASTERED. More screens = more stimulation.', outcome: 'level7_complete' } } @@ -806,43 +823,66 @@ const trainingGameData = { level8: { id: 'academy-level-8', - name: 'Vocal Commands', + name: 'Audio Immersion', arc: 'Feature Discovery', level: 8, - duration: 3000, // 50 minutes + duration: 2700, // 45 minutes interactiveType: 'scenario-adventure', interactiveData: { - title: 'Level 8: Vocal Commands', + title: 'Level 8: Audio Immersion', steps: { start: { type: 'story', mood: 'commanding', - story: 'You have eyes. You have hands. Now we add ears. Voice commands via text-to-speech will guide your edging. You will learn to obey the voice. To respond instantly. Gooners don\'t think - they obey.', - nextStep: 'tts_intro' + story: 'You have mastered visuals. Webcam. Dual video. Now we unlock a new dimension: audio. Hypnotic voices. Moaning. Instructions whispered directly into your brain. This changes everything.', + nextStep: 'audio_warmup' }, - tts_intro: { + audio_warmup: { type: 'action', - mood: 'authoritative', - story: 'Listen to the voice. It will tell you what to do. And you will do it. Without hesitation. This is training in obedience and responsiveness.', - interactiveType: 'tts-command', + mood: 'hypnotic', + story: 'First, experience the power of audio. 5 minutes of video with sound cranked up. Let the moans fill your ears. Let them guide your hand. Audio is not just background - it\'s fuel.', + interactiveType: 'video-start', params: { - text: 'Good gooner. You are learning to obey. Edge for me. Now.', - voice: 'feminine' + player: 'focus', + duration: 300, + ambientAudio: 'audio/ambient/moaning-1.mp3', + ambientVolume: 0.5 }, - nextStep: 'tts_edges' + nextStep: 'library_expansion' }, - tts_edges: { + library_expansion: { type: 'action', - mood: 'controlled', - story: 'The voice will count. You will edge each time it commands. 35 edges total. Listen. Obey. Edge.', - interactiveType: 'edge', - params: { count: 35, instruction: 'Edge 35 times on command' }, + mood: 'instructional', + story: 'Your library needs better organization. Tag 15 items with audio-focused labels: moaning, dirty-talk, wet-sounds. Proper tagging means better gooning sessions later.', + interactiveType: 'tag-files', + params: { + minFiles: 15, + suggestedTags: ['audio', 'moaning', 'dirty-talk', 'pov', 'wet-sounds'] + }, + nextStep: 'audio_rhythm_combo' + }, + audio_rhythm_combo: { + type: 'action', + mood: 'intense', + story: 'Now combine everything you\'ve learned. 25 minutes of rhythm training with video AND audio. Stroke to the beat. Pump to the moans. Let sound and sight merge into pure gooning bliss.', + interactiveType: 'rhythm', + params: { + enableVideo: true, + duration: 1500, + ambientAudio: 'audio/ambient/moaning-1.mp3', + ambientVolume: 0.4, + multiPattern: [ + { pattern: 'slow-fast-slow', duration: 450 }, + { pattern: 'varied-medium', duration: 600 }, + { pattern: 'varied-intense', duration: 450 } + ] + }, nextStep: 'completion' }, completion: { type: 'completion', - mood: 'authoritative', - story: '✅ Level 8 Complete - TTS UNLOCKED. You can hear your training now.', + mood: 'satisfied', + story: '✅ Level 8 Complete - AUDIO MASTERED. Sound is now part of your gooning arsenal.', outcome: 'level8_complete' } } diff --git a/src/features/tasks/interactiveTaskManager.js b/src/features/tasks/interactiveTaskManager.js index 287aab9..c755e69 100644 --- a/src/features/tasks/interactiveTaskManager.js +++ b/src/features/tasks/interactiveTaskManager.js @@ -4717,6 +4717,10 @@ class InteractiveTaskManager { const enableMetronomeSound = task.params?.enableMetronomeSound !== false; // Default to true const enableVideo = task.params?.enableVideo !== false; // Default to true const multiPattern = task.params?.multiPattern; // Array of pattern objects: [{pattern: 'fast-slow-fast', duration: 300}, ...] + const ambientAudio = task.params?.ambientAudio || null; + const ambientVolume = task.params?.ambientVolume || 0.5; + + let ambientAudioElement = null; const patterns = { 'slow-fast-slow': [60, 120, 60], @@ -4865,6 +4869,16 @@ class InteractiveTaskManager { await playRandomVideo(); } + // Start ambient audio if provided + if (ambientAudio) { + ambientAudioElement = new Audio(ambientAudio); + ambientAudioElement.loop = true; + ambientAudioElement.volume = ambientVolume; + ambientAudioElement.play().catch(err => { + console.warn('Could not play ambient audio:', err); + }); + } + // Setup video controls (only if video enabled) const skipVideoBtn = enableVideo ? container.querySelector('#rhythm-skip-video') : null; const videoVolumeSlider = enableVideo ? container.querySelector('#rhythm-video-volume') : null; @@ -4996,6 +5010,12 @@ class InteractiveTaskManager { audioContext.close().catch(err => console.warn('âš ī¸ Error closing audio context:', err)); } + // Stop ambient audio + if (ambientAudioElement) { + ambientAudioElement.pause(); + ambientAudioElement = null; + } + // Hide sidebar const sidebar = document.getElementById('campaign-sidebar'); if (sidebar) { @@ -5402,7 +5422,6 @@ class InteractiveTaskManager { â­ī¸ Skip to New Videos -
`; @@ -5411,28 +5430,80 @@ class InteractiveTaskManager { const skipBtn = container.querySelector('#skip-dual-video-btn'); const statusArea = container.querySelector('#dual-video-status'); const mainContainer = container.querySelector('#dual-video-main-container'); - const timerDisplay = container.querySelector('#dual-video-timer'); let availableVideos = []; const playDualVideos = async () => { - if (availableVideos.length < 2) return; - - // Select two random videos - const video1 = availableVideos[Math.floor(Math.random() * availableVideos.length)]; - let video2 = availableVideos[Math.floor(Math.random() * availableVideos.length)]; - while (video2 === video1 && availableVideos.length > 1) { - video2 = availableVideos[Math.floor(Math.random() * availableVideos.length)]; - } - // Clear existing videos mainContainer.innerHTML = ''; - // Play main video - await window.videoPlayerManager.playTaskVideo(video1, mainContainer); + // Handle main display based on mainVideo parameter + if (mainVideo === 'webcam') { + // Create webcam video element in main container + try { + const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); + const webcamVideo = document.createElement('video'); + webcamVideo.srcObject = stream; + webcamVideo.autoplay = true; + webcamVideo.muted = true; + webcamVideo.style.cssText = 'width: 100%; max-width: 800px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.3);'; + mainContainer.appendChild(webcamVideo); + statusArea.innerHTML = '
📸 Webcam active - watch yourself goon
'; + } catch (error) { + console.error('Webcam error:', error); + statusArea.innerHTML = '
âš ī¸ Could not access webcam
'; + return; + } + } else { + // mainVideo === 'focus' - play a video + if (availableVideos.length < 1) return; + + // Select random video for main display + const video1 = availableVideos[Math.floor(Math.random() * availableVideos.length)]; + await window.videoPlayerManager.playTaskVideo(video1, mainContainer); + } - // Play overlay video (PiP style) - await window.videoPlayerManager.playOverlayVideo(video2); + // Play overlay video (PiP style) - can be webcam or video + if (pipVideo === 'webcam') { + // Clean up any existing overlay player first + if (window.videoPlayerManager && window.videoPlayerManager.overlayPlayer) { + const oldOverlay = window.videoPlayerManager.overlayPlayer; + const oldVideo = oldOverlay.querySelector('video'); + if (oldVideo && oldVideo.srcObject) { + const tracks = oldVideo.srcObject.getTracks(); + tracks.forEach(track => track.stop()); + } + oldOverlay.remove(); + window.videoPlayerManager.overlayPlayer = null; + } + + // Create webcam in PiP overlay position + try { + const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); + const webcamPip = document.createElement('video'); + webcamPip.srcObject = stream; + webcamPip.autoplay = true; + webcamPip.muted = true; + webcamPip.style.cssText = 'width: 100%; border-radius: 8px;'; + + // Create PiP container for webcam + const webcamContainer = document.createElement('div'); + webcamContainer.className = 'pip-webcam-container'; + webcamContainer.appendChild(webcamPip); + document.body.appendChild(webcamContainer); + + // Store reference for positioning code below + window.videoPlayerManager = window.videoPlayerManager || {}; + window.videoPlayerManager.overlayPlayer = webcamContainer; + } catch (error) { + console.error('Webcam PiP error:', error); + statusArea.innerHTML = '
âš ī¸ Could not access webcam for PiP
'; + } + } else if (availableVideos.length >= 1) { + // pipVideo === 'overlay' - play a video in PiP + const video2 = availableVideos[Math.floor(Math.random() * availableVideos.length)]; + await window.videoPlayerManager.playOverlayVideo(video2); + } // Position the overlay player as PiP with draggable and resizable if (window.videoPlayerManager.overlayPlayer) { @@ -5466,29 +5537,33 @@ class InteractiveTaskManager { ${pos.left ? 'left: ' + pos.left : ''}; ${pos.right ? 'right: ' + pos.right : ''}; z-index: 100; + width: 300px; + display: inline-block; `; // Move player into container pipPlayer.parentElement.insertBefore(pipContainer, pipPlayer); pipContainer.appendChild(pipPlayer); pipPlayer.style.position = 'relative'; - pipPlayer.style.top = 'auto'; - pipPlayer.style.bottom = 'auto'; - pipPlayer.style.left = 'auto'; - pipPlayer.style.right = 'auto'; + pipPlayer.style.top = '0'; + pipPlayer.style.bottom = '0'; + pipPlayer.style.left = '0'; + pipPlayer.style.right = '0'; + pipPlayer.style.display = 'block'; // Create resize handle const resizeHandle = document.createElement('div'); resizeHandle.style.cssText = ` position: absolute; - bottom: 2px; - right: 2px; + bottom: 0; + right: 0; width: 20px; height: 20px; background: rgba(255,255,255,0.7); cursor: nwse-resize; border-radius: 0 0 6px 0; - z-index: 101; + z-index: 102; + pointer-events: auto; `; resizeHandle.innerHTML = '⋰'; resizeHandle.style.textAlign = 'center'; @@ -5557,9 +5632,17 @@ class InteractiveTaskManager { }; skipBtn.addEventListener('click', async () => { - if (availableVideos.length >= 2) { - await playDualVideos(); - statusArea.innerHTML = '
â­ī¸ Loaded new videos
'; + const minVideosRequired = (mainVideo === 'webcam') ? 1 : 2; + if (availableVideos.length >= minVideosRequired) { + // For webcam mode, only refresh the PiP video + if (mainVideo === 'webcam') { + const video = availableVideos[Math.floor(Math.random() * availableVideos.length)]; + await window.videoPlayerManager.playOverlayVideo(video); + statusArea.innerHTML = '
â­ī¸ Loaded new PiP video
'; + } else { + await playDualVideos(); + statusArea.innerHTML = '
â­ī¸ Loaded new videos
'; + } } }); @@ -5578,8 +5661,11 @@ class InteractiveTaskManager { try { const videos = window.videoPlayerManager.videoLibrary?.background || []; - if (videos.length < 2) { - statusArea.innerHTML = '
â„šī¸ Need at least 2 videos - continuing anyway
'; + // For webcam mode, we only need 1 video (for PiP) + const minVideosRequired = (mainVideo === 'webcam') ? 1 : 2; + + if (videos.length < minVideosRequired) { + statusArea.innerHTML = `
â„šī¸ Need at least ${minVideosRequired} video(s) - continuing anyway
`; task.completed = true; const completeBtn = document.getElementById('interactive-complete-btn'); if (completeBtn) completeBtn.disabled = false; @@ -5592,153 +5678,81 @@ class InteractiveTaskManager { btn.style.display = 'none'; skipBtn.style.display = 'inline-block'; - // Position the overlay player as PiP with draggable and resizable - if (window.videoPlayerManager.overlayPlayer) { - const pipPlayer = window.videoPlayerManager.overlayPlayer; - pipPlayer.style.position = 'fixed'; - pipPlayer.style.width = '300px'; - pipPlayer.style.height = 'auto'; - pipPlayer.style.zIndex = '100'; - pipPlayer.style.boxShadow = '0 0 20px rgba(0,0,0,0.5)'; - pipPlayer.style.cursor = 'move'; - pipPlayer.style.border = '2px solid rgba(255,255,255,0.3)'; - pipPlayer.style.borderRadius = '8px'; - - // Position based on parameter - const positions = { - 'bottom-right': { bottom: '20px', right: '20px', top: 'auto', left: 'auto' }, - 'bottom-left': { bottom: '20px', left: '20px', top: 'auto', right: 'auto' }, - 'top-right': { top: '20px', right: '20px', bottom: 'auto', left: 'auto' }, - 'top-left': { top: '20px', left: '20px', bottom: 'auto', right: 'auto' } + // Show sidebar and start countdown timer + const sidebar = document.getElementById('campaign-sidebar'); + + if (sidebar) { + let remainingTime = minDuration; + const formatTime = (seconds) => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; - const pos = positions[pipPosition] || positions['bottom-right']; - Object.assign(pipPlayer.style, pos); - - // Wrap in container for resize handle - const pipContainer = document.createElement('div'); - pipContainer.style.cssText = ` - position: fixed; - ${pos.top ? 'top: ' + pos.top : ''}; - ${pos.bottom ? 'bottom: ' + pos.bottom : ''}; - ${pos.left ? 'left: ' + pos.left : ''}; - ${pos.right ? 'right: ' + pos.right : ''}; - z-index: 100; + sidebar.style.display = 'block'; + sidebar.innerHTML = ` +
+
Time Remaining
+
${formatTime(remainingTime)}
+
`; - // Move player into container - pipPlayer.parentElement.insertBefore(pipContainer, pipPlayer); - pipContainer.appendChild(pipPlayer); - pipPlayer.style.position = 'relative'; - pipPlayer.style.top = 'auto'; - pipPlayer.style.bottom = 'auto'; - pipPlayer.style.left = 'auto'; - pipPlayer.style.right = 'auto'; - - // Create resize handle - const resizeHandle = document.createElement('div'); - resizeHandle.style.cssText = ` - position: absolute; - bottom: 2px; - right: 2px; - width: 20px; - height: 20px; - background: rgba(255,255,255,0.7); - cursor: nwse-resize; - border-radius: 0 0 6px 0; - z-index: 101; - `; - resizeHandle.innerHTML = '⋰'; - resizeHandle.style.textAlign = 'center'; - resizeHandle.style.lineHeight = '20px'; - resizeHandle.style.fontSize = '12px'; - pipContainer.appendChild(resizeHandle); - - // Make draggable - let isDragging = false; - let isResizing = false; - let startX, startY, startWidth, startHeight; - let currentX, currentY; - - // Drag functionality - pipPlayer.addEventListener('mousedown', (e) => { - if (e.target === resizeHandle) return; - isDragging = true; - const rect = pipContainer.getBoundingClientRect(); - startX = e.clientX - rect.left; - startY = e.clientY - rect.top; - pipPlayer.style.opacity = '0.8'; - e.preventDefault(); - }); - - // Resize functionality - resizeHandle.addEventListener('mousedown', (e) => { - e.stopPropagation(); - e.preventDefault(); - isResizing = true; - startX = e.clientX; - startY = e.clientY; - startWidth = pipPlayer.offsetWidth; - startHeight = pipPlayer.offsetHeight; - }); - - document.addEventListener('mousemove', (e) => { - if (isDragging) { - currentX = e.clientX - startX; - currentY = e.clientY - startY; + const timerInterval = setInterval(() => { + remainingTime--; + const timerEl = document.getElementById('dual-video-timer'); + + if (remainingTime > 0 && timerEl) { + timerEl.textContent = formatTime(remainingTime); + } else { + clearInterval(timerInterval); + if (sidebar) sidebar.style.display = 'none'; - // Keep within viewport bounds - currentX = Math.max(0, Math.min(currentX, window.innerWidth - pipContainer.offsetWidth)); - currentY = Math.max(0, Math.min(currentY, window.innerHeight - pipContainer.offsetHeight)); + statusArea.innerHTML = '
✅ Dual video session complete
'; + task.completed = true; - pipContainer.style.left = currentX + 'px'; - pipContainer.style.top = currentY + 'px'; - pipContainer.style.right = 'auto'; - pipContainer.style.bottom = 'auto'; - } else if (isResizing) { - const deltaX = e.clientX - startX; - const newWidth = Math.max(200, Math.min(800, startWidth + deltaX)); - pipPlayer.style.width = newWidth + 'px'; + const completeBtn = document.getElementById('interactive-complete-btn'); + if (completeBtn) { + completeBtn.disabled = false; + } } - }); + }, 1000); - document.addEventListener('mouseup', () => { - if (isDragging) { - isDragging = false; - pipPlayer.style.opacity = '1'; - } - if (isResizing) { - isResizing = false; - } - }); - } - - btn.style.display = 'none'; - timerDisplay.style.display = 'block'; - - // Start countdown timer - let remainingTime = minDuration; - timerDisplay.innerHTML = `âąī¸ Keep watching both videos... ${remainingTime}s remaining`; - - const timerInterval = setInterval(() => { - remainingTime--; - - if (remainingTime > 0) { - timerDisplay.innerHTML = `âąī¸ Keep watching both videos... ${remainingTime}s remaining`; - } else { + // Store cleanup function + task.cleanup = () => { clearInterval(timerInterval); - timerDisplay.innerHTML = '✅ Minimum viewing time complete!'; - timerDisplay.style.color = 'var(--color-success, #4caf50)'; + if (sidebar) sidebar.style.display = 'none'; - statusArea.innerHTML = '
✅ Dual video session complete
'; - task.completed = true; - - const completeBtn = document.getElementById('interactive-complete-btn'); - if (completeBtn) { - completeBtn.disabled = false; + // Clean up overlay player and its wrapper container + if (window.videoPlayerManager && window.videoPlayerManager.overlayPlayer) { + const overlay = window.videoPlayerManager.overlayPlayer; + + // Stop webcam stream if it's a webcam PiP + const video = overlay.querySelector('video'); + if (video && video.srcObject) { + const tracks = video.srcObject.getTracks(); + tracks.forEach(track => track.stop()); + } + + // Remove the wrapper container (which contains overlay + resize handle) + const wrapper = overlay.parentElement; + if (wrapper && wrapper !== document.body) { + wrapper.remove(); + } else { + // If no wrapper, just remove the overlay directly + overlay.remove(); + } + + window.videoPlayerManager.overlayPlayer = null; } - } - }, 1000); + + // Clean up main webcam if it exists + const mainVideo = mainContainer.querySelector('video'); + if (mainVideo && mainVideo.srcObject) { + const tracks = mainVideo.srcObject.getTracks(); + tracks.forEach(track => track.stop()); + } + }; + } } catch (error) { console.error('Dual video error:', error); @@ -6035,6 +6049,10 @@ class InteractiveTaskManager { const player = task.params?.player || 'task'; const tags = task.params?.tags || []; const minDuration = task.params?.minDuration || task.params?.duration || 60; // Support both minDuration and duration params + const ambientAudio = task.params?.ambientAudio || null; + const ambientVolume = task.params?.ambientVolume || 0.5; + + let ambientAudioElement = null; container.innerHTML = `
@@ -6108,6 +6126,16 @@ class InteractiveTaskManager { btn.style.display = 'none'; skipBtn.style.display = 'inline-block'; + // Start ambient audio if provided + if (ambientAudio) { + ambientAudioElement = new Audio(ambientAudio); + ambientAudioElement.loop = true; + ambientAudioElement.volume = ambientVolume; + ambientAudioElement.play().catch(err => { + console.warn('Could not play ambient audio:', err); + }); + } + // Add timer to sidebar const sidebar = document.getElementById('campaign-sidebar'); if (sidebar) { @@ -6170,6 +6198,10 @@ class InteractiveTaskManager { if (timerInterval) { clearInterval(timerInterval); } + if (ambientAudioElement) { + ambientAudioElement.pause(); + ambientAudioElement = null; + } // Hide sidebar const sidebar = document.getElementById('campaign-sidebar');