${step.mirrorInstructions || 'Look at yourself while completing the task'}
${step.mirrorTaskText || ''}
Time remaining: ${step.duration || 60}s
`;
choicesEl.appendChild(mirrorContainer);
// Add click handler for mirror button
const mirrorBtn = document.getElementById('scenario-mirror-btn');
mirrorBtn.addEventListener('click', async () => {
console.log('🪞 Opening webcam for scenario mirror task');
// Disable button while starting
mirrorBtn.disabled = true;
mirrorBtn.textContent = '🪞 Opening...';
try {
// Use webcam manager to start mirror mode
if (this.game && this.game.webcamManager) {
await this.game.webcamManager.startMirrorMode();
// Start countdown timer with slight delay to ensure overlay is ready
setTimeout(() => {
this.startMirrorTimer(task, scenario, step);
}, 100);
} else {
console.error('Webcam manager not available');
mirrorBtn.disabled = false;
mirrorBtn.textContent = '🪞 Try Again';
}
} catch (error) {
console.error('Failed to start mirror mode:', error);
mirrorBtn.disabled = false;
mirrorBtn.textContent = '🪞 Try Again';
}
});
}
startMirrorTimer(task, scenario, step) {
const duration = step.duration || 60;
console.log('🪞 Starting mirror timer for', duration, 'seconds');
// Look for timer elements in the mirror overlay first
const overlay = document.getElementById('mirror-overlay');
let timerEl, timeEl, progressBar;
if (overlay) {
timerEl = overlay.querySelector('#mirror-timer');
timeEl = overlay.querySelector('#mirror-time');
progressBar = overlay.querySelector('#mirror-progress-bar');
console.log('🪞 Found overlay elements:', {timerEl: !!timerEl, timeEl: !!timeEl, progressBar: !!progressBar});
}
// Fallback to scenario elements if overlay elements not found
if (!timerEl || !timeEl) {
timerEl = document.getElementById('mirror-timer');
timeEl = document.getElementById('mirror-time');
progressBar = document.getElementById('mirror-progress-bar');
console.log('🪞 Using fallback elements:', {timerEl: !!timerEl, timeEl: !!timeEl, progressBar: !!progressBar});
}
if (!timerEl || !timeEl) {
console.error('Timer elements not found in overlay or scenario');
return;
}
// Show timer and progress bar
timerEl.style.display = 'block';
let timeLeft = duration;
// Update time display
timeEl.textContent = timeLeft;
// Style and reset the progress bar if found
if (progressBar) {
progressBar.style.cssText = `
width: 0%;
height: 100%;
background: linear-gradient(90deg, #8a2be2, #9c27b0);
border-radius: 4px;
transition: width 1s ease;
`;
// Style the progress container
const progressContainer = progressBar.parentElement;
if (progressContainer) {
progressContainer.style.cssText = `
width: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
margin: 10px 0;
height: 8px;
overflow: hidden;
`;
}
}
// Update scenario button state if it exists
const mirrorBtn = document.getElementById('scenario-mirror-btn');
if (mirrorBtn) {
mirrorBtn.textContent = '🪞 Mirror Active';
mirrorBtn.disabled = true;
}
// Prevent camera closure during session
if (this.game && this.game.webcamManager) {
this.game.webcamManager.preventClose = true;
}
const countdown = setInterval(() => {
timeLeft--;
timeEl.textContent = timeLeft;
// Update progress bar
if (progressBar) {
const elapsed = duration - timeLeft;
const progressPercent = (elapsed / duration) * 100;
progressBar.style.width = `${progressPercent}%`;
}
if (timeLeft <= 0) {
clearInterval(countdown);
// Timer completed - allow camera closure
if (this.game && this.game.webcamManager) {
this.game.webcamManager.preventClose = false;
}
// Update progress bar to complete
if (progressBar) {
progressBar.style.width = '100%';
progressBar.style.background = '#4caf50';
}
// Timer completed
mirrorBtn.textContent = '🪞 Completed';
mirrorBtn.classList.add('completed');
timeEl.textContent = 'Complete!';
// Apply step effects
this.applyActionEffects(step, task.scenarioState);
// Move to next step
setTimeout(() => {
const nextStep = step.next || step.nextStep || this.getDefaultNextStep(task.scenarioState);
task.scenarioState.currentStep = nextStep;
task.scenarioState.stepNumber++;
this.displayScenarioStep(task, scenario, nextStep);
}, 1500);
}
}, 1000);
}
startStandaloneMirrorTimer(task, container) {
const duration = task.duration || 60;
const timerSection = container.querySelector('#mirror-timer-section');
const progressBar = container.querySelector('#standalone-mirror-progress-bar');
const timeDisplay = container.querySelector('#standalone-mirror-time');
const startBtn = container.querySelector('#start-mirror-btn');
if (!timerSection || !progressBar || !timeDisplay) return;
// Show timer section
timerSection.style.display = 'block';
let timeLeft = duration;
// Style the progress bar
progressBar.style.cssText = `
width: 0%;
height: 10px;
background: linear-gradient(90deg, #8a2be2, #9c27b0);
border-radius: 5px;
transition: width 1s ease;
`;
// Style the progress container
const progressContainer = progressBar.parentElement;
progressContainer.style.cssText = `
width: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
margin: 20px 0;
border: 1px solid rgba(138, 43, 226, 0.3);
`;
// Style the timer display
timeDisplay.style.cssText = `
font-size: 36px;
font-weight: bold;
color: #8a2be2;
margin: 15px 0;
`;
// Update button state
startBtn.textContent = '🪞 Mirror Active';
startBtn.disabled = true;
startBtn.style.opacity = '0.6';
// Prevent camera closure during session
if (this.game && this.game.webcamManager) {
this.game.webcamManager.preventClose = true;
}
const countdown = setInterval(() => {
timeLeft--;
timeDisplay.textContent = `${timeLeft}s`;
// Update progress bar
const elapsed = duration - timeLeft;
const progressPercent = (elapsed / duration) * 100;
progressBar.style.width = `${progressPercent}%`;
if (timeLeft <= 0) {
clearInterval(countdown);
// Timer completed - allow camera closure
if (this.game && this.game.webcamManager) {
this.game.webcamManager.preventClose = false;
}
// Update progress bar to complete
progressBar.style.width = '100%';
progressBar.style.background = '#4caf50';
// Timer completed
timeDisplay.textContent = 'Complete!';
timeDisplay.style.color = '#4caf50';
// Update timer label
const timerLabel = container.querySelector('.timer-label');
if (timerLabel) {
timerLabel.textContent = '✓ Mirror session completed successfully!';
timerLabel.style.color = '#4caf50';
}
console.log('🪞 Standalone mirror task completed');
// Complete the task
setTimeout(() => {
this.completeMirrorTask(task);
}, 1500);
}
}, 1000);
}
applyChoiceEffects(choice, state) {
// Effects system removed
}
applyActionEffects(step, state) {
// Effects system removed
}
applyScenarioEffects(step, state) {
// Could add visual effects, audio changes, etc. based on step properties
if (step.bgColor) {
document.querySelector('.interactive-task-container').style.background = step.bgColor;
}
}
// Scenario stats system removed - counters no longer used
getOutcomeText(outcome) {
const outcomes = {
'success': '🎉 Scenario Completed Successfully!',
'partial': '⚡ Partially Successful',
'failure': '🔥 Overwhelmed by Desire',
'denied': '🚫 Denied and Frustrated',
'reward': '🏆 Earned a Special Reward',
'punishment': '⛓️ Earned Punishment'
};
return outcomes[outcome] || 'Scenario Complete';
}
isChoiceAvailable(choice, state) {
// Counter-based conditions removed - all choices are available
return true;
}
processScenarioText(text, state) {
// Counter system removed - return text as-is
return text
.replace(/\{arousal\}/g, 'your state')
.replace(/\{control\}/g, 'your focus')
.replace(/\{intensity\}/g, 'the level');
}
/**
* Speak scenario text using TTS with cross-platform voice
*/
speakScenarioText(text, state, options = {}) {
if (!this.ttsEnabled || !text) {
return;
}
// Process the text to replace placeholders
const processedText = this.processScenarioText(text, state);
// Clean up HTML tags and excessive whitespace for better speech
const cleanText = processedText
.replace(/<[^>]*>/g, ' ') // Remove HTML tags
.replace(/\s+/g, ' ') // Collapse whitespace
.trim();
if (!cleanText) {
return;
}
// Default TTS settings optimized for scenario narration
const ttsOptions = {
rate: options.rate || 0.9, // Slightly slower for dramatic effect
pitch: options.pitch || 1.1, // Slightly higher for feminine voice
volume: options.volume || 0.8, // Not too loud
onStart: () => {
// Optional: could add subtle visual indicator
},
onEnd: () => {
if (options.onComplete) options.onComplete();
},
onError: (error) => {
console.warn('TTS error:', error.message || error);
if (options.onError) options.onError(error);
}
};
// Speak the text
this.voiceManager.speak(cleanText, ttsOptions);
}
/**
* Stop any current TTS playback
*/
stopTTS() {
if (this.voiceManager) {
this.voiceManager.stop();
}
}
/**
* Toggle TTS on/off
*/
toggleTTS() {
this.ttsEnabled = !this.ttsEnabled;
if (!this.ttsEnabled) {
this.stopTTS();
}
return this.ttsEnabled;
}
/**
* Get current TTS voice information
*/
getTTSInfo() {
if (!this.voiceManager) {
return { available: false, reason: 'Voice manager not initialized' };
}
return {
available: this.ttsEnabled,
voiceInfo: this.voiceManager.getVoiceInfo(),
supported: this.voiceManager.isSupported()
};
}
getScenarioStepCount(scenario) {
return Object.keys(scenario.steps || {}).length;
}
/**
* Check if a focus interruption should be triggered
*/
shouldTriggerFocusInterruption() {
const chance = window.game?.gameState?.focusInterruptionChance || 0;
console.log(`🧘 Focus interruption chance setting: ${chance}%`);
if (chance <= 0) {
console.log(`🧘 Focus interruption disabled (chance is ${chance}%)`);
return false;
}
const random = Math.random() * 100;
const shouldInterrupt = random < chance;
console.log(`🧘 Focus interruption check: ${random.toFixed(1)}% vs ${chance}% = ${shouldInterrupt ? 'INTERRUPT' : 'continue'}`);
return shouldInterrupt;
}
/**
* Trigger a focus-hold interruption during scenario
*/
triggerFocusInterruption(task, scenario, pendingChoice) {
console.log('🧘 ⚡ === FOCUS INTERRUPTION TRIGGERED ===');
console.log(`🧘 Scenario: ${scenario.id || scenario.title || 'unknown'}, Current Step: ${task.scenarioState.currentStep}`);
console.log(`🧘 Pending Choice: "${pendingChoice.text}" -> ${pendingChoice.nextStep}`);
// Show brief notification
this.showFeedback('info', '⚡ Focus interruption triggered! Prepare to concentrate...', 2000);
// Store the scenario state for resumption
const resumeData = {
task: task,
scenario: scenario,
pendingChoice: pendingChoice,
originalContainer: this.currentContainer
};
// Store resume data for later
this.scenarioResumeData = resumeData;
console.log(`🧘 Stored resume data for later:`, resumeData);
// Create interruption message
const container = document.getElementById('interactive-task-container');
if (!container) {
console.error('🧘 ❌ Interactive task container not found - cannot show interruption');
return;
}
console.log(`🧘 Found container, showing focus interruption interface`);
container.innerHTML = `
🧘 Focus Interruption
Your mind needs to focus. Complete this concentration task before continuing your adventure...
`;
// Create a focus-hold task
const focusTask = this.createFocusInterruptionTask();
// Set this as the current interactive task so validation works
this.currentInteractiveTask = focusTask;
console.log(`🧘 Set focus interruption as current task:`, focusTask);
// Insert focus task into the interruption content
setTimeout(() => {
const focusContainer = container.querySelector('#focus-interruption-content');
if (focusContainer) {
this.createFocusTask(focusTask, focusContainer);
}
}, 100);
}
/**
* Create a focus-hold task for interruption
*/
createFocusInterruptionTask() {
const durations = [30, 45, 60, 90]; // Different durations in seconds
const instructions = [
'Hold the position and maintain focus on the screen',
'Focus on your breathing and stay perfectly still',
'Clear your mind and concentrate on the task at hand',
'Hold steady and let your arousal build without release',
'Maintain perfect focus while staying on the edge'
];
return {
id: 'focus-interruption',
text: '🧘 Focus Training Interruption',
story: '⚡ Your adventure is interrupted by a moment requiring deep concentration. Complete this focus challenge to continue your journey.',
instructions: instructions[Math.floor(Math.random() * instructions.length)],
duration: durations[Math.floor(Math.random() * durations.length)],
interactiveType: 'focus-hold',
isInterruption: true
};
}
/**
* Resume scenario after focus interruption
*/
resumeScenarioAfterInterruption() {
console.log('🧘 === RESUMING SCENARIO AFTER INTERRUPTION ===');
if (!this.scenarioResumeData) {
console.warn('⚠️ No scenario resume data found');
return;
}
console.log('🧘 Found resume data:', this.scenarioResumeData);
// Show success message
this.showFeedback('success', '✨ Focus completed! Returning to your adventure...', 2000);
const { task, scenario, pendingChoice } = this.scenarioResumeData;
console.log(`🧘 Resuming scenario: ${scenario.id}`);
console.log(`🧘 Continuing with choice: "${pendingChoice.text}" -> ${pendingChoice.nextStep}`);
// Small delay to show the success message before continuing
setTimeout(() => {
// First, restore the scenario interface since interruption replaced it
console.log(`🧘 Restoring scenario interface...`);
const container = document.getElementById('interactive-task-container');
if (container) {
console.log(`🧘 Container found, calling createScenarioTask...`);
this.createScenarioTask(task, container);
// Wait a moment for the interface to be created
setTimeout(() => {
console.log(`🧘 Checking if scenario elements are now available...`);
const storyEl = document.getElementById('scenario-story');
const choicesEl = document.getElementById('scenario-choices');
const stepEl = document.getElementById('scenario-step');
console.log(`🧘 Elements found: story=${!!storyEl}, choices=${!!choicesEl}, step=${!!stepEl}`);
// Move to next step (the original choice processing)
const nextStep = pendingChoice.nextStep || this.getDefaultNextStep(task.scenarioState);
task.scenarioState.currentStep = nextStep;
task.scenarioState.stepNumber++;
console.log(`🧘 ✅ Scenario resumed - moved to step: ${nextStep} (Step #${task.scenarioState.stepNumber})`);
// Display next step
this.displayScenarioStep(task, scenario, nextStep);
// Clear resume data
this.scenarioResumeData = null;
console.log(`🧘 Resume data cleared - scenario flow restored`);
}, 100);
} else {
console.error(`🧘 ❌ Container not found for scenario restoration`);
}
}, 1000);
}
getDefaultNextStep(state) {
// Simple logic to determine next step based on state
// Can be overridden by specific choice nextStep properties
return `step${state.stepNumber + 1}`;
}
/**
* Validate scenario task completion
*/
async validateScenarioTask(task) {
console.log('🔍 ValidateScenarioTask called with task:', task);
console.log('🔍 Task scenarioState:', task.scenarioState);
console.log('🔍 Task scenarioState.completed:', task.scenarioState?.completed);
// Check if scenario is marked as completed
if (task.scenarioState && task.scenarioState.completed) {
console.log('✅ Scenario task validation: PASSED (completed = true)');
return true;
}
// For photography steps, check if photos have been taken
const currentStep = task.scenarioState?.currentStep;
if (currentStep && task.interactiveData?.steps) {
const step = task.interactiveData.steps[currentStep];
if (step && this.isPhotographyStep(step)) {
console.log('📸 Validating photography step completion');
// Photography steps are considered complete when photos are taken
// The webcam manager should have already handled completion
return true;
}
}
console.log('❌ Scenario task validation: FAILED');
return false;
}
/**
* Create mirror task - opens webcam for self-viewing
*/
async createMirrorTask(task, container) {
console.log('🪞 Creating mirror task');
// Create mirror task interface
container.innerHTML = `
🪞 Mirror Task
${task.story || 'Use the webcam to look at yourself and complete the task'}
${task.mirrorInstructions || task.story || 'Complete the task while looking at yourself'}
${task.mirrorTaskText || ''}
${task.duration || 60}s
Mirror session in progress...
`;
// Style the mirror task
const mirrorContainer = container.querySelector('.mirror-task-container');
// Add event listener for the start button
const startBtn = container.querySelector('#start-mirror-btn');
// Add hover effect
startBtn.addEventListener('mouseenter', () => {
startBtn.style.transform = 'translateY(-2px) scale(1.05)';
startBtn.style.boxShadow = '0 5px 15px rgba(138, 43, 226, 0.5)';
});
startBtn.addEventListener('mouseleave', () => {
startBtn.style.transform = 'translateY(0) scale(1)';
startBtn.style.boxShadow = '0 3px 10px rgba(138, 43, 226, 0.3)';
});
// Add click handler to start mirror mode
startBtn.addEventListener('click', () => {
if (this.game.webcamManager) {
const mirrorData = {
instructions: task.mirrorInstructions || 'Complete your task while looking at yourself',
taskText: task.mirrorTaskText || task.story,
task: task
};
// Start mirror mode
this.game.webcamManager.startMirrorMode(mirrorData);
// Start the timer and progress bar
this.startStandaloneMirrorTimer(task, container);
} else {
console.warn('Webcam not available - falling back to regular task completion');
if (this.game && this.game.showNotification) {
this.game.showNotification('Webcam not available. Task completed automatically.', 'warning');
}
setTimeout(() => {
this.completeMirrorTask(task);
}, 2000);
}
});
return true;
}
/**
* Validate mirror task completion
*/
async validateMirrorTask(task) {
return true; // Mirror tasks are completed when webcam mirror is closed
}
/**
* Check if a scenario step involves photography
*/
isPhotographyStep(step) {
if (!step) return false;
// Check for explicit photography step markers
if (step.type === 'action' && step.duration === 0) {
console.log('📸 Photography step detected: explicit 0 duration');
return true; // Steps with 0 duration are photo steps
}
// For dress-up scenarios, all action steps should be photography steps
const task = this.game?.gameState?.currentTask;
console.log('📸 Checking photography step detection for task:', task?.id, 'step type:', step.type);
if (task && task.id === 'scenario-dress-up-photo') {
const isAction = step.type === 'action';
console.log('📸 Dress-up scenario detected, is action step:', isAction);
return isAction;
}
const photoKeywords = ['photo', 'photograph', 'camera', 'picture', 'pose', 'submissive_photo', 'dress.*photo'];
const textToCheck = `${step.actionText || ''} ${step.story || ''}`.toLowerCase();
console.log('📸 Keyword detection - text to check:', textToCheck);
const result = photoKeywords.some(keyword => {
if (keyword.includes('.*')) {
// Handle regex patterns
const regex = new RegExp(keyword);
return regex.test(textToCheck);
}
return textToCheck.includes(keyword);
});
console.log('📸 Photography step detection result:', result);
return result;
}
/**
* Start photography session for scenario step
*/
async startPhotographySession(task, scenario, step) {
console.log('📸 Starting photography session for scenario step');
console.log('📸 Task:', task?.id, 'Step:', step?.nextStep || 'no-next-step');
console.log('📸 Step details:', { type: step?.type, actionText: step?.actionText });
// Determine photo requirements from step
const photoRequirements = this.getPhotoRequirements(step);
console.log('📸 Photo requirements:', photoRequirements);
const sessionType = this.getSessionTypeFromStep(step);
console.log('📸 Session type:', sessionType);
// Set up event listener for photo session completion
const handlePhotoCompletion = (event) => {
console.log('📸 EVENT TRIGGERED - Photo session completed - handling scenario progression');
console.log('📸 Event detail:', event.detail);
console.log('📸 Task state:', { id: task?.id, stepNumber: task?.scenarioState?.stepNumber });
console.log('📸 Current step:', task?.scenarioState?.currentStep);
// Remove this specific event listener
document.removeEventListener('photoSessionComplete', handlePhotoCompletion);
// Progress to next scenario step
console.log('📸 About to handle photography step completion');
this.handlePhotographyStepCompletion(task, scenario, step);
};
// Add the event listener
document.addEventListener('photoSessionComplete', handlePhotoCompletion);
console.log('📸 Event listener set up, starting photo session...');
const success = await this.game.webcamManager.startPhotoSessionWithProgress(sessionType, {
task: task,
scenario: scenario,
step: step,
requirements: photoRequirements
});
console.log('📸 Photo session start result:', success);
if (!success) {
console.log('📸 Camera not available - removing event listener');
document.removeEventListener('photoSessionComplete', handlePhotoCompletion);
this.showFeedback('error', 'Camera not available. Please complete the task manually.');
}
}
/**
* Handle completion of a photography step in scenario
*/
handlePhotographyStepCompletion(task, scenario, step) {
console.log('📸 ENTERING handlePhotographyStepCompletion');
console.log('📸 Task:', task?.id);
console.log('📸 Step:', step);
console.log('📸 Step nextStep:', step?.nextStep);
console.log('📸 Current scenario state:', task?.scenarioState);
// Check if this step has a specific nextStep
if (step.nextStep) {
console.log(`📸 Auto-progressing to next step: ${step.nextStep}`);
console.log('📸 Setting up auto-progression with 1500ms delay...');
// Add a small delay to show completion feedback
setTimeout(() => {
console.log('📸 TIMEOUT EXECUTING - updating scenario state');
console.log('📸 Previous step:', task.scenarioState.currentStep);
console.log('📸 Previous step number:', task.scenarioState.stepNumber);
task.scenarioState.currentStep = step.nextStep;
task.scenarioState.stepNumber++;
console.log('📸 New step:', task.scenarioState.currentStep);
console.log('📸 New step number:', task.scenarioState.stepNumber);
console.log('📸 About to call displayScenarioStep...');
this.displayScenarioStep(task, scenario, step.nextStep);
console.log('📸 displayScenarioStep called');
}, 1500);
} else {
console.log('📸 No nextStep found - enabling manual progression');
console.log('📸 Photo step completed - enabling manual progression');
// Show completion message and enable manual progression
this.showFeedback('success', '📸 Photo task completed! Click Complete to continue.');
// Enable the complete button for manual progression
const completeBtn = document.getElementById('interactive-complete-btn');
console.log('📸 Looking for complete button:', !!completeBtn);
if (completeBtn) {
completeBtn.disabled = false;
completeBtn.style.display = 'block';
console.log('📸 Complete button enabled for manual progression');
} else {
console.log('📸 WARNING: Complete button not found!');
}
}
// Show success feedback
this.showFeedback('success', '📸 Photo session completed successfully!');
}
/**
* Get photo requirements from step
*/
getPhotoRequirements(step) {
// First check if step has explicit photoCount property
if (step.photoCount) {
return {
count: step.photoCount,
description: `Take ${step.photoCount} photos to complete this task`
};
}
// Check if this is a scenario with dynamic photo count based on state
if (this.currentInteractiveTask && this.currentInteractiveTask.scenarioState) {
const state = this.currentInteractiveTask.scenarioState;
const photoCount = this.calculateDynamicPhotoCount(state, step);
return {
count: photoCount,
description: `Take ${photoCount} photos to complete this task`
};
}
// Otherwise parse step content to determine how many photos are needed
const actionText = step.actionText?.toLowerCase() || '';
const story = step.story?.toLowerCase() || '';
// Look for photo count hints in the text
if (actionText.includes('series') || story.includes('multiple') || story.includes('several')) {
return { count: 3, description: 'Take 3 photos in different poses' };
} else if (actionText.includes('photo session') || story.includes('photo shoot')) {
return { count: 5, description: 'Complete photo session (5 photos)' };
} else {
return { count: 1, description: 'Take 1 photo to complete task' };
}
}
/**
* Calculate dynamic photo count based on scenario state
*/
calculateDynamicPhotoCount(state, step) {
// Counter system removed
// Counter system removed
// Base photo count
let photoCount = 1;
// Increase photo count based on arousal level
photoCount += 1; // Static photo count
// Adjust based on control level
if (control <= 20) {
photoCount += 2; // Very low control = need more photos for submission
} else if (control <= 40) {
photoCount += 1; // Low control = need extra photos
}
// Cap at reasonable limits
photoCount = Math.max(1, Math.min(6, photoCount));
console.log(`📸 Dynamic photo count: ${photoCount}`);
return photoCount;
}
/**
* Get session type from scenario step
*/
getSessionTypeFromStep(step) {
const content = `${step.actionText || ''} ${step.story || ''}`.toLowerCase();
if (content.includes('submissive') || content.includes('pose')) {
return 'submissive_poses';
} else if (content.includes('dress') || content.includes('costume')) {
return 'dress_up_photos';
} else if (content.includes('humil') || content.includes('degrad')) {
return 'humiliation_photos';
} else {
return 'general_photography';
}
}
/**
* Offer webcam option for photography tasks
*/
async offerWebcamOption(task) {
return new Promise((resolve) => {
// Create modal dialog
const modal = document.createElement('div');
modal.id = 'webcam-offer-modal';
modal.innerHTML = `
📸 Photography Task Detected
This task involves photography. Would you like to use your webcam for a more immersive experience?
Task: ${task.text}
🔒 Photos are processed locally and never uploaded
`;
document.body.appendChild(modal);
this.addWebcamModalStyles();
// Handle button clicks
document.getElementById('use-webcam-btn').addEventListener('click', () => {
modal.remove();
resolve(true);
});
document.getElementById('skip-webcam-btn').addEventListener('click', () => {
modal.remove();
resolve(false);
});
});
}
/**
* Start webcam photo session for photography task
*/
async startWebcamPhotoSession(task) {
console.log('📸 Starting webcam photo session for task:', task.interactiveType);
const sessionType = this.game.webcamManager.getSessionTypeFromTask(task);
const success = await this.game.webcamManager.startPhotoSession(sessionType, task);
if (success) {
console.log('✅ Webcam photo session started successfully');
return true;
} else {
console.log('❌ Webcam photo session failed to start, falling back to text mode');
// Fall back to regular interactive task display
return this.displayRegularInteractiveTask(task);
}
}
/**
* Handle photo session completion (multiple photos)
*/
handlePhotoSessionCompletion(detail) {
console.log('🎉 Photo session completion handled:', detail.sessionType, `(${detail.photos.length} photos)`);
// Show task completion message
if (this.game.showNotification) {
this.game.showNotification(`Photography completed! ${detail.photos.length} photos taken 📸`, 'success', 3000);
}
// Progress the scenario to next step (don't end the game)
if (this.currentInteractiveTask && detail.taskData) {
const { task, scenario, step } = detail.taskData;
// Apply step effects based on photo count (more photos = more arousal/less control)
if (step.effects) {
this.applyStepEffects(step.effects, task.scenarioState, detail.photos.length);
}
// Move to next step
const nextStep = step.nextStep || 'completion';
setTimeout(() => {
this.displayScenarioStep(task, scenario, nextStep);
}, 1000);
}
}
/**
* Apply step effects with photo count modifiers
*/
applyStepEffects(effects, state, photoCount = 1) {
// Effects system removed
}
/**
* Handle photo completion from webcam (single photo - keep for compatibility)
*/
handlePhotoCompletion(detail) {
console.log('📸 Photo completion handled:', detail.sessionType);
// Show task completion message
if (this.game.showNotification) {
this.game.showNotification('Photography task completed! 📸', 'success', 3000);
}
// Mark current task as completed after a delay (but don't use completeInteractiveTask to avoid container issues)
setTimeout(() => {
// Check if game is still running before completing
if (this.currentInteractiveTask && this.game.gameState.isRunning) {
this.cleanupInteractiveTask();
this.game.completeTask();
}
}, 2000);
}
/**
* Resume from camera back to task
*/
resumeFromCamera() {
console.log('📱 Resuming from camera to task interface');
// Check if game is still running
if (!this.game.gameState.isRunning) {
console.log('Resume from camera cancelled - game no longer running');
return;
}
// Show completion message and progress to next task
if (this.currentInteractiveTask) {
// Mark task as completed without trying to show feedback in missing containers
this.cleanupInteractiveTask();
// Show success notification through game system instead
if (this.game && this.game.showNotification) {
this.game.showNotification('Photography task completed successfully! 📸', 'success', 3000);
}
// Complete the task directly
setTimeout(() => {
// Double-check game is still running
if (this.game.gameState.isRunning) {
this.game.completeTask();
}
}, 1000);
}
}
/**
* Complete mirror task from webcam
*/
completeMirrorTask(taskData) {
console.log('🪞 Mirror task completed through webcam');
// Check if game is still running
if (!this.game.gameState.isRunning) {
console.log('Mirror task completion cancelled - game no longer running');
return;
}
// Show completion message
if (this.game && this.game.showNotification) {
this.game.showNotification('Mirror task completed! 🪞', 'success', 3000);
}
// Clean up any interactive task
if (this.currentInteractiveTask) {
this.cleanupInteractiveTask();
}
// Complete the task
setTimeout(() => {
if (this.game.gameState.isRunning) {
this.game.completeTask();
}
}, 1000);
}
/**
* Handle mirror task abandonment
*/
abandonMirrorTask() {
console.log('❌ Mirror task abandoned by user');
// Check if game is still running
if (!this.game.gameState.isRunning) {
console.log('Mirror task abandonment cancelled - game no longer running');
return;
}
// Show abandonment message
if (this.game && this.game.showNotification) {
this.game.showNotification('Mirror task abandoned. You gave up like a quitter! 😤', 'error', 4000);
}
// Clean up any interactive task
if (this.currentInteractiveTask) {
this.cleanupInteractiveTask();
}
// Mark as failure and end task
setTimeout(() => {
if (this.game.gameState.isRunning) {
// Add penalty points or mark as failure
if (this.game.gameState.stats) {
this.game.gameState.stats.failures = (this.game.gameState.stats.failures || 0) + 1;
this.game.gameState.stats.abandonments = (this.game.gameState.stats.abandonments || 0) + 1;
}
// End task as failure
this.game.failTask('Mirror task abandoned');
}
}, 1000);
}
/**
* Display regular interactive task (fallback from webcam)
*/
async displayRegularInteractiveTask(task) {
// Continue with regular interactive task display
// (This is the original displayInteractiveTask logic)
// Handle task image for interactive tasks
const taskImage = document.getElementById('task-image');
if (taskImage && task.image) {
console.log(`🖼️ Setting interactive task image: ${task.image}`);
taskImage.src = task.image;
taskImage.onerror = () => {
console.log('Interactive task image failed to load:', task.image);
taskImage.src = this.createPlaceholderImage('Interactive Task');
};
} else if (taskImage) {
console.log(`⚠️ No image provided for interactive task, creating placeholder`);
taskImage.src = this.createPlaceholderImage('Interactive Task');
}
// Continue with rest of task display logic...
const taskContainer = this.taskContainer || document.querySelector('.task-display-container');
// Add interactive container
const interactiveContainer = document.createElement('div');
interactiveContainer.id = 'interactive-task-container';
interactiveContainer.innerHTML = `