refined level 6-8

This commit is contained in:
dilgenfritz 2025-12-03 00:57:41 -06:00
parent 5bd002e72d
commit c9a01f8ed6
3 changed files with 272 additions and 194 deletions

View File

@ -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) {

View File

@ -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'
}
}

View File

@ -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
</button>
</div>
<div id="dual-video-timer" class="timer-display" style="display: none; margin: 15px 0; font-size: 1.2em; font-weight: bold;"></div>
<div id="dual-video-status" class="status-area"></div>
</div>
`;
@ -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 = '<div class="info">📸 Webcam active - watch yourself goon</div>';
} catch (error) {
console.error('Webcam error:', error);
statusArea.innerHTML = '<div class="error">⚠️ Could not access webcam</div>';
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 = '<div class="error">⚠️ Could not access webcam for PiP</div>';
}
} 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 = '<div class="info">⏭️ Loaded new videos</div>';
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 = '<div class="info">⏭️ Loaded new PiP video</div>';
} else {
await playDualVideos();
statusArea.innerHTML = '<div class="info">⏭️ Loaded new videos</div>';
}
}
});
@ -5578,8 +5661,11 @@ class InteractiveTaskManager {
try {
const videos = window.videoPlayerManager.videoLibrary?.background || [];
if (videos.length < 2) {
statusArea.innerHTML = '<div class="info"> Need at least 2 videos - continuing anyway</div>';
// For webcam mode, we only need 1 video (for PiP)
const minVideosRequired = (mainVideo === 'webcam') ? 1 : 2;
if (videos.length < minVideosRequired) {
statusArea.innerHTML = `<div class="info"> Need at least ${minVideosRequired} video(s) - continuing anyway</div>`;
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 = `
<div style="background: rgba(0, 0, 0, 0.9); padding: 20px; border-radius: 10px; border: 2px solid var(--color-primary); text-align: center;">
<div style="font-size: 0.9em; color: var(--text-muted); margin-bottom: 8px;">Time Remaining</div>
<div id="dual-video-timer" style="font-size: 2em; font-weight: bold; color: var(--color-primary);">${formatTime(remainingTime)}</div>
</div>
`;
// 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 = '<div class="success">✅ Dual video session complete</div>';
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 = '<div class="success">✅ Dual video session complete</div>';
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 = `
<div class="academy-video-start-task">
@ -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');