Fix video loading and Quick Play image system migration

- Fixed porn cinema video loading to use correct linkedIndividualVideos storage
- Added CSS !important overrides to ensure video card visibility
- Migrated Quick Play from hardcoded image paths to linked storage system
- Added async getLinkedImages() methods to Game and PopupImageManager classes
- Fixed TypeError crashes from readDirectory async/sync issues
- Added comprehensive debug logging for media loading processes
This commit is contained in:
dilgenfritz 2025-11-03 15:01:01 -06:00
parent bdcb9bea18
commit 8a25b51168
21 changed files with 6926 additions and 428 deletions

View File

@ -0,0 +1,6 @@
{
"ExpandedNodes": [
""
],
"PreviewInSolutionExplorer": false
}

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

BIN
.vs/webGame/v17/.wsuo Normal file

Binary file not shown.

View File

@ -0,0 +1,12 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\drew\\webGame\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": []
}
]
}

View File

@ -0,0 +1,23 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\drew\\webGame\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": -1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{56df62a4-05a3-4e5b-aa1a-99371ccfb997}"
}
]
}
]
}
]
}

306
UNITY_SETUP_GUIDE.md Normal file
View File

@ -0,0 +1,306 @@
# Unity Setup & Prototype Guide - Phase 1
## 🎯 **Goal: Validate Unity Video Performance vs Web**
---
## 📋 **Step 1: Unity Project Setup**
### **Create New Project:**
1. **Open Unity Hub**
2. **New Project****3D Core** template
3. **Project Name**: `Gooner-Training-Academy-Unity`
4. **Unity Version**: 2022.3 LTS (recommended)
5. **Location**: Choose appropriate folder
### **Essential Packages to Install:**
1. **Window** → **Package Manager**
2. **Install these packages:**
- **TextMeshPro** (for UI text)
- **Video Player** (should be built-in)
- **UI Toolkit** (modern UI system)
- **Newtonsoft Json** (for data serialization)
---
## 🎬 **Step 2: Basic Video Player Test**
### **Create Basic Scene:**
1. **Create Empty GameObject** → Name: `VideoManager`
2. **Add Component** → **Video Player**
3. **Create UI Canvas** → **Screen Space - Overlay**
4. **Add Button** to Canvas → Name: `PlayButton`
### **Test Single Video:**
```csharp
// Create: Assets/Scripts/BasicVideoTest.cs
using UnityEngine;
using UnityEngine.Video;
using UnityEngine.UI;
public class BasicVideoTest : MonoBehaviour
{
[Header("Video Settings")]
public VideoPlayer videoPlayer;
public Button playButton;
public RenderTexture renderTexture;
void Start()
{
// Setup render texture for video output
renderTexture = new RenderTexture(1920, 1080, 24);
videoPlayer.targetTexture = renderTexture;
// Setup button
playButton.onClick.AddListener(ToggleVideo);
// Load test video (you'll need to provide a video file)
videoPlayer.url = System.IO.Path.Combine(Application.streamingAssetsPath, "test-video.mp4");
videoPlayer.Prepare();
}
void ToggleVideo()
{
if (videoPlayer.isPlaying)
{
videoPlayer.Pause();
playButton.GetComponentInChildren<Text>().text = "Play";
}
else
{
videoPlayer.Play();
playButton.GetComponentInChildren<Text>().text = "Pause";
}
}
}
```
---
## 🎮 **Step 3: Quad Video Prototype**
### **Create Quad Layout:**
1. **Create 4 UI Panels** in Canvas (2x2 grid layout)
2. **Add RawImage** to each panel
3. **Create 4 VideoPlayer GameObjects**
### **Quad Video Manager:**
```csharp
// Create: Assets/Scripts/QuadVideoManager.cs
using UnityEngine;
using UnityEngine.Video;
using UnityEngine.UI;
using System.Collections.Generic;
public class QuadVideoManager : MonoBehaviour
{
[Header("Video Players")]
public VideoPlayer[] videoPlayers = new VideoPlayer[4];
[Header("UI Elements")]
public RawImage[] videoDisplays = new RawImage[4];
public Button[] playButtons = new Button[4];
public Slider[] volumeSliders = new Slider[4];
[Header("Video Settings")]
public RenderTexture[] renderTextures = new RenderTexture[4];
public string[] testVideoUrls = new string[4];
void Start()
{
SetupQuadVideos();
}
void SetupQuadVideos()
{
for (int i = 0; i < 4; i++)
{
// Create render texture for each video
renderTextures[i] = new RenderTexture(1920, 1080, 24);
// Setup video player
videoPlayers[i].targetTexture = renderTextures[i];
videoPlayers[i].isLooping = true;
videoPlayers[i].audioOutputMode = VideoAudioOutputMode.Direct;
// Assign render texture to UI
videoDisplays[i].texture = renderTextures[i];
// Setup controls
int index = i; // Capture for closure
playButtons[i].onClick.AddListener(() => ToggleVideo(index));
volumeSliders[i].onValueChanged.AddListener((value) => SetVolume(index, value));
// Load test video
if (i < testVideoUrls.Length && !string.IsNullOrEmpty(testVideoUrls[i]))
{
videoPlayers[i].url = testVideoUrls[i];
videoPlayers[i].Prepare();
}
}
}
void ToggleVideo(int index)
{
if (videoPlayers[index].isPlaying)
{
videoPlayers[index].Pause();
}
else
{
videoPlayers[index].Play();
}
}
void SetVolume(int index, float volume)
{
videoPlayers[index].SetDirectAudioVolume(0, volume);
}
void Update()
{
// Monitor performance
if (Input.GetKeyDown(KeyCode.P))
{
Debug.Log($"FPS: {1f / Time.deltaTime:F1}");
Debug.Log($"Videos playing: {GetPlayingVideoCount()}");
}
}
int GetPlayingVideoCount()
{
int count = 0;
for (int i = 0; i < videoPlayers.Length; i++)
{
if (videoPlayers[i].isPlaying) count++;
}
return count;
}
}
```
---
## 📁 **Step 4: Setup Test Videos**
### **Create StreamingAssets Folder:**
1. **Assets****Create****Folder** → Name: `StreamingAssets`
2. **Copy 4 test videos** into this folder (MP4 format recommended)
3. **Name them**: `test-video-1.mp4`, `test-video-2.mp4`, etc.
### **Update Video URLs:**
```csharp
// In QuadVideoManager, set testVideoUrls in inspector:
testVideoUrls[0] = Application.streamingAssetsPath + "/test-video-1.mp4";
testVideoUrls[1] = Application.streamingAssetsPath + "/test-video-2.mp4";
testVideoUrls[2] = Application.streamingAssetsPath + "/test-video-3.mp4";
testVideoUrls[3] = Application.streamingAssetsPath + "/test-video-4.mp4";
```
---
## 🧪 **Step 5: Performance Testing**
### **Performance Monitor Script:**
```csharp
// Create: Assets/Scripts/PerformanceMonitor.cs
using UnityEngine;
using UnityEngine.UI;
public class PerformanceMonitor : MonoBehaviour
{
[Header("UI")]
public Text fpsText;
public Text memoryText;
public Text videoCountText;
private float deltaTime = 0.0f;
private QuadVideoManager quadManager;
void Start()
{
quadManager = FindObjectOfType<QuadVideoManager>();
}
void Update()
{
deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;
if (fpsText != null)
{
float fps = 1.0f / deltaTime;
fpsText.text = $"FPS: {fps:0.}";
// Color code based on performance
if (fps >= 60) fpsText.color = Color.green;
else if (fps >= 30) fpsText.color = Color.yellow;
else fpsText.color = Color.red;
}
if (memoryText != null)
{
long memory = System.GC.GetTotalMemory(false);
memoryText.text = $"Memory: {memory / 1024 / 1024}MB";
}
if (videoCountText != null && quadManager != null)
{
videoCountText.text = $"Playing: {quadManager.GetPlayingVideoCount()}/4";
}
}
}
```
---
## 🎯 **Step 6: Test Plan**
### **Performance Benchmarks:**
1. **Baseline Test**: Single video @ 1080p
2. **Dual Test**: 2 videos simultaneously
3. **Quad Test**: 4 videos simultaneously
4. **Stress Test**: 4 videos + UI interactions
### **Metrics to Track:**
- **FPS**: Target 60+ with 4 videos
- **Memory Usage**: Target <2GB
- **CPU Usage**: Monitor in Task Manager
- **Video Switching Speed**: Target <100ms
### **Comparison to Web:**
- **Run your web QuadVideoPlayer** alongside Unity
- **Monitor both performance profiles**
- **Document frame rate differences**
---
## 📋 **Your Immediate Tasks:**
### **Today:**
1. ✅ Create Unity project with packages
2. ✅ Implement BasicVideoTest script
3. ✅ Test single video playback
### **This Week:**
1. ✅ Build QuadVideoManager system
2. ✅ Add performance monitoring
3. ✅ Compare web vs Unity performance
4. ✅ Document results for migration decision
---
## 🚀 **Expected Results:**
**If Unity performs significantly better:**
- **Proceed with full migration**
- **Start porting game logic systems**
- **Plan Unity-specific enhancements**
**If performance is similar:**
- **Re-evaluate web optimizations**
- **Consider hybrid approach**
- **Focus on Unity for future features**
---
**Ready to start? Begin with the Unity project setup and let me know when you have the basic project created!** 🎮

2479
index.html

File diff suppressed because it is too large Load Diff

2075
quick-play.html Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,11 +21,12 @@ function validateDependencies() {
// Game state management
class TaskChallengeGame {
constructor() {
constructor(options = {}) {
// Show loading overlay immediately
this.showLoadingOverlay();
this.loadingProgress = 0;
this.isInitialized = false;
this.options = options; // Store options for later use
// Initialize data management system first
this.dataManager = new DataManager();
@ -136,34 +137,48 @@ class TaskChallengeGame {
this.initializeEventListeners();
this.setupKeyboardShortcuts();
this.setupWindowResizeHandling();
this.initializeCustomTasks();
this.initializeCustomTasks(this.options);
// Simple override for desktop mode - skip complex discovery
// Simple override for desktop mode - use linked images instead of old discovery
if (typeof DesktopFileManager !== 'undefined' && window.electronAPI) {
console.log('🖥️ Desktop mode detected - using simplified image discovery');
// Give desktop file manager time to scan, then force completion
setTimeout(() => {
const customImages = this.dataManager.get('customImages') || { task: [], consequence: [] };
let taskImages = [];
let consequenceImages = [];
if (Array.isArray(customImages)) {
taskImages = customImages;
} else {
taskImages = customImages.task || [];
consequenceImages = customImages.consequence || [];
console.log('🖥️ Desktop mode detected - using linked images');
// Give desktop file manager time to scan, then load linked images
setTimeout(async () => {
try {
const linkedImages = await this.getLinkedImages();
// Separate images into task and consequence categories if needed
// For now, use all linked images as task images
const taskImages = linkedImages.map(img => img.path);
const consequenceImages = []; // Could implement categorization later
gameData.discoveredTaskImages = taskImages;
gameData.discoveredConsequenceImages = consequenceImages;
console.log(`📸 Desktop mode - Linked images loaded: ${gameData.discoveredTaskImages.length} task images, ${gameData.discoveredConsequenceImages.length} consequence images`);
} catch (error) {
console.error('📸 Error loading linked images:', error);
// Fallback to empty arrays
gameData.discoveredTaskImages = [];
gameData.discoveredConsequenceImages = [];
}
gameData.discoveredTaskImages = taskImages.map(img => typeof img === 'string' ? img : img.name);
gameData.discoveredConsequenceImages = consequenceImages.map(img => typeof img === 'string' ? img : img.name);
console.log(`📸 Desktop mode - Task images: ${gameData.discoveredTaskImages.length}, Consequence images: ${gameData.discoveredConsequenceImages.length}`);
this.imageDiscoveryComplete = true;
this.showScreen('start-screen');
// Only show start screen if it exists (not in dedicated mode like Quick Play)
if (document.getElementById('start-screen')) {
this.showScreen('start-screen');
} else {
console.log('📱 Running in dedicated screen mode - skipping start screen display');
}
}, 3000);
} else {
this.discoverImages().then(() => {
this.showScreen('start-screen');
// Only show start screen if it exists (not in dedicated mode like Quick Play)
if (document.getElementById('start-screen')) {
this.showScreen('start-screen');
} else {
console.log('📱 Running in dedicated screen mode - skipping start screen display');
}
}).catch(error => {
console.error('🚨 Error in discoverImages():', error);
// Fallback: just mark as complete and show start screen
@ -261,12 +276,17 @@ class TaskChallengeGame {
setTimeout(async () => {
if (this.fileManager && this.fileManager.isElectron) {
console.log('🔍 Auto-scanning directories on startup...');
await this.fileManager.scanAllDirectories();
// Reload video player manager after scanning
if (window.videoPlayerManager && window.videoPlayerManager.loadVideoFiles) {
console.log('🔄 Reloading video library after directory scan...');
await window.videoPlayerManager.loadVideoFiles();
try {
// Refresh all linked directories instead of scanning all
await this.fileManager.refreshAllDirectories();
// Reload video player manager after scanning
if (window.videoPlayerManager && window.videoPlayerManager.loadVideoFiles) {
console.log('🔄 Reloading video library after directory scan...');
await window.videoPlayerManager.loadVideoFiles();
}
} catch (error) {
console.warn('⚠️ Auto-scan failed:', error);
}
}
}, 1000); // Wait 1 second for initialization to complete
@ -294,7 +314,110 @@ class TaskChallengeGame {
}, 100);
}
initializeCustomTasks() {
/**
* Get all images from linked directories and individual files (same as main library)
*/
async getLinkedImages() {
const allImages = [];
try {
// Get linked directories
let linkedDirs;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
} catch (e) {
console.log('Error parsing linkedImageDirectories:', e);
linkedDirs = [];
}
// Get individual linked images
let linkedIndividualImages;
try {
linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
if (!Array.isArray(linkedIndividualImages)) {
linkedIndividualImages = [];
}
} catch (e) {
console.log('Error parsing linkedIndividualImages:', e);
linkedIndividualImages = [];
}
console.log(`📸 Game found ${linkedDirs.length} linked directories and ${linkedIndividualImages.length} individual images`);
// Load images from linked directories using Electron API
if (window.electronAPI && linkedDirs.length > 0) {
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
for (const dir of linkedDirs) {
try {
console.log(`📸 Scanning directory: ${dir.path}`);
let files = [];
if (window.electronAPI.readDirectory) {
const result = window.electronAPI.readDirectory(dir.path);
// Handle both sync and async results
if (result && typeof result.then === 'function') {
try {
files = await result;
console.log(`📸 Async result - found ${files ? files.length : 0} files`);
} catch (asyncError) {
console.error(`📸 Async error for ${dir.path}:`, asyncError);
continue;
}
} else if (Array.isArray(result)) {
files = result;
console.log(`📸 Sync result - found ${files.length} files`);
} else {
console.error(`📸 Unexpected result type for ${dir.path}:`, typeof result);
continue;
}
if (files && Array.isArray(files)) {
const imageFiles = files.filter(file => imageExtensions.test(file.name));
console.log(`📸 Found ${imageFiles.length} image files in ${dir.name}`);
imageFiles.forEach(file => {
allImages.push({
name: file.name,
path: file.path,
category: 'directory',
directory: dir.name
});
});
} else {
console.log(`📸 No valid files array for ${dir.path}`);
}
}
} catch (error) {
console.error(`📸 Error loading images from directory ${dir.path}:`, error);
}
}
}
// Add individual linked images
linkedIndividualImages.forEach(image => {
allImages.push({
name: image.name || 'Unknown Image',
path: image.path,
category: 'individual',
directory: 'Individual Images'
});
});
console.log(`📸 Game loaded ${allImages.length} total linked images`);
} catch (error) {
console.error('📸 Error loading linked images in Game:', error);
}
return allImages;
}
initializeCustomTasks(options = {}) {
// Load custom tasks from localStorage or use defaults
const savedMainTasks = localStorage.getItem('customMainTasks');
const savedConsequenceTasks = localStorage.getItem('customConsequenceTasks');
@ -308,18 +431,56 @@ class TaskChallengeGame {
}
// Always add interactive tasks - mode filtering happens later
this.addInteractiveTasksToGameData();
this.addInteractiveTasksToGameData(options);
// Filter out scenario tasks if disabled in options
if (options && options.includeScenarioTasks === false) {
console.log('🎭 Filtering out scenario tasks from game data');
const originalCount = gameData.mainTasks.length;
gameData.mainTasks = gameData.mainTasks.filter(task => {
// Remove tasks with scenario-adventure interactive type
if (task.interactiveType === 'scenario-adventure') {
console.log('🗑️ Removing scenario task:', task.id || task.text);
return false;
}
return true;
});
console.log(`📊 Filtered tasks: ${originalCount}${gameData.mainTasks.length}`);
}
console.log(`Loaded ${gameData.mainTasks.length} main tasks and ${gameData.consequenceTasks.length} consequence tasks`);
// Debug: log interactive tasks
const interactiveTasks = gameData.mainTasks.filter(t => t.interactiveType);
console.log(`Interactive tasks available: ${interactiveTasks.length}`, interactiveTasks.map(t => `${t.id}:${t.interactiveType}`));
// Debug: check for scenario tasks specifically
const scenarioTasks = gameData.mainTasks.filter(t => t.interactiveType === 'scenario-adventure');
console.log(`🎭 Scenario tasks in final data: ${scenarioTasks.length}`, scenarioTasks.map(t => t.id || t.text));
}
addInteractiveTasksToGameData() {
// Define our interactive tasks - Scenario Adventures only
const interactiveTasks = [
addInteractiveTasksToGameData(options = {}) {
// Check configuration options (default: include all for backward compatibility)
const includeScenarioTasks = options.includeScenarioTasks !== false;
const includeStandardTasks = options.includeStandardTasks !== false;
console.log('🎯 Task filtering options:', options);
console.log('🎭 Include scenario tasks:', includeScenarioTasks);
console.log('📝 Include standard tasks:', includeStandardTasks);
// Define our interactive tasks
const interactiveTasks = [];
// Add mirror task (considered a standard interactive task)
if (includeStandardTasks) {
// Mirror task is already in gameData.js, so we don't need to add it here
console.log('🪞 Mirror tasks enabled (from gameData.js)');
}
// Only add scenario tasks if enabled
if (includeScenarioTasks) {
console.log('🎭 Scenario tasks enabled - adding scenario adventures');
interactiveTasks.push(
{
id: 'scenario-training-session',
text: "Enter a guided training session",
@ -1400,7 +1561,10 @@ class TaskChallengeGame {
},
hint: "Learn the value of unquestioning obedience"
}
];
); // Close the interactiveTasks.push
} else {
console.log('🎭 Scenario tasks disabled');
}
// Add interactive tasks to gameData.mainTasks if they don't already exist
for (const interactiveTask of interactiveTasks) {
@ -1620,78 +1784,98 @@ class TaskChallengeGame {
}
initializeEventListeners() {
// Helper function to safely add event listeners
const safeAddListener = (id, event, handler) => {
const element = document.getElementById(id);
if (element) {
element.addEventListener(event, handler);
} else {
console.log(`⚠️ Element '${id}' not found, skipping event listener`);
}
};
// Screen navigation
document.getElementById('start-btn').addEventListener('click', () => this.startGame());
document.getElementById('resume-btn').addEventListener('click', () => this.resumeGame());
document.getElementById('quit-btn').addEventListener('click', () => this.quitGame());
document.getElementById('play-again-btn').addEventListener('click', () => this.resetGame());
safeAddListener('start-btn', 'click', () => this.startGame());
safeAddListener('resume-btn', 'click', () => this.resumeGame());
safeAddListener('quit-btn', 'click', () => this.quitGame());
safeAddListener('play-again-btn', 'click', () => this.resetGame());
// Game mode selection
this.initializeGameModeListeners();
// Game actions
document.getElementById('complete-btn').addEventListener('click', () => this.completeTask());
document.getElementById('skip-btn').addEventListener('click', () => this.skipTask());
document.getElementById('mercy-skip-btn').addEventListener('click', () => this.mercySkip());
document.getElementById('pause-btn').addEventListener('click', () => this.pauseGame());
// Game actions - support both main game and Quick Play button IDs
safeAddListener('complete-btn', 'click', () => this.completeTask());
safeAddListener('complete-task', 'click', () => this.completeTask());
safeAddListener('skip-btn', 'click', () => this.skipTask());
safeAddListener('skip-task', 'click', () => this.skipTask());
safeAddListener('mercy-skip-btn', 'click', () => this.mercySkip());
safeAddListener('pause-btn', 'click', () => this.pauseGame());
// Theme selector
document.getElementById('theme-dropdown').addEventListener('change', (e) => this.changeTheme(e.target.value));
safeAddListener('theme-dropdown', 'change', (e) => this.changeTheme(e.target.value));
// Options menu toggle
document.getElementById('options-menu-btn').addEventListener('click', () => this.toggleOptionsMenu());
safeAddListener('options-menu-btn', 'click', () => this.toggleOptionsMenu());
// Window cleanup - stop audio when app is closed
let audioCleanupDone = false;
window.addEventListener('beforeunload', () => {
console.log('Window closing - stopping all audio');
this.audioManager.stopAllImmediate();
if (!audioCleanupDone && !window.isForceExiting) {
console.log('Window closing - stopping all audio');
audioCleanupDone = true;
try {
this.audioManager.stopAllImmediate();
} catch (error) {
console.warn('Error during audio cleanup:', error);
}
}
});
// Music controls
document.getElementById('music-toggle').addEventListener('click', () => this.toggleMusic());
document.getElementById('music-toggle-compact').addEventListener('click', (e) => {
safeAddListener('music-toggle', 'click', () => this.toggleMusic());
safeAddListener('music-toggle-compact', 'click', (e) => {
e.stopPropagation(); // Prevent event bubbling
// The hover panel will show automatically, just indicate it's interactive
});
document.getElementById('loop-btn').addEventListener('click', () => this.toggleLoop());
document.getElementById('shuffle-btn').addEventListener('click', () => this.toggleShuffle());
document.getElementById('track-selector').addEventListener('change', (e) => this.changeTrack(parseInt(e.target.value)));
document.getElementById('volume-slider').addEventListener('input', (e) => this.changeVolume(parseInt(e.target.value)));
safeAddListener('loop-btn', 'click', () => this.toggleLoop());
safeAddListener('shuffle-btn', 'click', () => this.toggleShuffle());
safeAddListener('track-selector', 'change', (e) => this.changeTrack(parseInt(e.target.value)));
safeAddListener('volume-slider', 'input', (e) => this.changeVolume(parseInt(e.target.value)));
// Task management
document.getElementById('manage-tasks-btn').addEventListener('click', () => this.showTaskManagement());
document.getElementById('back-to-start-btn').addEventListener('click', () => this.showScreen('start-screen'));
document.getElementById('add-task-btn').addEventListener('click', () => this.addNewTask());
document.getElementById('reset-tasks-btn').addEventListener('click', () => this.resetToDefaultTasks());
document.getElementById('main-tasks-tab').addEventListener('click', () => this.showTaskTab('main'));
document.getElementById('consequence-tasks-tab').addEventListener('click', () => this.showTaskTab('consequence'));
document.getElementById('new-task-type').addEventListener('change', () => this.toggleDifficultyDropdown());
safeAddListener('manage-tasks-btn', 'click', () => this.showTaskManagement());
safeAddListener('back-to-start-btn', 'click', () => this.showScreen('start-screen'));
safeAddListener('add-task-btn', 'click', () => this.addNewTask());
safeAddListener('reset-tasks-btn', 'click', () => this.resetToDefaultTasks());
safeAddListener('main-tasks-tab', 'click', () => this.showTaskTab('main'));
safeAddListener('consequence-tasks-tab', 'click', () => this.showTaskTab('consequence'));
safeAddListener('new-task-type', 'change', () => this.toggleDifficultyDropdown());
// Data management
document.getElementById('export-btn').addEventListener('click', () => this.exportData());
document.getElementById('import-btn').addEventListener('click', () => this.importData());
document.getElementById('import-file').addEventListener('change', (e) => this.handleFileImport(e));
document.getElementById('stats-btn').addEventListener('click', () => this.showStats());
document.getElementById('help-btn').addEventListener('click', () => this.showHelp());
document.getElementById('close-stats').addEventListener('click', () => this.hideStats());
document.getElementById('close-help').addEventListener('click', () => this.hideHelp());
document.getElementById('reset-stats-btn').addEventListener('click', () => this.resetStats());
document.getElementById('export-stats-btn').addEventListener('click', () => this.exportStatsOnly());
safeAddListener('export-btn', 'click', () => this.exportData());
safeAddListener('import-btn', 'click', () => this.importData());
safeAddListener('import-file', 'change', (e) => this.handleFileImport(e));
safeAddListener('stats-btn', 'click', () => this.showStats());
safeAddListener('help-btn', 'click', () => this.showHelp());
safeAddListener('close-stats', 'click', () => this.hideStats());
safeAddListener('close-help', 'click', () => this.hideHelp());
safeAddListener('reset-stats-btn', 'click', () => this.resetStats());
safeAddListener('export-stats-btn', 'click', () => this.exportStatsOnly());
// Audio controls
this.initializeAudioControls();
// Image management - only the main button, others will be attached when screen is shown
document.getElementById('manage-images-btn').addEventListener('click', () => this.showImageManagement());
safeAddListener('manage-images-btn', 'click', () => this.showImageManagement());
// Audio management - only the main button, others will be attached when screen is shown
document.getElementById('manage-audio-btn').addEventListener('click', () => this.showAudioManagement());
safeAddListener('manage-audio-btn', 'click', () => this.showAudioManagement());
// Photo gallery management
document.getElementById('photo-gallery-btn').addEventListener('click', () => this.showPhotoGallery());
safeAddListener('photo-gallery-btn', 'click', () => this.showPhotoGallery());
// Annoyance management - main button and basic controls
document.getElementById('manage-annoyance-btn').addEventListener('click', () => this.showAnnoyanceManagement());
safeAddListener('manage-annoyance-btn', 'click', () => this.showAnnoyanceManagement());
// Load saved theme
this.loadSavedTheme();
@ -1762,8 +1946,14 @@ class TaskChallengeGame {
}
initializeAudioControls() {
// Volume sliders
// Check if audio controls exist before trying to initialize them
const masterVolumeSlider = document.getElementById('master-volume');
if (!masterVolumeSlider) {
console.log('⚠️ Audio controls not found, skipping audio control initialization');
return;
}
// Volume sliders
const taskAudioSlider = document.getElementById('task-audio-volume');
const punishmentAudioSlider = document.getElementById('punishment-audio-volume');
const rewardAudioSlider = document.getElementById('reward-audio-volume');
@ -2068,41 +2258,23 @@ class TaskChallengeGame {
});
});
// Add listeners for dropdown changes (if elements exist)
const timeLimitSelect = document.getElementById('time-limit-select');
if (timeLimitSelect) {
console.log('⏱️ Found time limit select, adding listener');
timeLimitSelect.addEventListener('change', () => {
console.log('⏱️ Time limit select changed');
this.handleTimeLimitChange();
// Add listeners for Quick Play controls
const playTimeSelect = document.getElementById('play-time-select');
if (playTimeSelect) {
console.log('⏱️ Found play time select, adding listener');
playTimeSelect.addEventListener('change', () => {
console.log('⏱️ Play time select changed');
this.handleQuickPlayTimeChange();
});
} else {
console.log('⚠️ Time limit select not found');
}
const xpTargetSelect = document.getElementById('xp-target-select');
if (xpTargetSelect) {
console.log('⭐ Found XP target select, adding listener');
xpTargetSelect.addEventListener('change', () => {
console.log('⭐ XP target select changed');
this.handleXpTargetChange();
});
} else {
console.log('⚠️ XP target select not found');
console.log('⚠️ Play time select not found');
}
// Add listeners for custom input changes (if elements exist)
const customTimeValue = document.getElementById('custom-time-value');
if (customTimeValue) {
customTimeValue.addEventListener('input', () => {
this.handleCustomTimeChange();
});
}
const customXpValue = document.getElementById('custom-xp-value');
if (customXpValue) {
customXpValue.addEventListener('input', () => {
this.handleCustomXpChange();
const customPlayTimeValue = document.getElementById('custom-play-time-value');
if (customPlayTimeValue) {
customPlayTimeValue.addEventListener('input', () => {
this.handleCustomPlayTimeChange();
});
}
@ -2119,32 +2291,22 @@ class TaskChallengeGame {
console.log(`🎮 Game mode changed to: ${selectedMode}`);
// Show/hide configuration options based on selected mode (if elements exist)
const timedConfig = document.getElementById('timed-config');
const xpTargetConfig = document.getElementById('xp-target-config');
const quickPlayConfig = document.getElementById('quick-play-config');
// Hide all configs first
if (timedConfig) {
timedConfig.style.display = 'none';
}
if (xpTargetConfig) {
xpTargetConfig.style.display = 'none';
}
// Hide all configs first (in case there are any old ones)
const allConfigs = document.querySelectorAll('.mode-config');
allConfigs.forEach(config => config.style.display = 'none');
// Show appropriate config
if (selectedMode === 'timed' && timedConfig) {
timedConfig.style.display = 'block';
console.log('⏱️ Showing timed configuration options');
this.handleTimeLimitChange();
} else if (selectedMode === 'xp-target' && xpTargetConfig) {
xpTargetConfig.style.display = 'block';
console.log('⭐ Showing XP target configuration options');
this.handleXpTargetChange();
// Show Quick Play config (it's the only mode now)
if (selectedMode === 'quick-play' && quickPlayConfig) {
quickPlayConfig.style.display = 'block';
console.log('⚡ Showing Quick Play configuration options');
this.handleQuickPlayTimeChange();
}
console.log(`Game state updated:`, {
gameMode: this.gameState.gameMode,
timeLimit: this.gameState.timeLimit,
xpTarget: this.gameState.xpTarget
timeLimit: this.gameState.timeLimit
});
}
@ -2222,6 +2384,43 @@ class TaskChallengeGame {
}
}
handleQuickPlayTimeChange() {
const playTimeSelect = document.getElementById('play-time-select');
const customPlayTimeInput = document.getElementById('custom-play-time-input');
if (!playTimeSelect) {
console.log('⚠️ Play time select element not found');
return;
}
const selectedValue = playTimeSelect.value;
console.log(`⚡ Play time selection: ${selectedValue}`);
if (customPlayTimeInput) {
if (selectedValue === 'custom') {
customPlayTimeInput.style.display = 'block';
console.log('⚡ Showing custom play time input');
this.handleCustomPlayTimeChange();
} else {
customPlayTimeInput.style.display = 'none';
this.gameState.timeLimit = parseInt(selectedValue);
console.log(`⚡ Play time set to: ${this.gameState.timeLimit} seconds`);
}
} else if (selectedValue !== 'custom') {
this.gameState.timeLimit = parseInt(selectedValue);
console.log(`⚡ Play time set to: ${this.gameState.timeLimit} seconds`);
}
}
handleCustomPlayTimeChange() {
const customPlayTimeValue = document.getElementById('custom-play-time-value');
if (customPlayTimeValue) {
const minutes = parseInt(customPlayTimeValue.value) || 15;
this.gameState.timeLimit = minutes * 60; // Convert minutes to seconds
console.log(`Custom play time set to ${minutes} minutes (${this.gameState.timeLimit} seconds)`);
}
}
// XP Calculation Methods
calculateTimeBasedXp() {
if (!this.gameState.sessionStartTime) return 0;
@ -4593,11 +4792,64 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
}
// Get selected game mode
const selectedMode = document.querySelector('input[name="gameMode"]:checked')?.value || 'standard';
const selectedMode = document.querySelector('input[name="gameMode"]:checked')?.value || 'quick-play';
if (window.gameModeManager) {
window.gameModeManager.currentMode = selectedMode;
// Map the new mode names to the original game engine modes
this.gameState.gameMode = window.gameModeManager.getGameModeForEngine();
// Apply Quick Play settings
if (selectedMode === 'quick-play' && window.quickPlaySettings) {
// Set play time
this.gameState.timeLimit = window.quickPlaySettings.getPlayTime();
console.log(`⚡ Quick Play time limit set to: ${this.gameState.timeLimit} seconds`);
// Configure background audio
const backgroundAudioEnabled = window.quickPlaySettings.isBackgroundAudioEnabled();
if (this.audioManager) {
if (backgroundAudioEnabled) {
this.audioManager.resumePlaylist();
console.log('🎵 Background audio enabled for Quick Play');
} else {
this.audioManager.pausePlaylist();
console.log('🔇 Background audio disabled for Quick Play');
}
}
// Configure popup images
if (this.popupImageManager) {
const popupFrequency = window.quickPlaySettings.getPopupFrequency();
const popupDuration = window.quickPlaySettings.getPopupDuration();
// Update popup image settings using the correct method
this.popupImageManager.updateConfig({
displayDuration: popupDuration * 1000, // Convert to milliseconds
});
// Update periodic popup settings
if (this.popupImageManager.updatePeriodicSettings) {
this.popupImageManager.updatePeriodicSettings({
minInterval: popupFrequency,
maxInterval: popupFrequency,
displayDuration: popupDuration
});
}
console.log(`🖼️ Popup images: ${popupFrequency}s frequency, ${popupDuration}s duration`);
}
// Configure flash messages
if (this.flashMessageManager) {
const messageFrequency = window.quickPlaySettings.getMessageFrequency();
const messageDuration = window.quickPlaySettings.getMessageDuration();
// Update flash message settings
this.flashMessageManager.updateConfig({
displayDuration: messageDuration * 1000, // Convert to milliseconds
intervalDelay: messageFrequency * 1000 // Convert to milliseconds
});
console.log(`💬 Flash messages: ${messageFrequency}s frequency, ${messageDuration}s duration`);
}
}
} else {
// Fallback if gameModeManager is not available
this.gameState.gameMode = selectedMode === 'timed' ? 'timed' :
@ -4841,7 +5093,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
const taskContainer = document.querySelector('.task-container');
const taskTypeIndicator = document.getElementById('task-type-indicator');
const mercySkipBtn = document.getElementById('mercy-skip-btn');
const skipBtn = document.getElementById('skip-btn');
const skipBtn = document.getElementById('skip-btn') || document.getElementById('skip-task');
// Note: task-difficulty and task-points elements were removed during XP conversion
// Note: task-type-indicator element may not exist in current HTML
@ -4859,15 +5111,17 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
}
// Update skip button text for scenario modes
if (isScenarioMode) {
if (isScenarioMode && skipBtn) {
skipBtn.textContent = 'Give Up';
skipBtn.className = 'btn btn-danger'; // Change to red for more serious action
} else {
} else if (skipBtn) {
skipBtn.textContent = 'Skip';
skipBtn.className = 'btn btn-warning'; // Default yellow
}
taskText.textContent = this.gameState.currentTask.text;
if (taskText) {
taskText.textContent = this.gameState.currentTask.text;
}
// Hide task image for scenario games to prevent scrolling issues
if (isScenarioMode) {
@ -4892,7 +5146,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
}
// Set image with error handling (only for non-scenario modes)
if (!isScenarioMode) {
if (!isScenarioMode && taskImage) {
taskImage.src = this.gameState.currentTask.image;
taskImage.onerror = () => {
console.log('Image failed to load:', this.gameState.currentTask.image);
@ -4924,25 +5178,33 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
taskTypeIndicator.classList.add('consequence');
}
// Hide regular skip button for consequence tasks
skipBtn.style.display = 'none';
if (skipBtn) {
skipBtn.style.display = 'none';
}
// Show mercy skip button for consequence tasks
const originalTask = this.findOriginalSkippedTask();
if (originalTask) {
if (originalTask && mercySkipBtn) {
// In XP system, mercy skip costs 5 XP (flat rate as per ROADMAP)
const mercyCost = 5;
const canAfford = this.gameState.xp >= mercyCost;
if (canAfford) {
mercySkipBtn.style.display = 'block';
document.getElementById('mercy-skip-cost').textContent = `-${mercyCost} XP`;
const mercyCostElement = document.getElementById('mercy-skip-cost');
if (mercyCostElement) {
mercyCostElement.textContent = `-${mercyCost} XP`;
}
mercySkipBtn.disabled = false;
} else {
mercySkipBtn.style.display = 'block';
document.getElementById('mercy-skip-cost').textContent = `-${mercyCost} XP (Not enough!)`;
const mercyCostElement = document.getElementById('mercy-skip-cost');
if (mercyCostElement) {
mercyCostElement.textContent = `-${mercyCost} XP (Not enough!)`;
}
mercySkipBtn.disabled = true;
}
} else {
} else if (mercySkipBtn) {
mercySkipBtn.style.display = 'none';
}
} else {
@ -4952,13 +5214,23 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
}
// Show regular skip button for main tasks
skipBtn.style.display = 'block';
if (skipBtn) {
skipBtn.style.display = 'block';
}
// Hide mercy skip button for main tasks
mercySkipBtn.style.display = 'none';
if (mercySkipBtn) {
mercySkipBtn.style.display = 'none';
}
// Note: In XP system, no need to display difficulty/points separately
// The XP is awarded based on time and activities, not task difficulty
}
// Show complete button for all tasks (support both ID patterns)
const completeBtn = document.getElementById('complete-btn') || document.getElementById('complete-task');
if (completeBtn) {
completeBtn.style.display = 'block';
}
}
findOriginalSkippedTask() {
@ -5714,32 +5986,39 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
formattedTime = this.formatTime(remainingMs);
// Change color when time is running low (less than 30 seconds)
const timerElement = document.getElementById('timer');
if (remainingMs <= 30000) {
timerElement.style.color = '#ff4757';
timerElement.style.fontWeight = 'bold';
} else if (remainingMs <= 60000) {
timerElement.style.color = '#ffa502';
timerElement.style.fontWeight = 'bold';
} else {
timerElement.style.color = '';
timerElement.style.fontWeight = '';
const timerElement = document.getElementById('timer') || document.getElementById('game-timer');
if (timerElement) {
if (remainingMs <= 30000) {
timerElement.style.color = '#ff4757';
timerElement.style.fontWeight = 'bold';
} else if (remainingMs <= 60000) {
timerElement.style.color = '#ffa502';
timerElement.style.fontWeight = 'bold';
} else {
timerElement.style.color = '';
timerElement.style.fontWeight = '';
}
}
} else {
// Normal elapsed timer for other modes
formattedTime = this.formatTime(elapsed);
}
document.getElementById('timer').textContent = formattedTime;
const timerDisplayElement = document.getElementById('timer') || document.getElementById('game-timer');
if (timerDisplayElement) {
timerDisplayElement.textContent = formattedTime;
}
// Update timer status
const timerStatus = document.getElementById('timer-status');
if (this.gameState.isPaused) {
timerStatus.textContent = '(PAUSED)';
} else if (this.gameState.gameMode === 'timed') {
timerStatus.textContent = '(TIME LEFT)';
} else {
timerStatus.textContent = '';
if (timerStatus) {
if (this.gameState.isPaused) {
timerStatus.textContent = '(PAUSED)';
} else if (this.gameState.gameMode === 'timed') {
timerStatus.textContent = '(TIME LEFT)';
} else {
timerStatus.textContent = '';
}
}
}
@ -5752,10 +6031,18 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
}
updateStats() {
document.getElementById('completed-count').textContent = this.gameState.completedCount;
document.getElementById('skipped-count').textContent = this.gameState.skippedCount;
document.getElementById('consequence-count').textContent = this.gameState.consequenceCount;
document.getElementById('xp').textContent = this.gameState.xp || 0;
// Helper function to safely update element text content
const safeUpdateText = (id, value) => {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
}
};
safeUpdateText('completed-count', this.gameState.completedCount);
safeUpdateText('skipped-count', this.gameState.skippedCount);
safeUpdateText('consequence-count', this.gameState.consequenceCount);
safeUpdateText('xp', this.gameState.xp || 0);
// Update streak display
const streakElement = document.getElementById('current-streak');
@ -6453,6 +6740,12 @@ class MusicManager {
const volumePercent = document.getElementById('volume-percent');
const trackSelector = document.getElementById('track-selector');
// Check if UI elements exist before updating them
if (!volumeSlider || !volumePercent || !trackSelector) {
// UI elements not available (probably in dedicated screen mode)
return;
}
volumeSlider.value = this.volume;
volumePercent.textContent = `${this.volume}%`;
@ -6482,6 +6775,8 @@ class MusicManager {
updateLoopButton() {
const loopBtn = document.getElementById('loop-btn');
if (!loopBtn) return;
switch(this.loopMode) {
case 0:
loopBtn.classList.remove('active');
@ -6503,6 +6798,8 @@ class MusicManager {
updateShuffleButton() {
const shuffleBtn = document.getElementById('shuffle-btn');
if (!shuffleBtn) return;
if (this.shuffleMode) {
shuffleBtn.classList.add('active');
shuffleBtn.title = 'Shuffle: On';
@ -6700,6 +6997,12 @@ class MusicManager {
const trackSelector = document.getElementById('track-selector');
const currentTrack = this.tracks[this.currentTrackIndex];
// Check if UI elements exist before updating them
if (!toggleBtn || !statusSpan || !trackSelector) {
// UI elements not available (probably in dedicated screen mode)
return;
}
// Update track selector
trackSelector.value = this.currentTrackIndex;
@ -8502,6 +8805,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (window.game && window.game.isInitialized) {
enableElements();
console.log('✅ Game fully initialized and ready for interaction');
// Dispatch game ready event for other systems
window.dispatchEvent(new CustomEvent('gameReady', {
detail: { game: window.game }
}));
} else {
setTimeout(checkInitialization, 100);
}

View File

@ -5,22 +5,12 @@
class GameModeManager {
constructor() {
this.currentMode = 'standard'; // Default to standard mode
this.currentMode = 'quick-play'; // Default to quick play mode
this.availableModes = {
'standard': {
name: 'Standard Game',
description: 'Classic endless task mode with regular tasks only',
icon: '🎮'
},
'timed': {
name: 'Timed Challenge',
description: 'Race against the clock to complete as many tasks as possible',
icon: '⏰'
},
'scored': {
name: 'Score Target',
description: 'Reach a target score by completing tasks based on difficulty',
icon: '🎯'
'quick-play': {
name: 'Quick Play',
description: 'Timed challenge mode with customizable options',
icon: '⚡'
},
'photography-studio': {
name: 'Photography Studio',
@ -92,62 +82,83 @@ class GameModeManager {
console.log('🔍 Looking for .game-mode-selection element:', gameModeSelection);
if (gameModeSelection) {
console.log('✅ Found game mode selection, replacing with new modes');
console.log('✅ Found game mode selection, replacing with updated modes');
gameModeSelection.innerHTML = `
<h3>Choose Your Game Mode</h3>
<div class="game-mode-grid">
${Object.entries(this.availableModes).map(([key, mode]) => `
<div class="game-mode-card" data-mode="${key}">
<div class="game-mode-card ${key === 'quick-play' ? 'selected' : ''}" data-mode="${key}">
<div class="mode-icon">${mode.icon}</div>
<div class="mode-info">
<h4>${mode.name}</h4>
<p>${mode.description}</p>
</div>
<input type="radio" name="gameMode" value="${key}" id="mode-${key}" ${key === 'standard' ? 'checked' : ''}>
<input type="radio" name="gameMode" value="${key}" id="mode-${key}" ${key === 'quick-play' ? 'checked' : ''}>
</div>
`).join('')}
</div>
<div class="mode-options" id="mode-options">
<!-- Timed Challenge Configuration -->
<div class="mode-config" id="timed-config" style="display: none;">
<!-- Quick Play Configuration -->
<div class="mode-config" id="quick-play-config" style="display: block;">
<div class="config-section">
<h4> Time Limit Configuration</h4>
<label>Time Limit:
<select id="time-limit-select">
<option value="300">5 minutes</option>
<option value="600">10 minutes</option>
<option value="900">15 minutes</option>
<option value="1200">20 minutes</option>
<option value="1800">30 minutes</option>
<option value="custom">Custom...</option>
</select>
</label>
<div id="custom-time-input" style="display: none; margin-top: 10px;">
<label>Custom time (minutes):
<input type="number" id="custom-time-value" min="1" max="180" value="15" style="width: 60px;">
<h4> Quick Play Options</h4>
<!-- Play Time Configuration -->
<div class="option-group">
<label> Play Time:
<select id="play-time-select">
<option value="300">5 minutes</option>
<option value="600">10 minutes</option>
<option value="900">15 minutes</option>
<option value="1200">20 minutes</option>
<option value="1800">30 minutes</option>
<option value="custom">Custom...</option>
</select>
</label>
<div id="custom-play-time-input" style="display: none; margin-top: 10px;">
<label>Custom time (minutes):
<input type="number" id="custom-play-time-value" min="1" max="180" value="15" style="width: 60px;">
</label>
</div>
</div>
<!-- Background Audio Configuration -->
<div class="option-group">
<label class="checkbox-label">
<input type="checkbox" id="enable-background-audio" checked>
🎵 Enable Background Audio Tracks
</label>
</div>
</div>
</div>
<!-- Score Target Configuration -->
<div class="mode-config" id="score-target-config" style="display: none;">
<div class="config-section">
<h4>🏆 Score Target Configuration</h4>
<label>Target Score:
<select id="score-target-select">
<option value="100">100 points</option>
<option value="200">200 points</option>
<option value="300">300 points</option>
<option value="500">500 points</option>
<option value="custom">Custom...</option>
</select>
</label>
<div id="custom-score-input" style="display: none; margin-top: 10px;">
<label>Custom target score:
<input type="number" id="custom-score-value" min="50" max="10000" value="300" style="width: 80px;">
</label>
<!-- Popup Image Configuration -->
<div class="option-group">
<h5>🖼 Popup Images</h5>
<div class="sub-option">
<label>Frequency (seconds between popups):
<input type="number" id="popup-frequency" min="10" max="300" value="60" style="width: 70px;">
</label>
</div>
<div class="sub-option">
<label>Duration (seconds displayed):
<input type="number" id="popup-duration" min="2" max="30" value="8" style="width: 70px;">
</label>
</div>
</div>
<!-- Flash Message Configuration -->
<div class="option-group">
<h5>💬 Flash Messages</h5>
<div class="sub-option">
<label>Frequency (seconds between messages):
<input type="number" id="message-frequency" min="5" max="300" value="45" style="width: 70px;">
</label>
</div>
<div class="sub-option">
<label>Duration (seconds displayed):
<input type="number" id="message-duration" min="1" max="10" value="3" style="width: 70px;">
</label>
</div>
</div>
</div>
</div>
@ -160,38 +171,80 @@ class GameModeManager {
<input type="checkbox" id="include-custom-tasks" checked>
Include your custom tasks alongside scenarios
</label>
<div class="frequency-control" id="custom-task-frequency">
<label>Custom task frequency:
<span id="frequency-display">30%</span>
</label>
<input type="range" id="frequency-slider" min="10" max="70" value="30" step="10">
<small>How often custom tasks appear between scenarios</small>
<div class="frequency-control">
<label>Custom Task Frequency: <span id="frequency-display">30%</span></label>
<input type="range" id="custom-task-frequency" min="0" max="100" value="30" step="10">
<small class="help-text">How often custom tasks appear between scenario tasks</small>
</div>
</div>
<div class="config-section">
<h4> Scenario Settings</h4>
<label class="checkbox-label">
<input type="checkbox" id="randomize-scenario-order" checked>
Randomize scenario order
</label>
<label class="checkbox-label">
<input type="checkbox" id="allow-scenario-repeats">
Allow scenario repeats after completion
</label>
</div>
</div>
</div>
`;
// Add event listeners for the new controls
this.setupQuickPlayListeners();
// Verify the radio buttons were created
const radioButtons = gameModeSelection.querySelectorAll('input[name="gameMode"]');
console.log(`🔘 Created ${radioButtons.length} radio buttons:`, Array.from(radioButtons).map(r => r.value));
} else {
console.error('❌ Could not find .game-mode-selection element');
console.log('⚠️ Game mode selection element not found - skipping UI setup (likely in dedicated screen)');
}
}
/**
* Setup event listeners for Quick Play configuration
*/
setupQuickPlayListeners() {
// Play time selection
const playTimeSelect = document.getElementById('play-time-select');
const customPlayTimeInput = document.getElementById('custom-play-time-input');
const customPlayTimeValue = document.getElementById('custom-play-time-value');
if (playTimeSelect) {
playTimeSelect.addEventListener('change', () => {
if (playTimeSelect.value === 'custom') {
customPlayTimeInput.style.display = 'block';
} else {
customPlayTimeInput.style.display = 'none';
}
});
}
// Store all settings in a global object for easy access
window.quickPlaySettings = {
getPlayTime: () => {
const select = document.getElementById('play-time-select');
if (select.value === 'custom') {
const customValue = document.getElementById('custom-play-time-value');
return parseInt(customValue.value) * 60; // Convert minutes to seconds
} else {
return parseInt(select.value); // Already in seconds
}
},
isBackgroundAudioEnabled: () => {
const checkbox = document.getElementById('enable-background-audio');
return checkbox ? checkbox.checked : true;
},
getPopupFrequency: () => {
const input = document.getElementById('popup-frequency');
return input ? parseInt(input.value) : 60;
},
getPopupDuration: () => {
const input = document.getElementById('popup-duration');
return input ? parseInt(input.value) : 8;
},
getMessageFrequency: () => {
const input = document.getElementById('message-frequency');
return input ? parseInt(input.value) : 45;
},
getMessageDuration: () => {
const input = document.getElementById('message-duration');
return input ? parseInt(input.value) : 3;
}
};
}
/**
* Remove hint buttons from scenarios in game.js
*/
@ -264,26 +317,19 @@ class GameModeManager {
const allConfigs = document.querySelectorAll('.mode-config');
allConfigs.forEach(config => config.style.display = 'none');
// Show scenario config for scenario modes
if (mode !== 'standard' && mode !== 'timed' && mode !== 'scored') {
// Show appropriate config based on mode
if (mode === 'quick-play') {
const quickPlayConfig = document.getElementById('quick-play-config');
if (quickPlayConfig) {
quickPlayConfig.style.display = 'block';
console.log('⚡ Showing Quick Play configuration');
}
} else {
// Show scenario config for scenario modes
const scenarioConfig = document.getElementById('scenario-config');
if (scenarioConfig) {
scenarioConfig.style.display = 'block';
}
}
// For timed and scored modes, show the original configuration UI
if (mode === 'timed') {
const timedConfig = document.getElementById('timed-config');
if (timedConfig) {
timedConfig.style.display = 'block';
console.log('⏱️ Showing timed challenge configuration');
}
} else if (mode === 'scored') {
const scoreTargetConfig = document.getElementById('score-target-config');
if (scoreTargetConfig) {
scoreTargetConfig.style.display = 'block';
console.log('🏆 Showing score target configuration');
console.log('🎮 Showing scenario configuration');
}
}
@ -298,7 +344,7 @@ class GameModeManager {
* Get tasks for the current game mode
*/
getTasksForMode() {
if (this.currentMode === 'standard' || this.currentMode === 'timed' || this.currentMode === 'scored') {
if (this.currentMode === 'standard' || this.currentMode === 'timed' || this.currentMode === 'scored' || this.currentMode === 'quick-play') {
return this.getStandardTasks();
} else {
return this.getScenarioModeTasks();
@ -309,15 +355,12 @@ class GameModeManager {
* Map new mode names to original game functionality
*/
getGameModeForEngine() {
switch(this.currentMode) {
case 'standard':
return 'complete-all';
case 'timed':
return 'timed';
case 'scored':
return 'score-target';
default:
return 'complete-all'; // Scenario modes use complete-all as base
// Quick Play mode uses timed mode as the base
if (this.currentMode === 'quick-play') {
return 'timed';
} else {
// Scenario modes use complete-all as base
return 'complete-all';
}
}
@ -652,7 +695,7 @@ class GameModeManager {
* Check if current mode is scenario-based
*/
isScenarioMode() {
return this.currentMode && this.currentMode !== 'standard';
return this.currentMode && this.currentMode !== 'standard' && this.currentMode !== 'quick-play';
}
/**

View File

@ -4,87 +4,87 @@ const gameData = {
mainTasks: [
{
id: 1,
text: "Hold your breath for 30 seconds while maintaining focus",
text: "Watch porn and stroke slowly for 2 minutes, no cumming",
difficulty: "Easy"
},
{
id: 2,
text: "Close your eyes and count to 20 without rushing",
text: "Edge while watching BBC content sitting in chair for 3 minutes",
difficulty: "Easy"
},
{
id: 3,
text: "Keep your hands completely still for 45 seconds",
text: "Stroke on your knees while watching lesbian porn for 5 minutes",
difficulty: "Medium"
},
{
id: 4,
text: "Stare at a fixed point without blinking for 30 seconds",
text: "Watch cuckold videos standing up and edge for 4 minutes without release",
difficulty: "Medium"
},
{
id: 5,
text: "Take 10 slow, deep breaths while focusing on control",
text: "Goon to compilation videos sitting for 6 minutes, multiple edges",
difficulty: "Easy"
},
{
id: 6,
text: "Hold a stress position (arms extended) for 60 seconds",
text: "Watch interracial porn on your back, legs up, stroke for 7 minutes",
difficulty: "Medium"
},
{
id: 7,
text: "Repeat 'I am learning self-control' 10 times slowly",
text: "Edge to femdom content while lying face down for 4 minutes",
difficulty: "Easy"
},
{
id: 8,
text: "Maintain perfect posture for 2 minutes without moving",
text: "Watch sissy hypno standing while stroking with your non-dominant hand for 8 minutes",
difficulty: "Medium"
},
{
id: 9,
text: "Edge for 30 seconds then stop immediately",
text: "Goon to gangbang videos sitting in chair for 10 minutes, edge every 2 minutes",
difficulty: "Easy"
},
{
id: 10,
text: "Close your eyes and focus on breathing for 90 seconds",
text: "Watch MILF porn while in doggy position, stroke for 6 minutes",
difficulty: "Easy"
},
{
id: 11,
text: "Hold a challenging yoga pose for 60 seconds",
text: "Edge to cuckold captions standing for 12 minutes with multiple stops",
difficulty: "Easy"
},
{
id: 12,
text: "Count backwards from 100 by 7s without losing focus",
text: "Watch trans porn while stroking sitting and standing alternately for 9 minutes",
difficulty: "Medium"
},
{
id: 13,
text: "Edge for 2 minutes with 3 stops, then wait 30 seconds",
text: "Goon session: 15 minutes of continuous stroking sitting to random videos",
difficulty: "Hard"
},
{
id: 14,
text: "Hold a plank position for 3 minutes while edging",
text: "Watch hotwife content while edging on knees for 12 minutes",
difficulty: "Hard"
},
{
id: 15,
text: "Practice edging with varying speeds for 5 minutes",
text: "Extended goon session: 15 minutes of edging standing to BBC compilations",
difficulty: "Hard"
},
{
id: 16,
text: "Complete a 10-minute endurance session with no release",
text: "Marathon edge session: 15 minutes alternating sitting/standing while watching porn",
difficulty: "Hard"
},
{
id: 17,
text: "Edge to near climax 5 times, then stop for 2 minutes",
text: "Ultimate goon challenge: 15 minutes of edging in doggy position to humiliation content",
difficulty: "Hard"
},
],
@ -93,51 +93,51 @@ const gameData = {
consequenceTasks: [
{
id: 101,
text: "Hold the edge for 60 seconds without any movement"
text: "Watch your girlfriend with another man fantasy while sitting in chastity for 5 minutes"
},
{
id: 102,
text: "Perform 50 squats while maintaining arousal"
text: "Stand in corner and repeat 'I am a sissy slut' 25 times out loud"
},
{
id: 103,
text: "Stay at the edge for 2 minutes with hands behind back"
text: "Watch cuckold porn sitting while in panties, no touching for 8 minutes"
},
{
id: 104,
text: "Write 'I will practice self-control' 25 times"
text: "Stand and recite 'I am inferior to real men' 50 times while watching BBC porn"
},
{
id: 105,
text: "Hold a wall sit for 3 minutes while edging"
text: "Wear feminine clothing and pose submissively sitting for 10 minutes"
},
{
id: 106,
text: "Edge for 30 seconds then cold shower for 1 minute"
text: "Practice sucking motions on a dildo while standing and watching gay porn for 5 minutes"
},
{
id: 107,
text: "Maintain edge position for 5 minutes without touching"
text: "Kneel and worship alpha male images for 8 minutes, no touching"
},
{
id: 108,
text: "Complete 100 push-ups in sets while staying aroused"
text: "Sit in feminine attire and practice feminine poses while denied release for 12 minutes"
},
{
id: 109,
text: "Kneel in stress position for 10 minutes, no relief"
text: "Listen to sissy hypno audio while standing in feminine attire for 10 minutes"
},
{
id: 110,
text: "Edge to near climax then stop for 5 minutes - repeat 3 times"
text: "Watch hotwife videos sitting while locked in chastity, repeat 'I am a cuckold' 50 times"
},
{
id: 111,
text: "Perform degrading poses while edging for 3 minutes"
text: "Practice walking in heels while standing and watching feminization content for 8 minutes"
},
{
id: 112,
text: "Worship your training material for 10 minutes, no touching"
text: "Sit and study alpha male photos while reciting why you're inferior for 15 minutes"
}
],
@ -156,124 +156,124 @@ const gameData = {
// Motivational messages
{
id: 1,
text: "Your self-control is improving! Keep training!",
text: "Good goon! Keep stroking and watching!",
category: "motivational",
enabled: true
},
{
id: 2,
text: "Every edge builds your endurance!",
text: "Every edge makes you more submissive!",
category: "motivational",
enabled: true
},
{
id: 3,
text: "Discipline over impulse!",
text: "Porn is your new reality!",
category: "motivational",
enabled: true
},
{
id: 4,
text: "You're learning to control yourself! Stay focused!",
text: "You're becoming such a good little gooner!",
category: "motivational",
enabled: true
},
{
id: 5,
text: "Small steps lead to better self-control!",
text: "Real men fuck while you watch!",
category: "motivational",
enabled: true
},
// Encouraging messages
{
id: 6,
text: "Your dedication to training is impressive!",
text: "Your dedication to gooning is impressive!",
category: "encouraging",
enabled: true
},
{
id: 7,
text: "Look how much your endurance has improved!",
text: "Look how much porn you can handle now!",
category: "encouraging",
enabled: true
},
{
id: 8,
text: "You're building incredible self-discipline!",
text: "You're building incredible porn tolerance!",
category: "encouraging",
enabled: true
},
{
id: 9,
text: "Master your desires - you have the strength!",
text: "Accept your place as a beta watcher!",
category: "encouraging",
enabled: true
},
{
id: 10,
text: "Your future self will appreciate this training!",
text: "Your future is endless gooning sessions!",
category: "encouraging",
enabled: true
},
// Achievement messages
{
id: 11,
text: "Excellent control on that task!",
text: "Excellent edging performance!",
category: "achievement",
enabled: true
},
{
id: 12,
text: "You're on fire! Maintain that discipline!",
text: "You're such a dedicated gooner!",
category: "achievement",
enabled: true
},
{
id: 13,
text: "Another victory for self-control!",
text: "Another victory for porn addiction!",
category: "achievement",
enabled: true
},
{
id: 14,
text: "Perfect execution!",
text: "Perfect submission!",
category: "achievement",
enabled: true
},
{
id: 15,
text: "You're mastering your training goals!",
text: "You're mastering the art of denial!",
category: "achievement",
enabled: true
},
// Persistence messages
{
id: 16,
text: "Don't give in now - show your discipline!",
text: "Don't stop gooning now - embrace it!",
category: "persistence",
enabled: true
},
{
id: 17,
text: "Every challenge strengthens your self-control!",
text: "Every stroke makes you weaker!",
category: "persistence",
enabled: true
},
{
id: 18,
text: "Push through - mastery awaits!",
text: "Push deeper into submission!",
category: "persistence",
enabled: true
},
{
id: 19,
text: "You're stronger than any urge!",
text: "You belong on your knees watching!",
category: "persistence",
enabled: true
},
{
id: 20,
text: "True control is forged in moments like this!",
text: "True submission is forged through endless edging!",
category: "persistence",
enabled: true
}

View File

@ -97,14 +97,14 @@ class PopupImageManager {
}
/**
* Get random image for periodic popups (from both folders)
* Get random image for periodic popups (from linked directories and individual files)
*/
getRandomPeriodicImage() {
const taskImages = gameData?.discoveredTaskImages || [];
const consequenceImages = gameData?.discoveredConsequenceImages || [];
const allImages = [...taskImages, ...consequenceImages];
// Get images from the same sources as the main library
const allImages = this.getLinkedImages();
if (allImages.length === 0) {
console.log('⚠️ No linked images available for popups');
return null;
}
@ -113,7 +113,7 @@ class PopupImageManager {
const { history } = this.periodicSystem;
const availableImages = allImages.filter(img => {
const imagePath = typeof img === 'string' ? img : (img.cachedPath || img.originalName);
const imagePath = typeof img === 'string' ? img : (img.path || img.cachedPath || img.originalName);
return !disabledImages.includes(imagePath) && !history.includes(imagePath);
});
@ -147,6 +147,83 @@ class PopupImageManager {
return selectedImage;
}
/**
* Get all images from linked directories and individual files (same as main library)
*/
getLinkedImages() {
const allImages = [];
try {
// Get linked directories
let linkedDirs;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
} catch (e) {
console.log('Error parsing linkedImageDirectories:', e);
linkedDirs = [];
}
// Get individual linked images
let linkedIndividualImages;
try {
linkedIndividualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
if (!Array.isArray(linkedIndividualImages)) {
linkedIndividualImages = [];
}
} catch (e) {
console.log('Error parsing linkedIndividualImages:', e);
linkedIndividualImages = [];
}
console.log(`📸 PopupImageManager found ${linkedDirs.length} linked directories and ${linkedIndividualImages.length} individual images`);
// Load images from linked directories using Electron API
if (window.electronAPI && linkedDirs.length > 0) {
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
for (const dir of linkedDirs) {
try {
if (window.electronAPI.readDirectory) {
const files = window.electronAPI.readDirectory(dir.path);
const imageFiles = files.filter(file => imageExtensions.test(file.name));
imageFiles.forEach(file => {
allImages.push({
name: file.name,
path: file.path,
category: 'directory',
directory: dir.name
});
});
}
} catch (error) {
console.error(`Error loading images from directory ${dir.path}:`, error);
}
}
}
// Add individual linked images
linkedIndividualImages.forEach(image => {
allImages.push({
name: image.name || 'Unknown Image',
path: image.path,
category: 'individual',
directory: 'Individual Images'
});
});
console.log(`📸 PopupImageManager loaded ${allImages.length} total images for popups`);
} catch (error) {
console.error('📸 Error loading linked images:', error);
}
return allImages;
}
/**
* Display periodic popup
*/
@ -859,12 +936,19 @@ class PopupImageManager {
}
getImageSrc(imageData) {
// Handle both old path format and new cached metadata format
// Handle both old path format and new linked image format
if (typeof imageData === 'string') {
return imageData;
} else if (imageData.dataUrl) {
return imageData.dataUrl;
} else if (imageData.path) {
// New linked image format - convert path for Electron if needed
if (window.electronAPI && imageData.path.match(/^[A-Za-z]:\\/)) {
return `file:///${imageData.path.replace(/\\/g, '/')}`;
}
return imageData.path;
} else {
// Fallback to old format
return imageData.cachedPath || imageData.originalName || '';
}
}

View File

@ -39,6 +39,23 @@ class VideoLibrary {
// Content container
this.libraryContent = document.getElementById('library-content');
// Debug: Log which elements were found
console.log(`📁 VideoLibrary elements initialized:`, {
gridViewBtn: !!this.gridViewBtn,
listViewBtn: !!this.listViewBtn,
sortSelect: !!this.sortSelect,
sortDirectionBtn: !!this.sortDirectionBtn,
searchInput: !!this.searchInput,
refreshBtn: !!this.refreshBtn,
createPlaylistBtn: !!this.createPlaylistBtn,
selectModeBtn: !!this.selectModeBtn,
libraryContent: !!this.libraryContent
});
if (!this.libraryContent) {
console.error(`📁 ❌ library-content element not found!`);
}
}
attachEventListeners() {
@ -61,106 +78,135 @@ class VideoLibrary {
async loadVideoLibrary() {
try {
console.log('📁 Loading video library...');
// Check if desktop file manager is available
if (!window.desktopFileManager) {
console.error('Desktop file manager not available');
this.displayEmptyLibrary('Desktop file manager not available');
return;
}
// Wait for desktop file manager to be fully initialized
// Check if video directories have been set up
let retries = 0;
const maxRetries = 20; // Wait up to 2 seconds
while (retries < maxRetries) {
if (window.desktopFileManager.videoDirectories &&
window.desktopFileManager.videoDirectories.background) {
console.log('✅ Desktop file manager video directories are ready');
break;
}
console.log(`⏳ Waiting for video directories to initialize... (${retries + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, 100));
retries++;
}
if (retries >= maxRetries) {
console.warn('⚠️ Video directories not initialized after waiting, falling back to stored videos');
this.loadStoredVideos();
return;
}
// Get video files from unified video library
let allVideos = [];
console.log('📁 Loading video library for Porn Cinema...');
// Use the same video loading logic as the main library interface
let linkedDirs;
try {
if (window.desktopFileManager) {
allVideos = window.desktopFileManager.getAllVideos();
console.log(`📁 Got ${allVideos.length} videos from desktop file manager`);
// If no videos from desktop file manager, try fallback methods
if (allVideos.length === 0) {
console.log('📁 No videos from desktop file manager, trying unified storage...');
// Try unified storage first - read fresh from localStorage to get preserved data
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
allVideos = unifiedData.allVideos || [];
console.log(`📁 Found ${allVideos.length} videos in unified storage`);
// If still no videos, try legacy video storage
if (allVideos.length === 0) {
console.log('📁 No videos in unified storage, trying legacy videoFiles...');
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
allVideos = Object.values(storedVideos).flat();
console.log(`📁 Found ${allVideos.length} videos in legacy storage`);
} else {
console.log('📁 Using videos from unified storage, skipping legacy check');
}
} else {
console.log('📁 Using videos from desktop file manager');
}
} else {
// Fallback to unified storage
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
allVideos = unifiedData.allVideos || [];
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
console.log(`📁 Found ${allVideos.length} videos total (unified library)`);
} catch (error) {
console.error('Error loading video library:', error);
// Fallback to localStorage
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
console.log('📁 Falling back to stored videos');
allVideos = Object.values(storedVideos).flat();
} catch (e) {
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
linkedDirs = [];
}
if (!allVideos || allVideos.length === 0) {
console.log('No videos found in any storage method');
this.displayEmptyLibrary('No videos found. Upload some videos first!');
let linkedIndividualVideos;
try {
linkedIndividualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
if (!Array.isArray(linkedIndividualVideos)) {
linkedIndividualVideos = [];
}
} catch (e) {
console.log('Error parsing linkedIndividualVideos, resetting to empty array:', e);
linkedIndividualVideos = [];
}
console.log(`📁 Found ${linkedDirs.length} linked directories and ${linkedIndividualVideos.length} individual videos in localStorage`);
const allVideos = [];
// Load videos from linked directories using Electron API
if (window.electronAPI && linkedDirs.length > 0) {
const videoExtensions = /\.(mp4|webm|avi|mov|mkv|wmv|flv|m4v)$/i;
for (const dir of linkedDirs) {
try {
console.log(`🎬 Scanning directory: ${dir.path}`);
// Use video-specific directory reading for better results
let files = [];
if (window.electronAPI.readVideoDirectory) {
console.log(`🎬 Using readVideoDirectory for ${dir.path}`);
files = await window.electronAPI.readVideoDirectory(dir.path);
} else if (window.electronAPI.readVideoDirectoryRecursive) {
console.log(`🎬 Using readVideoDirectoryRecursive for ${dir.path}`);
files = await window.electronAPI.readVideoDirectoryRecursive(dir.path);
} else if (window.electronAPI.readDirectory) {
console.log(`🎬 Using generic readDirectory for ${dir.path}`);
const allFiles = await window.electronAPI.readDirectory(dir.path);
files = allFiles.filter(file => videoExtensions.test(file.name));
}
console.log(`🎬 Found ${files ? files.length : 0} videos in ${dir.path}`);
if (files && files.length > 0) {
files.forEach(file => {
allVideos.push({
name: file.name,
path: file.path,
size: file.size || 0,
duration: file.duration || 0,
format: this.getFormatFromPath(file.path),
dateAdded: new Date().toISOString(),
category: 'directory',
directory: dir.path
});
});
}
} catch (error) {
console.error(`❌ Error loading videos from directory ${dir.path}:`, error);
continue;
}
}
}
// Add individual linked video files using the same key as main library
linkedIndividualVideos.forEach(video => {
allVideos.push({
name: video.name || 'Unknown Video',
path: video.path,
size: video.size || 0,
duration: video.duration || 0,
format: this.getFormatFromPath(video.path),
dateAdded: video.dateAdded || new Date().toISOString(),
category: 'individual',
directory: 'Individual Videos'
});
});
console.log(`📁 Total videos loaded: ${allVideos.length}`);
console.log(`📁 From directories: ${allVideos.filter(v => v.category === 'directory').length}`);
console.log(`📁 Individual files: ${allVideos.filter(v => v.category === 'individual').length}`);
console.log(`📁 Final total videos loaded: ${allVideos.length}`);
if (allVideos.length === 0) {
console.log('No videos found in current library system');
this.displayEmptyLibrary('No videos found. Add video directories or files in the main library first!');
return;
}
// Process video data
// Process video data with enhanced metadata
this.videos = allVideos.map(video => ({
name: video.title || video.name || 'Unknown Video',
path: video.path || video.filePath,
name: video.name,
path: video.path,
size: video.size || 0,
duration: video.duration || 0,
thumbnail: video.thumbnail || null,
resolution: video.resolution || 'Unknown',
format: video.format || this.getFormatFromPath(video.path || video.filePath),
format: video.format || this.getFormatFromPath(video.path),
bitrate: video.bitrate || 0,
dateAdded: video.dateAdded || new Date().toISOString(),
category: video.category || 'unknown',
directory: video.directory || 'Unknown',
qualities: this.detectVideoQualities(video)
}));
console.log(`📁 Loaded ${this.videos.length} videos`);
console.log(`📁 Processed ${this.videos.length} videos for library display`);
// Debug: Show some sample video names and paths
if (this.videos.length > 0) {
console.log(`📁 Sample videos loaded:`);
this.videos.slice(0, 5).forEach((video, index) => {
console.log(` ${index + 1}. "${video.name}" (${video.category}) - ${video.path}`);
});
if (this.videos.length > 5) {
console.log(` ... and ${this.videos.length - 5} more videos`);
}
}
// Apply current filters and display
this.applyFiltersAndSort();
@ -239,6 +285,8 @@ class VideoLibrary {
video.name.toLowerCase().includes(this.searchQuery)
);
console.log(`📁 Filter applied: "${this.searchQuery}" -> ${this.filteredVideos.length}/${this.videos.length} videos`);
// Apply sorting
this.filteredVideos.sort((a, b) => {
let aValue, bValue;
@ -272,11 +320,16 @@ class VideoLibrary {
}
displayLibrary() {
console.log(`📁 Displaying library: ${this.filteredVideos.length} videos in ${this.currentView} view`);
if (this.filteredVideos.length === 0) {
console.log(`📁 No videos to display (total videos: ${this.videos.length}, search: "${this.searchQuery}")`);
this.displayEmptyLibrary('No videos match your search');
return;
}
console.log(`📁 First few videos being displayed:`, this.filteredVideos.slice(0, 3).map(v => v.name));
const containerClass = this.currentView === 'grid' ? 'library-grid' : 'library-list';
this.libraryContent.innerHTML = `
@ -285,6 +338,22 @@ class VideoLibrary {
</div>
`;
console.log(`📁 HTML generated for ${this.filteredVideos.length} videos`);
// Debug: Check if libraryContent element exists and verify HTML
console.log(`📁 libraryContent element:`, this.libraryContent);
console.log(`📁 libraryContent innerHTML length:`, this.libraryContent ? this.libraryContent.innerHTML.length : 'N/A');
if (this.libraryContent && this.filteredVideos.length > 0) {
// Check if the HTML actually contains video cards
const videoCards = this.libraryContent.querySelectorAll('.video-card, .video-list-item');
console.log(`📁 Video cards found in DOM:`, videoCards.length);
if (videoCards.length === 0) {
console.log(`📁 No video cards found! Checking generated HTML sample:`, this.libraryContent.innerHTML.substring(0, 500));
}
}
// Attach click events to video elements
this.attachVideoEvents();
}

View File

@ -218,17 +218,35 @@ class InteractiveTaskManager {
async completeInteractiveTask() {
console.log('🔍 CompleteInteractiveTask called - currentInteractiveTask:', this.currentInteractiveTask);
if (!this.currentInteractiveTask) {
console.log('❌ No current interactive task found!');
return;
}
// Check if game is still running - prevent completion after game has ended
if (!this.game.gameState.isRunning) {
console.log('Interactive task completion cancelled - game no longer running');
return;
}
// Handle scenario completion even if currentInteractiveTask is null
if (!this.currentInteractiveTask) {
console.log('⚠️ No current interactive task found - checking for scenario completion');
// Check if this is a scenario completion
const currentTask = this.game.gameState.currentTask;
if (currentTask && currentTask.interactiveType === 'scenario-adventure' &&
currentTask.scenarioState && currentTask.scenarioState.completed) {
console.log('✅ Found completed scenario - proceeding with completion');
this.showFeedback('success', 'Scenario completed successfully! 🎉');
setTimeout(() => {
if (this.game.gameState.isRunning) {
this.cleanupInteractiveTask();
this.game.completeTask();
}
}, 1500);
return;
}
console.log('❌ No current interactive task or completed scenario found!');
return;
}
console.log(`🔍 Attempting to complete interactive task:`, this.currentInteractiveTask);
console.log(`🔍 Task interactiveType: ${this.currentInteractiveTask.interactiveType}`);
console.log(`🔍 Task isInterruption: ${this.currentInteractiveTask.isInterruption}`);
@ -1751,6 +1769,10 @@ class InteractiveTaskManager {
async createScenarioTask(task, container) {
const scenario = task.interactiveData || {};
// Set this task as the current interactive task
this.currentInteractiveTask = task;
console.log('🎭 Set scenario task as current interactive task:', task.id);
container.innerHTML = `
<div class="scenario-task">
<div class="scenario-header">

View File

@ -1452,31 +1452,35 @@ body.cinema-mode {
}
.library-content {
min-height: 120px;
min-height: 120px !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
overflow-y: auto !important;
max-height: 400px !important;
}
.library-loading {
text-align: center;
padding: 30px;
color: #666;
font-size: 0.9rem;
}
/* ===== LIBRARY GRID VIEW ===== */
.library-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important;
gap: 15px !important;
visibility: visible !important;
opacity: 1 !important;
}
.video-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
position: relative;
background: rgba(255, 255, 255, 0.05) !important;
border-radius: 10px !important;
overflow: hidden !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
border: 2px solid transparent !important;
position: relative !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
height: auto !important;
min-height: 200px !important;
}
.video-card:hover {

File diff suppressed because it is too large Load Diff