-
-
- 🌄 Background
- 🎯 Tasks
- 🏆 Rewards
- ⚠️ Punishments
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1797,61 +1793,29 @@
const category = activeTab.id.replace('-video-tab', '');
loadVideoGalleryContent(category);
}
- } // Video tab buttons
- const videoTabs = [
- { id: 'background-video-tab', gallery: 'background-video-gallery', category: 'background' },
- { id: 'task-video-tab', gallery: 'task-video-gallery', category: 'task' },
- { id: 'reward-video-tab', gallery: 'reward-video-gallery', category: 'reward' },
- { id: 'punishment-video-tab', gallery: 'punishment-video-gallery', category: 'punishment' }
- ];
+ }
- videoTabs.forEach(tab => {
- const tabBtn = document.getElementById(tab.id);
- if (tabBtn) {
- tabBtn.addEventListener('click', () => {
- console.log(`Switching to ${tab.category} videos`);
-
- // Update active tab
- document.querySelectorAll('.video-tabs .tab-btn').forEach(btn => {
- btn.classList.remove('active');
- });
- tabBtn.classList.add('active');
+ // Directory management buttons
+ const addDirectoryBtn = document.getElementById('add-video-directory-btn');
+ if (addDirectoryBtn) {
+ addDirectoryBtn.addEventListener('click', () => {
+ handleAddVideoDirectory();
+ });
+ }
- // Hide all galleries first
- document.querySelectorAll('.video-gallery').forEach(gallery => {
- gallery.classList.remove('active');
- gallery.style.display = 'none';
- });
-
- // Show the selected gallery
- const targetGallery = document.getElementById(tab.gallery);
- if (targetGallery) {
- targetGallery.classList.add('active');
- targetGallery.style.display = 'grid';
- }
+ const refreshAllBtn = document.getElementById('refresh-all-directories-btn');
+ if (refreshAllBtn) {
+ refreshAllBtn.addEventListener('click', () => {
+ handleRefreshAllDirectories();
+ });
+ }
- // Load videos for this category
- loadVideoGalleryContent(tab.category);
- });
- }
- });
-
- // Import video buttons
- const importButtons = [
- { id: 'import-background-video-btn', category: 'background' },
- { id: 'import-task-video-btn', category: 'task' },
- { id: 'import-reward-video-btn', category: 'reward' },
- { id: 'import-punishment-video-btn', category: 'punishment' }
- ];
-
- importButtons.forEach(button => {
- const btn = document.getElementById(button.id);
- if (btn) {
- btn.addEventListener('click', () => {
- handleVideoImport(button.category);
- });
- }
- });
+ const clearAllDirectoriesBtn = document.getElementById('clear-all-directories-btn');
+ if (clearAllDirectoriesBtn) {
+ clearAllDirectoriesBtn.addEventListener('click', () => {
+ handleClearAllDirectories();
+ });
+ }
// Gallery control buttons
const selectAllBtn = document.getElementById('select-all-video-btn');
@@ -1952,50 +1916,20 @@
// Settings handlers
setupVideoSettingsHandlers();
- // Initial load - ensure only background tab is active
- // Hide all galleries first
- document.querySelectorAll('.video-gallery').forEach(gallery => {
- gallery.classList.remove('active');
- gallery.style.display = 'none';
- });
-
- // Show only background gallery
- const backgroundGallery = document.getElementById('background-video-gallery');
- if (backgroundGallery) {
- backgroundGallery.classList.add('active');
- backgroundGallery.style.display = 'grid';
- }
-
- // Scan for new videos if in desktop mode
+ // Initialize the unified video system
if (window.electronAPI && window.desktopFileManager) {
- console.log('🔍 Scanning for new videos...');
+ console.log('🔍 Initializing unified video library...');
- // Clear existing video storage to force fresh scan
- localStorage.removeItem('videoFiles');
-
- Promise.all([
- window.desktopFileManager.scanDirectoryForVideos('background'),
- window.desktopFileManager.scanDirectoryForVideos('tasks'),
- window.desktopFileManager.scanDirectoryForVideos('rewards'),
- window.desktopFileManager.scanDirectoryForVideos('punishments')
- ]).then(([backgroundVideos, taskVideos, rewardVideos, punishmentVideos]) => {
- const allVideos = [...backgroundVideos, ...taskVideos, ...rewardVideos, ...punishmentVideos];
- if (allVideos.length > 0) {
- window.desktopFileManager.updateVideoStorage(allVideos);
- console.log(`📹 Found ${allVideos.length} videos in directories`);
- }
- // Reload all galleries after scanning
- loadVideoGalleryContent('background');
- loadVideoGalleryContent('task');
- loadVideoGalleryContent('reward');
- loadVideoGalleryContent('punishment');
- }).catch(error => {
- console.error('Error scanning video directories:', error);
- // Still load the galleries even if scanning fails
- loadVideoGalleryContent('background');
- });
+ // The file manager will automatically load linked directories
+ // No need to scan old category directories anymore
+ setTimeout(() => {
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
+ }, 500);
} else {
- loadVideoGalleryContent('background');
+ // Browser fallback - try to load from unified storage
+ loadUnifiedVideoGallery();
}
// Clean up any existing invalid blob URLs from previous sessions
@@ -2029,6 +1963,392 @@
}
}
+ function handleAddVideoDirectory() {
+ console.log('Adding new video directory...');
+
+ if (window.electronAPI && window.desktopFileManager) {
+ window.desktopFileManager.addVideoDirectory().then((result) => {
+ console.log('Directory addition result:', result);
+
+ if (result) {
+ console.log('Updating UI after successful directory addition...');
+
+ // Update UI components
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
+
+ console.log('Current video count:', window.desktopFileManager.getAllVideos().length);
+
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification(`✅ Added directory: ${result.directory.name} (${result.videoCount} videos)`, 'success');
+ }
+ } else {
+ console.log('Directory addition returned null/failed');
+ }
+ }).catch(error => {
+ console.error('Directory addition failed:', error);
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification('❌ Failed to add directory', 'error');
+ }
+ });
+ } else {
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification('⚠️ Directory management only available in desktop version', 'warning');
+ }
+ }
+ }
+
+ function handleRefreshAllDirectories() {
+ console.log('Refreshing all directories...');
+
+ if (window.electronAPI && window.desktopFileManager) {
+ window.desktopFileManager.refreshAllDirectories().then(() => {
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
+ }).catch(error => {
+ console.error('Directory refresh failed:', error);
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification('❌ Failed to refresh directories', 'error');
+ }
+ });
+ }
+ }
+
+ function handleClearAllDirectories() {
+ if (!confirm('Are you sure you want to unlink all video directories? This will not delete your actual video files.')) {
+ return;
+ }
+
+ console.log('Clearing all directories...');
+
+ if (window.electronAPI && window.desktopFileManager) {
+ // Remove all directories
+ const directories = [...window.desktopFileManager.externalVideoDirectories];
+
+ Promise.all(directories.map(dir => window.desktopFileManager.removeVideoDirectory(dir.id)))
+ .then(() => {
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
+
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification('�️ All directories unlinked', 'success');
+ }
+ }).catch(error => {
+ console.error('Failed to clear directories:', error);
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification('❌ Failed to clear directories', 'error');
+ }
+ });
+ }
+ }
+
+ function updateDirectoryList() {
+ const listContainer = document.getElementById('linked-directories-list');
+ if (!listContainer) return;
+
+ if (window.electronAPI && window.desktopFileManager) {
+ const directories = window.desktopFileManager.getDirectoriesInfo();
+
+ if (directories.length === 0) {
+ listContainer.innerHTML = '
No directories linked yet. Click "Add Directory" to get started.
';
+ return;
+ }
+
+ listContainer.innerHTML = directories.map(dir => `
+
+
+ ${dir.name}
+ ${dir.path}
+ ${dir.videoCount} videos
+
+
Remove
+
+ `).join('');
+ }
+ }
+
+ function removeDirectory(directoryId) {
+ if (window.electronAPI && window.desktopFileManager) {
+ window.desktopFileManager.removeVideoDirectory(directoryId).then((success) => {
+ if (success) {
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
+ }
+ });
+ }
+ }
+
+ function updateVideoStats() {
+ const totalCountEl = document.getElementById('total-video-count');
+ const directoryCountEl = document.getElementById('directory-count');
+
+ if (window.electronAPI && window.desktopFileManager) {
+ const allVideos = window.desktopFileManager.getAllVideos();
+ const directories = window.desktopFileManager.getDirectoriesInfo();
+
+ if (totalCountEl) totalCountEl.textContent = `${allVideos.length} videos total`;
+ if (directoryCountEl) directoryCountEl.textContent = `${directories.length} directories linked`;
+ } else {
+ if (totalCountEl) totalCountEl.textContent = '0 videos total';
+ if (directoryCountEl) directoryCountEl.textContent = '0 directories linked';
+ }
+ }
+
+ function loadUnifiedVideoGallery() {
+ const gallery = document.getElementById('unified-video-gallery');
+ console.log('Gallery element found:', !!gallery);
+ if (!gallery) {
+ console.error('❌ unified-video-gallery element not found!');
+ return;
+ }
+
+ // Show loading status for videos
+ const videoCount = document.querySelector('.video-count');
+ if (videoCount) {
+ videoCount.textContent = 'Loading videos...';
+ }
+
+ // Update main loading progress if still loading
+ if (window.game && !window.game.isInitialized) {
+ window.game.updateLoadingProgress(85, 'Loading video library...');
+ }
+
+ let videos = [];
+
+ if (window.electronAPI && window.desktopFileManager) {
+ videos = window.desktopFileManager.getAllVideos();
+ console.log(`Loading unified video gallery: ${videos.length} videos found`);
+ console.log('Sample video:', videos[0]);
+ } else {
+ // Fallback: try to load from unified storage
+ const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
+ videos = unifiedData.allVideos || [];
+ console.log('Loaded from localStorage:', videos.length, 'videos');
+ }
+
+ // Update video count display
+ if (videoCount) {
+ videoCount.textContent = `${videos.length} videos found`;
+ console.log('Updated video count display');
+ }
+
+ // Update main loading progress if still loading
+ if (window.game && !window.game.isInitialized) {
+ window.game.updateLoadingProgress(95, 'Video library loaded...');
+ }
+
+ if (videos.length === 0) {
+ console.log('No videos found, showing empty message');
+ gallery.innerHTML = `
+
+
📁 No videos found
+
Link video directories above to build your library
+
+ `;
+ return;
+ }
+
+ console.log('Proceeding to load gallery with', videos.length, 'videos');
+
+ // For large collections, use performance optimizations
+ if (videos.length > 100) {
+ console.log('Using large gallery loading');
+ loadLargeVideoGallery(gallery, videos);
+ } else {
+ console.log('Using standard gallery loading');
+ loadStandardVideoGallery(gallery, videos);
+ }
+ }
+
+ function loadStandardVideoGallery(gallery, videos) {
+ console.log('🎬 Loading standard gallery with', videos.length, 'videos');
+
+ // Standard loading for smaller collections
+ const videoHTML = videos.map((video, index) => createVideoItem(video, index)).join('');
+
+ gallery.innerHTML = videoHTML;
+
+ setupVideoItemHandlers(gallery);
+ }
+
+ function loadLargeVideoGallery(gallery, videos) {
+ console.log(`🚀 Loading large video gallery with ${videos.length} videos using performance optimizations`);
+
+ // Show loading message
+ gallery.innerHTML = `
+
+
📼 Loading ${videos.length} videos...
+
+
Processing in batches for optimal performance
+
+ `;
+
+ // Process videos in chunks to prevent UI blocking
+ const chunkSize = 50; // Process 50 videos at a time
+ let currentChunk = 0;
+ const totalChunks = Math.ceil(videos.length / chunkSize);
+
+ function processNextChunk() {
+ const startIndex = currentChunk * chunkSize;
+ const endIndex = Math.min(startIndex + chunkSize, videos.length);
+ const chunk = videos.slice(startIndex, endIndex);
+
+ // Update progress
+ const progress = ((currentChunk + 1) / totalChunks) * 100;
+ const progressBar = document.getElementById('video-load-progress');
+ if (progressBar) {
+ progressBar.style.width = `${progress}%`;
+ }
+
+ if (currentChunk === 0) {
+ // First chunk - replace loading message with video grid
+ gallery.innerHTML = '
';
+ }
+
+ const container = gallery.querySelector('.video-grid-container');
+ if (container) {
+ // Add this chunk's videos
+ const chunkHTML = chunk.map((video, localIndex) =>
+ createVideoItemLazy(video, startIndex + localIndex)
+ ).join('');
+ container.insertAdjacentHTML('beforeend', chunkHTML);
+ }
+
+ currentChunk++;
+
+ if (currentChunk < totalChunks) {
+ // Process next chunk with a small delay to keep UI responsive
+ setTimeout(processNextChunk, 10);
+ } else {
+ // All chunks processed
+ console.log(`✅ Gallery loading complete: ${videos.length} videos rendered`);
+ setupVideoItemHandlers(gallery);
+ setupLazyThumbnailLoading();
+ }
+ }
+
+ // Start processing
+ setTimeout(processNextChunk, 100);
+ }
+
+ function createVideoItem(video, index) {
+ return `
+
+
+
+
+
+
+
+
+
+
${video.title}
+
+ 📁 ${video.directoryName || 'Unknown'}
+ ${video.size ? ` • ${formatFileSize(video.size)} ` : ''}
+
+
▶️ Preview
+
+
+
+ `;
+ }
+
+ function createVideoItemLazy(video, index) {
+ return `
+
+
+
+
+
+
${video.title}
+
+ 📁 ${video.directoryName || 'Unknown'}
+ ${video.size ? ` • ${formatFileSize(video.size)} ` : ''}
+
+
▶️ Preview
+
+
+
+ `;
+ }
+
+ function setupLazyThumbnailLoading() {
+ const lazyThumbnails = document.querySelectorAll('.lazy-thumbnail');
+
+ const observer = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ const thumbnail = entry.target;
+ const videoUrl = thumbnail.dataset.videoUrl;
+ const videoType = thumbnail.dataset.videoType;
+
+ // Replace placeholder with actual video thumbnail
+ thumbnail.innerHTML = `
+
+
+
+ `;
+
+ thumbnail.classList.remove('lazy-thumbnail');
+ observer.unobserve(thumbnail);
+ }
+ });
+ }, {
+ rootMargin: '100px' // Start loading thumbnails 100px before they come into view
+ });
+
+ lazyThumbnails.forEach(thumbnail => {
+ observer.observe(thumbnail);
+ });
+ }
+
+ function setupVideoItemHandlers(gallery) {
+ // Add click handlers for video selection
+ gallery.querySelectorAll('.video-item').forEach(item => {
+ item.addEventListener('click', (e) => {
+ if (e.target.type !== 'checkbox' && !e.target.closest('button')) {
+ const checkbox = item.querySelector('.video-checkbox');
+ checkbox.checked = !checkbox.checked;
+ item.classList.toggle('selected', checkbox.checked);
+ }
+ });
+ });
+ }
+
+ // Global function for removing directories (called from HTML onclick)
+ window.removeDirectory = function(directoryId) {
+ if (window.electronAPI && window.desktopFileManager) {
+ window.desktopFileManager.removeVideoDirectory(directoryId).then((success) => {
+ if (success) {
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
+ }
+ });
+ }
+ };
+
+ function formatFileSize(bytes) {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
+ }
+
function handleVideoImport(category) {
console.log(`Importing ${category} videos...`);
@@ -2163,8 +2483,10 @@
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
const dirCategory = category === 'task' ? 'tasks' :
category === 'reward' ? 'rewards' :
- category === 'punishment' ? 'punishments' : category;
+ category === 'punishment' ? 'punishments' :
+ category; // Keep cinema, background as-is
videos = storedVideos[dirCategory] || [];
+ console.log(`Loading ${category} videos:`, videos.length, 'videos found');
} else {
// Fallback to localStorage
videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
@@ -2468,33 +2790,20 @@
function refreshVideoLibrary() {
if (window.electronAPI && window.desktopFileManager) {
- // Clear existing video storage
- localStorage.removeItem('videoFiles');
-
if (window.game && window.game.showNotification) {
window.game.showNotification('🔄 Refreshing video library...', 'info');
}
- // Rescan all directories
- Promise.all([
- window.desktopFileManager.scanDirectoryForVideos('background'),
- window.desktopFileManager.scanDirectoryForVideos('tasks'),
- window.desktopFileManager.scanDirectoryForVideos('rewards'),
- window.desktopFileManager.scanDirectoryForVideos('punishments')
- ]).then(([backgroundVideos, taskVideos, rewardVideos, punishmentVideos]) => {
- const allVideos = [...backgroundVideos, ...taskVideos, ...rewardVideos, ...punishmentVideos];
- if (allVideos.length > 0) {
- window.desktopFileManager.updateVideoStorage(allVideos);
- }
-
- // Reload all galleries
- loadVideoGalleryContent('background');
- loadVideoGalleryContent('task');
- loadVideoGalleryContent('reward');
- loadVideoGalleryContent('punishment');
+ // Use the new unified system - refresh all linked directories
+ window.desktopFileManager.refreshAllDirectories().then(() => {
+ // Update UI
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
if (window.game && window.game.showNotification) {
- window.game.showNotification(`✅ Video library refreshed! Found ${allVideos.length} videos`, 'success');
+ const videoCount = window.desktopFileManager.getAllVideos().length;
+ window.game.showNotification(`✅ Video library refreshed! Found ${videoCount} videos`, 'success');
}
}).catch(error => {
console.error('Error refreshing video library:', error);
@@ -2611,6 +2920,13 @@
if (window.electronAPI && typeof DesktopFileManager !== 'undefined') {
window.desktopFileManager = new DesktopFileManager(window.game?.dataManager);
console.log('🖥️ Desktop File Manager initialized for video management');
+
+ // Initialize the new unified video system
+ setTimeout(() => {
+ updateDirectoryList();
+ updateVideoStats();
+ loadUnifiedVideoGallery();
+ }, 1000);
}
// Set up video management button (only once)
diff --git a/porn-cinema.html b/porn-cinema.html
index b7e68e5..28ded3a 100644
--- a/porn-cinema.html
+++ b/porn-cinema.html
@@ -248,9 +248,40 @@
window.desktopFileManager = new DesktopFileManager(minimalDataManager);
console.log('🖥️ Desktop File Manager initialized for porn cinema');
- // Wait a moment for the desktop file manager to fully initialize
- // The init() method is async and sets up video directories
- await new Promise(resolve => setTimeout(resolve, 500));
+ // Wait for the desktop file manager to fully initialize
+ // This includes loading linked directories and video files
+ let retries = 0;
+ const maxRetries = 50; // Wait up to 5 seconds
+
+ while (retries < maxRetries) {
+ // Check if initialization is complete by verifying video directories are set up
+ if (window.desktopFileManager.videoDirectories &&
+ window.desktopFileManager.videoDirectories.background) {
+ console.log('✅ Desktop file manager video directories are ready');
+ break;
+ }
+
+ console.log(`⏳ Waiting for desktop file manager to initialize... (${retries + 1}/${maxRetries})`);
+ await new Promise(resolve => setTimeout(resolve, 100));
+ retries++;
+ }
+
+ if (retries >= maxRetries) {
+ console.warn('⚠️ Desktop file manager took too long to initialize');
+ }
+
+ // Force refresh of linked directories to ensure we have the latest video data
+ try {
+ // Force reload from storage first
+ await window.desktopFileManager.loadLinkedDirectories();
+ console.log('✅ Force reloaded linked directories');
+
+ // Then refresh all directories to get current video lists
+ await window.desktopFileManager.refreshAllDirectories();
+ console.log('✅ Refreshed all video directories');
+ } catch (error) {
+ console.warn('⚠️ Error refreshing directories:', error);
+ }
} else if (!window.electronAPI) {
console.warn('⚠️ Running in browser mode - video management limited');
diff --git a/remove-game-effects.js b/remove-game-effects.js
deleted file mode 100644
index b4c988e..0000000
--- a/remove-game-effects.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const fs = require('fs');
-
-// Remove effects and conditions objects from game.js
-const filePath = 'src/core/game.js';
-let content = fs.readFileSync(filePath, 'utf8');
-
-// Remove effects objects
-content = content.replace(/,?\s*effects:\s*{[^}]+}/g, '');
-
-// Remove conditions objects
-content = content.replace(/,?\s*conditions:\s*{[^}]+}/g, '');
-
-// Clean up orphaned commas
-content = content.replace(/\{\s*,/g, '{');
-content = content.replace(/,\s*\}/g, '}');
-content = content.replace(/,(\s*),/g, ',');
-
-// Clean up any lines that only have commas
-content = content.split('\n').map(line => {
- const trimmed = line.trim();
- if (trimmed === ',' || trimmed === ',,' || trimmed === ',,,') {
- return '';
- }
- return line;
-}).join('\n');
-
-fs.writeFileSync(filePath, content, 'utf8');
-console.log('Effects and conditions objects removed from game.js');
\ No newline at end of file
diff --git a/remove-game-templates.js b/remove-game-templates.js
deleted file mode 100644
index 5bde57a..0000000
--- a/remove-game-templates.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const fs = require('fs');
-
-// Remove template variables from game.js story text
-const filePath = 'src/core/game.js';
-let content = fs.readFileSync(filePath, 'utf8');
-
-// Replace template variables with generic text
-content = content.replace(/Your \{arousal\} level is showing, and your \{control\} needs work/g, 'Your level is showing, and you need to focus');
-content = content.replace(/Your final stats show \{arousal\} arousal and \{control\} control/g, 'You have completed the session');
-content = content.replace(/Your final arousal level of \{arousal\} and control level of \{control\}/g, 'Your performance');
-content = content.replace(/Your arousal is at \{arousal\} and your control is \{control\}/g, 'The punishment is having its effect');
-content = content.replace(/Your final arousal of \{arousal\} and broken control of \{control\}/g, 'Your state');
-content = content.replace(/Arousal: \{arousal\}, Control: \{control\}/g, 'Your session state');
-content = content.replace(/Final state - Arousal: \{arousal\}, Control: \{control\}/g, 'Final state recorded');
-content = content.replace(/Your \{arousal\} is showing/g, 'Your state is evident');
-content = content.replace(/Your arousal at \{arousal\} and diminished control at \{control\}/g, 'Your state and responses');
-content = content.replace(/Final arousal: \{arousal\}, Control: \{control\}/g, 'Final state recorded');
-
-// Remove any remaining isolated template variables
-content = content.replace(/\{arousal\}/g, 'your state');
-content = content.replace(/\{control\}/g, 'your focus');
-content = content.replace(/\{intensity\}/g, 'the level');
-
-fs.writeFileSync(filePath, content, 'utf8');
-console.log('Template variables removed from game.js story text');
\ No newline at end of file
diff --git a/remove-interactive-counters.js b/remove-interactive-counters.js
deleted file mode 100644
index 4595133..0000000
--- a/remove-interactive-counters.js
+++ /dev/null
@@ -1,27 +0,0 @@
-const fs = require('fs');
-
-// Remove all remaining counter code from interactiveTaskManager.js
-const filePath = 'src/features/tasks/interactiveTaskManager.js';
-let content = fs.readFileSync(filePath, 'utf8');
-
-// Remove the default state initialization
-content = content.replace(/arousal: 50,.*?\/\/ 0-100 scale\s*\n/g, '');
-content = content.replace(/control: 50,.*?\/\/ 0-100 scale.*?\n/g, '');
-content = content.replace(/intensity: 1,.*?\/\/ 1-3 scale\s*\n/g, '');
-
-// Remove the effect application functions
-content = content.replace(/applyChoiceEffects\(choice, state\)\s*\{[\s\S]*?\n\s*\}/g, 'applyChoiceEffects(choice, state) {\n // Effects system removed\n }');
-content = content.replace(/applyActionEffects\(step, state\)\s*\{[\s\S]*?\n\s*\}/g, 'applyActionEffects(step, state) {\n // Effects system removed\n }');
-
-// Remove any remaining counter processing in photo selection logic
-content = content.replace(/const arousal = state\.arousal \|\| 50;/g, '// Counter system removed');
-content = content.replace(/const control = state\.control \|\| 50;/g, '// Counter system removed');
-
-// Replace arousal-based photo logic with simple static logic
-content = content.replace(/if \(arousal >= 80\) \{[\s\S]*?\} else if \(arousal >= 60\) \{[\s\S]*?\} else if \(arousal >= 40\) \{[\s\S]*?\}/g, 'photoCount += 1; // Static photo count');
-
-// Remove any conditional logic based on control
-content = content.replace(/if \(control >= 70\) \{[\s\S]*?\}/g, '// Control-based logic removed');
-
-fs.writeFileSync(filePath, content, 'utf8');
-console.log('Counter code removed from interactiveTaskManager.js');
\ No newline at end of file
diff --git a/scripts/clean-orphaned-commas.js b/scripts/clean-orphaned-commas.js
deleted file mode 100644
index 2c994c1..0000000
--- a/scripts/clean-orphaned-commas.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * Script to remove orphaned commas and fix JSON structure in game mode files
- */
-
-const fs = require('fs');
-const path = require('path');
-
-const gameModeFiles = [
- 'src/data/modes/trainingGameData.js',
- 'src/data/modes/humiliationGameData.js',
- 'src/data/modes/dressUpGameData.js',
- 'src/data/modes/enduranceGameData.js'
-];
-
-function cleanOrphanedCommas(filePath) {
- console.log(`Cleaning orphaned commas in: ${filePath}`);
-
- let content = fs.readFileSync(filePath, 'utf8');
-
- // Remove lines that are just commas with whitespace
- content = content.replace(/^\s*,\s*$/gm, '');
-
- // Remove double commas
- content = content.replace(/,,+/g, ',');
-
- // Fix lines where comma is separated from the property
- // Find patterns like: property: "value"\n,\n and fix to property: "value",\n
- content = content.replace(/(["\w]+:\s*"[^"]*")\s*\n\s*,\s*\n/g, '$1,\n');
- content = content.replace(/(["\w]+:\s*\d+)\s*\n\s*,\s*\n/g, '$1,\n');
- content = content.replace(/(["\w]+:\s*'[^']*')\s*\n\s*,\s*\n/g, '$1,\n');
-
- fs.writeFileSync(filePath, content);
- console.log(`Cleaned orphaned commas in: ${filePath}`);
-}
-
-function main() {
- console.log('Cleaning orphaned commas in game mode files...');
-
- gameModeFiles.forEach(file => {
- const fullPath = path.join(process.cwd(), file);
- if (fs.existsSync(fullPath)) {
- cleanOrphanedCommas(fullPath);
- } else {
- console.log(`File not found: ${fullPath}`);
- }
- });
-
- console.log('All orphaned commas cleaned!');
-}
-
-main();
\ No newline at end of file
diff --git a/scripts/fix-commas.js b/scripts/fix-commas.js
deleted file mode 100644
index d4aee24..0000000
--- a/scripts/fix-commas.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Script to fix missing commas in game mode data files after effects removal
- */
-
-const fs = require('fs');
-const path = require('path');
-
-const gameModeFiles = [
- 'src/data/modes/trainingGameData.js',
- 'src/data/modes/humiliationGameData.js',
- 'src/data/modes/dressUpGameData.js',
- 'src/data/modes/enduranceGameData.js'
-];
-
-function fixCommasInFile(filePath) {
- console.log(`Fixing commas in: ${filePath}`);
-
- let content = fs.readFileSync(filePath, 'utf8');
-
- // Fix missing commas after preview/type/text lines followed by nextStep with proper spacing
- content = content.replace(/("preview": "[^"]*")\s*\n(\s*nextStep:)/g, '$1,\n$2');
- content = content.replace(/("type": "[^"]*")\s*\n(\s*nextStep:)/g, '$1,\n$2');
- content = content.replace(/("text": "[^"]*")\s*\n(\s*nextStep:)/g, '$1,\n$2');
-
- // Fix missing commas after any property followed by nextStep (without quotes around nextStep)
- content = content.replace(/([^,])\s*\n(\s*nextStep:\s*"[^"]*")/g, '$1,\n$2');
-
- // Fix missing commas in choice/step objects - between } and { on different lines
- content = content.replace(/(\})\s*\n(\s*\{)/g, '$1,\n$2');
-
- // Fix missing commas after duration/actionText followed by nextStep
- content = content.replace(/(duration: \d+)\s*\n(\s*nextStep:)/g, '$1,\n$2');
- content = content.replace(/("actionText": "[^"]*")\s*\n(\s*nextStep:)/g, '$1,\n$2');
-
- fs.writeFileSync(filePath, content);
- console.log(`Fixed commas in: ${filePath}`);
-}
-
-function main() {
- console.log('Fixing missing commas in game mode files...');
-
- gameModeFiles.forEach(file => {
- const fullPath = path.join(process.cwd(), file);
- if (fs.existsSync(fullPath)) {
- fixCommasInFile(fullPath);
- } else {
- console.log(`File not found: ${fullPath}`);
- }
- });
-
- console.log('All comma issues fixed!');
-}
-
-main();
\ No newline at end of file
diff --git a/scripts/fix-gamemanager.js b/scripts/fix-gamemanager.js
deleted file mode 100644
index 89f9ecb..0000000
--- a/scripts/fix-gamemanager.js
+++ /dev/null
@@ -1,48 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-// Read the current gameModeManager.js
-const filePath = path.join(__dirname, '..', 'src', 'core', 'gameModeManager.js');
-const content = fs.readFileSync(filePath, 'utf8');
-
-// Find the start of getScenarioData method and the start of getScenarioFromGame method
-const lines = content.split('\n');
-
-let startRemoval = -1;
-let endRemoval = -1;
-
-// Find where the problematic data starts
-for (let i = 0; i < lines.length; i++) {
- if (lines[i].trim() === 'return this.getScenarioFromGame(scenarioId);' && startRemoval === -1) {
- startRemoval = i + 1; // Start removing after this line
- }
- if (lines[i].trim().includes('if (window.gameData && window.gameData.mainTasks)')) {
- endRemoval = i - 1; // Stop removing before this line
- break;
- }
-}
-
-console.log(`Found removal range: lines ${startRemoval + 1} to ${endRemoval + 1}`);
-
-if (startRemoval !== -1 && endRemoval !== -1 && startRemoval < endRemoval) {
- // Remove the problematic lines
- const cleanedLines = [
- ...lines.slice(0, startRemoval),
- ' }',
- '',
- ' /**',
- ' * Get scenario data from the current game.js implementation',
- ' */',
- ' getScenarioFromGame(scenarioId) {',
- ' // This accesses the scenarios currently defined in game.js',
- ...lines.slice(endRemoval + 1)
- ];
-
- const cleanedContent = cleanedLines.join('\n');
- fs.writeFileSync(filePath, cleanedContent, 'utf8');
- console.log('Fixed gameModeManager.js by removing orphaned scenario data');
- console.log(`Removed ${endRemoval - startRemoval + 1} lines`);
-} else {
- console.log('Could not find proper removal boundaries');
- console.log(`Start: ${startRemoval}, End: ${endRemoval}`);
-}
\ No newline at end of file
diff --git a/scripts/remove-effects.js b/scripts/remove-effects.js
deleted file mode 100644
index b1b2f82..0000000
--- a/scripts/remove-effects.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * Script to remove all arousal/control/intensity effects from game mode data files
- */
-
-const fs = require('fs');
-const path = require('path');
-
-const gameModeFiles = [
- 'src/data/modes/trainingGameData.js',
- 'src/data/modes/humiliationGameData.js',
- 'src/data/modes/dressUpGameData.js',
- 'src/data/modes/enduranceGameData.js'
-];
-
-function removeEffectsFromFile(filePath) {
- console.log(`Processing: ${filePath}`);
-
- let content = fs.readFileSync(filePath, 'utf8');
-
- // Remove effects objects with various formats
- // Match: effects: { ... },
- content = content.replace(/\s*effects: \{[^}]*\},?\s*/g, '');
-
- // Remove effects objects without trailing comma
- content = content.replace(/\s*effects: \{[^}]*\}\s*/g, '');
-
- // Clean up any orphaned commas and spacing issues
- content = content.replace(/,(\s*nextStep:)/g, '\n $1');
- content = content.replace(/,(\s*\})/g, '$1');
-
- // Remove effects from ending text interpolation
- content = content.replace(/\{arousal\}/g, 'HIGH');
- content = content.replace(/\{control\}/g, 'VARIABLE');
- content = content.replace(/\{intensity\}/g, 'MAXIMUM');
-
- // Clean up any references to arousal/control/intensity in story text
- content = content.replace(/building arousal/g, 'building excitement');
- content = content.replace(/Your arousal/g, 'Your excitement');
- content = content.replace(/maximize arousal/g, 'maximize pleasure');
- content = content.replace(/maintaining arousal/g, 'maintaining excitement');
- content = content.replace(/perfect arousal/g, 'perfect excitement');
- content = content.replace(/arousal level/g, 'excitement level');
- content = content.replace(/control is /g, 'focus is ');
- content = content.replace(/edge control/g, 'edge focus');
- content = content.replace(/perfect control/g, 'perfect focus');
-
- fs.writeFileSync(filePath, content);
- console.log(`Completed: ${filePath}`);
-}
-
-function main() {
- console.log('Removing arousal/control/intensity effects from game mode files...');
-
- gameModeFiles.forEach(file => {
- const fullPath = path.join(process.cwd(), file);
- if (fs.existsSync(fullPath)) {
- removeEffectsFromFile(fullPath);
- } else {
- console.log(`File not found: ${fullPath}`);
- }
- });
-
- console.log('All effects removed successfully!');
-}
-
-main();
\ No newline at end of file
diff --git a/src/core/game.js b/src/core/game.js
index 5411f70..94dfc45 100644
--- a/src/core/game.js
+++ b/src/core/game.js
@@ -174,6 +174,13 @@ class TaskChallengeGame {
this.discoverAudio();
this.updateLoadingProgress(90, 'Audio discovery started...');
+ // Load video library
+ setTimeout(() => {
+ if (typeof loadUnifiedVideoGallery === 'function') {
+ loadUnifiedVideoGallery();
+ }
+ }, 100);
+
// Check for auto-resume after initialization
this.checkAutoResume();
@@ -188,7 +195,7 @@ class TaskChallengeGame {
// Initialize overall XP display on home screen
this.updateOverallXpDisplay();
}, 500);
- }, 1000);
+ }, 1500); // Increased delay to allow video loading
}
showLoadingOverlay() {
diff --git a/src/core/main.js b/src/core/main.js
index 31f36ab..e7bfec8 100644
--- a/src/core/main.js
+++ b/src/core/main.js
@@ -312,6 +312,72 @@ ipcMain.handle('read-video-directory', async (event, dirPath) => {
}
});
+// Recursive video directory scanner
+ipcMain.handle('read-video-directory-recursive', async (event, dirPath) => {
+ const videoExtensions = ['.mp4', '.webm', '.ogv', '.mov', '.avi', '.mkv', '.m4v', '.3gp', '.flv'];
+ const mimeTypeMap = {
+ '.mp4': 'video/mp4',
+ '.webm': 'video/webm',
+ '.ogv': 'video/ogg',
+ '.mov': 'video/quicktime',
+ '.avi': 'video/x-msvideo',
+ '.mkv': 'video/x-matroska',
+ '.m4v': 'video/mp4',
+ '.3gp': 'video/3gpp',
+ '.flv': 'video/x-flv'
+ };
+
+ async function scanDirectory(currentPath) {
+ let videoFiles = [];
+
+ try {
+ const items = await fs.readdir(currentPath, { withFileTypes: true });
+
+ for (const item of items) {
+ const itemPath = path.join(currentPath, item.name);
+
+ if (item.isDirectory()) {
+ // Recursively scan subdirectories
+ const subDirVideos = await scanDirectory(itemPath);
+ videoFiles.push(...subDirVideos);
+ } else if (item.isFile()) {
+ const ext = path.extname(item.name).toLowerCase();
+ if (videoExtensions.includes(ext)) {
+ try {
+ const stats = await fs.stat(itemPath);
+ videoFiles.push({
+ name: item.name,
+ path: itemPath,
+ url: `file:///${itemPath.replace(/\\/g, '/')}`,
+ title: item.name.replace(/\.[^/.]+$/, "").replace(/[-_]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()),
+ size: stats.size,
+ type: mimeTypeMap[ext] || 'video/mp4',
+ directory: path.dirname(itemPath)
+ });
+ } catch (statError) {
+ console.warn(`Could not stat video file: ${itemPath}`, statError);
+ }
+ }
+ }
+ }
+ } catch (readError) {
+ console.warn(`Could not read directory: ${currentPath}`, readError);
+ }
+
+ return videoFiles;
+ }
+
+ try {
+ console.log(`🔍 Starting recursive scan of: ${dirPath}`);
+ const allVideos = await scanDirectory(dirPath);
+ console.log(`✅ Recursive scan complete: Found ${allVideos.length} videos`);
+ return allVideos;
+ } catch (error) {
+ console.error('Error in recursive video directory scan:', error);
+ return [];
+ }
+});
+
ipcMain.handle('copy-video', async (event, sourcePath, destPath) => {
try {
await fs.mkdir(path.dirname(destPath), { recursive: true });
diff --git a/src/core/preload.js b/src/core/preload.js
index bca3c54..2040017 100644
--- a/src/core/preload.js
+++ b/src/core/preload.js
@@ -17,6 +17,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
// Video file operations
selectVideos: () => ipcRenderer.invoke('select-videos'),
readVideoDirectory: (dirPath) => ipcRenderer.invoke('read-video-directory', dirPath),
+ readVideoDirectoryRecursive: (dirPath) => ipcRenderer.invoke('read-video-directory-recursive', dirPath),
copyVideo: (sourcePath, destPath) => ipcRenderer.invoke('copy-video', sourcePath, destPath),
deleteVideo: (filePath) => ipcRenderer.invoke('delete-video', filePath),
diff --git a/src/features/media/pornCinema-backup.js b/src/features/media/pornCinema-backup.js
deleted file mode 100644
index 1286f28..0000000
--- a/src/features/media/pornCinema-backup.js
+++ /dev/null
@@ -1,982 +0,0 @@
-/**
- * Porn Cinema Media Player
- * Full-featured video player with playlist support and one-handed controls
- * Now extends BaseVideoPlayer for shared functionality
- */
-
-class PornCinema extends BaseVideoPlayer {
- constructor() {
- // Initialize base video player with full features enabled
- super('#video-container', {
- showControls: true,
- autoHide: true,
- showProgress: true,
- showVolume: true,
- showFullscreen: true,
- showQuality: true,
- showSpeed: true,
- keyboardShortcuts: true,
- minimal: false
- });
-
- // Cinema-specific properties
- this.shouldAutoPlay = false;
- this.fallbackMimeTypes = null;
- this.currentVideoSrc = null;
-
- // Playlist
- this.playlist = [];
- this.currentPlaylistIndex = -1;
- this.shuffleMode = false;
- this.originalPlaylistOrder = [];
-
- // Video library
- this.videoLibrary = null;
-
- // Cinema-specific keyboard shortcuts (extend base shortcuts)
- this.cinemaShortcuts = {
- 'n': () => this.nextVideo(),
- 'N': () => this.nextVideo(),
- 'p': () => this.previousVideo(),
- 'P': () => this.previousVideo(),
- 's': () => this.shufflePlaylist(),
- 'S': () => this.shufflePlaylist(),
- '1': () => this.setQuality('1080p'),
- '2': () => this.setQuality('720p'),
- '3': () => this.setQuality('480p'),
- '4': () => this.setQuality('360p'),
- 'Enter': () => this.addCurrentToPlaylist()
- };
-
- this.initializeCinemaElements();
- this.attachCinemaEventListeners();
- }
-
- /**
- * Initialize cinema-specific elements (extends BaseVideoPlayer initialization)
- */
- initializeCinemaElements() {
- // Call parent initialization first
- super.initializeElements();
-
- // Initialize cinema-specific elements
- this.initializeCinemaSpecificElements();
- this.extendCinemaKeyboardShortcuts();
- }
-
- /**
- * Initialize cinema-specific UI elements and controls
- */
- initializeCinemaSpecificElements() {
- // Cinema-specific navigation elements
- this.controls.prevVideo = document.getElementById('prev-video-btn');
- this.controls.nextVideo = document.getElementById('next-video-btn');
- this.controls.addToPlaylist = document.getElementById('add-to-playlist-btn');
- this.controls.theater = document.getElementById('theater-mode-btn');
-
- // Playlist management elements
- this.playlistContent = document.getElementById('playlist-content');
- this.shuffleBtn = document.getElementById('shuffle-playlist');
- this.clearPlaylistBtn = document.getElementById('clear-playlist');
- this.savePlaylistBtn = document.getElementById('save-playlist');
- this.loadPlaylistBtn = document.getElementById('load-playlist');
-
- // Cinema-specific display elements
- this.videoTitle = document.getElementById('video-title');
- this.videoInfo = document.getElementById('video-info');
- this.playOverlay = document.getElementById('play-overlay');
- this.playButtonLarge = document.getElementById('play-button-large');
-
- console.log('🎬 Cinema-specific elements initialized');
- }
-
- /**
- * Extend base keyboard shortcuts with cinema-specific shortcuts
- */
- extendCinemaKeyboardShortcuts() {
- // Add cinema shortcuts to the base shortcuts
- Object.assign(this.keyboardShortcuts, this.cinemaShortcuts);
- console.log('🎹 Cinema keyboard shortcuts extended');
- }
-
- async initialize() {
- console.log('🎬 Initializing Porn Cinema...');
-
- // Initialize video library
- this.videoLibrary = new VideoLibrary(this);
- await this.videoLibrary.loadVideoLibrary();
-
- // Set initial volume
- this.setVolume(this.volume);
-
- console.log('✅ Porn Cinema initialized');
- }
-
- /**
- * Attach cinema-specific event listeners (extends BaseVideoPlayer listeners)
- */
- attachCinemaEventListeners() {
- // Call parent event listeners first
- super.attachEventListeners();
-
- // Cinema-specific control buttons
- if (this.controls.prevVideo) {
- this.controls.prevVideo.addEventListener('click', () => this.previousVideo());
- }
- if (this.controls.nextVideo) {
- this.controls.nextVideo.addEventListener('click', () => this.nextVideo());
- }
- if (this.controls.addToPlaylist) {
- this.controls.addToPlaylist.addEventListener('click', () => this.addCurrentToPlaylist());
- }
- if (this.controls.theater) {
- this.controls.theater.addEventListener('click', () => this.toggleTheaterMode());
- }
-
- // Playlist management buttons
- if (this.shuffleBtn) {
- this.shuffleBtn.addEventListener('click', () => this.shufflePlaylist());
- }
- if (this.clearPlaylistBtn) {
- this.clearPlaylistBtn.addEventListener('click', () => this.clearPlaylist());
- }
- if (this.savePlaylistBtn) {
- this.savePlaylistBtn.addEventListener('click', () => this.savePlaylist());
- }
- if (this.loadPlaylistBtn) {
- this.loadPlaylistBtn.addEventListener('click', () => this.loadPlaylist());
- }
-
- // Large play button overlay (cinema-specific)
- if (this.playButtonLarge) {
- this.playButtonLarge.addEventListener('click', () => this.togglePlayPause());
- }
-
- // Override video ended event for playlist functionality
- if (this.videoElement) {
- this.videoElement.addEventListener('ended', () => this.onCinemaVideoEnded());
- }
-
- console.log('🎬 Cinema event listeners attached');
- }
-
- /**
- * Handle video ended event with playlist progression
- */
- onCinemaVideoEnded() {
- console.log('🎬 Video ended in cinema mode');
-
- // Auto-play next video in playlist if available
- if (this.playlist.length > 0 && this.currentPlaylistIndex < this.playlist.length - 1) {
- console.log('🎬 Auto-playing next video in playlist');
- this.nextVideo();
- } else {
- // Call parent ended handler
- super.onEnded();
- }
- }
-
- // Progress bar
- this.controls.progressBar.addEventListener('click', (e) => this.seekToPosition(e));
- this.controls.progressBar.addEventListener('mousedown', () => this.startSeeking());
-
- // Quality and speed controls
- this.controls.quality.addEventListener('change', (e) => this.setQuality(e.target.value));
- this.controls.speed.addEventListener('change', (e) => this.setPlaybackRate(e.target.value));
-
- // Playlist controls
- this.shuffleBtn.addEventListener('click', () => this.shufflePlaylist());
- this.clearPlaylistBtn.addEventListener('click', () => this.clearPlaylist());
- this.savePlaylistBtn.addEventListener('click', () => this.savePlaylist());
- this.loadPlaylistBtn.addEventListener('click', () => this.loadPlaylist());
-
- // Keyboard shortcuts
- document.addEventListener('keydown', (e) => this.handleKeyboardShortcut(e));
-
- // Fullscreen events
- document.addEventListener('fullscreenchange', () => this.onFullscreenChange());
- document.addEventListener('webkitfullscreenchange', () => this.onFullscreenChange());
- document.addEventListener('mozfullscreenchange', () => this.onFullscreenChange());
-
- // Theater mode buttons
- document.getElementById('theater-mode').addEventListener('click', () => this.toggleTheaterMode());
- document.getElementById('fullscreen-toggle').addEventListener('click', () => this.toggleFullscreen());
-
- // Video container hover for controls
- this.videoContainer.addEventListener('mouseenter', () => this.showControls());
- this.videoContainer.addEventListener('mouseleave', () => this.hideControls());
- this.videoContainer.addEventListener('mousemove', () => this.showControls());
- }
-
- // Video loading and playback methods
- selectVideo(video) {
- this.currentVideo = video;
- this.updateVideoInfo();
- this.updateVideoTitle();
- }
-
- async playVideo(video) {
- try {
- this.currentVideo = video;
- this.showLoading();
-
- // Convert path to proper format for Electron
- let videoSrc = video.path;
- if (window.electronAPI && video.path.match(/^[A-Za-z]:\\/)) {
- // Absolute Windows path - convert to file:// URL
- videoSrc = `file:///${video.path.replace(/\\/g, '/')}`;
- } else if (window.electronAPI && !video.path.startsWith('file://')) {
- // Relative path in Electron - use as is
- videoSrc = video.path;
- }
-
- // Check if browser can play this format
- const canPlayResult = this.checkCanPlay(video.format || video.path);
- console.log(`🎬 Playing ${video.name} (${video.format}) - Can play: ${canPlayResult.canPlay ? 'Yes' : 'No'}`);
-
- // For .mov files, try multiple MIME types as fallback
- const mimeTypes = this.getVideoMimeTypes(video.format || video.path);
- console.log(`🎬 Trying MIME types: ${mimeTypes.join(', ')}`);
-
- // Update video source - try primary MIME type first
- this.videoSource.src = videoSrc;
- this.videoSource.type = canPlayResult.mimeType || mimeTypes[0];
-
- // Store fallback MIME types for error handling
- this.fallbackMimeTypes = mimeTypes.slice(1);
- this.currentVideoSrc = videoSrc;
-
- // Set flag to auto-play when loaded
- this.shouldAutoPlay = true;
-
- // Load the video
- this.videoElement.load();
-
- // Update UI
- this.updateVideoInfo();
- this.updateVideoTitle();
- this.updatePlaylistSelection();
-
- } catch (error) {
- console.error('Error playing video:', error);
- this.showError('Failed to load video');
- }
- }
-
- checkCanPlay(formatOrPath) {
- let extension = formatOrPath;
- if (formatOrPath.includes('.')) {
- extension = formatOrPath.toLowerCase().split('.').pop();
- }
-
- const testVideo = document.createElement('video');
- const mimeTypes = this.getVideoMimeTypes(formatOrPath);
-
- for (const mimeType of mimeTypes) {
- const canPlay = testVideo.canPlayType(mimeType);
- if (canPlay === 'probably' || canPlay === 'maybe') {
- return { canPlay: true, mimeType };
- }
- }
-
- return { canPlay: false, mimeType: null };
- }
-
- getVideoMimeTypes(formatOrPath) {
- let extension = formatOrPath;
- if (formatOrPath.includes('.')) {
- extension = formatOrPath.toLowerCase().split('.').pop();
- }
-
- switch (extension.toLowerCase()) {
- case 'mp4':
- return ['video/mp4'];
- case 'webm':
- return ['video/webm'];
- case 'mov':
- case 'qt':
- // Try multiple MIME types for .mov files
- return ['video/quicktime', 'video/mp4', 'video/x-quicktime'];
- case 'avi':
- return ['video/avi', 'video/x-msvideo'];
- case 'mkv':
- return ['video/x-matroska'];
- case 'ogg':
- return ['video/ogg'];
- case 'm4v':
- return ['video/mp4'];
- default:
- return ['video/mp4']; // Default fallback
- }
- }
-
- togglePlayPause() {
- if (!this.videoElement.src) {
- // No video loaded, try to play first video from library or playlist
- this.playFirstAvailable();
- return;
- }
-
- if (this.videoElement.paused) {
- this.play();
- } else {
- this.pause();
- }
- }
-
- play() {
- const playPromise = this.videoElement.play();
-
- if (playPromise !== undefined) {
- playPromise.then(() => {
- this.isPlaying = true;
- this.updatePlayButton();
- this.hidePlayOverlay();
- }).catch(error => {
- console.error('Error playing video:', error);
- this.showError('Failed to play video');
- });
- }
- }
-
- pause() {
- this.videoElement.pause();
- this.isPlaying = false;
- this.updatePlayButton();
- this.showPlayOverlay();
- }
-
- seek(seconds) {
- if (this.videoElement.duration) {
- const newTime = Math.max(0, Math.min(this.videoElement.duration, this.videoElement.currentTime + seconds));
- this.videoElement.currentTime = newTime;
- }
- }
-
- seekToPosition(event) {
- if (this.videoElement.duration) {
- const rect = this.controls.progressBar.getBoundingClientRect();
- const pos = (event.clientX - rect.left) / rect.width;
- this.videoElement.currentTime = pos * this.videoElement.duration;
- }
- }
-
- // Volume and audio controls
- setVolume(volume) {
- this.volume = Math.max(0, Math.min(1, volume));
- this.videoElement.volume = this.volume;
- this.controls.volume.value = this.volume * 100;
- this.controls.volumePercentage.textContent = Math.round(this.volume * 100) + '%';
- this.updateMuteButton();
- }
-
- adjustVolume(delta) {
- this.setVolume(this.volume + delta);
- }
-
- toggleMute() {
- if (this.videoElement.muted) {
- this.videoElement.muted = false;
- this.setVolume(this.volume > 0 ? this.volume : 0.7);
- } else {
- this.videoElement.muted = true;
- }
- this.updateMuteButton();
- }
-
- updateMuteButton() {
- const isMuted = this.videoElement.muted || this.volume === 0;
- this.controls.mute.textContent = isMuted ? '🔇' : '🔊';
- }
-
- // Quality and playback controls
- setQuality(quality) {
- this.currentQuality = quality;
- this.controls.quality.value = quality;
-
- // In a real implementation, you would switch video sources here
- // For now, we'll just update the UI
- console.log(`Quality set to: ${quality}`);
- }
-
- setPlaybackRate(rate) {
- this.playbackRate = parseFloat(rate);
- this.videoElement.playbackRate = this.playbackRate;
- this.controls.speed.value = rate;
- }
-
- // Fullscreen and theater mode
- toggleFullscreen() {
- if (!this.isFullscreen) {
- this.enterFullscreen();
- } else {
- this.exitFullscreen();
- }
- }
-
- enterFullscreen() {
- const element = this.videoContainer;
-
- if (element.requestFullscreen) {
- element.requestFullscreen();
- } else if (element.webkitRequestFullscreen) {
- element.webkitRequestFullscreen();
- } else if (element.mozRequestFullScreen) {
- element.mozRequestFullScreen();
- }
- }
-
- exitFullscreen() {
- if (document.exitFullscreen) {
- document.exitFullscreen();
- } else if (document.webkitExitFullscreen) {
- document.webkitExitFullscreen();
- } else if (document.mozCancelFullScreen) {
- document.mozCancelFullScreen();
- }
- }
-
- toggleTheaterMode() {
- this.theaterMode = !this.theaterMode;
- document.body.classList.toggle('theater-mode', this.theaterMode);
-
- const button = document.getElementById('theater-mode');
- if (button) {
- button.textContent = this.theaterMode ? '💡' : '🎭';
- button.title = this.theaterMode ? 'Exit Theater Mode' : 'Theater Mode (Dim UI)';
- }
- }
-
- // Playlist management
- async addToPlaylist(video) {
- if (!this.playlist.find(v => v.path === video.path)) {
- // Create a copy of the video with duration loaded if missing
- const videoWithDuration = {...video};
-
- // If duration is missing or 0, try to load it
- if (!videoWithDuration.duration || videoWithDuration.duration === 0) {
- try {
- videoWithDuration.duration = await this.getVideoDuration(video);
- } catch (error) {
- console.warn(`Could not load duration for ${video.name}:`, error);
- }
- }
-
- this.playlist.push(videoWithDuration);
- this.updatePlaylistDisplay();
- console.log(`➕ Added to playlist: ${video.name}`);
- }
- }
-
- async addCurrentToPlaylist() {
- if (this.currentVideo) {
- await this.addToPlaylist(this.currentVideo);
- } else if (this.videoLibrary && this.videoLibrary.getSelectedVideo()) {
- await this.addToPlaylist(this.videoLibrary.getSelectedVideo());
- }
- }
-
- removeFromPlaylist(index) {
- if (index >= 0 && index < this.playlist.length) {
- const removed = this.playlist.splice(index, 1)[0];
- console.log(`➖ Removed from playlist: ${removed.name}`);
-
- // Adjust current index if necessary
- if (this.currentPlaylistIndex > index) {
- this.currentPlaylistIndex--;
- } else if (this.currentPlaylistIndex === index) {
- this.currentPlaylistIndex = -1;
- }
-
- this.updatePlaylistDisplay();
- }
- }
-
- clearPlaylist() {
- if (confirm('Clear entire playlist?')) {
- this.playlist = [];
- this.currentPlaylistIndex = -1;
- this.updatePlaylistDisplay();
- console.log('🗑️ Playlist cleared');
- }
- }
-
- shufflePlaylist() {
- if (this.playlist.length <= 1) return;
-
- this.shuffleMode = !this.shuffleMode;
-
- if (this.shuffleMode) {
- // Save original order
- this.originalPlaylistOrder = [...this.playlist];
-
- // Shuffle playlist
- for (let i = this.playlist.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [this.playlist[i], this.playlist[j]] = [this.playlist[j], this.playlist[i]];
- }
-
- this.shuffleBtn.textContent = '🔀';
- this.shuffleBtn.title = 'Disable Shuffle';
- console.log('🔀 Playlist shuffled');
- } else {
- // Restore original order
- this.playlist = [...this.originalPlaylistOrder];
- this.shuffleBtn.textContent = '🔀';
- this.shuffleBtn.title = 'Shuffle Playlist';
- console.log('📝 Playlist order restored');
- }
-
- this.currentPlaylistIndex = this.playlist.findIndex(v =>
- this.currentVideo && v.path === this.currentVideo.path
- );
-
- this.updatePlaylistDisplay();
- }
-
- nextVideo() {
- if (this.playlist.length === 0) return;
-
- let nextIndex = this.currentPlaylistIndex + 1;
- if (nextIndex >= this.playlist.length) {
- nextIndex = 0; // Loop back to start
- }
-
- this.playVideoFromPlaylist(nextIndex);
- }
-
- previousVideo() {
- if (this.playlist.length === 0) return;
-
- let prevIndex = this.currentPlaylistIndex - 1;
- if (prevIndex < 0) {
- prevIndex = this.playlist.length - 1; // Loop to end
- }
-
- this.playVideoFromPlaylist(prevIndex);
- }
-
- playVideoFromPlaylist(index) {
- if (index >= 0 && index < this.playlist.length) {
- this.currentPlaylistIndex = index;
- this.playVideo(this.playlist[index]);
- }
- }
-
- updatePlaylistDisplay() {
- if (this.playlist.length === 0) {
- this.playlistContent.innerHTML = `
-
-
Playlist is empty. Add videos by clicking ➕ or pressing Enter while a video is selected.
-
- `;
- return;
- }
-
- this.playlistContent.innerHTML = this.playlist.map((video, index) => `
-
-
- ${video.thumbnail ?
- `
` :
- `
🎬
`
- }
-
-
-
${video.name}
-
${this.formatDuration(video.duration)}
-
-
- ▶
- ❌
-
-
- `).join('');
-
- // Attach playlist item events
- this.attachPlaylistEvents();
- }
-
- attachPlaylistEvents() {
- const playlistItems = this.playlistContent.querySelectorAll('.playlist-item');
- playlistItems.forEach((item, index) => {
- item.addEventListener('click', (e) => {
- if (e.target.closest('.playlist-actions')) return;
- this.playVideoFromPlaylist(index);
- });
- });
-
- const playButtons = this.playlistContent.querySelectorAll('.play-playlist-item');
- playButtons.forEach((button, index) => {
- button.addEventListener('click', (e) => {
- e.stopPropagation();
- this.playVideoFromPlaylist(index);
- });
- });
-
- const removeButtons = this.playlistContent.querySelectorAll('.remove-playlist-item');
- removeButtons.forEach((button, index) => {
- button.addEventListener('click', (e) => {
- e.stopPropagation();
- this.removeFromPlaylist(index);
- });
- });
- }
-
- updatePlaylistSelection() {
- if (this.currentVideo) {
- this.currentPlaylistIndex = this.playlist.findIndex(v => v.path === this.currentVideo.path);
- this.updatePlaylistDisplay();
- }
- }
-
- // Keyboard shortcuts
- handleKeyboardShortcut(event) {
- // Don't handle shortcuts if typing in an input
- if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
- return;
- }
-
- const key = event.key;
- if (this.shortcuts[key]) {
- event.preventDefault();
- this.shortcuts[key]();
- }
- }
-
- // Video event handlers
- onLoadStart() {
- this.showLoading();
- }
-
- onLoadedMetadata() {
- this.updateTimeDisplay();
- this.hideLoading();
- }
-
- onLoadedData() {
- this.hideLoading();
-
- // Auto-play if requested
- if (this.shouldAutoPlay) {
- this.shouldAutoPlay = false;
- this.videoElement.play().catch(error => {
- console.error('🎬 Error auto-playing video:', error);
- this.showError('Failed to play video');
- });
- }
- }
-
- onCanPlay() {
- this.hideLoading();
- this.updatePlayButton();
- }
-
- onPlay() {
- this.isPlaying = true;
- this.updatePlayButton();
- this.hidePlayOverlay();
- this.hideVideoOverlay();
- }
-
- onPause() {
- this.isPlaying = false;
- this.updatePlayButton();
- this.showPlayOverlay();
- this.showVideoOverlay();
- }
-
- onEnded() {
- this.isPlaying = false;
- this.updatePlayButton();
- this.showPlayOverlay();
- this.showVideoOverlay();
-
- // Auto-play next video in playlist
- if (this.playlist.length > 0 && this.currentPlaylistIndex >= 0) {
- setTimeout(() => this.nextVideo(), 1000);
- }
- }
-
- onTimeUpdate() {
- this.updateProgressBar();
- this.updateTimeDisplay();
- }
-
- onVolumeChange() {
- this.updateMuteButton();
- }
-
- onError(event) {
- const video = event.target;
- const error = video.error;
-
- // Try fallback MIME types for .mov files
- if (this.fallbackMimeTypes && this.fallbackMimeTypes.length > 0) {
- const nextMimeType = this.fallbackMimeTypes.shift();
- console.log(`🔄 Trying fallback MIME type: ${nextMimeType}`);
-
- this.videoSource.type = nextMimeType;
- this.videoElement.load();
- return; // Don't show error yet, try the fallback
- }
-
- let errorMessage = 'Error loading video';
- let detailedMessage = 'Unknown error';
-
- if (error) {
- switch (error.code) {
- case error.MEDIA_ERR_ABORTED:
- detailedMessage = 'Video loading was aborted';
- break;
- case error.MEDIA_ERR_NETWORK:
- detailedMessage = 'Network error while loading video';
- break;
- case error.MEDIA_ERR_DECODE:
- detailedMessage = 'Video format not supported or corrupted';
- errorMessage = 'Unsupported video format';
- break;
- case error.MEDIA_ERR_SRC_NOT_SUPPORTED:
- detailedMessage = 'Video format or codec not supported';
- errorMessage = 'Video format not supported';
- break;
- default:
- detailedMessage = `Unknown error (code: ${error.code})`;
- }
- }
-
- console.error(`🎬 Video error for ${this.currentVideo?.name || 'unknown'}:`, {
- code: error?.code,
- message: error?.message,
- details: detailedMessage,
- format: this.currentVideo?.format,
- src: video.src,
- mimeType: this.videoSource.type
- });
-
- this.hideLoading();
- this.showError(errorMessage);
-
- // Reset fallback MIME types
- this.fallbackMimeTypes = null;
-
- // If it's a .mov file that failed, suggest alternatives
- if (this.currentVideo?.format?.toLowerCase() === 'mov') {
- console.warn('💡 .mov file failed to play. This format may contain codecs not supported by browsers.');
- console.warn('💡 Consider converting .mov files to .mp4 for better compatibility.');
- }
- }
-
- onFullscreenChange() {
- this.isFullscreen = !!(document.fullscreenElement ||
- document.webkitFullscreenElement ||
- document.mozFullScreenElement);
-
- this.controls.fullscreen.textContent = this.isFullscreen ? '🗗' : '⛶';
- this.controls.fullscreen.title = this.isFullscreen ? 'Exit Fullscreen (Esc)' : 'Fullscreen (F)';
- }
-
- // UI update methods
- updateVideoInfo() {
- if (this.currentVideo) {
- const info = `${this.currentVideo.resolution || 'Unknown'} • ${this.formatFileSize(this.currentVideo.size)}`;
- this.videoInfo.textContent = info;
- } else {
- this.videoInfo.textContent = '';
- }
- }
-
- updateVideoTitle() {
- if (this.currentVideo) {
- this.videoTitle.textContent = this.currentVideo.name;
- } else {
- this.videoTitle.textContent = 'Select a video to begin';
- }
- }
-
- updatePlayButton() {
- const playText = this.isPlaying ? '⏸' : '▶';
- this.controls.playPause.textContent = playText;
- this.playButtonLarge.textContent = this.isPlaying ? '⏸' : '▶';
- }
-
- updateProgressBar() {
- if (this.videoElement.duration) {
- const progress = (this.videoElement.currentTime / this.videoElement.duration) * 100;
- this.controls.progressFilled.style.width = progress + '%';
- this.controls.progressThumb.style.left = progress + '%';
- }
- }
-
- updateTimeDisplay() {
- this.controls.currentTime.textContent = this.formatDuration(this.videoElement.currentTime);
- this.controls.totalTime.textContent = this.formatDuration(this.videoElement.duration);
- }
-
- // UI state methods
- showLoading() {
- this.videoLoading.style.display = 'block';
- }
-
- hideLoading() {
- this.videoLoading.style.display = 'none';
- }
-
- showPlayOverlay() {
- this.playOverlay.style.display = 'block';
- }
-
- hidePlayOverlay() {
- this.playOverlay.style.display = 'none';
- }
-
- showVideoOverlay() {
- this.videoOverlay.classList.remove('hidden');
- }
-
- hideVideoOverlay() {
- this.videoOverlay.classList.add('hidden');
- }
-
- showControls() {
- this.controls.container.classList.add('visible');
- this.videoContainer.classList.remove('hide-controls', 'auto-hide');
-
- // Clear existing timeout
- if (this.hideControlsTimeout) {
- clearTimeout(this.hideControlsTimeout);
- }
-
- // Set timeout to auto-hide controls after 3 seconds of no interaction
- if (this.videoElement && !this.videoElement.paused) {
- this.hideControlsTimeout = setTimeout(() => {
- this.videoContainer.classList.add('auto-hide');
- }, 3000);
- }
- }
-
- hideControls() {
- if (!this.videoElement.paused) {
- this.videoContainer.classList.add('auto-hide');
- }
- }
-
- showError(message) {
- this.hideLoading();
- this.videoTitle.textContent = message;
- this.videoInfo.textContent = '';
- }
-
- // Utility methods
- playFirstAvailable() {
- if (this.playlist.length > 0) {
- this.playVideoFromPlaylist(0);
- } else if (this.videoLibrary && this.videoLibrary.getFilteredVideos().length > 0) {
- this.playVideo(this.videoLibrary.getFilteredVideos()[0]);
- }
- }
-
- startSeeking() {
- // Add mouse move listener for seeking
- // This would be implemented for smooth seeking while dragging
- }
-
- savePlaylist() {
- if (this.playlist.length === 0) {
- alert('Playlist is empty');
- return;
- }
-
- const playlistData = {
- name: prompt('Enter playlist name:') || 'Untitled Playlist',
- videos: this.playlist,
- created: new Date().toISOString()
- };
-
- // Save to localStorage (in a real app, you might save to a file)
- const savedPlaylists = JSON.parse(localStorage.getItem('pornCinemaPlaylists') || '[]');
- savedPlaylists.push(playlistData);
- localStorage.setItem('pornCinemaPlaylists', JSON.stringify(savedPlaylists));
-
- console.log(`💾 Playlist saved: ${playlistData.name}`);
- }
-
- loadPlaylist() {
- const savedPlaylists = JSON.parse(localStorage.getItem('pornCinemaPlaylists') || '[]');
-
- if (savedPlaylists.length === 0) {
- alert('No saved playlists found');
- return;
- }
-
- // Create a simple selection dialog
- const playlistNames = savedPlaylists.map((p, i) => `${i + 1}. ${p.name}`).join('\n');
- const selection = prompt(`Select playlist to load:\n${playlistNames}\n\nEnter number:`);
-
- if (selection) {
- const index = parseInt(selection) - 1;
- if (index >= 0 && index < savedPlaylists.length) {
- this.playlist = savedPlaylists[index].videos;
- this.currentPlaylistIndex = -1;
- this.updatePlaylistDisplay();
- console.log(`📁 Playlist loaded: ${savedPlaylists[index].name}`);
- }
- }
- }
-
- async getVideoDuration(video) {
- return new Promise((resolve, reject) => {
- // Create a temporary video element to load metadata
- const tempVideo = document.createElement('video');
- tempVideo.preload = 'metadata';
-
- // Convert path to proper format for Electron
- let videoSrc = video.path;
- if (window.electronAPI && video.path.match(/^[A-Za-z]:\\/)) {
- // Absolute Windows path - convert to file:// URL
- videoSrc = `file:///${video.path.replace(/\\/g, '/')}`;
- }
-
- tempVideo.addEventListener('loadedmetadata', () => {
- const duration = tempVideo.duration;
- tempVideo.remove(); // Clean up
- resolve(duration);
- });
-
- tempVideo.addEventListener('error', (e) => {
- tempVideo.remove(); // Clean up
- reject(new Error(`Failed to load video metadata: ${e.message}`));
- });
-
- // Set timeout to avoid hanging
- setTimeout(() => {
- tempVideo.remove();
- reject(new Error('Timeout loading video metadata'));
- }, 5000);
-
- tempVideo.src = videoSrc;
- });
- }
-
- formatDuration(seconds) {
- if (!seconds || isNaN(seconds)) return '0:00';
-
- const hours = Math.floor(seconds / 3600);
- const minutes = Math.floor((seconds % 3600) / 60);
- const secs = Math.floor(seconds % 60);
-
- if (hours > 0) {
- return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
- } else {
- return `${minutes}:${secs.toString().padStart(2, '0')}`;
- }
- }
-
- formatFileSize(bytes) {
- if (!bytes || bytes === 0) return '0 B';
-
- const sizes = ['B', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
- return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
- }
-}
\ No newline at end of file
diff --git a/src/features/media/videoLibrary.js b/src/features/media/videoLibrary.js
index 471f65c..7400cc7 100644
--- a/src/features/media/videoLibrary.js
+++ b/src/features/media/videoLibrary.js
@@ -132,9 +132,6 @@ class VideoLibrary {
}));
console.log(`📁 Loaded ${this.videos.length} videos`);
- if (this.videos.length > 0) {
- console.log('📁 Sample video object:', this.videos[0]);
- }
// Apply current filters and display
this.applyFiltersAndSort();
diff --git a/src/features/tasks/interactiveTaskManager.js b/src/features/tasks/interactiveTaskManager.js
index f4600bb..f15b85a 100644
--- a/src/features/tasks/interactiveTaskManager.js
+++ b/src/features/tasks/interactiveTaskManager.js
@@ -1161,10 +1161,11 @@ class InteractiveTaskManager {
🔊
+ style="width: 60px !important; max-width: 60px !important; min-width: 60px !important; height: 3px !important; accent-color: #673ab7;">
50%
diff --git a/src/features/video/videoPlayerManager.js b/src/features/video/videoPlayerManager.js
index feb5bbf..ceecb2e 100644
--- a/src/features/video/videoPlayerManager.js
+++ b/src/features/video/videoPlayerManager.js
@@ -148,24 +148,21 @@ class VideoPlayerManager {
try {
// In Electron environment, use the desktop file manager
if (window.electronAPI && window.desktopFileManager) {
- // Map category names to match file manager
- const dirCategory = category === 'task' ? 'tasks' :
- category === 'reward' ? 'rewards' :
- category === 'punishment' ? 'punishments' : category;
- const videos = await window.desktopFileManager.scanDirectoryForVideos(dirCategory);
- this.videoLibrary[category] = videos;
+ // Use unified video library - get all videos and filter by category if needed
+ const allVideos = window.desktopFileManager.getAllVideos();
+
+ // For now, just use all videos for any category since we removed categories
+ // In the future, we could add tags or other filtering mechanisms
+ this.videoLibrary[category] = allVideos;
} else {
- // In browser environment, try to load from localStorage
- const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
- const dirCategory = category === 'task' ? 'tasks' :
- category === 'reward' ? 'rewards' :
- category === 'punishment' ? 'punishments' : category;
- this.videoLibrary[category] = storedVideos[dirCategory] || [];
+ // In browser environment, try to load from unified storage
+ const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
+ this.videoLibrary[category] = unifiedData.allVideos || [];
}
- console.log(`📹 Found ${this.videoLibrary[category].length} ${category} videos`);
+ console.log(`📹 Found ${this.videoLibrary[category].length} ${category} videos (from unified library)`);
} catch (error) {
- console.warn(`⚠️ Could not scan ${category} videos:`, error);
+ console.warn(`⚠️ Could not load ${category} videos:`, error);
this.videoLibrary[category] = [];
}
}
diff --git a/src/styles/styles-original-backup.css b/src/styles/styles-original-backup.css
deleted file mode 100644
index 3a4d7a7..0000000
--- a/src/styles/styles-original-backup.css
+++ /dev/null
@@ -1,2931 +0,0 @@
-/* Reset and base styles */
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-body {
- font-family: 'Arial', sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.game-container {
- background: white;
- border-radius: 15px;
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
- width: 95%;
- max-width: 1400px; /* Increased from 800px for larger screens */
- min-width: 780px; /* Ensure minimum usable width */
- min-height: 600px;
- overflow: hidden;
- margin: 10px auto; /* Center and add some margin */
-}
-
-/* Header */
-.game-header {
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- color: white;
- padding: 20px;
- text-align: center;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.game-header h1 {
- font-size: 2em;
- margin: 0;
-}
-
-.tagline {
- font-size: 1.2em;
- color: #666;
- font-style: italic;
- margin: 10px 0 20px 0;
- opacity: 0.9;
-}
-
-/* Compact Timer (top-right corner) */
-.timer-compact {
- position: absolute;
- top: 10px;
- right: 80px;
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 0.9em;
- background: rgba(0, 0, 0, 0.1);
- padding: 5px 10px;
- border-radius: 15px;
- backdrop-filter: blur(5px);
-}
-
-.timer {
- font-weight: bold;
- font-family: 'Courier New', monospace;
-}
-
-.timer-status {
- font-size: 0.8em;
- opacity: 0.8;
-}
-
-/* Compact Music Controls (expandable on hover) */
-.music-controls-compact {
- position: absolute;
- top: 10px;
- right: 20px;
-}
-
-.music-icon-btn {
- background: rgba(0, 0, 0, 0.1);
- border: none;
- color: white;
- font-size: 1.5em;
- padding: 8px;
- border-radius: 50%;
- cursor: pointer;
- transition: all 0.3s ease;
- backdrop-filter: blur(5px);
- width: 45px;
- height: 45px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.music-icon-btn:hover {
- background: rgba(255, 255, 255, 0.2);
- transform: scale(1.1);
-}
-
-/* Expandable Music Panel */
-.music-panel-expanded {
- position: absolute;
- top: 55px;
- right: 0;
- background: rgba(255, 255, 255, 0.95);
- color: #333;
- border-radius: 10px;
- padding: 15px;
- box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
- backdrop-filter: blur(10px);
- opacity: 0;
- visibility: hidden;
- transform: translateY(-10px);
- transition: all 0.3s ease;
- min-width: 200px;
- z-index: 1000;
-}
-
-.music-controls-compact:hover .music-panel-expanded {
- opacity: 1;
- visibility: visible;
- transform: translateY(0);
-}
-
-.music-row {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 10px;
- margin-bottom: 10px;
-}
-
-.music-row:last-child {
- margin-bottom: 0;
-}
-
-.music-btn-small {
- background: #007bff;
- border: none;
- color: white;
- font-size: 1em;
- padding: 6px 10px;
- border-radius: 5px;
- cursor: pointer;
- transition: all 0.2s ease;
- min-width: 35px;
-}
-
-.music-btn-small:hover {
- background: #0056b3;
- transform: scale(1.05);
-}
-
-.track-dropdown-compact {
- background: white;
- border: 1px solid #ddd;
- border-radius: 5px;
- padding: 5px 8px;
- font-size: 0.9em;
- width: 100%;
- cursor: pointer;
-}
-
-.volume-control-compact {
- display: flex;
- align-items: center;
- gap: 8px;
- width: 100%;
-}
-
-.volume-icon {
- font-size: 1em;
-}
-
-.volume-slider-compact {
- flex: 1;
- height: 4px;
- background: #ddd;
- border-radius: 2px;
- outline: none;
- cursor: pointer;
-}
-
-.volume-slider-compact::-webkit-slider-thumb {
- appearance: none;
- width: 16px;
- height: 16px;
- background: #007bff;
- border-radius: 50%;
- cursor: pointer;
-}
-
-.volume-percent {
- font-size: 0.8em;
- min-width: 30px;
- text-align: center;
-}
-
-.music-status-compact {
- text-align: center;
- font-size: 0.8em;
- color: #666;
- font-style: italic;
-}
-
-/* Data Management Controls */
-.data-controls {
- display: flex;
- gap: 10px;
- margin: 10px 0;
- flex-wrap: wrap;
- justify-content: center;
-}
-
-.data-btn, .btn-secondary {
- background: rgba(255, 255, 255, 0.2);
- border: 2px solid rgba(255, 255, 255, 0.3);
- color: white;
- padding: 8px 16px;
- border-radius: 8px;
- cursor: pointer;
- font-size: 0.9em;
- transition: all 0.3s ease;
-}
-
-.data-btn:hover, .btn-secondary:hover {
- background: rgba(255, 255, 255, 0.3);
- transform: translateY(-2px);
-}
-
-/* Statistics Modal */
-.modal {
- position: fixed;
- z-index: 1000;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.7);
- backdrop-filter: blur(5px);
-}
-
-.modal-content {
- background: var(--card-bg);
- margin: 5% auto;
- padding: 0;
- border-radius: 16px;
- width: 90%;
- max-width: 800px;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
- animation: modalSlideIn 0.3s ease;
-}
-
-@keyframes modalSlideIn {
- from { transform: translateY(-50px); opacity: 0; }
- to { transform: translateY(0); opacity: 1; }
-}
-
-.modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20px 30px;
- border-bottom: 2px solid var(--primary-color);
- background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
- color: white;
- border-radius: 16px 16px 0 0;
-}
-
-.modal-header h2 {
- margin: 0;
- font-size: 1.5em;
-}
-
-.close {
- font-size: 28px;
- font-weight: bold;
- cursor: pointer;
- opacity: 0.8;
- transition: opacity 0.3s ease;
-}
-
-.close:hover {
- opacity: 1;
- transform: scale(1.1);
-}
-
-.modal-body {
- padding: 30px;
-}
-
-.stats-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 20px;
- margin-bottom: 30px;
-}
-
-.stat-card {
- background: rgba(255, 255, 255, 0.05);
- border: 2px solid rgba(255, 255, 255, 0.1);
- border-radius: 12px;
- padding: 20px;
- text-align: center;
- transition: all 0.3s ease;
-}
-
-.stat-card:hover {
- border-color: var(--primary-color);
- transform: translateY(-2px);
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
-}
-
-.stat-card h3 {
- margin: 0 0 10px 0;
- font-size: 1em;
- color: var(--text-color);
- opacity: 0.8;
-}
-
-.stat-value {
- font-size: 2.5em;
- font-weight: bold;
- color: var(--primary-color);
- line-height: 1;
-}
-
-.stats-actions {
- display: flex;
- gap: 15px;
- justify-content: center;
- flex-wrap: wrap;
-}
-
-.btn-warning {
- background: #ff6b35;
- border-color: #ff6b35;
-}
-
-.btn-warning:hover {
- background: #e55a2e;
-}
-
-/* Notification System */
-.notification {
- position: fixed;
- top: 20px;
- right: 20px;
- background: var(--card-bg);
- border: 2px solid var(--primary-color);
- color: var(--text-color);
- padding: 15px 20px;
- border-radius: 8px;
- font-weight: 500;
- transform: translateX(400px);
- opacity: 0;
- transition: all 0.3s ease;
- z-index: 1001;
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
-}
-
-.notification.show {
- transform: translateX(0);
- opacity: 1;
-}
-
-.notification.success {
- border-color: #28a745;
- background: rgba(40, 167, 69, 0.1);
-}
-
-.notification.error {
- border-color: #dc3545;
- background: rgba(220, 53, 69, 0.1);
-}
-
-.notification.info {
- border-color: var(--primary-color);
- background: rgba(var(--primary-color-rgb), 0.1);
-}
-
-/* Streak Bonus Notification */
-.streak-bonus-notification {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%) scale(0.8);
- opacity: 0;
- background: linear-gradient(135deg, #ff6b35, #f7931e);
- color: white;
- padding: 20px;
- border-radius: 15px;
- text-align: center;
- box-shadow: 0 10px 30px rgba(255, 107, 53, 0.4);
- z-index: 1002;
- transition: all 0.3s ease;
- backdrop-filter: blur(10px);
- border: 2px solid rgba(255, 255, 255, 0.2);
-}
-
-.streak-bonus-notification.show {
- transform: translate(-50%, -50%) scale(1);
- opacity: 1;
-}
-
-.streak-bonus-content {
- display: flex;
- align-items: center;
- gap: 15px;
-}
-
-.streak-icon {
- font-size: 2.5em;
- animation: fireFlicker 1s ease-in-out infinite alternate;
-}
-
-.streak-text {
- text-align: left;
-}
-
-.streak-title {
- font-size: 1.2em;
- font-weight: bold;
- margin-bottom: 5px;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
-}
-
-.streak-bonus {
- font-size: 1em;
- opacity: 0.9;
- font-weight: 500;
-}
-
-@keyframes fireFlicker {
- 0% { transform: rotate(-2deg) scale(1); }
- 100% { transform: rotate(2deg) scale(1.05); }
-}
-
-/* Streak Milestone Styling */
-.streak-stat.streak-milestone {
- background: linear-gradient(45deg, rgba(255, 107, 53, 0.1), rgba(247, 147, 30, 0.1));
- border-radius: 8px;
- padding: 5px 8px;
- border: 1px solid rgba(255, 107, 53, 0.3);
- animation: streakGlow 2s ease-in-out infinite alternate;
-}
-
-@keyframes streakGlow {
- 0% { box-shadow: 0 0 5px rgba(255, 107, 53, 0.3); }
- 100% { box-shadow: 0 0 15px rgba(255, 107, 53, 0.5); }
-}
-.audio-bar:nth-child(4) { animation-delay: 0.6s; }
-.audio-bar:nth-child(5) { animation-delay: 0.8s; }
-
-@keyframes audioWave {
- 0%, 100% { height: 4px; }
- 50% { height: 16px; }
-}
-
-/* Desktop Application Styles */
-.desktop-mode .upload-controls {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- margin-bottom: 10px;
-}
-
-.desktop-mode .upload-controls .btn {
- flex: 1;
- min-width: 180px;
-}
-
-.desktop-only {
- display: none;
-}
-
-.desktop-mode .desktop-only {
- display: inline-block;
-}
-
-.desktop-feature {
- display: none;
-}
-
-.desktop-mode .desktop-feature {
- display: inline;
-}
-
-.web-feature {
- display: inline;
-}
-
-.desktop-mode .web-feature {
- display: none;
-}
-
-/* Enhanced button styling for desktop */
-.desktop-mode .btn {
- padding: 12px 20px;
- font-size: 14px;
- font-weight: 500;
- border-radius: 6px;
- transition: all 0.2s ease;
-}
-
-.desktop-mode .btn:hover {
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-}
-
-.desktop-mode .btn-primary {
- background: linear-gradient(135deg, #007bff, #0056b3);
- border: none;
- color: white;
-}
-
-.desktop-mode .btn-warning {
- background: linear-gradient(135deg, #ffc107, #e0a800);
- border: none;
- color: #212529;
-}
-
-/* Desktop-specific upload section */
-.desktop-mode .upload-section h3 {
- color: var(--primary-color);
- margin-bottom: 15px;
- font-size: 18px;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.desktop-mode .upload-info {
- font-size: 13px;
- color: var(--text-secondary);
- font-style: italic;
- margin-top: 8px;
-}
-
-/* Responsive design for music controls */
-@media (max-width: 768px) {
- .game-header {
- flex-direction: column;
- gap: 15px;
- }
-
- .game-header h1 {
- font-size: 1.6em;
- }
-
- .timer-container {
- position: static;
- transform: none;
- }
-
- .music-controls {
- justify-content: center;
- gap: 10px;
- }
-
- .track-dropdown {
- min-width: 120px;
- font-size: 0.8em;
- }
-
- .volume-control {
- padding: 4px 8px;
- }
-
- .volume-slider {
- width: 60px;
- }
-
- .music-status {
- display: none; /* Hide on mobile to save space */
- }
-}
-
-@media (max-width: 480px) {
- .music-controls {
- flex-wrap: wrap;
- justify-content: center;
- }
-
- .track-dropdown {
- order: 3;
- flex-basis: 100%;
- margin-top: 5px;
- }
-}
-
-.timer {
- font-family: 'Courier New', monospace;
- font-size: 1.5em;
- font-weight: bold;
-}
-
-.timer-status {
- font-size: 0.9em;
- opacity: 0.8;
-}
-
-/* Screen management */
-.screen {
- display: none;
- padding: 20px;
- text-align: center;
- min-height: 500px;
-}
-
-.screen.active {
- display: block;
-}
-
-/* Start screen */
-#start-screen h2 {
- color: #333;
- margin-bottom: 15px;
-}
-
-#start-screen p {
- color: #666;
- margin-bottom: 25px;
- font-size: 1.1em;
-}
-
-/* Theme Selector */
-.theme-selector {
- margin-bottom: 25px;
-}
-
-.theme-label {
- display: block;
- margin-bottom: 10px;
- font-size: 1.1em;
- font-weight: 600;
- color: #333;
-}
-
-.theme-dropdown {
- padding: 10px 15px;
- font-size: 1em;
- border: 2px solid #ddd;
- border-radius: 8px;
- background: white;
- cursor: pointer;
- min-width: 200px;
- transition: border-color 0.3s ease;
-}
-
-.theme-dropdown:hover {
- border-color: #007bff;
-}
-
-.theme-dropdown:focus {
- outline: none;
- border-color: #007bff;
- box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
-}
-
-/* Task Management Screen */
-#task-management-screen h2 {
- color: #333;
- margin-bottom: 25px;
-}
-
-.task-editor-section {
- background: #f8f9fa;
- border-radius: 10px;
- padding: 20px;
- margin-bottom: 25px;
-}
-
-.task-editor-section h3 {
- color: #333;
- margin-bottom: 15px;
- font-size: 1.2em;
-}
-
-.task-input-group {
- margin-bottom: 15px;
-}
-
-.task-input-group label {
- display: block;
- margin-bottom: 5px;
- font-weight: 600;
- color: #555;
-}
-
-.task-input-group textarea,
-.task-input-group select {
- width: 100%;
- padding: 10px;
- border: 2px solid #ddd;
- border-radius: 6px;
- font-size: 1em;
- transition: border-color 0.3s ease;
-}
-
-.task-input-group textarea:focus,
-.task-input-group select:focus {
- outline: none;
- border-color: #007bff;
- box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
-}
-
-.task-list-section {
- margin-bottom: 25px;
-}
-
-.task-list-section h3 {
- color: #333;
- margin-bottom: 15px;
- font-size: 1.2em;
-}
-
-.task-tabs {
- display: flex;
- margin-bottom: 15px;
- border-bottom: 2px solid #ddd;
-}
-
-.tab-btn {
- padding: 10px 20px;
- border: none;
- background: none;
- cursor: pointer;
- font-size: 1em;
- font-weight: 600;
- color: #666;
- border-bottom: 3px solid transparent;
- transition: all 0.3s ease;
-}
-
-.tab-btn.active {
- color: #007bff;
- border-bottom-color: #007bff;
-}
-
-.tab-btn:hover {
- color: #007bff;
-}
-
-.task-list {
- display: none;
- max-height: 300px;
- overflow-y: auto;
- border: 1px solid #ddd;
- border-radius: 6px;
- background: white;
-}
-
-.task-list.active {
- display: block;
-}
-
-.task-item {
- padding: 15px;
- border-bottom: 1px solid #eee;
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
-}
-
-.task-item:last-child {
- border-bottom: none;
-}
-
-.task-text-display {
- flex: 1;
- margin-right: 15px;
- line-height: 1.4;
-}
-
-.task-difficulty-display {
- font-size: 0.85em;
- color: #666;
- margin: 5px 15px 5px 0;
- font-weight: bold;
-}
-
-.task-actions {
- display: flex;
- gap: 8px;
-}
-
-.btn-small {
- padding: 5px 10px;
- font-size: 0.85em;
- min-width: auto;
-}
-
-.management-buttons {
- display: flex;
- gap: 15px;
- justify-content: center;
-}
-
-.empty-list {
- padding: 40px;
- text-align: center;
- color: #666;
- font-style: italic;
-}
-
-/* Task container */
-.task-container {
- background: #f8f9fa;
- border-radius: 15px;
- padding: 25px;
- margin-bottom: 20px;
-}
-
-.task-type {
- margin-bottom: 15px;
-}
-
-#task-type-indicator {
- background: #28a745;
- color: white;
- padding: 5px 15px;
- border-radius: 20px;
- font-size: 0.9em;
- font-weight: bold;
-}
-
-#task-type-indicator.consequence {
- background: #dc3545;
-}
-
-.task-image-container {
- margin-bottom: 20px;
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: max(200px, 25vh); /* Dynamic minimum height based on viewport */
- max-height: 85vh; /* Allow container to take up most of screen */
- width: 100%; /* Full width for centering */
-}
-
-.task-image {
- /* Hybrid responsive sizing - scales with viewport but has reasonable limits */
- max-width: min(90vw, 1200px); /* 90% of viewport width, capped at 1200px */
- max-height: min(70vh, 800px); /* 70% of viewport height, capped at 800px */
- min-height: max(200px, 20vh); /* Minimum 200px or 20% of viewport height */
- min-width: 250px; /* Prevent images from being too narrow */
-
- /* Maintain aspect ratio and image quality */
- width: auto;
- height: auto;
- object-fit: contain; /* Preserve aspect ratio, fit within bounds */
-
- /* Visual enhancements */
- border-radius: 15px;
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
- transition: all 0.3s ease; /* Smooth transitions when resizing */
- object-fit: cover;
-}
-
-.task-text-container {
- margin-bottom: 25px;
-}
-
-.task-text {
- color: #333;
- font-size: 1.2em;
- line-height: 1.4;
-}
-
-/* Task difficulty and points display */
-.task-difficulty {
- background: rgba(255, 255, 255, 0.8);
- padding: 8px 16px;
- border-radius: 20px;
- font-size: 0.9em;
- font-weight: bold;
- margin-bottom: 15px;
- text-align: center;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
-}
-
-.task-points {
- background: #ffd700;
- color: #333;
- padding: 6px 12px;
- border-radius: 15px;
- font-size: 0.8em;
- font-weight: bold;
- margin-left: 10px;
- box-shadow: 0 1px 3px rgba(0,0,0,0.2);
-}
-
-/* Buttons */
-.action-buttons {
- display: flex;
- gap: 15px;
- justify-content: center;
- margin-bottom: 20px;
-}
-
-.btn {
- padding: 12px 24px;
- border: none;
- border-radius: 6px;
- font-size: 1em;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.3s ease;
- min-width: 100px;
-}
-
-.btn:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
-}
-
-.btn-primary {
- background: #007bff;
- color: white;
-}
-
-.btn-primary:hover {
- background: #0056b3;
-}
-
-.btn-success {
- background: #28a745;
- color: white;
-}
-
-.btn-success:hover {
- background: #1e7e34;
-}
-
-.btn-warning {
- background: #ffc107;
- color: #212529;
-}
-
-.btn-warning:hover {
- background: #e0a800;
-}
-
-.btn-info {
- background: #17a2b8;
- color: white;
-}
-
-.btn-info:hover {
- background: #117a8b;
-}
-
-.btn-danger {
- background: #dc3545;
- color: white;
- position: relative;
-}
-
-.btn-danger:hover {
- background: #c82333;
-}
-
-.btn-danger:disabled {
- background: #6c757d;
- color: #ffffff;
- opacity: 0.65;
- cursor: not-allowed;
-}
-
-.btn-danger:disabled:hover {
- background: #6c757d;
- transform: none;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-/* Options Menu Styles */
-.options-section {
- margin-top: 20px;
- position: relative;
-}
-
-.btn-tertiary {
- background: #6c757d;
- color: white;
- margin-bottom: 10px;
-}
-
-.btn-tertiary:hover {
- background: #545b62;
-}
-
-.options-menu {
- background: #f8f9fa;
- border: 1px solid #dee2e6;
- border-radius: 8px;
- padding: 20px;
- margin-top: 10px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
-}
-
-.option-item {
- margin-bottom: 15px;
- padding-bottom: 15px;
- border-bottom: 1px solid #e9ecef;
-}
-
-.option-item:last-child {
- margin-bottom: 0;
- padding-bottom: 0;
- border-bottom: none;
-}
-
-.option-label {
- display: block;
- font-weight: 600;
- margin-bottom: 8px;
- color: #495057;
-}
-
-.theme-dropdown {
- width: 100%;
- padding: 8px 12px;
- border: 1px solid #ced4da;
- border-radius: 4px;
- font-size: 0.95em;
-}
-
-.data-controls, .other-controls {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
-}
-
-.btn-option {
- background: #e9ecef;
- color: #495057;
- padding: 8px 16px;
- font-size: 0.9em;
- min-width: auto;
-}
-
-.btn-option:hover {
- background: #dee2e6;
- transform: translateY(-1px);
-}
-
-.main-actions {
- display: flex;
- flex-direction: column;
- gap: 15px;
- margin-bottom: 20px;
-}
-
-.main-actions .btn {
- width: 100%;
- max-width: 300px;
- margin: 0 auto;
-}
-
-/* Game Mode Selection */
-.game-mode-selection {
- background: #f8f9fa;
- border-radius: 10px;
- padding: 20px;
- margin: 20px 0;
- border: 2px solid #e0e0e0;
-}
-
-.game-mode-selection h3 {
- color: #333;
- margin-bottom: 15px;
- text-align: center;
- font-size: 1.2em;
-}
-
-.game-mode-options {
- display: flex;
- flex-direction: column;
- gap: 15px;
-}
-
-.game-mode-option {
- position: relative;
-}
-
-.game-mode-option input[type="radio"] {
- position: absolute;
- opacity: 0;
- cursor: pointer;
-}
-
-.game-mode-option label {
- display: block;
- background: white;
- border: 2px solid #ddd;
- border-radius: 8px;
- padding: 15px;
- cursor: pointer;
- transition: all 0.3s ease;
- position: relative;
-}
-
-.game-mode-option label:hover {
- border-color: #4facfe;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(79, 172, 254, 0.2);
-}
-
-.game-mode-option input[type="radio"]:checked + label {
- border-color: #4facfe;
- background: linear-gradient(135deg, #4facfe22, #00f2fe11);
- box-shadow: 0 4px 12px rgba(79, 172, 254, 0.3);
-}
-
-.game-mode-option label strong {
- color: #333;
- font-size: 1.1em;
- display: block;
- margin-bottom: 5px;
-}
-
-.game-mode-option label p {
- color: #666;
- font-size: 0.9em;
- margin: 0;
- line-height: 1.4;
-}
-
-.mode-config {
- margin-top: 15px;
- padding-top: 15px;
- border-top: 1px solid #eee;
-}
-
-.mode-config label {
- font-size: 0.9em;
- color: #555;
- font-weight: 500;
-}
-
-.mode-config select {
- margin-left: 10px;
- padding: 5px 10px;
- border: 1px solid #ddd;
- border-radius: 4px;
- background: white;
- font-size: 0.9em;
-}
-
-.custom-time-input,
-.custom-score-input {
- margin-left: 10px;
- padding: 5px 10px;
- border: 1px solid #ddd;
- border-radius: 4px;
- background: white;
- font-size: 0.9em;
- width: 80px;
- display: none;
-}
-
-.custom-time-input:focus,
-.custom-score-input:focus {
- border-color: #007bff;
- outline: none;
- box-shadow: 0 0 3px rgba(0, 123, 255, 0.3);
-}
-
-.mercy-cost {
- display: block;
- font-size: 0.8em;
- opacity: 0.9;
- font-weight: bold;
-}
-
-.btn-secondary {
- background: #6c757d;
- color: white;
-}
-
-.btn-secondary:hover {
- background: #545b62;
-}
-
-/* Loading indicator styles */
-.btn:disabled {
- opacity: 0.7;
- cursor: not-allowed;
-}
-
-.btn:disabled:hover {
- transform: none;
- box-shadow: none;
-}
-
-.btn-loading {
- animation: pulse 1.5s ease-in-out infinite;
-}
-
-@keyframes pulse {
- 0% { opacity: 1; }
- 50% { opacity: 0.6; }
- 100% { opacity: 1; }
-}
-
-/* Game stats */
-.game-stats {
- display: flex;
- justify-content: space-around;
- background: #e9ecef;
- padding: 15px;
- border-radius: 8px;
-}
-
-.stat {
- text-align: center;
-}
-
-#score {
- color: #ffd700;
- font-weight: bold;
- font-size: 1.3em;
- text-shadow: 0 1px 3px rgba(0,0,0,0.3);
- background: rgba(255, 215, 0, 0.1);
- padding: 4px 8px;
- border-radius: 6px;
- border: 1px solid rgba(255, 215, 0, 0.3);
-}
-
-.stat-label {
- display: block;
- font-size: 0.9em;
- color: #666;
- margin-bottom: 5px;
-}
-
-.stat-value {
- display: block;
- font-size: 1.5em;
- font-weight: bold;
- color: #333;
-}
-
-/* Paused screen */
-#paused-screen h2 {
- color: #333;
- margin-bottom: 15px;
-}
-
-#paused-screen p {
- color: #666;
- margin-bottom: 25px;
-}
-
-#paused-screen .btn {
- margin: 0 10px;
-}
-
-/* Game over screen */
-#game-over-screen h2 {
- color: #333;
- margin-bottom: 20px;
-}
-
-.final-stats {
- background: #f8f9fa;
- padding: 20px;
- border-radius: 8px;
- margin-bottom: 25px;
-}
-
-.final-stats p {
- margin-bottom: 10px;
- font-size: 1.1em;
- color: #333;
-}
-
-.final-stats span {
- font-weight: bold;
- color: #007bff;
-}
-
-/* Responsive design */
-@media (max-width: 480px) {
- .game-container {
- width: 98%;
- }
-
- .task-image-container {
- min-height: 250px;
- }
-
- .task-image {
- max-width: 95%;
- max-height: 250px;
- min-height: 200px;
- }
-
- .action-buttons {
- flex-direction: column;
- align-items: center;
- }
-
- .btn {
- width: 200px;
- }
-
- .game-stats {
- flex-direction: column;
- gap: 10px;
- }
-
- .timer-container {
- flex-direction: column;
- gap: 5px;
- }
-
- .screen {
- padding: 15px;
- }
-}
-
-/* THEME SYSTEM */
-/* Ocean Theme (Default) - Already defined above */
-
-/* Sunset Theme */
-body.theme-sunset {
- background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%);
-}
-.theme-sunset .game-header {
- background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%);
-}
-.theme-sunset .btn-primary { background: #ff6b35; }
-.theme-sunset .btn-primary:hover { background: #e55a2b; }
-.theme-sunset .btn-success { background: #ff8c00; }
-.theme-sunset .btn-success:hover { background: #e67e00; }
-
-/* Forest Theme */
-body.theme-forest {
- background: linear-gradient(135deg, #134e5e 0%, #71b280 100%);
-}
-.theme-forest .game-header {
- background: linear-gradient(135deg, #2d5016 0%, #a8e6cf 100%);
-}
-.theme-forest .btn-primary { background: #2d5016; }
-.theme-forest .btn-primary:hover { background: #1e3510; }
-.theme-forest .btn-success { background: #4caf50; }
-.theme-forest .btn-success:hover { background: #45a049; }
-
-/* Midnight Theme */
-body.theme-midnight {
- background: linear-gradient(135deg, #0c0c0c 0%, #434343 100%);
-}
-.theme-midnight .game-container {
- background: #1a1a1a;
- color: white;
-}
-.theme-midnight .game-header {
- background: linear-gradient(135deg, #000000 0%, #434343 100%);
-}
-.theme-midnight .task-container {
- background: #2a2a2a;
- color: white;
-}
-.theme-midnight .task-text {
- color: white;
-}
-.theme-midnight .game-stats {
- background: #2a2a2a;
-}
-.theme-midnight .btn-primary { background: #6c757d; }
-.theme-midnight .btn-primary:hover { background: #545b62; }
-
-/* Pastel Theme */
-body.theme-pastel {
- background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
-}
-.theme-pastel .game-header {
- background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
-}
-.theme-pastel .btn-primary { background: #ff9a9e; }
-.theme-pastel .btn-primary:hover { background: #ff8a8e; }
-.theme-pastel .btn-success { background: #a8e6cf; }
-.theme-pastel .btn-success:hover { background: #98d6bf; }
-.theme-pastel .btn-warning { background: #ffd3a5; color: #333; }
-.theme-pastel .btn-warning:hover { background: #ffc385; }
-
-/* Neon Theme */
-body.theme-neon {
- background: linear-gradient(135deg, #0f0f0f 0%, #2a0845 100%);
-}
-.theme-neon .game-container {
- background: #1a1a1a;
- color: #00ff00;
- border: 2px solid #00ff00;
- box-shadow: 0 0 30px rgba(0, 255, 0, 0.3);
-}
-.theme-neon .game-header {
- background: linear-gradient(135deg, #000000 0%, #ff006e 100%);
- border-bottom: 2px solid #00ff00;
-}
-.theme-neon .task-container {
- background: #0a0a0a;
- color: #00ff00;
- border: 1px solid #00ff00;
-}
-.theme-neon .task-text {
- color: #00ff00;
-}
-.theme-neon .btn-primary {
- background: #ff006e;
- border: 2px solid #00ff00;
- box-shadow: 0 0 10px rgba(255, 0, 110, 0.5);
-}
-.theme-neon .btn-success {
- background: #00ff00;
- color: black;
- box-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
-}
-
-/* Autumn Theme */
-body.theme-autumn {
- background: linear-gradient(135deg, #d2691e 0%, #8b4513 100%);
-}
-.theme-autumn .game-header {
- background: linear-gradient(135deg, #cd853f 0%, #a0522d 100%);
-}
-.theme-autumn .btn-primary { background: #8b4513; }
-.theme-autumn .btn-primary:hover { background: #7a3f12; }
-.theme-autumn .btn-success { background: #daa520; }
-.theme-autumn .btn-success:hover { background: #b8941b; }
-.theme-autumn .btn-warning { background: #ff8c00; }
-.theme-autumn .btn-warning:hover { background: #e67e00; }
-
-/* Monochrome Theme */
-body.theme-monochrome {
- background: linear-gradient(135deg, #000000 0%, #434343 100%);
-}
-.theme-monochrome .game-container {
- background: white;
- border: 3px solid black;
-}
-.theme-monochrome .game-header {
- background: black;
- border-bottom: 3px solid white;
-}
-.theme-monochrome .task-container {
- background: #f8f8f8;
- border: 2px solid black;
-}
-.theme-monochrome .btn-primary {
- background: black;
- color: white;
- border: 2px solid black;
-}
-.theme-monochrome .btn-primary:hover {
- background: white;
- color: black;
-}
-.theme-monochrome .btn-success {
- background: white;
- color: black;
- border: 2px solid black;
-}
-.theme-monochrome .btn-success:hover {
- background: black;
- color: white;
-}
-
-/* Help Menu Styles */
-.help-section {
- margin-bottom: 25px;
-}
-
-.help-section h3 {
- color: #333;
- margin-bottom: 15px;
- padding-bottom: 8px;
- border-bottom: 2px solid #e9ecef;
- font-size: 1.1em;
-}
-
-.shortcut-list {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.shortcut-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 12px;
- background: #f8f9fa;
- border-radius: 6px;
- border-left: 4px solid #007bff;
-}
-
-.shortcut-key {
- background: #343a40;
- color: white;
- padding: 4px 12px;
- border-radius: 4px;
- font-family: monospace;
- font-weight: bold;
- font-size: 0.9em;
- min-width: 60px;
- text-align: center;
-}
-
-.shortcut-action {
- flex: 1;
- margin-left: 15px;
- color: #555;
- font-weight: 500;
-}
-
-.help-tips {
- background: #e3f2fd;
- padding: 15px;
- border-radius: 8px;
- border-left: 4px solid #2196f3;
- margin: 0;
-}
-
-.help-tips li {
- margin-bottom: 8px;
- color: #1565c0;
- line-height: 1.4;
-}
-
-.help-tips li:last-child {
- margin-bottom: 0;
-}
-
-/* Image Management Styles */
-.upload-section {
- background: #f8f9fa;
- padding: 20px;
- border-radius: 8px;
- margin-bottom: 30px;
- border: 2px dashed #dee2e6;
-}
-
-.upload-controls {
- display: flex;
- align-items: center;
- gap: 15px;
-}
-
-.upload-info {
- font-size: 0.9em;
- color: #6c757d;
- font-style: italic;
-}
-
-.gallery-section {
- margin-bottom: 30px;
-}
-
-.gallery-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- flex-wrap: wrap;
- gap: 15px;
-}
-
-.gallery-controls {
- display: flex;
- align-items: center;
- gap: 10px;
- flex-wrap: wrap;
-}
-
-.image-count {
- font-size: 0.9em;
- color: #6c757d;
- font-weight: 500;
-}
-
-.image-gallery {
- display: none; /* Hide galleries by default */
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 15px;
- max-height: 400px;
- overflow-y: auto;
- padding: 10px;
- border: 1px solid #dee2e6;
- border-radius: 8px;
-}
-
-.image-gallery.active {
- display: grid; /* Show active gallery as grid */
-}
-
-.image-item {
- position: relative;
- background: white;
- border-radius: 8px;
- overflow: hidden;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- transition: all 0.3s ease;
-}
-
-.image-item:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
-}
-
-.image-item.selected {
- border: 3px solid #007bff;
- transform: scale(0.95);
-}
-
-.image-preview {
- width: 100%;
- height: 150px;
- object-fit: cover;
- cursor: pointer;
-}
-
-.image-info {
- padding: 10px;
- background: white;
-}
-
-.image-name {
- font-size: 0.8em;
- color: #333;
- margin-bottom: 5px;
- word-break: break-word;
-}
-
-.image-controls {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.image-checkbox {
- margin: 0;
-}
-
-.image-status {
- font-size: 0.7em;
- padding: 2px 6px;
- border-radius: 4px;
- font-weight: bold;
-}
-
-.image-enabled {
- background: #d4edda;
- color: #155724;
-}
-
-.image-disabled {
- background: #f8d7da;
- color: #721c24;
-}
-
-.loading, .no-images {
- text-align: center;
- padding: 40px;
- color: #6c757d;
- font-style: italic;
-}
-
-.no-images {
- background: #f8f9fa;
- border-radius: 8px;
- border: 2px dashed #dee2e6;
-}
-
-/* Audio Management Styles */
-.audio-tabs {
- display: flex;
- margin-bottom: 15px;
- border-bottom: 2px solid #dee2e6;
-}
-
-.audio-tabs .tab-btn {
- flex: 1;
- padding: 12px 16px;
- border: none;
- background: #f8f9fa;
- color: #6c757d;
- cursor: pointer;
- border-bottom: 3px solid transparent;
- transition: all 0.3s ease;
- font-weight: 600;
-}
-
-.audio-tabs .tab-btn:hover {
- background: #e9ecef;
- color: #495057;
-}
-
-.audio-tabs .tab-btn.active {
- background: white;
- color: #007bff;
- border-bottom-color: #007bff;
-}
-
-.audio-count {
- font-size: 0.9em;
- color: #6c757d;
- font-weight: 500;
-}
-
-.audio-gallery {
- display: none; /* Hide galleries by default */
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
- gap: 15px;
- max-height: 400px;
- overflow-y: auto;
- padding: 10px;
- border: 1px solid #dee2e6;
- border-radius: 8px;
-}
-
-.audio-gallery.active {
- display: grid; /* Show active gallery as grid */
-}
-
-.audio-item {
- position: relative;
- background: white;
- border-radius: 8px;
- overflow: hidden;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- transition: all 0.3s ease;
- padding: 15px;
-}
-
-.audio-item:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
-}
-
-.audio-item.selected {
- border: 3px solid #007bff;
- transform: scale(0.98);
- background: #f8f9ff;
-}
-
-.audio-icon {
- font-size: 2.5em;
- text-align: center;
- margin-bottom: 10px;
- color: #007bff;
-}
-
-.audio-title {
- font-size: 0.9em;
- color: #333;
- margin-bottom: 8px;
- font-weight: 600;
- word-break: break-word;
- text-align: center;
-}
-
-.audio-filename {
- font-size: 0.7em;
- color: #6c757d;
- margin-bottom: 10px;
- text-align: center;
- word-break: break-word;
-}
-
-.audio-controls {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 10px;
-}
-
-.audio-preview-btn {
- background: #28a745;
- color: white;
- border: none;
- padding: 4px 8px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 0.8em;
- transition: background 0.3s ease;
-}
-
-.audio-preview-btn:hover {
- background: #218838;
-}
-
-.audio-checkbox {
- margin: 0;
-}
-
-.audio-status {
- font-size: 0.7em;
- padding: 2px 6px;
- border-radius: 4px;
- font-weight: bold;
-}
-
-.audio-enabled {
- background: #d4edda;
- color: #155724;
-}
-
-.audio-disabled {
- background: #f8d7da;
- color: #721c24;
-}
-
-.audio-preview-section {
- background: #f8f9fa;
- border: 1px solid #dee2e6;
- border-radius: 8px;
- padding: 20px;
- margin: 20px 0;
-}
-
-.audio-preview-section h4 {
- margin-bottom: 15px;
- color: #333;
-}
-
-.preview-controls {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.preview-info {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 10px;
-}
-
-.preview-info span {
- font-size: 0.9em;
- color: #6c757d;
- font-weight: 500;
-}
-
-.no-audio {
- text-align: center;
- padding: 40px;
- color: #6c757d;
- font-style: italic;
- background: #f8f9fa;
- border-radius: 8px;
- border: 2px dashed #dee2e6;
-}
-
-/* Audio category icons */
-.audio-item[data-category="background"] .audio-icon::before {
- content: "🎵";
-}
-
-.audio-item[data-category="ambient"] .audio-icon::before {
- content: "🌿";
-}
-
-.audio-item[data-category="effects"] .audio-icon::before {
- content: "🔊";
-}
-
-/* ===========================
- RESPONSIVE DESIGN ENHANCEMENTS
- =========================== */
-
-/* Large screens - take advantage of extra space */
-@media (min-width: 1200px) {
- .game-container {
- width: 90%;
- max-width: 1400px;
- }
-
- .game-header h1 {
- font-size: 2.2em;
- }
-
- /* Make better use of horizontal space */
- .main-actions {
- flex-direction: row;
- justify-content: center;
- flex-wrap: wrap;
- gap: 20px;
- }
-
- .main-actions .btn {
- width: auto;
- min-width: 200px;
- max-width: 250px;
- margin: 0;
- }
-
- /* Multi-column layouts for management screens */
- .image-gallery, .audio-gallery {
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 15px;
- }
-
- /* Game mode selection in columns */
- .game-mode-options {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
- gap: 20px;
- }
-}
-
-/* Medium screens - balanced layout */
-@media (min-width: 900px) and (max-width: 1199px) {
- .game-container {
- width: 95%;
- }
-
- .main-actions {
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: center;
- gap: 15px;
- }
-
- .main-actions .btn {
- width: auto;
- min-width: 180px;
- max-width: 220px;
- margin: 0;
- }
-}
-
-/* Small screens - compact layout */
-@media (max-width: 899px) {
- .game-container {
- width: 98%;
- margin: 5px auto;
- border-radius: 10px;
- min-width: 760px; /* Ensure minimum usability */
- }
-
- .game-header {
- padding: 15px;
- }
-
- .game-header h1 {
- font-size: 1.8em;
- }
-
- .main-actions {
- flex-direction: column;
- gap: 12px;
- }
-
- .main-actions .btn {
- width: 100%;
- max-width: 280px;
- }
-
- /* Compact game mode selection */
- .game-mode-selection {
- padding: 15px;
- }
-
- .game-mode-option label {
- padding: 12px;
- }
-
- /* Adjust image and audio galleries */
- .image-gallery, .audio-gallery {
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 10px;
- }
-}
-
-/* Handle very wide screens */
-@media (min-width: 1600px) {
- .game-container {
- max-width: 1600px;
- }
-
- .game-content {
- padding: 30px;
- }
-
- /* Use more columns for galleries on wide screens */
- .image-gallery, .audio-gallery {
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
- gap: 20px;
- }
-}
-
-/* Ensure game screen task display scales well */
-@media (min-width: 1400px) {
- .task-display {
- max-width: 900px;
- margin: 0 auto;
- }
-
- .task-image {
- max-height: min(75vh, 900px); /* Much larger images on very large screens */
- max-width: min(85vw, 1400px); /* Can be much wider on large screens */
- }
-
- .task-image-container {
- min-height: max(300px, 35vh); /* Taller container for larger images */
- }
-}
-
-@media (min-width: 1000px) and (max-width: 1399px) {
- .task-display {
- max-width: 700px;
- margin: 0 auto;
- }
-
- .task-image {
- max-height: min(65vh, 700px); /* Bigger images for medium-large screens */
- max-width: min(88vw, 1000px); /* More width utilization */
- }
-}
-
-/* Compact sizing for smaller windows */
-@media (max-width: 999px) {
- .task-image {
- max-height: min(55vh, 500px); /* Still quite large on compact screens */
- max-width: min(92vw, 600px); /* Take up most of the width */
- min-height: max(180px, 18vh); /* Smaller minimum height */
- }
-
- .task-image-container {
- min-height: max(180px, 20vh); /* Compact container */
- margin-bottom: 15px;
- }
-}
-
-/* Very small windows - still allow decent image size */
-@media (max-width: 800px) {
- .task-image {
- max-height: min(50vh, 400px); /* Half the viewport height on small windows */
- max-width: min(95vw, 500px); /* Nearly full width */
- min-height: max(150px, 15vh); /* Smaller minimum */
- min-width: 200px; /* Smaller minimum width */
- }
-
- .task-image-container {
- min-height: max(150px, 18vh);
- margin-bottom: 10px;
- }
-}
-
-/* High DPI / Retina display adjustments */
-@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
- .game-container {
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
- }
-
- .btn {
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
-}
-
-/* ===========================
- DYNAMIC WINDOW SIZE CLASSES
- =========================== */
-
-/* Extra fine-tuning based on JavaScript window size detection */
-.game-container.window-xl .task-image {
- max-width: min(85vw, 1500px); /* Huge images on ultra-wide screens */
- max-height: min(80vh, 1000px); /* Take up most of the vertical space */
- min-height: max(400px, 30vh); /* Large minimum size */
-}
-
-.game-container.window-large .task-image {
- max-width: min(88vw, 1200px); /* Very large images */
- max-height: min(75vh, 800px); /* Most of the screen height */
- min-height: max(300px, 25vh); /* Good minimum size */
-}
-
-.game-container.window-medium .task-image {
- max-width: min(90vw, 800px); /* Large images on medium screens */
- max-height: min(65vh, 600px); /* Good portion of screen height */
- min-height: max(250px, 22vh); /* Reasonable minimum */
-}
-
-.game-container.window-small .task-image {
- max-width: min(95vw, 500px); /* Nearly full width on small screens */
- max-height: min(55vh, 400px); /* More than half the screen height */
- min-height: max(200px, 20vh); /* Decent minimum size */
- min-width: 180px;
-}
-
-/* Smooth transitions between window size changes */
-.task-image {
- transition: max-width 0.3s ease, max-height 0.3s ease, min-height 0.3s ease;
-}
-
-.task-image-container {
- transition: min-height 0.3s ease;
-}
-
-/* ===========================
- FLASH MESSAGE SYSTEM
- =========================== */
-
-/* Flash message overlay */
-.flash-message-overlay {
- position: fixed;
- display: none;
- pointer-events: none; /* Don't block clicks */
- font-family: 'Arial', sans-serif;
- user-select: none; /* Prevent text selection */
- opacity: 0;
- transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
-}
-
-/* Flash message animations */
-@keyframes flashBounceIn {
- 0% {
- opacity: 0;
- transform: scale(0.3) translate(-50%, -50%);
- }
- 50% {
- opacity: 1;
- transform: scale(1.05) translate(-50%, -50%);
- }
- 70% {
- transform: scale(0.9) translate(-50%, -50%);
- }
- 100% {
- opacity: 1;
- transform: scale(1) translate(-50%, -50%);
- }
-}
-
-@keyframes flashPulseIn {
- 0% {
- opacity: 0;
- transform: scale(1) translate(-50%, -50%);
- }
- 20% {
- opacity: 0.8;
- transform: scale(1.1) translate(-50%, -50%);
- }
- 40% {
- opacity: 1;
- transform: scale(0.95) translate(-50%, -50%);
- }
- 60% {
- opacity: 1;
- transform: scale(1.05) translate(-50%, -50%);
- }
- 80% {
- opacity: 1;
- transform: scale(0.98) translate(-50%, -50%);
- }
- 100% {
- opacity: 1;
- transform: scale(1) translate(-50%, -50%);
- }
-}
-
-@keyframes flashFadeOut {
- 0% {
- opacity: 1;
- transform: scale(1);
- }
- 100% {
- opacity: 0;
- transform: scale(0.95);
- }
-}
-
-/* Flash message responsive styles */
-@media (max-width: 768px) {
- .flash-message-overlay {
- max-width: 90% !important;
- padding: 15px 20px !important;
- font-size: 18px !important;
- }
-}
-
-@media (max-width: 480px) {
- .flash-message-overlay {
- max-width: 95% !important;
- padding: 12px 16px !important;
- font-size: 16px !important;
- border-radius: 10px !important;
- }
-}
-
-/* ===========================
- ANNOYANCE MANAGEMENT SCREEN
- =========================== */
-
-/* Tab Navigation */
-.annoyance-tabs {
- display: flex;
- gap: 2px;
- margin-bottom: 20px;
- background: #e9ecef;
- border-radius: 8px;
- padding: 4px;
-}
-
-.annoyance-tab {
- flex: 1;
- background: transparent;
- border: none;
- padding: 12px 16px;
- border-radius: 6px;
- cursor: pointer;
- font-weight: 500;
- transition: all 0.2s ease;
- color: #666;
-}
-
-.annoyance-tab:hover {
- background: #dee2e6;
- color: #333;
-}
-
-.annoyance-tab.active {
- background: #007bff;
- color: white;
- box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
-}
-
-/* Tab Content */
-.annoyance-tab-content {
- display: none;
-}
-
-.annoyance-tab-content.active {
- display: block;
- animation: fadeIn 0.3s ease-in-out;
-}
-
-@keyframes fadeIn {
- from { opacity: 0; transform: translateY(10px); }
- to { opacity: 1; transform: translateY(0); }
-}
-
-/* Section Styling */
-.annoyance-section {
- background: #f8f9fa;
- border-radius: 10px;
- padding: 20px;
- margin-bottom: 20px;
-}
-
-.section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- padding-bottom: 10px;
- border-bottom: 2px solid #e9ecef;
-}
-
-.header-controls {
- display: flex;
- align-items: center;
- gap: 15px;
-}
-
-/* Message Editor */
-.message-editor {
- background: white;
- border: 2px solid #007bff;
- border-radius: 10px;
- padding: 20px;
- margin-bottom: 20px;
- box-shadow: 0 4px 12px rgba(0, 123, 255, 0.1);
-}
-
-.editor-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 1px solid #e9ecef;
-}
-
-.editor-header h4 {
- margin: 0;
- color: #007bff;
-}
-
-.editor-form {
- display: flex;
- flex-direction: column;
- gap: 15px;
-}
-
-.form-group {
- display: flex;
- flex-direction: column;
- gap: 5px;
-}
-
-.form-group label {
- font-weight: 600;
- color: #333;
-}
-
-.form-group textarea {
- padding: 12px;
- border: 2px solid #ddd;
- border-radius: 6px;
- font-size: 14px;
- resize: vertical;
- min-height: 80px;
- transition: border-color 0.2s;
-}
-
-.form-group textarea:focus {
- border-color: #007bff;
- outline: none;
-}
-
-.form-group select {
- padding: 10px 12px;
- border: 2px solid #ddd;
- border-radius: 6px;
- background: white;
- font-size: 14px;
- cursor: pointer;
- transition: border-color 0.2s;
-}
-
-.form-group select:focus {
- border-color: #007bff;
- outline: none;
-}
-
-.form-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 15px;
-}
-
-.char-counter {
- font-size: 12px;
- color: #666;
- text-align: right;
-}
-
-.char-counter.warning {
- color: #ffc107;
-}
-
-.char-counter.error {
- color: #dc3545;
-}
-
-.editor-actions {
- display: flex;
- gap: 10px;
- justify-content: flex-start;
-}
-
-/* Message List */
-.message-list-section {
- background: white;
- border-radius: 8px;
- overflow: hidden;
-}
-
-.list-header {
- background: #f8f9fa;
- padding: 15px 20px;
- border-bottom: 1px solid #e9ecef;
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- gap: 10px;
-}
-
-.list-filters {
- display: flex;
- align-items: center;
- gap: 15px;
- flex-wrap: wrap;
-}
-
-.list-filters label {
- font-size: 14px;
- color: #666;
- display: flex;
- align-items: center;
- gap: 5px;
-}
-
-.list-filters select {
- padding: 6px 10px;
- border: 1px solid #ddd;
- border-radius: 4px;
- background: white;
- font-size: 13px;
-}
-
-.list-stats {
- font-size: 14px;
- color: #666;
- font-weight: 500;
-}
-
-.message-list {
- max-height: 400px;
- overflow-y: auto;
-}
-
-.message-item {
- display: flex;
- align-items: center;
- padding: 15px 20px;
- border-bottom: 1px solid #f1f3f4;
- transition: background-color 0.2s;
-}
-
-.message-item:hover {
- background-color: #f8f9fa;
-}
-
-.message-item.disabled {
- opacity: 0.6;
- background-color: #f8f9fa;
-}
-
-.message-content {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 5px;
-}
-
-.message-text {
- font-size: 14px;
- color: #333;
- line-height: 1.4;
-}
-
-.message-meta {
- display: flex;
- gap: 15px;
- font-size: 12px;
- color: #666;
-}
-
-.message-category {
- display: inline-flex;
- align-items: center;
- gap: 3px;
- padding: 2px 8px;
- background: #e9ecef;
- border-radius: 12px;
- font-size: 11px;
- font-weight: 500;
-}
-
-.message-category.motivational { background: #d4edda; color: #155724; }
-.message-category.encouraging { background: #d1ecf1; color: #0c5460; }
-.message-category.achievement { background: #fff3cd; color: #856404; }
-.message-category.persistence { background: #f8d7da; color: #721c24; }
-.message-category.custom { background: #e2e3e5; color: #383d41; }
-
-.message-actions {
- display: flex;
- gap: 8px;
- align-items: center;
-}
-
-.message-toggle {
- width: 40px;
- height: 20px;
- background: #ccc;
- border-radius: 10px;
- position: relative;
- cursor: pointer;
- transition: background-color 0.3s;
-}
-
-.message-toggle.enabled {
- background: #28a745;
-}
-
-.message-toggle::after {
- content: '';
- position: absolute;
- top: 2px;
- left: 2px;
- width: 16px;
- height: 16px;
- background: white;
- border-radius: 50%;
- transition: transform 0.3s;
-}
-
-.message-toggle.enabled::after {
- transform: translateX(20px);
-}
-
-/* Control Layouts */
-.control-row {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 20px;
-}
-
-.control-group {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.control-group label {
- font-weight: 600;
- color: #333;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.control-group input[type="range"] {
- width: 100%;
- height: 6px;
- border-radius: 3px;
- background: #ddd;
- outline: none;
- opacity: 0.7;
- transition: opacity 0.2s;
- cursor: pointer;
-}
-
-.control-group input[type="range"]:hover {
- opacity: 1;
-}
-
-.control-group input[type="range"]::-webkit-slider-thumb {
- appearance: none;
- width: 18px;
- height: 18px;
- border-radius: 50%;
- background: #007bff;
- cursor: pointer;
- transition: background 0.2s;
-}
-
-.control-group input[type="range"]::-webkit-slider-thumb:hover {
- background: #0056b3;
-}
-
-.control-group input[type="checkbox"] {
- width: 18px;
- height: 18px;
- cursor: pointer;
-}
-
-.control-group input[type="color"] {
- width: 60px;
- height: 40px;
- border: 2px solid #ddd;
- border-radius: 6px;
- cursor: pointer;
- transition: border-color 0.2s;
-}
-
-.control-group input[type="color"]:focus {
- border-color: #007bff;
- outline: none;
-}
-
-.help-text {
- font-size: 12px;
- color: #666;
- font-style: italic;
- margin-top: 2px;
-}
-
-.help-text.danger {
- color: #dc3545;
-}
-
-/* Import/Export Sections */
-.control-section {
- background: white;
- border-radius: 8px;
- padding: 20px;
- margin-bottom: 15px;
- border: 1px solid #e9ecef;
-}
-
-.control-section h4 {
- margin: 0 0 15px 0;
- color: #333;
- font-size: 16px;
-}
-
-.export-options, .import-options, .reset-options {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- margin-bottom: 10px;
-}
-
-.import-mode {
- margin-top: 10px;
-}
-
-.radio-group {
- display: flex;
- gap: 15px;
- margin-top: 5px;
-}
-
-.radio-group label {
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 14px;
- cursor: pointer;
-}
-
-/* Responsive Design */
-@media (max-width: 768px) {
- .annoyance-tabs {
- flex-direction: column;
- }
-
- .section-header {
- flex-direction: column;
- align-items: flex-start;
- gap: 10px;
- }
-
- .form-row, .control-row {
- grid-template-columns: 1fr;
- gap: 10px;
- }
-
- .list-header {
- flex-direction: column;
- align-items: flex-start;
- }
-
- .message-item {
- flex-direction: column;
- align-items: flex-start;
- gap: 10px;
- }
-
- .message-actions {
- align-self: stretch;
- justify-content: space-between;
- }
-
- .export-options, .import-options, .reset-options {
- flex-direction: column;
- }
-
- .annoyance-section {
- padding: 15px;
- }
-}
-
-/* ======================================
- Punishment Popup System Styles
- ====================================== */
-
-/* Background blur for punishment popups */
-.punishment-popup-blur {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.3);
- backdrop-filter: blur(3px);
- z-index: 9999;
- pointer-events: none;
-}
-
-/* Individual punishment popup */
-.punishment-popup {
- position: fixed;
- z-index: 10000;
- background: white;
- border: 3px solid #dc3545;
- border-radius: 10px;
- box-shadow: 0 8px 32px rgba(220, 53, 69, 0.4);
- overflow: hidden;
- display: flex;
- flex-direction: column;
- font-family: var(--font-family);
- min-width: 200px;
- min-height: 150px;
- max-width: 500px;
- max-height: 400px;
-}
-
-/* Popup header with timer and title */
-.punishment-popup-header {
- background: #dc3545;
- color: white;
- padding: 8px 12px;
- font-size: 12px;
- font-weight: bold;
- display: flex;
- justify-content: space-between;
- align-items: center;
- user-select: none;
-}
-
-.punishment-popup-timer {
- background: rgba(255, 255, 255, 0.2);
- padding: 2px 6px;
- border-radius: 4px;
- font-family: monospace;
- font-size: 11px;
- min-width: 30px;
- text-align: center;
-}
-
-/* Image container within popup */
-.punishment-popup img {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- border-radius: 5px;
-}
-
-/* Popup Images Tab specific styles */
-.range-inputs {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 15px;
- margin-top: 10px;
-}
-
-.range-inputs > div {
- display: flex;
- flex-direction: column;
- gap: 5px;
-}
-
-.range-inputs input[type="number"] {
- padding: 8px;
- border: 1px solid #ddd;
- border-radius: 5px;
- font-size: 14px;
- background: white;
- color: #333;
-}
-
-.range-inputs input[type="number"]:focus {
- outline: none;
- border-color: #007bff;
- box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
-}
-
-.warning-text {
- background: #fff3cd;
- color: #856404;
- padding: 10px;
- border: 1px solid #ffeaa7;
- border-radius: 5px;
- margin-top: 10px;
- font-size: 14px;
- font-weight: 500;
-}
-
-.test-buttons {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- margin-top: 10px;
-}
-
-.test-buttons .btn {
- flex: 1;
- min-width: 120px;
- padding: 10px 15px;
- font-size: 14px;
- font-weight: 500;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.2s ease;
- text-align: center;
-}
-
-.test-buttons .btn-info {
- background: #17a2b8;
- color: white;
-}
-
-.test-buttons .btn-info:hover {
- background: #138496;
- transform: translateY(-1px);
-}
-
-.test-buttons .btn-primary {
- background: #007bff;
- color: white;
-}
-
-.test-buttons .btn-primary:hover {
- background: #0056b3;
- transform: translateY(-1px);
-}
-
-.test-buttons .btn-danger {
- background: #dc3545;
- color: white;
-}
-
-.test-buttons .btn-danger:hover {
- background: #c82333;
- transform: translateY(-1px);
-}
-
-.info-display {
- background: #f8f9fa;
- padding: 15px;
- border-radius: 8px;
- border-left: 4px solid #007bff;
- margin-top: 10px;
-}
-
-.info-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 5px 0;
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
-.info-item:last-child {
- border-bottom: none;
-}
-
-.info-label {
- font-weight: 500;
- color: #495057;
-}
-
-.info-item span:last-child {
- font-weight: bold;
- color: #007bff;
- background: rgba(0, 123, 255, 0.1);
- padding: 2px 8px;
- border-radius: 12px;
- font-size: 12px;
- min-width: 30px;
- text-align: center;
-}
-
-/* Responsive styles for popup images tab */
-@media (max-width: 768px) {
- .range-inputs {
- grid-template-columns: 1fr;
- gap: 10px;
- }
-
- .test-buttons {
- flex-direction: column;
- }
-
- .test-buttons .btn {
- min-width: 100%;
- }
-
- .punishment-popup {
- max-width: 90vw;
- max-height: 80vh;
- }
-
- .info-item {
- flex-direction: column;
- align-items: flex-start;
- gap: 5px;
- }
-
- .info-item span:last-child {
- align-self: flex-end;
- }
-}
-
-/* Animation for popup appearance */
-@keyframes popupFadeIn {
- from {
- opacity: 0;
- transform: scale(0.8);
- }
- to {
- opacity: 1;
- transform: scale(1);
- }
-}
-
-@keyframes popupFadeOut {
- from {
- opacity: 1;
- transform: scale(1);
- }
- to {
- opacity: 0;
- transform: scale(0.9);
- }
-}
-
-.punishment-popup {
- animation: popupFadeIn 0.3s ease-out;
-}
-
-.punishment-popup.fade-out {
- animation: popupFadeOut 0.3s ease-in;
-}
\ No newline at end of file
diff --git a/src/styles/styles.css b/src/styles/styles.css
index 2925b41..d8d4b4d 100644
--- a/src/styles/styles.css
+++ b/src/styles/styles.css
@@ -2289,12 +2289,35 @@ body.theme-monochrome {
/* Video Management Styles */
.video-gallery {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ grid-auto-rows: minmax(320px, auto);
gap: var(--space-lg);
padding: var(--space-lg);
background: var(--bg-secondary);
border-radius: 12px;
border: 1px solid var(--border-color);
+ align-content: start;
+ max-height: 80vh;
+ overflow-y: auto;
+}
+
+/* Custom scrollbar styling for video gallery */
+.video-gallery::-webkit-scrollbar {
+ width: 12px;
+}
+
+.video-gallery::-webkit-scrollbar-track {
+ background: #333;
+ border-radius: 6px;
+}
+
+.video-gallery::-webkit-scrollbar-thumb {
+ background: #666;
+ border-radius: 6px;
+}
+
+.video-gallery::-webkit-scrollbar-thumb:hover {
+ background: #888;
}
.video-gallery.active {
@@ -2309,6 +2332,7 @@ body.theme-monochrome {
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
cursor: pointer;
+ min-height: 320px;
}
.video-item:hover {
@@ -2326,9 +2350,10 @@ body.theme-monochrome {
.video-thumbnail {
position: relative;
width: 100%;
- height: 120px;
+ height: 140px;
overflow: hidden;
background: var(--bg-tertiary);
+ border-radius: 8px;
}
.video-thumbnail video {
@@ -3400,6 +3425,56 @@ body.theme-monochrome {
background: #0056b3;
}
+/* Focus video player volume slider override */
+.focus-volume-slider {
+ width: 60px !important;
+ max-width: 60px !important;
+ min-width: 60px !important;
+ height: 3px !important;
+ background: #666 !important;
+}
+
+/* Performance optimizations for large video galleries */
+.video-galleries-container {
+ height: 60vh;
+ max-height: 800px;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#unified-video-gallery {
+ height: 60vh;
+ max-height: 800px;
+}
+
+.video-grid-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 15px;
+ padding: 15px;
+}
+
+.video-item {
+ transform: translateZ(0); /* Force hardware acceleration */
+ will-change: transform; /* Hint to browser for optimization */
+ contain: layout style paint; /* CSS containment for better performance */
+}
+
+.lazy-thumbnail {
+ background: #2a2a2a;
+ border-radius: 4px;
+}
+
+.thumbnail-placeholder {
+ font-size: 24px;
+ transition: opacity 0.3s ease;
+}
+
+/* Virtual scrolling optimization */
+.video-gallery.active {
+ contain: strict; /* Strict containment for performance */
+}
+
.control-group input[type="checkbox"] {
width: 18px;
height: 18px;
diff --git a/src/utils/desktop-file-manager.js b/src/utils/desktop-file-manager.js
index 3a2f509..5c51180 100644
--- a/src/utils/desktop-file-manager.js
+++ b/src/utils/desktop-file-manager.js
@@ -18,6 +18,10 @@ class DesktopFileManager {
punishments: null
};
+ // External video directories (linked, not copied)
+ this.externalVideoDirectories = []; // Array of linked directory objects
+ this.allLinkedVideos = []; // Cached array of all videos from all directories
+
this.init();
}
@@ -47,15 +51,16 @@ class DesktopFileManager {
await window.electronAPI.createDirectory(this.audioDirectories.background);
await window.electronAPI.createDirectory(this.audioDirectories.ambient);
- await window.electronAPI.createDirectory(this.videoDirectories.background);
- await window.electronAPI.createDirectory(this.videoDirectories.tasks);
- await window.electronAPI.createDirectory(this.videoDirectories.rewards);
- await window.electronAPI.createDirectory(this.videoDirectories.punishments);
+ // Note: No longer creating/using local video directories
+ // All videos come from external linked directories only
console.log('Desktop file manager initialized');
console.log('App path:', this.appPath);
console.log('Image directories:', this.imageDirectories);
console.log('Audio directories:', this.audioDirectories);
+
+ // Load any previously linked external directories
+ await this.loadLinkedDirectories();
} else {
console.error('Failed to get app path');
}
@@ -276,6 +281,282 @@ class DesktopFileManager {
}
}
+ async addVideoDirectory(customName = null) {
+ if (!this.isElectron) {
+ this.showNotification('Directory linking only available in desktop version', 'warning');
+ return null;
+ }
+
+ try {
+ // Open directory dialog
+ const directoryPath = await window.electronAPI.selectDirectory();
+
+ if (!directoryPath) {
+ return null;
+ }
+
+ // Check if directory is already linked
+ const existingDir = this.externalVideoDirectories.find(dir => dir.path === directoryPath);
+ if (existingDir) {
+ this.showNotification('Directory is already linked!', 'warning');
+ return existingDir;
+ }
+
+ // Show scanning notification
+ this.showNotification('🔍 Scanning directory for videos... This may take a moment for large collections.', 'info');
+
+ // Scan the directory recursively for video files
+ console.log(`🔍 Scanning directory recursively: ${directoryPath}`);
+ const videoFiles = await window.electronAPI.readVideoDirectoryRecursive(directoryPath);
+
+ console.log(`Found ${videoFiles.length} video files in directory:`, directoryPath);
+
+ if (videoFiles.length === 0) {
+ this.showNotification('No video files found in selected directory', 'warning');
+ return null;
+ }
+
+ // Create directory object
+ const directoryName = customName || this.getDirectoryName(directoryPath);
+ const directoryObj = {
+ id: Date.now(), // Unique ID
+ name: directoryName,
+ path: directoryPath,
+ videoCount: videoFiles.length,
+ dateAdded: new Date().toISOString(),
+ isRecursive: true
+ };
+
+ // Process videos in chunks to avoid blocking UI
+ const linkedVideos = await this.processVideosInChunks(videoFiles, directoryObj);
+
+ // Add to linked directories
+ this.externalVideoDirectories.push(directoryObj);
+
+ // Update cached video list
+ this.allLinkedVideos.push(...linkedVideos);
+
+ // Save to persistent storage
+ await this.saveLinkedDirectories();
+
+ // Update video storage
+ await this.updateUnifiedVideoStorage();
+
+ this.showNotification(
+ `✅ Linked directory "${directoryName}"!\nFound ${videoFiles.length} videos`,
+ 'success'
+ );
+
+ console.log(`🔗 Linked directory: ${directoryName} (${videoFiles.length} videos)`);
+
+ return {
+ directory: directoryObj,
+ videoCount: videoFiles.length,
+ videos: linkedVideos
+ };
+
+ } catch (error) {
+ console.error('Error linking video directory:', error);
+ this.showNotification('Failed to link video directory', 'error');
+ return null;
+ }
+ }
+
+ async processVideosInChunks(videoFiles, directoryObj) {
+ const chunkSize = 100; // Process 100 videos at a time
+ const linkedVideos = [];
+
+ for (let i = 0; i < videoFiles.length; i += chunkSize) {
+ const chunk = videoFiles.slice(i, i + chunkSize);
+ const progress = Math.round(((i + chunk.length) / videoFiles.length) * 100);
+
+ console.log(`Processing videos ${i + 1}-${i + chunk.length} of ${videoFiles.length} (${progress}%)`);
+
+ const chunkProcessed = chunk.map(video => ({
+ id: `${directoryObj.id}_${video.name}`,
+ name: video.name,
+ path: video.path,
+ url: `file:///${video.path.replace(/\\/g, '/')}`,
+ title: this.getVideoTitle(video.name),
+ size: video.size || 0,
+ directoryId: directoryObj.id,
+ directoryName: directoryObj.name,
+ isExternal: true,
+ relativePath: video.path.replace(directoryObj.path, '').replace(/^[\\/]/, '')
+ }));
+
+ linkedVideos.push(...chunkProcessed);
+
+ // Small delay to keep UI responsive
+ if (i + chunkSize < videoFiles.length) {
+ await new Promise(resolve => setTimeout(resolve, 10));
+ }
+ }
+
+ return linkedVideos;
+ }
+
+ async removeVideoDirectory(directoryId) {
+ if (!this.isElectron) {
+ return false;
+ }
+
+ try {
+ // Find directory
+ const dirIndex = this.externalVideoDirectories.findIndex(dir => dir.id === directoryId);
+ if (dirIndex === -1) {
+ this.showNotification('Directory not found', 'error');
+ return false;
+ }
+
+ const directory = this.externalVideoDirectories[dirIndex];
+
+ // Remove from arrays
+ this.externalVideoDirectories.splice(dirIndex, 1);
+ this.allLinkedVideos = this.allLinkedVideos.filter(video => video.directoryId !== directoryId);
+
+ // Save to persistent storage
+ await this.saveLinkedDirectories();
+
+ // Update video storage
+ await this.updateUnifiedVideoStorage();
+
+ this.showNotification(`Unlinked directory: ${directory.name}`, 'success');
+ return true;
+
+ } catch (error) {
+ console.error('Error unlinking directory:', error);
+ this.showNotification('Failed to unlink directory', 'error');
+ return false;
+ }
+ }
+
+ async refreshAllDirectories() {
+ if (!this.isElectron) {
+ return;
+ }
+
+ console.log('🔄 Refreshing all linked directories...');
+ this.allLinkedVideos = [];
+
+ for (const directory of this.externalVideoDirectories) {
+ try {
+ console.log(`🔍 Rescanning: ${directory.name}`);
+ const videoFiles = await window.electronAPI.readVideoDirectoryRecursive(directory.path);
+
+ const linkedVideos = videoFiles.map(video => ({
+ id: `${directory.id}_${video.name}`,
+ name: video.name,
+ path: video.path,
+ url: `file:///${video.path.replace(/\\/g, '/')}`,
+ title: this.getVideoTitle(video.name),
+ size: video.size || 0,
+ directoryId: directory.id,
+ directoryName: directory.name,
+ isExternal: true,
+ relativePath: video.path.replace(directory.path, '').replace(/^[\\/]/, '')
+ }));
+
+ this.allLinkedVideos.push(...linkedVideos);
+
+ // Update directory video count
+ directory.videoCount = videoFiles.length;
+
+ console.log(`✅ Found ${videoFiles.length} videos in ${directory.name}`);
+
+ } catch (error) {
+ console.warn(`Could not access directory ${directory.name}:`, error);
+ // Directory might be unavailable (external drive, network, etc.)
+ }
+ }
+
+ await this.saveLinkedDirectories();
+ await this.updateUnifiedVideoStorage();
+
+ const totalVideos = this.allLinkedVideos.length;
+ this.showNotification(`🔄 Refreshed ${this.externalVideoDirectories.length} directories, found ${totalVideos} videos`, 'success');
+ }
+
+ async saveLinkedDirectories() {
+ const data = {
+ directories: this.externalVideoDirectories,
+ lastUpdated: new Date().toISOString()
+ };
+ this.dataManager.set('linkedVideoDirectories', data);
+ }
+
+ async loadLinkedDirectories() {
+ if (!this.isElectron) {
+ return;
+ }
+
+ try {
+ const data = this.dataManager.get('linkedVideoDirectories');
+ if (data && data.directories) {
+ this.externalVideoDirectories = data.directories;
+ console.log(`📁 Loaded ${this.externalVideoDirectories.length} linked directories`);
+
+ // Refresh all directories to get current video lists
+ await this.refreshAllDirectories();
+ }
+ } catch (error) {
+ console.error('Error loading linked directories:', error);
+ }
+ }
+
+ async updateUnifiedVideoStorage() {
+ // Store all videos in a single unified list (no categories)
+
+ // If we have no linked directories but there are existing videos in storage,
+ // don't overwrite the existing data
+ if (this.externalVideoDirectories.length === 0 && this.allLinkedVideos.length === 0) {
+ // Check if there are existing videos in storage
+ try {
+ const existingData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
+ if (existingData.allVideos && existingData.allVideos.length > 0) {
+ console.log(`📹 Preserving existing unified video library: ${existingData.allVideos.length} videos`);
+ // Don't overwrite - preserve existing data
+ return;
+ }
+ } catch (error) {
+ console.warn('Error checking existing unified video library:', error);
+ }
+ }
+
+ const videoData = {
+ allVideos: this.allLinkedVideos,
+ directories: this.externalVideoDirectories,
+ lastUpdated: new Date().toISOString()
+ };
+
+ localStorage.setItem('unifiedVideoLibrary', JSON.stringify(videoData));
+ console.log(`📹 Updated unified video library: ${this.allLinkedVideos.length} videos from ${this.externalVideoDirectories.length} directories`);
+
+ // Trigger video manager reload if it exists
+ if (window.videoPlayerManager) {
+ window.videoPlayerManager.loadVideoFiles();
+ }
+ }
+
+ getDirectoryName(directoryPath) {
+ // Extract just the folder name from the full path
+ return directoryPath.split(/[\\/]/).pop() || 'Unknown Directory';
+ }
+
+ getAllVideos() {
+ return this.allLinkedVideos;
+ }
+
+ getDirectoriesInfo() {
+ return this.externalVideoDirectories.map(dir => ({
+ id: dir.id,
+ name: dir.name,
+ path: dir.path,
+ videoCount: dir.videoCount,
+ dateAdded: dir.dateAdded
+ }));
+ }
+
async scanDirectoryForImages(category = 'task') {
if (!this.isElectron) {
return [];
@@ -303,51 +584,6 @@ class DesktopFileManager {
}
}
- async scanAllDirectories() {
- if (!this.isElectron) {
- this.showNotification('Directory scanning only available in desktop version', 'warning');
- return { task: [], consequence: [], videos: { background: [], tasks: [], rewards: [], punishments: [] } };
- }
-
- // Scan image directories
- const taskImages = await this.scanDirectoryForImages('task');
- const consequenceImages = await this.scanDirectoryForImages('consequence');
-
- // Scan video directories
- const backgroundVideos = await this.scanDirectoryForVideos('background');
- const taskVideos = await this.scanDirectoryForVideos('tasks');
- const rewardVideos = await this.scanDirectoryForVideos('rewards');
- const punishmentVideos = await this.scanDirectoryForVideos('punishments');
-
- const results = {
- task: taskImages,
- consequence: consequenceImages,
- videos: {
- background: backgroundVideos,
- tasks: taskVideos,
- rewards: rewardVideos,
- punishments: punishmentVideos
- }
- };
-
- // Update image storage
- if (taskImages.length > 0 || consequenceImages.length > 0) {
- await this.updateImageStorage([...taskImages, ...consequenceImages]);
- }
-
- // Update video storage
- const allVideos = [...backgroundVideos, ...taskVideos, ...rewardVideos, ...punishmentVideos];
- if (allVideos.length > 0) {
- await this.updateVideoStorage(allVideos);
- }
-
- const totalImages = taskImages.length + consequenceImages.length;
- const totalVideos = allVideos.length;
- // this.showNotification(`Found ${totalImages} images (${taskImages.length} tasks, ${consequenceImages.length} consequences) and ${totalVideos} videos (${backgroundVideos.length} background, ${taskVideos.length} tasks, ${rewardVideos.length} rewards, ${punishmentVideos.length} punishments)`, 'success');
-
- return results;
- }
-
async updateImageStorage(images) {
// Get existing images
let customImages = this.dataManager.get('customImages') || { task: [], consequence: [] };
@@ -666,19 +902,28 @@ class DesktopFileManager {
// Get existing videos from localStorage
const existingVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
+ console.log('Updating video storage with', videoFiles.length, 'videos');
+
// Add new videos
videoFiles.forEach(video => {
+ console.log(`Adding video to category: ${video.category}, name: ${video.name}`);
if (!existingVideos[video.category]) {
existingVideos[video.category] = [];
+ console.log(`Created new category: ${video.category}`);
}
// Check if video already exists (prevent duplicates)
const exists = existingVideos[video.category].some(existing => existing.name === video.name);
if (!exists) {
existingVideos[video.category].push(video);
+ console.log(`Added video: ${video.name} to ${video.category}`);
+ } else {
+ console.log(`Video already exists: ${video.name} in ${video.category}`);
}
});
+ console.log('Final video storage:', existingVideos);
+
// Save back to localStorage
localStorage.setItem('videoFiles', JSON.stringify(existingVideos));
diff --git a/test-cinema.js b/test-cinema.js
deleted file mode 100644
index b899cf7..0000000
Binary files a/test-cinema.js and /dev/null differ
diff --git a/theme-mockup-option-e.html b/theme-mockup-option-e.html
deleted file mode 100644
index d296ad5..0000000
--- a/theme-mockup-option-e.html
+++ /dev/null
@@ -1,413 +0,0 @@
-
-
-
-
-
-
Theme Mockup - Balanced Green
-
-
-
-
-
-
-
-
-
-
🌲 Balanced Forest Green Theme
-
Natural forest green accents with neutral black/gray backgrounds - Organic, earthy feel
-
-
-
-
-
-
-
42
-
Sessions Completed
-
-
-
18h 32m
-
Total Training Time
-
-
-
Level 7
-
Current Rank
-
-
-
95%
-
Dedication Score
-
-
-
-
-
-
- 🎯 Start Training Session
- 📚 Browse Content
- 🏆 View Achievements
- ⚙️ Settings & Preferences
- 📊 Progress Analytics
- 🎪 Interactive Tasks
-
-
-
-
-
Current Progress
-
-
65% through Intermediate Training Module
-
-
-
-
-
Recent Activity
-
- Completed Focus Session: Advanced Techniques
- 2 hours ago
-
-
- Achievement Unlocked: Dedication Master
- Yesterday
-
-
- Training Session: Endurance Building
- 2 days ago
-
-
- Mirror Task: Self-Reflection Exercise
- 3 days ago
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/title-color-variations.html b/title-color-variations.html
deleted file mode 100644
index 69c3aa0..0000000
--- a/title-color-variations.html
+++ /dev/null
@@ -1,509 +0,0 @@
-
-
-
-
-
-
Option 4 - Color Variations & Game Palettes
-
-
-
-
-
-
-
Option 4: Gaming Aesthetic - Color Variations
-
Audiowide font with different color schemes and complete game palettes
-
-
-
-
-
-
A
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
-
-
🌈 Pink/Orange Gaming Palette
-
-
-
-
-
-
-
-
Glow
-
rgba(255,0,128,0.1)
-
-
-
- Game Implementation: High-energy gaming vibe with pink primary for buttons/highlights, orange for progress bars/completion states, dark backgrounds for contrast. Perfect for an intense, exciting gaming experience.
-
-
-
-
-
-
-
-
B
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
-
-
💙 Electric Blue/Cyan Tech Palette
-
-
-
-
-
-
-
-
Glow
-
rgba(0,255,255,0.15)
-
-
-
- Game Implementation: Professional tech aesthetic with blue primary for navigation/buttons, cyan for active states/progress, dark blue-grey backgrounds. Creates a sophisticated, high-tech training environment feel.
-
-
-
-
-
-
-
-
C
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
-
-
💜 Purple/Violet Luxe Palette
-
-
-
-
-
-
-
-
Glow
-
rgba(138,43,226,0.12)
-
-
-
- Game Implementation: Luxurious, premium feel with purple primary for main actions, orchid for highlights/hover states, deep purple backgrounds. Creates an exclusive, high-end academy atmosphere.
-
-
-
-
-
-
-
-
D
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
-
-
💚 Green/Lime Matrix Palette
-
-
-
-
-
-
-
-
Glow
-
rgba(0,255,0,0.08)
-
-
-
- Game Implementation: Matrix/hacker aesthetic with bright green primary for success states/progress, lime for active elements, very dark green backgrounds. Perfect for a digital training simulation vibe.
-
-
-
-
-
-
-
-
E
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
-
-
❤️ Red/Crimson Power Palette
-
-
-
-
-
-
-
-
Glow
-
rgba(220,20,60,0.1)
-
-
-
- Game Implementation: Bold, intense atmosphere with crimson primary for warnings/important actions, coral for completion states, dark red backgrounds. Creates a powerful, commanding training environment.
-
-
-
-
-
-
-
-
F
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
-
-
🏆 Gold/Amber Elite Palette
-
-
-
-
-
-
-
-
Glow
-
rgba(255,215,0,0.08)
-
-
-
- Game Implementation: Premium, achievement-focused design with gold primary for rewards/completion, orange for progress indicators, warm dark backgrounds. Perfect for highlighting accomplishments and elite status.
-
-
-
-
-
-
🎨 Choose Your Academy's Identity!
-
Each color scheme creates a completely different atmosphere for your training academy. Consider what mood and personality best fits your vision:
-
- A - Pink/Orange: High-energy gaming excitement
- B - Blue/Cyan: Professional tech sophistication
- C - Purple/Violet: Luxurious premium experience
- D - Green/Lime: Matrix-style digital simulation
- E - Red/Crimson: Bold commanding intensity
- F - Gold/Amber: Elite achievement-focused
-
-
Just tell me which letter you prefer!
-
-
-
-
\ No newline at end of file
diff --git a/title-design-test.html b/title-design-test.html
deleted file mode 100644
index 3cac846..0000000
--- a/title-design-test.html
+++ /dev/null
@@ -1,385 +0,0 @@
-
-
-
-
-
-
Gooner Training Academy - Title Design Options
-
-
-
-
-
-
-
Gooner Training Academy - Title Design Options
-
Choose your favorite design for the main header!
-
-
-
-
1
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Futuristic Tech - Orbitron font with cyan gradients and glow effects
-
-
-
-
2
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Military Academy - Bebas Neue with orange theme and 3D perspective
-
-
-
-
3
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Sleek Modern - Exo 2 font with purple-blue gradients
-
-
-
-
4
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Gaming Aesthetic - Audiowide font with pink-orange gradients and glow animation
-
-
-
-
5
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Professional Bold - Russo One font with green theme and layered shadows
-
-
-
-
6
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Elegant Minimalist - Saira Condensed with clean white text and subtle shadows
-
-
-
-
7
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Neon Cyberpunk - Rajdhani font with red neon glow and flicker animation
-
-
-
-
8
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Gold Luxury - Titillium Web with gold gradients for premium feel
-
-
-
-
9
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Purple Power - Exo 2 font with purple gradients and ethereal glow
-
-
-
-
10
-
GOONER TRAINING ACADEMY
-
Master Your Dedication
-
Fire Theme - Bebas Neue with red-orange fire gradients and pulse animation
-
-
-
-
🎯 Your Choice Matters!
-
Each design option has its own personality and vibe. Consider which one best represents the sophisticated training platform you've built with interactive scenarios, TTS narration, and advanced focus challenges.
-
Just tell me which number(s) you prefer!
-
-
-
-
\ No newline at end of file
diff --git a/tts-test.html b/tts-test.html
deleted file mode 100644
index 0237260..0000000
--- a/tts-test.html
+++ /dev/null
@@ -1,535 +0,0 @@
-
-
-
-
-
-
Text-to-Speech Testing
-
-
-
-
🎙️ Electron Female Voice TTS Testing Lab
-
Test female voices available in your Electron app. Shows system voices and Chromium built-ins that will work in your desktop application.
-
-
-
Electron Female Voice Selection & Settings
-
🚺 Female voices only - Filtered to show female voices available in Electron applications.
-
- ⚡ Electron Environment: Shows system voices (Windows SAPI, macOS System) and Chromium built-ins. These are the actual voices your users will hear.
-
-
-
-
- Voice:
-
- Loading voices...
-
-
-
-
-
Select a voice to see details...
-
-
-
- Speed:
-
- 0.8x
-
-
-
- Pitch:
-
- 1.0
-
-
-
- Volume:
-
- 70%
-
-
-
-
-
-
Sample Scenario Content
-
-
- 🎭 Scenario Introduction
- 📋 Task Instruction
- 💬 Feedback Message
- 🏁 Scenario Ending
-
-
-
Custom Text Testing
-
-
-
- 🗣️ Speak Custom Text
- ⏹️ Stop
- ⏸️ Pause
- ▶️ Resume
-
-
-
-
-
Sample Texts Being Tested
-
-
-
Scenario Introduction:
-
-
-
-
-
-
-
-
-
-
-
-
Electron Female Voices by Platform
-
-
-
🖥️ Windows in Electron:
-
- Microsoft Zira Desktop ⭐ (Default)
- Microsoft Hazel Desktop
- Microsoft Eva Desktop
- Microsoft Aria (if updated)
- Microsoft Jenny (if updated)
-
-
-
-
🍎 macOS in Electron:
-
- Samantha ⭐ (Default female)
- Alex (Can be female-sounding)
- Victoria
- Karen
- Fiona
-
-
-
-
⚡ Electron Notes:
-
- System voices only - No Google/Amazon voices
- Quality varies - Depends on OS updates
- Local processing - No internet required
- Consistent - Same voice for all users on same OS
-
-
-
-
-
-
💡 Electron TTS Best Practices:
-
- Windows: Zira is most reliable, Hazel for variety
- macOS: Samantha is the go-to female voice
- Fallback: Always provide text backup for accessibility
- Testing: Test on actual target OS, not just browser
- Chrome users: Online voices often sound better than local ones
- Speed matters: 0.7-0.8x often sounds more natural and intimate
- Pitch adjustment: Slightly lower pitch (0.9) can sound more mature
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/video-player-test.html b/video-player-test.html
deleted file mode 100644
index 6db9cca..0000000
--- a/video-player-test.html
+++ /dev/null
@@ -1,205 +0,0 @@
-
-
-
-
-
-
Video Player Test
-
-
-
-
-
-
🎬 Video Player Integration Test
-
-
-
BaseVideoPlayer Test
-
-
-
- Your browser does not support the video tag.
-
-
-
- Test BaseVideoPlayer
- Clear Log
-
-
-
-
-
FocusVideoPlayer Test
-
-
-
- Your browser does not support the video tag.
-
-
- ⏸️
- 🔊
-
- 50%
-
-
-
- Ready to play videos
-
-
-
- Test FocusVideoPlayer
- Stop Focus Player
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file