feat: Enhanced mirror task timer system and condescending game over dialog
- Fixed mirror task timer display to show actual duration (not hardcoded 60s) - Added working progress bar with real-time countdown - Disabled complete button until timer expires - prevents early completion - Complete button shows countdown and enables only when timer finished - Changed close mirror button to trigger immediate game over instead of confirmation - Added condescending but professional game over dialog for training abandonment - Fixed WebcamManager syntax errors that prevented proper initialization - Enhanced training academy button styling for professional appearance - Improved timer synchronization between training academy and webcam interfaces
This commit is contained in:
parent
2112142563
commit
dbadd4a828
234
quick-play.html
234
quick-play.html
|
|
@ -41,7 +41,13 @@
|
|||
<h1>⚡ Quick Play</h1>
|
||||
<div class="quick-play-controls">
|
||||
<button id="back-to-home" class="btn btn-secondary">🏠 Home</button>
|
||||
<button id="overlay-video-btn" class="btn btn-secondary">📺 Overlay Video</button>
|
||||
<div class="overlay-video-controls" style="display: flex; align-items: center; gap: 8px;">
|
||||
<button id="overlay-video-btn" class="btn btn-secondary">📺 Overlay Video</button>
|
||||
<select id="playlist-selector" class="btn btn-secondary" style="max-width: 200px;">
|
||||
<option value="random">🎲 Random Videos</option>
|
||||
<option value="" disabled>Loading playlists...</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="force-exit" class="btn btn-danger" title="Force close application">❌ Force Exit</button>
|
||||
<button id="pause-game-btn" class="btn btn-secondary" style="display: none;">⏸️ Pause</button>
|
||||
<button id="settings-btn" class="btn btn-secondary">⚙️ Settings</button>
|
||||
|
|
@ -1073,6 +1079,9 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Load playlist selector for overlay video player
|
||||
loadPlaylistSelector();
|
||||
|
||||
// Setup screen interactions
|
||||
setupSetupScreenListeners();
|
||||
|
||||
|
|
@ -2686,6 +2695,137 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Load available playlists into the selector
|
||||
function loadPlaylistSelector() {
|
||||
const selector = document.getElementById('playlist-selector');
|
||||
if (!selector) return;
|
||||
|
||||
try {
|
||||
// Get saved playlists from localStorage (same as VideoLibrary)
|
||||
const saved = localStorage.getItem('pornCinema_savedPlaylists');
|
||||
const playlists = saved ? JSON.parse(saved) : [];
|
||||
|
||||
console.log(`📺 Found ${playlists.length} saved playlists`);
|
||||
|
||||
// Clear existing options (except Random)
|
||||
const randomOption = selector.querySelector('option[value="random"]');
|
||||
selector.innerHTML = '';
|
||||
selector.appendChild(randomOption);
|
||||
|
||||
// Add playlist options
|
||||
if (playlists.length > 0) {
|
||||
playlists.forEach(playlist => {
|
||||
const option = document.createElement('option');
|
||||
option.value = playlist.name;
|
||||
option.textContent = `📋 ${playlist.name} (${playlist.videos.length} videos)`;
|
||||
selector.appendChild(option);
|
||||
});
|
||||
} else {
|
||||
const noPlaylistsOption = document.createElement('option');
|
||||
noPlaylistsOption.value = '';
|
||||
noPlaylistsOption.disabled = true;
|
||||
noPlaylistsOption.textContent = '📝 No playlists created yet';
|
||||
selector.appendChild(noPlaylistsOption);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Failed to load playlists:', error);
|
||||
const errorOption = document.createElement('option');
|
||||
errorOption.value = '';
|
||||
errorOption.disabled = true;
|
||||
errorOption.textContent = '⚠️ Error loading playlists';
|
||||
selector.appendChild(errorOption);
|
||||
}
|
||||
}
|
||||
|
||||
// Load videos from a specific playlist
|
||||
async function loadPlaylistVideos(playlistName) {
|
||||
try {
|
||||
const saved = localStorage.getItem('pornCinema_savedPlaylists');
|
||||
const playlists = saved ? JSON.parse(saved) : [];
|
||||
|
||||
const playlist = playlists.find(p => p.name === playlistName);
|
||||
if (!playlist) {
|
||||
console.warn(`Playlist "${playlistName}" not found`);
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log(`📋 Found playlist "${playlistName}" with ${playlist.videos.length} videos`);
|
||||
return playlist.videos || [];
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load playlist videos:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get all available videos (same logic as background video loading)
|
||||
async function getAllAvailableVideos() {
|
||||
let allVideos = [];
|
||||
|
||||
// First check if we have a video library instance
|
||||
if (window.videoLibrary && typeof window.videoLibrary.getAllVideos === 'function') {
|
||||
allVideos = window.videoLibrary.getAllVideos();
|
||||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary instance`);
|
||||
}
|
||||
|
||||
// Try to access VideoLibrary videos directly if it exists
|
||||
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
|
||||
allVideos = window.VideoLibrary.videos;
|
||||
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
|
||||
}
|
||||
|
||||
// Try desktop file manager (this is likely where your videos are)
|
||||
if (allVideos.length === 0 && window.desktopFileManager) {
|
||||
try {
|
||||
if (typeof window.desktopFileManager.getAllVideos === 'function') {
|
||||
allVideos = window.desktopFileManager.getAllVideos();
|
||||
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
|
||||
} else {
|
||||
console.log('🎬 DesktopFileManager.getAllVideos not available');
|
||||
}
|
||||
} catch (dmError) {
|
||||
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to unified storage (use same logic as background video)
|
||||
if (allVideos.length === 0) {
|
||||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||||
allVideos = unifiedData.allVideos || [];
|
||||
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
|
||||
}
|
||||
|
||||
// Fallback to legacy storage
|
||||
if (allVideos.length === 0) {
|
||||
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
|
||||
allVideos = Object.values(storedVideos).flat();
|
||||
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
|
||||
}
|
||||
|
||||
// Try accessing VideoLibrary legacy storage directly
|
||||
if (allVideos.length === 0) {
|
||||
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
|
||||
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
|
||||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
|
||||
}
|
||||
|
||||
// If still no videos, try to initialize video library (same as background loading)
|
||||
if (allVideos.length === 0) {
|
||||
console.log('🎬 Overlay: No videos found, attempting to initialize video library...');
|
||||
const initSuccess = await initializeVideoLibrary();
|
||||
|
||||
if (initSuccess) {
|
||||
// Try again after initialization
|
||||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||||
allVideos = unifiedData.allVideos || [];
|
||||
console.log(`🎬 Overlay: After initialization: ${allVideos.length} videos available`);
|
||||
}
|
||||
}
|
||||
|
||||
return allVideos;
|
||||
}
|
||||
|
||||
// Open overlay video player
|
||||
async function openOverlayVideoPlayer() {
|
||||
console.log('📺 Opening overlay video player...');
|
||||
|
|
@ -2698,67 +2838,25 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Get available videos using the same logic as loadRandomBackgroundVideo
|
||||
// Check if user selected a playlist
|
||||
const playlistSelector = document.getElementById('playlist-selector');
|
||||
const selectedPlaylist = playlistSelector ? playlistSelector.value : 'random';
|
||||
|
||||
let allVideos = [];
|
||||
|
||||
// First check if we have a video library instance
|
||||
if (window.videoLibrary && typeof window.videoLibrary.getAllVideos === 'function') {
|
||||
allVideos = window.videoLibrary.getAllVideos();
|
||||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary instance`);
|
||||
}
|
||||
|
||||
// Try to access VideoLibrary videos directly if it exists
|
||||
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
|
||||
allVideos = window.VideoLibrary.videos;
|
||||
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
|
||||
}
|
||||
|
||||
// Try desktop file manager (this is likely where your videos are)
|
||||
if (allVideos.length === 0 && window.desktopFileManager) {
|
||||
try {
|
||||
if (typeof window.desktopFileManager.getAllVideos === 'function') {
|
||||
allVideos = window.desktopFileManager.getAllVideos();
|
||||
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
|
||||
} else {
|
||||
console.log('🎬 DesktopFileManager.getAllVideos not available');
|
||||
}
|
||||
} catch (dmError) {
|
||||
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to unified storage (use same logic as background video)
|
||||
if (allVideos.length === 0) {
|
||||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||||
allVideos = unifiedData.allVideos || [];
|
||||
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
|
||||
}
|
||||
|
||||
// Fallback to legacy storage
|
||||
if (allVideos.length === 0) {
|
||||
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
|
||||
allVideos = Object.values(storedVideos).flat();
|
||||
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
|
||||
}
|
||||
|
||||
// Try accessing VideoLibrary legacy storage directly
|
||||
if (allVideos.length === 0) {
|
||||
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
|
||||
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
|
||||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
|
||||
}
|
||||
|
||||
// If still no videos, try to initialize video library (same as background loading)
|
||||
if (allVideos.length === 0) {
|
||||
console.log('🎬 Overlay: No videos found, attempting to initialize video library...');
|
||||
const initSuccess = await initializeVideoLibrary();
|
||||
if (selectedPlaylist !== 'random') {
|
||||
// Load specific playlist
|
||||
console.log(`📋 Loading playlist: ${selectedPlaylist}`);
|
||||
allVideos = await loadPlaylistVideos(selectedPlaylist);
|
||||
|
||||
if (initSuccess) {
|
||||
// Try again after initialization
|
||||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||||
allVideos = unifiedData.allVideos || [];
|
||||
console.log(`🎬 Overlay: After initialization: ${allVideos.length} videos available`);
|
||||
if (allVideos.length === 0) {
|
||||
alert(`Playlist "${selectedPlaylist}" is empty or could not be loaded.`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Use random mode - get all available videos
|
||||
console.log('🎲 Using random video mode');
|
||||
allVideos = await getAllAvailableVideos();
|
||||
}
|
||||
|
||||
if (allVideos.length === 0) {
|
||||
|
|
@ -2821,14 +2919,26 @@
|
|||
window.quickPlayOverlayPlayer = overlayPlayer;
|
||||
}
|
||||
|
||||
// Select a random video and show the overlay player
|
||||
// Select a random video
|
||||
const randomIndex = Math.floor(Math.random() * allVideos.length);
|
||||
const selectedVideo = allVideos[randomIndex];
|
||||
|
||||
console.log(`🎬 Selected video for overlay: ${selectedVideo.name || selectedVideo.filename}`);
|
||||
console.log(`🎬 Selected video for overlay: ${selectedVideo.name || selectedVideo.filename || selectedVideo.title}`);
|
||||
console.log(`🎬 Video path: ${selectedVideo.path || selectedVideo.filePath}`);
|
||||
|
||||
// Show the overlay player first
|
||||
await overlayPlayer.show();
|
||||
|
||||
// Then load the specific video
|
||||
const videoPath = selectedVideo.path || selectedVideo.filePath;
|
||||
if (videoPath) {
|
||||
overlayPlayer.loadVideo(videoPath, true);
|
||||
overlayPlayer.currentVideo = selectedVideo;
|
||||
overlayPlayer.updateVideoInfo(selectedVideo);
|
||||
} else {
|
||||
console.error('❌ No valid video path found for selected video:', selectedVideo);
|
||||
}
|
||||
|
||||
// Show the overlay player with the selected video
|
||||
overlayPlayer.show(selectedVideo);
|
||||
console.log('✅ Overlay video player opened');
|
||||
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1153,11 +1153,11 @@ class WebcamManager {
|
|||
<div class="mirror-progress" id="mirror-progress">
|
||||
<div class="progress-bar" id="mirror-progress-bar"></div>
|
||||
</div>
|
||||
<div class="timer-text">Time remaining: <span id="mirror-time">60</span>s</div>
|
||||
<div class="timer-text">Time remaining: <span id="mirror-time">${taskData?.duration || 60}</span>s</div>
|
||||
</div>
|
||||
<div class="mirror-controls">
|
||||
<button id="mirror-complete-btn" class="btn btn-primary">
|
||||
✅ Complete Task
|
||||
<button id="mirror-complete-btn" class="btn btn-primary" disabled>
|
||||
⏱️ Complete Task (${taskData?.duration || 60}s)
|
||||
</button>
|
||||
<button id="mirror-close-btn" class="btn btn-secondary">
|
||||
❌ Close Mirror
|
||||
|
|
@ -1227,8 +1227,17 @@ class WebcamManager {
|
|||
btn.style.cssText = buttonStyle;
|
||||
});
|
||||
|
||||
// Style specific buttons
|
||||
// Style disabled complete button
|
||||
const completeBtn = overlay.querySelector('#mirror-complete-btn');
|
||||
if (completeBtn && completeBtn.disabled) {
|
||||
completeBtn.style.cssText += `
|
||||
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
`;
|
||||
}
|
||||
|
||||
// Style specific buttons
|
||||
const closeBtn = overlay.querySelector('#mirror-close-btn');
|
||||
|
||||
console.log('🔍 Button elements found:', {
|
||||
|
|
@ -1337,25 +1346,95 @@ class WebcamManager {
|
|||
});
|
||||
|
||||
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;
|
||||
console.log('❌ CLOSE BUTTON CLICKED - Training task abandoned');
|
||||
|
||||
// Clear timer if running
|
||||
if (this.mirrorTimer) {
|
||||
clearInterval(this.mirrorTimer);
|
||||
this.mirrorTimer = null;
|
||||
}
|
||||
|
||||
// Show harsh confirmation dialog for abandonment
|
||||
console.log('🛑 Showing abandonment confirmation dialog');
|
||||
this.showAbandonmentConfirmation();
|
||||
// Close mirror interface
|
||||
this.closeMirrorMode();
|
||||
|
||||
// Trigger game over for abandoning training task
|
||||
console.log('💀 Training task abandoned - triggering game over');
|
||||
|
||||
if (this.game && this.game.triggerGameOver) {
|
||||
this.game.triggerGameOver('Training task abandoned. You must complete your assigned tasks to progress.');
|
||||
} else if (window.game && window.game.triggerGameOver) {
|
||||
window.game.triggerGameOver('Training task abandoned. You must complete your assigned tasks to progress.');
|
||||
} else {
|
||||
// Create condescending styled game over dialog
|
||||
this.showCondescendingGameOver();
|
||||
}
|
||||
});
|
||||
|
||||
// Start timer if duration is specified
|
||||
if (taskData?.duration) {
|
||||
this.startMirrorTimer(taskData.duration, overlay);
|
||||
}
|
||||
|
||||
console.log('🪞 Mirror interface displayed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Start mirror task timer with visual countdown
|
||||
*/
|
||||
startMirrorTimer(duration, overlay) {
|
||||
console.log(`⏱️ Starting mirror timer for ${duration} seconds`);
|
||||
|
||||
const timerDisplay = overlay.querySelector('#mirror-time');
|
||||
const progressBar = overlay.querySelector('#mirror-progress-bar');
|
||||
const timerElement = overlay.querySelector('#mirror-timer');
|
||||
|
||||
// Show timer
|
||||
if (timerElement) {
|
||||
timerElement.style.display = 'block';
|
||||
}
|
||||
|
||||
let timeLeft = duration;
|
||||
const completeBtn = overlay.querySelector('#mirror-complete-btn');
|
||||
|
||||
const timer = setInterval(() => {
|
||||
timeLeft--;
|
||||
|
||||
// Update timer display
|
||||
if (timerDisplay) {
|
||||
timerDisplay.textContent = timeLeft;
|
||||
}
|
||||
|
||||
// Update button text with countdown
|
||||
if (completeBtn && timeLeft > 0) {
|
||||
completeBtn.textContent = `⏱️ Complete Task (${timeLeft}s)`;
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
if (progressBar) {
|
||||
const progress = ((duration - timeLeft) / duration) * 100;
|
||||
progressBar.style.width = progress + '%';
|
||||
}
|
||||
|
||||
// Timer complete
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(timer);
|
||||
console.log('⏰ Mirror timer completed');
|
||||
|
||||
// Enable complete button
|
||||
if (completeBtn) {
|
||||
completeBtn.disabled = false;
|
||||
completeBtn.textContent = '✅ Time Complete - Click to Continue';
|
||||
completeBtn.style.background = '#27ae60';
|
||||
completeBtn.style.animation = 'pulse 1s infinite';
|
||||
completeBtn.style.cursor = 'pointer';
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Store timer reference for cleanup
|
||||
this.mirrorTimer = timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the mirror task
|
||||
*/
|
||||
|
|
@ -1515,6 +1594,13 @@ class WebcamManager {
|
|||
closeMirrorMode() {
|
||||
console.log('🔚 Closing mirror mode');
|
||||
|
||||
// Clean up timer
|
||||
if (this.mirrorTimer) {
|
||||
clearInterval(this.mirrorTimer);
|
||||
this.mirrorTimer = null;
|
||||
console.log('⏱️ Mirror timer cleared');
|
||||
}
|
||||
|
||||
// Track webcam mirror end for XP
|
||||
if (this.game && this.game.trackWebcamMirror) {
|
||||
this.game.trackWebcamMirror(false);
|
||||
|
|
@ -1530,6 +1616,174 @@ class WebcamManager {
|
|||
this.stopCamera();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show condescending game over dialog for training abandonment
|
||||
*/
|
||||
showCondescendingGameOver() {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'condescending-game-over';
|
||||
overlay.innerHTML = `
|
||||
<div class="game-over-container">
|
||||
<div class="game-over-header">
|
||||
<h1>Training Abandoned</h1>
|
||||
<p class="subtitle">Task Failed</p>
|
||||
</div>
|
||||
|
||||
<div class="condescending-content">
|
||||
<p class="main-message">
|
||||
Really? You couldn't even handle a simple training exercise.
|
||||
</p>
|
||||
|
||||
<div class="failure-details">
|
||||
<p>• <strong>Status:</strong> Task abandoned before completion</p>
|
||||
<p>• <strong>Reason:</strong> Lack of commitment and discipline</p>
|
||||
<p>• <strong>Result:</strong> Complete failure to follow instructions</p>
|
||||
</div>
|
||||
|
||||
<div class="harsh-verdict">
|
||||
<p>This training was designed to test your dedication. You failed that test miserably.</p>
|
||||
|
||||
<p>If you can't handle basic mirror exercises, maybe you're not ready for real training. Consider whether you actually want to improve yourself or if you're just wasting everyone's time.</p>
|
||||
</div>
|
||||
|
||||
<div class="consequence-section">
|
||||
<p><strong>Consequence:</strong> Session will restart. Try to show some backbone next time.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="game-over-controls">
|
||||
<button id="restart-session-btn" class="failure-btn">
|
||||
Restart Session
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Clean, condescending styling without excessive theatrics
|
||||
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: 99999;
|
||||
font-family: 'Arial', sans-serif;
|
||||
`;
|
||||
|
||||
const container = overlay.querySelector('.game-over-container');
|
||||
container.style.cssText = `
|
||||
background: linear-gradient(135deg, #2c3e50, #34495e);
|
||||
border: 2px solid #e74c3c;
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
const header = overlay.querySelector('.game-over-header');
|
||||
header.style.cssText = `
|
||||
margin-bottom: 25px;
|
||||
border-bottom: 1px solid #e74c3c;
|
||||
padding-bottom: 15px;
|
||||
`;
|
||||
|
||||
const title = overlay.querySelector('h1');
|
||||
title.style.cssText = `
|
||||
color: #e74c3c;
|
||||
font-size: 1.8em;
|
||||
margin: 0 0 10px 0;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const subtitle = overlay.querySelector('.subtitle');
|
||||
subtitle.style.cssText = `
|
||||
color: #bdc3c7;
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const content = overlay.querySelector('.condescending-content');
|
||||
content.style.cssText = `
|
||||
color: #ecf0f1;
|
||||
line-height: 1.6;
|
||||
font-size: 1em;
|
||||
`;
|
||||
|
||||
const mainMessage = overlay.querySelector('.main-message');
|
||||
mainMessage.style.cssText = `
|
||||
font-size: 1.1em;
|
||||
color: #e67e22;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const failureDetails = overlay.querySelector('.failure-details');
|
||||
failureDetails.style.cssText = `
|
||||
background: rgba(231, 76, 60, 0.1);
|
||||
border-left: 3px solid #e74c3c;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
text-align: left;
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
const harshVerdict = overlay.querySelector('.harsh-verdict');
|
||||
harshVerdict.style.cssText = `
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin: 20px 0;
|
||||
border: 1px solid #34495e;
|
||||
`;
|
||||
|
||||
const consequenceSection = overlay.querySelector('.consequence-section');
|
||||
consequenceSection.style.cssText = `
|
||||
background: rgba(231, 76, 60, 0.15);
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin: 20px 0;
|
||||
border: 1px solid #e74c3c;
|
||||
`;
|
||||
|
||||
const restartBtn = overlay.querySelector('#restart-session-btn');
|
||||
restartBtn.style.cssText = `
|
||||
background: linear-gradient(135deg, #e74c3c, #c0392b);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 12px 24px;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 20px;
|
||||
`;
|
||||
|
||||
// Simple hover effect
|
||||
restartBtn.addEventListener('mouseenter', () => {
|
||||
restartBtn.style.background = 'linear-gradient(135deg, #c0392b, #a93226)';
|
||||
});
|
||||
|
||||
restartBtn.addEventListener('mouseleave', () => {
|
||||
restartBtn.style.background = 'linear-gradient(135deg, #e74c3c, #c0392b)';
|
||||
});
|
||||
|
||||
// Add event listener for restart
|
||||
restartBtn.addEventListener('click', () => {
|
||||
console.log('🔄 Restarting session after training abandonment...');
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
console.log('💀 Condescending game over dialog displayed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop camera stream
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -59,55 +59,61 @@
|
|||
}
|
||||
|
||||
.academy-mode-selection {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
margin: 1rem auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.training-mode-card {
|
||||
background: linear-gradient(135deg, #2c3e50, #34495e);
|
||||
border: 2px solid #3498db;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
padding: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
margin: 0.5rem;
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.training-mode-card:hover {
|
||||
border-color: #e74c3c;
|
||||
box-shadow: 0 8px 25px rgba(231, 76, 60, 0.3);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.training-mode-card.selected {
|
||||
border-color: #e74c3c;
|
||||
background: linear-gradient(135deg, #c0392b, #e74c3c);
|
||||
box-shadow: 0 8px 25px rgba(231, 76, 60, 0.4);
|
||||
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
|
||||
}
|
||||
|
||||
.mode-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.mode-name {
|
||||
font-size: 1.3rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
color: #ecf0f1;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.mode-description {
|
||||
color: #bdc3c7;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.academy-start-controls {
|
||||
text-align: center;
|
||||
margin: 2rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.start-training-btn {
|
||||
|
|
@ -158,6 +164,67 @@
|
|||
border-color: #3498db;
|
||||
}
|
||||
|
||||
/* Mirror Task Action Buttons */
|
||||
.action-btn {
|
||||
background: linear-gradient(135deg, #27ae60, #2ecc71);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: linear-gradient(135deg, #219a52, #27ae60);
|
||||
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.skip-btn {
|
||||
background: linear-gradient(135deg, #95a5a6, #bdc3c7);
|
||||
color: #2c3e50;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(149, 165, 166, 0.3);
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.skip-btn:hover {
|
||||
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
|
||||
box-shadow: 0 6px 20px rgba(149, 165, 166, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.next-btn {
|
||||
background: linear-gradient(135deg, #3498db, #5dade2);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.next-btn:hover {
|
||||
background: linear-gradient(135deg, #2980b9, #3498db);
|
||||
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Video player container for background videos */
|
||||
.training-video-container {
|
||||
position: fixed;
|
||||
|
|
@ -576,7 +643,6 @@
|
|||
|
||||
<!-- Training Mode Selection -->
|
||||
<div class="training-controls">
|
||||
<h2>🎯 Choose Your Training Path</h2>
|
||||
<div class="academy-mode-selection" id="trainingModeSelection">
|
||||
<!-- Training modes will be populated here -->
|
||||
</div>
|
||||
|
|
@ -837,26 +903,41 @@
|
|||
try {
|
||||
console.log('📸 Training Academy: Initializing photo library...');
|
||||
|
||||
// Check if we're in Electron environment with proper API access
|
||||
const isElectron = window.electronAPI && typeof window.electronAPI.getImageFiles === 'function';
|
||||
// Debug: Check what APIs are available
|
||||
console.log('🔍 Available APIs:', {
|
||||
electronAPI: !!window.electronAPI,
|
||||
getImageFiles: window.electronAPI && typeof window.electronAPI.getImageFiles,
|
||||
readDirectory: window.electronAPI && typeof window.electronAPI.readDirectory,
|
||||
desktopFileManager: !!window.desktopFileManager
|
||||
});
|
||||
|
||||
if (!isElectron) {
|
||||
// Check if we're in Electron environment with proper API access
|
||||
const isElectron = window.electronAPI && (
|
||||
typeof window.electronAPI.getImageFiles === 'function' ||
|
||||
typeof window.electronAPI.readDirectory === 'function'
|
||||
);
|
||||
|
||||
if (!isElectron && !window.desktopFileManager) {
|
||||
document.getElementById('photoLibraryStatus').innerHTML =
|
||||
'<span style="color: #f39c12;">⚠️ Photo library unavailable - Electron API not accessible</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
const linkedPhotoDirectories = JSON.parse(localStorage.getItem('linkedPhotoDirectories') || '[]');
|
||||
const linkedPhotoDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
|
||||
const linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
|
||||
console.log('📁 Linked photo directories:', linkedPhotoDirectories);
|
||||
console.log('📷 Linked individual images:', linkedIndividualImages);
|
||||
|
||||
if (linkedPhotoDirectories.length === 0) {
|
||||
if (linkedPhotoDirectories.length === 0 && linkedIndividualImages.length === 0) {
|
||||
document.getElementById('photoLibraryStatus').innerHTML =
|
||||
'<span style="color: #f39c12;">⚠️ No photo directories linked</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
trainingPhotoLibrary = [];
|
||||
|
||||
let totalPhotos = 0;
|
||||
|
||||
// Process linked directories
|
||||
for (const directoryData of linkedPhotoDirectories) {
|
||||
try {
|
||||
// Handle both string paths and directory objects
|
||||
|
|
@ -868,7 +949,23 @@
|
|||
}
|
||||
|
||||
console.log(`📂 Scanning photo directory: ${directoryPath}`);
|
||||
const photos = await window.electronAPI.getImageFiles(directoryPath);
|
||||
|
||||
let photos = [];
|
||||
|
||||
// Try different Electron APIs for reading image files
|
||||
if (window.electronAPI && window.electronAPI.getImageFiles) {
|
||||
photos = await window.electronAPI.getImageFiles(directoryPath);
|
||||
} else if (window.electronAPI && window.electronAPI.readDirectory) {
|
||||
const allFiles = await window.electronAPI.readDirectory(directoryPath);
|
||||
// Filter for image files
|
||||
photos = allFiles.filter(file =>
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name || file.filename)
|
||||
);
|
||||
} else if (window.desktopFileManager) {
|
||||
// Fallback to desktop file manager
|
||||
photos = window.desktopFileManager.getImagesFromDirectory(directoryPath) || [];
|
||||
}
|
||||
|
||||
console.log(`📷 Found ${photos.length} photos in ${directoryPath}`);
|
||||
|
||||
const photosWithPath = photos.map(photo => ({
|
||||
|
|
@ -878,13 +975,28 @@
|
|||
}));
|
||||
|
||||
trainingPhotoLibrary.push(...photosWithPath);
|
||||
totalPhotos += photos.length;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error scanning photo directory ${directoryData}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`📸 Total photos loaded: ${trainingPhotoLibrary.length}`);
|
||||
// Process individual linked images
|
||||
for (const imageData of linkedIndividualImages) {
|
||||
try {
|
||||
const imageWithPath = {
|
||||
...imageData,
|
||||
fullPath: imageData.path || imageData.filePath
|
||||
};
|
||||
trainingPhotoLibrary.push(imageWithPath);
|
||||
totalPhotos++;
|
||||
} catch (error) {
|
||||
console.error(`❌ Error processing individual image ${imageData}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`📸 Total photos loaded: ${totalPhotos} (${linkedPhotoDirectories.length} directories + ${linkedIndividualImages.length} individual images)`);
|
||||
|
||||
// Load previously captured webcam photos from localStorage
|
||||
try {
|
||||
|
|
@ -1806,7 +1918,7 @@
|
|||
const isPhotoAction = (step.actionText && step.actionText.toLowerCase().includes('photograph')) ||
|
||||
(step.story && step.story.toLowerCase().includes('photograph')) ||
|
||||
(step.story && step.story.toLowerCase().includes('photo')) ||
|
||||
currentMode === 'photography-studio';
|
||||
selectedTrainingMode === 'photography-studio';
|
||||
|
||||
stepHtml = `
|
||||
<div class="training-task scenario-action">
|
||||
|
|
@ -1916,6 +2028,9 @@
|
|||
// Store next step for completion callback
|
||||
window.trainingAcademyNextStep = nextStep;
|
||||
|
||||
// Always start the visual timer regardless of webcam status
|
||||
startActionTimer(duration, nextStep, true);
|
||||
|
||||
// Create mirror task data for the webcam system
|
||||
const mirrorTaskData = {
|
||||
mirrorInstructions: currentScenarioTask.interactiveData.steps[currentScenarioStep].mirrorInstructions,
|
||||
|
|
@ -1934,24 +2049,15 @@
|
|||
window.game.webcamManager.startMirrorMode(mirrorTaskData).then((success) => {
|
||||
if (success) {
|
||||
console.log('🪞 Mirror mode started successfully');
|
||||
// Set up timer to automatically proceed after duration (fallback)
|
||||
setTimeout(() => {
|
||||
console.log('🪞 Mirror task timer completed, proceeding to:', nextStep);
|
||||
proceedToNextStep(nextStep);
|
||||
}, duration * 1000);
|
||||
} else {
|
||||
console.warn('⚠️ Mirror mode failed to start, continuing anyway');
|
||||
selectScenarioChoice(nextStep);
|
||||
console.warn('⚠️ Mirror mode failed to start, timer will handle completion');
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('❌ Mirror mode failed:', error);
|
||||
// Continue anyway
|
||||
selectScenarioChoice(nextStep);
|
||||
console.log('⚠️ Timer will handle completion');
|
||||
});
|
||||
} else {
|
||||
console.warn('⚠️ Webcam manager not available, skipping mirror task');
|
||||
// Fallback to regular timer
|
||||
startActionTimer(duration, nextStep, true);
|
||||
console.warn('⚠️ Webcam manager not available, using timer only');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue