`;
document.body.appendChild(viewer);
}
/**
* Confirm ending session before requirements are met
*/
confirmEndSession() {
if (!this.currentPhotoSession) {
this.endPhotoSession();
return;
}
const photosTaken = this.currentPhotoSession.photos.length;
const photosNeeded = this.currentPhotoSession.photosNeeded;
if (photosTaken < photosNeeded) {
const confirmed = confirm(
`You have only taken ${photosTaken} out of ${photosNeeded} required photos.\n\n` +
`Ending now will not complete the photography task.\n\n` +
`Are you sure you want to end the session?`
);
if (!confirmed) {
return; // Don't end session
}
}
this.endPhotoSession();
}
/**
* Start a photo session for interactive tasks (original method - keep for compatibility)
*/
async startPhotoSession(sessionType, taskData) {
console.log(`๐ธ Starting photo session: ${sessionType}`);
// Request camera if not already active
if (!this.isActive) {
const accessGranted = await this.requestCameraAccess();
if (!accessGranted) return false;
}
this.currentPhotoSession = {
type: sessionType,
taskData: taskData,
photos: [],
startTime: Date.now()
};
// Show camera interface
this.showCameraInterface();
return true;
}
/**
* Display camera interface for photo tasks
*/
showCameraInterface() {
// Create camera overlay
const overlay = document.createElement('div');
overlay.id = 'camera-overlay';
overlay.innerHTML = `
๐ธ Photography Session
Position yourself according to the task instructions
Photos taken: 0
โ ๏ธ Photos are stored locally and not uploaded anywhere
`;
document.body.appendChild(overlay);
// Connect video stream to preview
const cameraFeed = document.getElementById('camera-feed');
cameraFeed.srcObject = this.stream;
// Bind camera controls
this.bindCameraControls();
// Add CSS styles
this.addCameraStyles();
}
/**
* Bind camera control events
*/
bindCameraControls() {
document.getElementById('capture-photo').addEventListener('click', () => {
this.startPhotoTimer();
});
document.getElementById('retake-photo').addEventListener('click', () => {
this.showCameraPreview();
});
document.getElementById('accept-photo').addEventListener('click', () => {
this.acceptPhoto();
});
}
/**
* Capture a photo from the video stream
*/
capturePhoto() {
const video = document.getElementById('camera-feed');
// Set canvas size to video dimensions
this.canvas.width = video.videoWidth;
this.canvas.height = video.videoHeight;
// Draw video frame to canvas
this.context.drawImage(video, 0, 0);
// Convert to image data
const imageDataURL = this.canvas.toDataURL('image/jpeg', 0.8);
// Show photo preview
this.showPhotoPreview(imageDataURL);
console.log('๐ธ Photo captured');
}
/**
* Start photo countdown timer
*/
startPhotoTimer(duration = 3) {
const captureBtn = document.getElementById('capture-photo');
const originalText = captureBtn.innerHTML;
let timeLeft = duration;
let cancelled = false;
// Disable capture button and show cancel option
captureBtn.disabled = true;
captureBtn.style.opacity = '0.7';
// Create large countdown overlay
const cameraPreview = document.querySelector('.camera-preview');
const countdownOverlay = document.createElement('div');
countdownOverlay.id = 'countdown-overlay';
countdownOverlay.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
border-radius: 10px;
`;
const countdownText = document.createElement('div');
countdownText.id = 'countdown-text';
countdownText.style.cssText = `
font-size: 72px;
font-weight: bold;
color: white;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
animation: pulse 1s infinite;
margin-bottom: 20px;
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'โ Cancel';
cancelBtn.style.cssText = `
background: rgba(220, 53, 69, 0.8);
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
`;
cancelBtn.addEventListener('click', () => {
cancelled = true;
countdownOverlay.remove();
captureBtn.innerHTML = originalText;
captureBtn.disabled = false;
captureBtn.style.opacity = '1';
console.log('๐ฑ Photo timer cancelled');
});
countdownOverlay.appendChild(countdownText);
countdownOverlay.appendChild(cancelBtn);
cameraPreview.style.position = 'relative';
cameraPreview.appendChild(countdownOverlay);
// Add pulse animation
if (!document.getElementById('pulse-animation-style')) {
const style = document.createElement('style');
style.id = 'pulse-animation-style';
style.textContent = `
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
`;
document.head.appendChild(style);
}
// Create countdown display
const updateCountdown = () => {
if (cancelled) return; // Stop if cancelled
if (timeLeft > 0) {
captureBtn.innerHTML = `๐ท Get Ready...`;
countdownText.textContent = timeLeft;
timeLeft--;
setTimeout(updateCountdown, 1000);
} else {
// Show capture indicator
countdownText.textContent = '๐ธ';
countdownText.style.fontSize = '96px';
cancelBtn.style.display = 'none'; // Hide cancel button
captureBtn.innerHTML = '๐ธ Capturing...';
setTimeout(() => {
if (!cancelled) {
// Take the photo
this.capturePhoto();
// Remove countdown overlay
countdownOverlay.remove();
// Reset button after capture
setTimeout(() => {
captureBtn.innerHTML = originalText;
captureBtn.disabled = false;
captureBtn.style.opacity = '1';
}, 500);
}
}, 500);
}
};
// Start countdown
updateCountdown();
console.log(`๐ฑ Photo timer started: ${duration} seconds`);
}
/**
* Show captured photo preview
*/
showPhotoPreview(imageDataURL) {
const preview = document.getElementById('photo-preview');
const image = document.getElementById('captured-image');
image.src = imageDataURL;
preview.style.display = 'block';
// Update button visibility
document.getElementById('capture-photo').style.display = 'none';
document.getElementById('retake-photo').style.display = 'inline-block';
document.getElementById('accept-photo').style.display = 'inline-block';
}
/**
* Show camera preview (for retaking)
*/
showCameraPreview() {
const preview = document.getElementById('photo-preview');
preview.style.display = 'none';
// Update button visibility
document.getElementById('capture-photo').style.display = 'inline-block';
document.getElementById('retake-photo').style.display = 'none';
document.getElementById('accept-photo').style.display = 'none';
}
/**
* Accept and save the captured photo
*/
acceptPhoto() {
const image = document.getElementById('captured-image');
const photoData = {
dataURL: image.src,
timestamp: Date.now(),
sessionType: this.currentPhotoSession.type,
taskData: this.currentPhotoSession.taskData
};
// Add to session photos
this.currentPhotoSession.photos.push(photoData);
this.capturedPhotos.push(photoData);
// Track photo for XP (1 XP per photo)
if (this.game && this.game.incrementPhotosTaken) {
this.game.incrementPhotosTaken();
}
// Update photo count
document.getElementById('photo-count').textContent = this.currentPhotoSession.photos.length;
// Save to local storage (optional - user privacy)
this.savePhotoData(photoData);
// Return to camera view for more photos
this.showCameraPreview();
// Trigger task progression
this.notifyTaskComplete(photoData);
console.log(`โ Photo accepted and saved (${this.currentPhotoSession.photos.length} total)`);
}
/**
* Save photo data (respecting privacy)
*/
savePhotoData(photoData) {
// Save metadata to session storage (temporary)
const metadata = {
timestamp: photoData.timestamp,
sessionType: photoData.sessionType,
taskId: photoData.taskData?.id
};
const sessionPhotos = JSON.parse(sessionStorage.getItem('photoSession') || '[]');
sessionPhotos.push(metadata);
sessionStorage.setItem('photoSession', JSON.stringify(sessionPhotos));
// Save actual photo data to localStorage with user consent
this.savePersistentPhoto(photoData);
}
/**
* Save photo data persistently with user consent
*/
savePersistentPhoto(photoData) {
try {
// Check if user has given consent for photo storage
const photoStorageConsent = localStorage.getItem('photoStorageConsent');
if (photoStorageConsent === null) {
// First time - ask for consent
this.requestPhotoStorageConsent(photoData);
return;
}
if (photoStorageConsent === 'true') {
// User has consented - save the photo
this.storePhotoInLocalStorage(photoData);
}
// If consent is 'false', don't save (but still allow session use)
} catch (error) {
console.warn('โ ๏ธ Failed to save photo persistently:', error);
// Continue without persistent storage
}
}
/**
* Request user consent for photo storage
*/
requestPhotoStorageConsent(photoData) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7); display: flex; align-items: center;
justify-content: center; z-index: 10000;
`;
modal.innerHTML = `
๐ธ Save Photos for Later?
Would you like to save your captured photos so you can view and download them later?
Photos are stored locally on your device only. You can change this setting anytime in options.
`;
document.body.appendChild(modal);
document.getElementById('consent-yes').onclick = () => {
localStorage.setItem('photoStorageConsent', 'true');
this.storePhotoInLocalStorage(photoData);
document.body.removeChild(modal);
this.showNotification('๐ธ Photos will be saved for later viewing!', 'success');
};
document.getElementById('consent-no').onclick = () => {
localStorage.setItem('photoStorageConsent', 'false');
document.body.removeChild(modal);
this.showNotification('Photos will only be available during this session', 'info');
};
}
/**
* Store photo in localStorage
*/
storePhotoInLocalStorage(photoData) {
try {
const savedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
const photoToSave = {
id: `photo_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
dataURL: photoData.dataURL,
timestamp: photoData.timestamp,
sessionType: photoData.sessionType,
taskId: photoData.taskData?.id || 'unknown',
dateCreated: new Date(photoData.timestamp).toISOString(),
size: Math.round(photoData.dataURL.length * 0.75) // Approximate size in bytes
};
savedPhotos.push(photoToSave);
localStorage.setItem('capturedPhotos', JSON.stringify(savedPhotos));
console.log(`๐ธ Photo saved persistently (ID: ${photoToSave.id})`);
} catch (error) {
console.warn('โ ๏ธ Failed to save photo to localStorage:', error);
if (error.name === 'QuotaExceededError') {
this.showNotification('โ ๏ธ Storage full - consider downloading and clearing old photos', 'warning');
}
}
}
/**
* Show notification helper
*/
showNotification(message, type = 'info', duration = 3000) {
// Create notification if it doesn't exist
let notification = document.getElementById('photo-notification');
if (!notification) {
notification = document.createElement('div');
notification.id = 'photo-notification';
notification.style.cssText = `
position: fixed; top: 20px; right: 20px; padding: 15px 20px;
border-radius: 8px; color: white; z-index: 5000; font-weight: bold;
transition: all 0.3s ease; opacity: 0; transform: translateX(100%);
`;
document.body.appendChild(notification);
}
// Set message and style based on type
notification.textContent = message;
const colors = {
success: '#28a745',
warning: '#ffc107',
error: '#dc3545',
info: '#17a2b8'
};
notification.style.background = colors[type] || colors.info;
// Show notification
notification.style.opacity = '1';
notification.style.transform = 'translateX(0)';
// Hide after duration
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateX(100%)';
}, duration);
}
/**
* Get all saved photos from localStorage
*/
getSavedPhotos() {
try {
return JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
} catch (error) {
console.warn('โ ๏ธ Failed to retrieve saved photos:', error);
return [];
}
}
/**
* Delete a specific photo by ID
*/
deletePhoto(photoId) {
try {
const savedPhotos = this.getSavedPhotos();
const filteredPhotos = savedPhotos.filter(photo => photo.id !== photoId);
localStorage.setItem('capturedPhotos', JSON.stringify(filteredPhotos));
return true;
} catch (error) {
console.warn('โ ๏ธ Failed to delete photo:', error);
return false;
}
}
/**
* Clear all saved photos
*/
clearAllPhotos() {
try {
localStorage.removeItem('capturedPhotos');
return true;
} catch (error) {
console.warn('โ ๏ธ Failed to clear photos:', error);
return false;
}
}
/**
* Download a photo as a file
*/
downloadPhoto(photo, filename = null) {
try {
const link = document.createElement('a');
link.download = filename || `photo_${new Date(photo.timestamp).toISOString().slice(0, 19).replace(/[:.]/g, '-')}.jpg`;
link.href = photo.dataURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
return true;
} catch (error) {
console.warn('โ ๏ธ Failed to download photo:', error);
return false;
}
}
/**
* Download selected photos - single photo normally, multiple photos as zip
*/
async downloadSelectedPhotos(selectedPhotos) {
if (!selectedPhotos || selectedPhotos.length === 0) {
this.showNotification('No photos selected for download', 'warning');
return;
}
if (selectedPhotos.length === 1) {
// Single photo - download normally
const photo = selectedPhotos[0];
const filename = `photo_${new Date(photo.timestamp).toISOString().slice(0, 19).replace(/[:.]/g, '-')}.jpg`;
const success = this.downloadPhoto(photo, filename);
if (success) {
this.showNotification(`โ Photo downloaded as ${filename}`, 'success');
console.log(`๐ธ Single photo download completed: ${filename}`);
}
} else {
// Multiple photos - create zip
await this.downloadPhotosAsZip(selectedPhotos);
}
}
/**
* Convert data URL to blob without using fetch (CSP-safe)
*/
dataURLToBlob(dataURL) {
try {
const [header, data] = dataURL.split(',');
const mime = header.match(/:(.*?);/)[1];
const binary = atob(data);
const array = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
array[i] = binary.charCodeAt(i);
}
return new Blob([array], { type: mime });
} catch (error) {
console.error('Failed to convert data URL to blob:', error);
return null;
}
}
/**
* Download photos as a zip file
*/
async downloadPhotosAsZip(photos, zipFilename = null) {
// Wait for JSZip to be available
let JSZipLibrary = null;
let attempts = 0;
const maxAttempts = 50; // 5 seconds max wait time
while (!JSZipLibrary && attempts < maxAttempts) {
if (typeof JSZip !== 'undefined') {
JSZipLibrary = JSZip;
break;
} else if (typeof window.JSZip !== 'undefined') {
JSZipLibrary = window.JSZip;
break;
}
// Wait 100ms before trying again
await new Promise(resolve => setTimeout(resolve, 100));
attempts++;
}
// Check if JSZip is available after waiting
if (!JSZipLibrary) {
console.error('JSZip library not available after waiting. Please ensure JSZip is loaded.');
this.showNotification('โ Zip functionality not available. JSZip library failed to load.', 'error');
return;
}
try {
this.showNotification(`๐ฆ Creating zip with ${photos.length} photos...`, 'info');
const zip = new JSZipLibrary();
// Add each photo to the zip
for (let i = 0; i < photos.length; i++) {
const photo = photos[i];
const timestamp = new Date(photo.timestamp).toISOString().slice(0, 19).replace(/[:.]/g, '-');
const filename = `photo_${i + 1}_${timestamp}.jpg`;
// Convert data URL to blob using helper function
const blob = this.dataURLToBlob(photo.dataURL);
if (!blob) {
console.warn(`Failed to convert photo ${i + 1} to blob, skipping`);
continue;
}
zip.file(filename, blob);
}
// Generate the zip file
const content = await zip.generateAsync({type: 'blob'});
// Create unique filename with date/time if not provided
let finalFilename = zipFilename;
if (!finalFilename) {
const now = new Date();
const dateStr = now.toISOString().slice(0, 10); // YYYY-MM-DD
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '-'); // HH-MM-SS
finalFilename = `photos_${dateStr}_${timeStr}.zip`;
}
// Download the zip
const link = document.createElement('a');
link.href = URL.createObjectURL(content);
link.download = finalFilename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Clean up the object URL
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
// Show success confirmation with filename
this.showNotification(`โ Downloaded ${photos.length} photos as ${finalFilename}`, 'success');
console.log(`๐ฆ Zip download completed: ${finalFilename} (${photos.length} photos)`);
} catch (error) {
console.error('Failed to create zip:', error);
this.showNotification('โ Failed to create zip file', 'error');
}
}
/**
* Download all photos as a zip file
*/
async downloadAllPhotos() {
const photos = this.getSavedPhotos();
if (photos.length === 0) {
this.showNotification('No photos to download', 'warning');
return;
}
if (photos.length === 1) {
// Single photo - download normally
const photo = photos[0];
const filename = `photo_${new Date(photo.timestamp).toISOString().slice(0, 19).replace(/[:.]/g, '-')}.jpg`;
const success = this.downloadPhoto(photo, filename);
if (success) {
this.showNotification(`โ Photo downloaded as ${filename}`, 'success');
console.log(`๐ธ Single photo download completed: ${filename}`);
}
} else {
// Multiple photos - create zip (filename will be auto-generated with timestamp)
await this.downloadPhotosAsZip(photos);
}
}
/**
* Get photo storage statistics
*/
getPhotoStats() {
const photos = this.getSavedPhotos();
const totalSize = photos.reduce((sum, photo) => sum + (photo.size || 0), 0);
return {
count: photos.length,
totalSize: totalSize,
oldestPhoto: photos.length > 0 ? Math.min(...photos.map(p => p.timestamp)) : null,
newestPhoto: photos.length > 0 ? Math.max(...photos.map(p => p.timestamp)) : null,
sessionTypes: [...new Set(photos.map(p => p.sessionType))],
storageConsent: localStorage.getItem('photoStorageConsent')
};
}
/**
* Notify the task system that a photo was taken
*/
notifyTaskComplete(photoData) {
if (this.game && this.game.interactiveTaskManager) {
// Trigger task completion or progression
const event = new CustomEvent('photoTaken', {
detail: {
photoData: photoData,
sessionType: this.currentPhotoSession.type
}
});
document.dispatchEvent(event);
}
}
/**
* End the current photo session
*/
endPhotoSession() {
console.log('๐ Ending photo session');
// Remove camera overlay
const overlay = document.getElementById('camera-overlay');
if (overlay) {
overlay.remove();
}
// Stop camera stream
this.stopCamera();
// Clear current session
if (this.currentPhotoSession) {
console.log(`๐ Session ended: ${this.currentPhotoSession.photos.length} photos taken`);
// Check if this was a scenario-based photo session
const isScenarioSession = this.currentPhotoSession.taskData &&
this.currentPhotoSession.taskData.task &&
this.currentPhotoSession.taskData.task.scenarioState;
this.currentPhotoSession = null;
// Only call resumeFromCamera for non-scenario sessions
// Scenario sessions are handled by handlePhotoSessionCompletion
if (!isScenarioSession && this.game && this.game.interactiveTaskManager) {
console.log('๐ฑ Non-scenario session - resuming to task interface');
this.game.interactiveTaskManager.resumeFromCamera();
} else if (isScenarioSession) {
console.log('๐ญ Scenario session - completion handled by scenario system');
}
}
}
/**
* Start mirror mode - shows camera feed for self-viewing without taking photos
*/
async startMirrorMode(taskData) {
console.log('๐ช Starting webcam mirror mode');
// Request camera if not already active
if (!this.isActive) {
const accessGranted = await this.requestCameraAccess();
if (!accessGranted) {
console.warn('๐ท Camera access required for mirror mode');
return false;
}
}
// Track webcam mirror start for XP (5 XP per minute)
if (this.game && this.game.trackWebcamMirror) {
this.game.trackWebcamMirror(true);
}
// Show mirror interface
this.showMirrorInterface(taskData);
return true;
}
/**
* Display mirror interface - webcam feed without photo capture
*/
showMirrorInterface(taskData) {
// Create mirror overlay
const overlay = document.createElement('div');
overlay.id = 'mirror-overlay';
overlay.innerHTML = `
๐ช Look at Yourself
${taskData?.instructions || 'Use the webcam as your mirror'}
Time remaining: 60s
${taskData?.taskText ? `
${taskData.taskText}
` : ''}
`;
// Style the mirror overlay
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
`;
// Style the mirror container
const containerStyle = `
background: rgba(50, 50, 55, 0.95);
border: 2px solid #8a2be2;
border-radius: 15px;
padding: 20px;
max-width: 800px;
max-height: 90vh;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.7);
text-align: center;
overflow-y: auto;
color: #e0e0e0;
`;
const videoStyle = `
width: 100%;
max-width: 640px;
height: auto;
border-radius: 10px;
margin: 15px 0;
transform: scaleX(-1); /* Mirror effect */
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
border: 2px solid #8a2be2;
`;
const buttonStyle = `
margin: 10px;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
background: linear-gradient(135deg, #8a2be2, #5e35b1);
color: white;
font-weight: 600;
min-width: 140px;
`;
// Apply styles
overlay.querySelector('.mirror-container').style.cssText = containerStyle;
overlay.querySelector('#mirror-video').style.cssText = videoStyle;
overlay.querySelectorAll('button').forEach(btn => {
btn.style.cssText = buttonStyle;
});
// Style specific buttons
const completeBtn = overlay.querySelector('#mirror-complete-btn');
const closeBtn = overlay.querySelector('#mirror-close-btn');
console.log('๐ Button elements found:', {
completeBtn: !!completeBtn,
closeBtn: !!closeBtn,
completeBtnText: completeBtn?.textContent,
closeBtnText: closeBtn?.textContent
});
completeBtn.style.cssText += 'background: linear-gradient(135deg, #8a2be2, #5e35b1); color: white;';
// Update close button based on preventClose state
if (this.preventClose) {
closeBtn.style.cssText += 'background: linear-gradient(135deg, #666666, #555555); color: #999999; cursor: not-allowed; opacity: 0.6;';
closeBtn.textContent = '๐ Timer Active';
} else {
closeBtn.style.cssText += 'background: linear-gradient(135deg, #8a2be2, #5e35b1); color: white;';
}
// Style task text if present
const taskTextEl = overlay.querySelector('.mirror-task-text');
if (taskTextEl) {
taskTextEl.style.cssText = `
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin-top: 15px;
color: #e0e0e0;
font-style: italic;
border-left: 4px solid #8a2be2;
`;
}
// Style the timer and progress bar elements
const timerEl = overlay.querySelector('#mirror-timer');
const progressEl = overlay.querySelector('#mirror-progress');
const progressBar = overlay.querySelector('#mirror-progress-bar');
if (timerEl) {
timerEl.style.cssText = `
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin: 15px 0;
color: #e0e0e0;
text-align: center;
`;
}
if (progressEl) {
progressEl.style.cssText = `
width: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
margin: 10px 0;
height: 8px;
overflow: hidden;
`;
}
if (progressBar) {
progressBar.style.cssText = `
width: 0%;
height: 100%;
background: linear-gradient(90deg, #8a2be2, #9c27b0);
border-radius: 4px;
transition: width 1s ease;
`;
}
document.body.appendChild(overlay);
// Style header elements for consistency
const headerH3 = overlay.querySelector('.mirror-header h3');
const headerP = overlay.querySelector('.mirror-header p');
if (headerH3) {
headerH3.style.cssText = `
color: #e0e0e0;
font-size: 1.5em;
margin-bottom: 15px;
text-shadow: none;
`;
}
if (headerP) {
headerP.style.cssText = `
color: #c0c0c0;
font-size: 1em;
margin-bottom: 20px;
line-height: 1.6;
`;
}
// Connect video stream to mirror video element
const mirrorVideo = overlay.querySelector('#mirror-video');
if (this.stream && mirrorVideo) {
mirrorVideo.srcObject = this.stream;
}
// Add event listeners
completeBtn.addEventListener('click', () => {
// Complete task - close mirror and continue scenario
console.log('โ COMPLETE BUTTON CLICKED - Mirror task completed by user');
this.completeMirrorTask(taskData);
});
closeBtn.addEventListener('click', () => {
console.log('โ CLOSE BUTTON CLICKED - Attempting to close mirror');
if (this.preventClose) {
// Show warning that camera cannot be closed during timer
if (this.game && this.game.showNotification) {
this.game.showNotification('Cannot close mirror during active session. Please wait for timer to complete.', 'warning');
} else {
console.log('โ ๏ธ Cannot close mirror during active session');
}
return;
}
// Show harsh confirmation dialog for abandonment
console.log('๐ Showing abandonment confirmation dialog');
this.showAbandonmentConfirmation();
});
console.log('๐ช Mirror interface displayed');
}
/**
* Complete the mirror task
*/
completeMirrorTask(taskData) {
console.log('โ Mirror task completed');
this.closeMirrorMode();
// Notify the game that the mirror task is complete
if (this.game && this.game.interactiveTaskManager) {
this.game.interactiveTaskManager.completeMirrorTask(taskData);
}
// Dispatch completion event
const event = new CustomEvent('mirrorTaskComplete', {
detail: { taskData }
});
document.dispatchEvent(event);
}
/**
* Show harsh confirmation dialog for abandoning mirror task
*/
showAbandonmentConfirmation() {
console.log('๐ CREATING ABANDONMENT CONFIRMATION DIALOG');
// Create confirmation overlay
const confirmOverlay = document.createElement('div');
confirmOverlay.id = 'abandon-confirm-overlay';
confirmOverlay.innerHTML = `
๐ Giving Up Already?
Are you sure you want to give up like a pathetic quitter?
Your weak attempt will be recorded as a FAILURE.
Everyone will know you couldn't even complete a simple mirror task.