training-academy/index.html

6893 lines
347 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: file: blob: http://localhost:* https:; connect-src 'self' http://localhost:* https: ws://localhost:*; img-src 'self' data: file: blob:; media-src 'self' data: file: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;">
<title>Gooner Training Academy - Master Your Dedication</title>
<link rel="stylesheet" href="src/styles/color-variables.css">
<link rel="stylesheet" href="src/styles/styles.css">
<link rel="stylesheet" href="src/styles/base-video-player.css">
<link rel="stylesheet" href="src/styles/overlay-video-player.css">
<link href="https://fonts.googleapis.com/css2?family=Audiowide&family=Michroma&family=Electrolize:wght@400&display=swap" rel="stylesheet">
<style>
/* Verification Photo Styles */
.verification-photo-info {
border-left: 4px solid var(--color-error);
background: var(--bg-primary-overlay-10);
padding: 8px !important;
}
.verification-type {
color: var(--color-error);
font-weight: bold;
font-size: 0.9em;
margin: 2px 0;
}
.verification-message {
color: var(--color-error);
font-style: italic;
font-size: 0.8em;
margin: 4px 0;
line-height: 1.2;
}
.verification-timestamp {
color: var(--text-dim);
font-size: 0.75em;
margin-top: 4px;
}
.verification-modal .verification-header {
display: flex;
flex-direction: column;
gap: 5px;
}
.verification-modal .verification-details {
display: flex;
gap: 15px;
font-size: 0.9em;
}
.verification-message-display {
background: var(--bg-primary-overlay-10);
border: 1px solid var(--color-error);
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
.verification-message-display h4 {
color: var(--color-error);
margin: 0 0 10px 0;
font-size: 1.1em;
}
.verification-message-display p {
color: var(--color-error);
font-style: italic;
margin: 0;
font-size: 1.1em;
line-height: 1.4;
}
/* Gallery Verification Photo Styles */
.verification-photo-item {
border: 2px solid var(--color-error);
border-radius: 8px;
background: var(--bg-primary-overlay-10);
}
.verification-photo-container {
background: var(--bg-primary-overlay-10);
}
.verification-photo-info {
background: var(--bg-primary-overlay-20);
border-top: 1px solid var(--color-error);
padding: 8px !important;
}
.verification-photo-info .photo-type {
color: var(--color-error);
font-weight: bold;
font-size: 0.9em;
}
.verification-photo-info .verification-message {
display: block;
color: var(--color-error);
font-style: italic;
font-size: 0.8em;
margin-top: 4px;
line-height: 1.2;
max-width: 100%;
word-wrap: break-word;
}
.verification-photo-select:checked + .checkbox-label {
background-color: var(--color-error);
border-color: var(--color-error);
}
</style>
<script src="https://unpkg.com/jszip@3.10.1/dist/jszip.min.js" crossorigin="anonymous"></script>
<script>
// Test JSZip availability when page loads
window.addEventListener('load', function() {
if (typeof JSZip !== 'undefined') {
console.log('✅ JSZip loaded successfully from global scope');
} else if (typeof window.JSZip !== 'undefined') {
console.log('✅ JSZip loaded successfully from window scope');
} else {
console.error('❌ JSZip failed to load');
}
});
</script>
</head>
<body>
<!-- Loading Overlay -->
<div id="loading-overlay" class="loading-overlay">
<div class="loading-content">
<div class="loading-spinner"></div>
<h2>Initializing Game...</h2>
<p class="loading-status" id="loading-status">Loading components...</p>
<div class="loading-progress">
<div class="loading-progress-fill" id="loading-progress-fill"></div>
</div>
<div class="loading-percentage" id="loading-percentage">0%</div>
</div>
</div>
<div class="game-container">
<!-- Side Characters -->
<div class="character-side left" style="background-image: url('assets/hentai/1.png'); top: 15%;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/11.png'); top: 20%;"></div>
<div class="character-side left" style="background-image: url('assets/hentai/3.png'); top: 35%; left: -40px;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/4.png'); top: 40%; right: -40px;"></div>
<div class="character-side left" style="background-image: url('assets/hentai/5.png'); top: 55%; left: -30px;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/6.png'); top: 60%; right: -35px;"></div>
<div class="character-side left" style="background-image: url('assets/hentai/7.png'); top: 75%; left: -35px;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/8.png'); top: 80%; right: -30px;"></div>
<div class="character-side left" style="background-image: url('assets/hentai/9.png'); top: 90%; left: -25px;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/10.png'); top: 95%; right: -25px;"></div>
<div class="character-side left" style="background-image: url('assets/hentai/12.png'); top: 100%; left: -20px;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/13.png'); top: 105%; right: -20px;"></div>
<div class="character-side left" style="background-image: url('assets/hentai/14.png'); top: 110%; left: -15px;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/15.png'); top: 115%; right: -15px;"></div>
<div class="character-side left" style="background-image: url('assets/hentai/16.png'); top: 120%; left: -10px;"></div>
<div class="character-side right" style="background-image: url('assets/hentai/17.png'); top: 125%; right: -10px;"></div>
<!-- Game Header -->
<header class="game-header hero-header">
<div class="hero-background"></div>
<div class="neon-grid"></div>
<div class="hero-content">
<div class="hero-title-section">
<h1 class="hero-title">
<span class="hero-title-main">Gooner Training Academy</span>
<span class="hero-title-accent">Professional Development</span>
</h1>
<p class="hero-tagline">
<span class="tagline-emphasis">Master Your Dedication</span>
<span class="tagline-subtitle">• Advanced Training System • v3.0</span>
</p>
<!-- Feature Highlights - Now Main Action Buttons -->
<div class="hero-features">
<button class="hero-feature btn-feature" id="training-academy-btn">
<span class="feature-icon">🎬</span>
<span class="feature-text">Training Academy</span>
</button>
<button class="hero-feature btn-feature" id="quick-play-btn">
<span class="feature-icon">📊</span>
<span class="feature-text">Quick Play</span>
</button>
<button class="hero-feature btn-feature" id="porn-cinema-btn">
<span class="feature-icon">🏆</span>
<span class="feature-text">Porn Cinema</span>
</button>
<button class="hero-feature btn-feature" id="hypno-gallery-btn">
<span class="feature-icon">🌀</span>
<span class="feature-text">Hypno Gallery</span>
</button>
<button class="hero-feature btn-feature" id="library-btn" onclick="window.location.href='library.html'">
<span class="cassie-icon"></span>
<span class="feature-text">Library</span>
</button>
</div>
</div>
<!-- Status Panel -->
<div class="hero-status-panel">
<!-- Level and XP Display -->
<div class="status-card level-display">
<div class="status-label">Current Level</div>
<div class="status-value level-info">
<div class="level-title" id="current-level-name">Virgin</div>
<div class="level-number">Level <span id="current-level-number">1</span></div>
</div>
<div class="level-xp-info">
<div class="xp-display">
<span id="level-xp-header" class="xp-number">0</span>
<span class="xp-suffix">XP</span>
</div>
<div class="level-progress-container">
<div class="status-bar">
<div class="status-progress" id="level-xp-progress" style="width: 0%"></div>
</div>
<div class="level-progress-text">
<span id="level-progress-current">0</span> / <span id="level-progress-next">100</span> XP
</div>
</div>
</div>
</div>
</div>
<!-- Cyberpunk Video Billboard -->
<div class="video-billboard-container">
<div class="video-billboard">
<div class="billboard-frame">
<video id="billboard-video" muted playsinline preload="metadata">
<source id="billboard-video-source" src="" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="billboard-overlay">
<div class="billboard-controls">
<button id="billboard-mute" class="billboard-btn" title="Toggle Mute">🔇</button>
<button id="billboard-pause" class="billboard-btn" title="Toggle Pause">⏸️</button>
</div>
</div>
<div class="neon-border">
<div class="neon-edge top"></div>
<div class="neon-edge bottom"></div>
<div class="neon-edge left"></div>
<div class="neon-edge right"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Compact Music Controls (repositioned) -->
<div class="music-controls-compact hero-music-controls">
<button id="music-toggle-compact" class="music-icon-btn" title="Music Controls">🎵</button>
<div class="music-panel-expanded">
<div class="music-row">
<button id="music-toggle" class="music-btn-small" title="Play/Pause">▶️</button>
<button id="loop-btn" class="music-btn-small" title="Loop">🔁</button>
<button id="shuffle-btn" class="music-btn-small" title="Shuffle">🔀</button>
</div>
<div class="music-row">
<select id="track-selector" class="track-dropdown-compact">
<option value="0">Colorful Flowers</option>
<option value="1">New Beginnings</option>
<option value="2">Storm Clouds</option>
<option value="3">Brunch For Two</option>
</select>
</div>
<div class="music-row">
<div class="volume-control-compact">
<span class="volume-icon">🔊</span>
<input type="range" id="volume-slider" min="0" max="100" value="30" class="volume-slider-compact">
<span class="volume-percent" id="volume-percent">30%</span>
</div>
</div>
<div class="music-status-compact" id="music-status">Music: Off</div>
</div>
</div>
</header>
<!-- Game Content -->
<main class="game-content">
<!-- Start Screen -->
<div id="start-screen" class="screen active">
<!-- Secondary Action Buttons -->
<div class="main-actions">
<button id="user-profile-btn" class="btn btn-secondary">👤 Profile</button>
</div>
<!-- Game Guide Section -->
<div class="game-guide-section">
<button id="game-guide-btn" class="btn btn-guide">📋 Game Guide</button>
<div id="game-guide" class="game-guide" style="display: none;">
<div class="guide-content">
<h3>🎮 Game Modes Overview</h3>
<div class="guide-section">
<div class="guide-item">
<div class="guide-icon">🎬</div>
<div class="guide-info">
<h4>Training Academy</h4>
<p>Immersive scenario-based training sessions with branching storylines. Earn XP through time-based rewards (1 XP/2min), webcam activities (1 XP/min), and photo sessions (2 XP/photo).</p>
</div>
</div>
<div class="guide-item">
<div class="guide-icon">📊</div>
<div class="guide-info">
<h4>Quick Play</h4>
<p>Fast-paced task-based gameplay with real-time XP tracking. Complete tasks to earn 1-3 XP based on completion time, plus session bonuses every 15 minutes (2 XP + 1 XP if recording).</p>
</div>
</div>
<div class="guide-item">
<div class="guide-icon">🏆</div>
<div class="guide-info">
<h4>Porn Cinema</h4>
<p>Video viewing experience with XP rewards. Earn 1 XP per 5 minutes of viewing time. Videos must reach 90% completion to count toward your stats and achievements.</p>
</div>
</div>
<div class="guide-item">
<div class="guide-icon">🌀</div>
<div class="guide-info">
<h4>Hypno Gallery</h4>
<p>Immersive slideshow experience with configurable timing modes, visual effects, and transitions. View your image library in constant, random, or wave timing patterns with customizable durations and effects.</p>
</div>
</div>
<div class="guide-item">
<div class="guide-icon">📁</div>
<div class="guide-info">
<h4>Library</h4>
<p>Manage your video and photo collections. Organize files, create playlists, and browse your media library. Essential for setting up content for other game modes.</p>
</div>
</div>
<div class="guide-item">
<div class="guide-icon">👤</div>
<div class="guide-info">
<h4>Profile</h4>
<p>View your progress, stats, and achievements. Track your level, XP breakdown by game mode, session history, and unlock status. Monitor your gooning journey!</p>
</div>
</div>
<div class="guide-item">
<div class="guide-icon">⚙️</div>
<div class="guide-info">
<h4>Options</h4>
<p>Customize your experience with themes, popup settings, and debug tools. Switch between Hentai/Pornstar themes, adjust popup intervals, and manage system settings.</p>
</div>
</div>
</div>
<div class="guide-tips">
<h4>💡 Pro Tips</h4>
<ul>
<li><strong>Level Up:</strong> Combine different game modes to maximize XP earning</li>
<li><strong>Recording Bonus:</strong> Enable session recording in Quick Play for extra XP</li>
<li><strong>Consistency:</strong> Daily play builds streaks and unlocks achievements</li>
<li><strong>Library First:</strong> Set up your media library before starting sessions</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Options Menu -->
<div class="options-section">
<button id="options-menu-btn" class="btn btn-tertiary">⚙️ Options</button>
<div id="options-menu" class="options-menu" style="display: none;">
<!-- Image Theme Selector -->
<div class="option-item theme-selector">
<label for="theme-dropdown" class="option-label">Choose Your Image Theme:</label>
<select id="theme-dropdown" class="theme-dropdown">
<option value="hentai" selected>🎭 Hentai</option>
<option value="pornstars">⭐ Pornstars</option>
<option value="BBC">🔥 BBC</option>
<option value="feet">👣 Feet</option>
<option value="library">📂 Library (Random)</option>
<option value="none">🚫 None</option>
</select>
</div>
<!-- Color Theme Selector -->
<div class="option-item theme-selector">
<label for="color-theme-dropdown" class="option-label">🎨 Choose Your Color Theme:</label>
<select id="color-theme-dropdown" class="theme-dropdown">
<option value="electric-blue">⚡ Electric Blue</option>
<option value="electric-violet">💜 Electric Violet</option>
<option value="magenta-dream">💖 Magenta Dream</option>
<option value="hot-pink-fury">🔥 Hot Pink Fury</option>
<option value="purple-neon-dream">🌈 Purple Neon Dream</option>
<option value="crimson-passion">❤️ Crimson Passion</option>
<option value="golden-ember">✨ Golden Ember</option>
<option value="aqua-mist">🌊 Aqua Mist</option>
<option value="coral-blaze">🔴 Coral Blaze</option>
</select>
</div>
</div>
<input type="file" id="import-file" accept=".json" style="display: none;">
</div>
</div>
<!-- Image Management Screen -->
<div id="image-management-screen" class="screen">
<h2>🖼️ Image Library Management</h2>
<p>Upload and organize image content to enhance your gaming experience</p>
<!-- Upload Section -->
<div class="upload-section">
<h3><EFBFBD> Import Image Files</h3>
<div class="upload-controls">
<button id="import-task-images-btn" class="btn btn-primary">🎯 Task Images</button>
<button id="import-consequence-images-btn" class="btn btn-warning">⚠️ Consequence Images</button>
<input type="file" id="image-upload-input" accept="image/*" multiple style="display: none;">
</div>
<div class="upload-info desktop-feature">
<span>💻 Desktop: Native file dialogs • Supports JPEG, PNG, GIF, WebP formats</span>
</div>
<div class="upload-info web-feature" style="display: none;">
<span>🌐 Web: Limited browser upload functionality</span>
</div>
<div class="directory-controls">
<button id="storage-info-btn" class="btn btn-outline">📊 Storage Info</button>
<button id="cleanup-invalid-images-btn" class="btn btn-warning">🧹 Cleanup</button>
<button id="clear-all-images-btn" class="btn btn-danger">🗑️ Clear All</button>
<span class="scan-info">📡 Auto-scan on startup</span>
</div>
</div>
<!-- Image Gallery Section -->
<div class="gallery-section">
<div class="gallery-header">
<h3>🖼️ Current Image Library</h3>
<div class="gallery-controls">
<!-- Selection buttons removed - lib-image-gallery uses click-to-preview instead of checkboxes -->
<button id="refresh-images-btn" class="btn btn-secondary btn-small">🔄 Refresh</button>
<span class="image-count">Loading images...</span>
</div>
</div>
<!-- Task/Consequence image galleries removed - consolidated into lib-image-gallery in Library section -->
</div>
<!-- Image Preview Section -->
<div class="image-preview-section" id="image-preview-section" style="display: none;">
<h4>🖼️ Image Preview</h4>
<div class="preview-controls">
<div class="image-preview-container">
<img id="image-preview-img" src="" alt="Image Preview" style="max-width: 100%; max-height: 600px; border-radius: 8px;">
</div>
<div class="preview-info">
<span id="preview-image-name">No image selected</span>
<div class="preview-buttons">
<button id="close-image-preview-btn" class="btn btn-small btn-outline">✕ Close</button>
</div>
</div>
</div>
</div>
<div class="management-buttons">
<button id="back-to-start-from-images-btn" class="btn btn-secondary">← Back to Start</button>
</div>
</div>
<!-- Audio Management Screen -->
<div id="audio-management-screen" class="screen">
<h2>🎵 Audio Library Management</h2>
<p>Upload and organize audio content to enhance your gaming experience</p>
<!-- Upload Section -->
<div class="upload-section">
<h3>🎵 Import Audio Files</h3>
<div class="upload-controls">
<button id="import-background-audio-btn" class="btn btn-primary">🎶 Background Music</button>
<button id="import-ambient-audio-btn" class="btn btn-success">🌿 Ambient Sounds</button>
<input type="file" id="audio-upload-input" accept="audio/*" multiple style="display: none;">
</div>
<div class="upload-info desktop-feature">
<span>💻 Desktop: Native file dialogs • Supports MP3, WAV, OGG, M4A, AAC, FLAC formats</span>
</div>
<div class="upload-info web-feature" style="display: none;">
<span>🌐 Web: Limited browser upload functionality</span>
</div>
<div class="directory-controls">
<button id="scan-audio-directories-btn" class="btn btn-primary">🔍 Scan Directories</button>
<button id="audio-storage-info-btn" class="btn btn-outline">📊 Storage Info</button>
<button id="cleanup-invalid-audio-btn" class="btn btn-warning">🧹 Cleanup</button>
<button id="clear-all-audio-btn" class="btn btn-danger">🗑️ Clear All</button>
<span class="scan-info">📡 Auto-scan on startup</span>
</div>
</div>
<!-- Audio Gallery Section -->
<div class="gallery-section">
<div class="gallery-header">
<h3>🎧 Current Audio Library</h3>
<div class="gallery-controls">
<!-- Selection buttons removed - lib-audio-gallery uses click-to-play instead of checkboxes -->
<span class="audio-count">Loading audio files...</span>
</div>
</div>
<!-- Audio tabs and galleries removed - using lib-audio-gallery in Library section instead -->
</div>
<!-- Audio Preview Section -->
<div class="audio-preview-section" id="audio-preview-section" style="display: none;">
<h4>🎧 Audio Preview</h4>
<div class="preview-controls">
<div class="audio-preview-container">
<audio id="audio-preview-player" controls style="width: 100%; max-width: 600px;">
Your browser does not support the audio element.
</audio>
</div>
<div class="preview-info">
<span id="preview-file-name">No file selected</span>
<div class="preview-buttons">
<button id="close-preview-btn" class="btn btn-small btn-outline">✕ Close</button>
</div>
</div>
</div>
</div>
<div class="management-buttons">
<button id="back-to-start-from-audio-btn" class="btn btn-secondary">← Back to Start</button>
</div>
</div>
<!-- Video Management Screen -->
<div id="video-management-screen" class="screen">
<h2>🎬 Video Library Management</h2>
<p>Upload and organize video content to enhance your gaming experience</p>
<!-- Upload Section -->
<div class="upload-section">
<h3>🎥 Video Library Management</h3>
<p style="color: var(--text-dim); font-size: 12px; margin-bottom: 15px;">
Link external directories to build your video library. All videos are scanned recursively (including subdirectories).
</p>
<!-- Directory Management Section -->
<div class="directory-management-section" style="margin-bottom: 20px; padding: 15px; background: var(--bg-secondary); border-radius: 8px;">
<h4><EFBFBD> Linked Directories</h4>
<div class="directory-controls" style="margin-bottom: 15px;">
<button id="add-video-directory-btn" class="btn btn-primary"> Add Directory</button>
<button id="refresh-all-directories-btn" class="btn btn-secondary">🔄 Refresh All</button>
<button id="clear-all-directories-btn" class="btn btn-warning"><EFBFBD> Clear All</button>
</div>
<div id="linked-directories-list" style="max-height: 200px; overflow-y: auto;">
<!-- Linked directories will be populated here -->
</div>
</div>
<!-- Video Count Display -->
<div class="video-stats-section" style="padding: 10px; background: var(--bg-tertiary); border-radius: 4px; text-align: center;">
<strong id="total-video-count">0 videos total</strong>
<span id="directory-count" style="color: var(--text-muted); margin-left: 10px;">0 directories linked</span>
</div>
<div class="upload-info desktop-feature">
<span>💻 Desktop: Native file dialogs • Supports MP4, WebM, OGV, MOV formats</span>
</div>
<div class="upload-info web-feature" style="display: none;">
<span>🌐 Web: Limited browser upload functionality</span>
</div>
<div class="directory-controls">
<button id="video-storage-info-btn" class="btn btn-outline">📊 Storage Info</button>
<button id="cleanup-invalid-videos-btn" class="btn btn-warning">🧹 Cleanup</button>
<button id="clear-all-videos-btn" class="btn btn-danger">🗑️ Clear All</button>
<span class="scan-info">📡 Auto-scan on startup</span>
</div>
</div>
<!-- Video Gallery Section -->
<div class="gallery-section">
<div class="gallery-header">
<h3>📹 Video Library</h3>
<div class="gallery-controls">
<!-- Selection buttons removed - lib-video-gallery uses click-to-preview instead of checkboxes -->
<button id="refresh-videos-btn" class="btn btn-secondary btn-small">🔄 Refresh</button>
<span class="video-count">Loading videos...</span>
</div>
</div>
<!-- Unified video gallery removed - using lib-video-gallery in Library section instead -->
</div>
<!-- Video Preview Section -->
<div class="video-preview-section" id="video-preview-section" style="display: none;">
<h4>🎬 Video Preview</h4>
<div class="preview-controls">
<div class="video-preview-container">
<video id="video-preview-player" controls style="width: 100%; max-width: 800px; max-height: 450px; border-radius: 8px;">
Your browser does not support the video element.
</video>
</div>
<div class="preview-info">
<span id="preview-video-name">No video selected</span>
<div class="preview-buttons">
<button id="toggle-video-loop-btn" class="btn btn-small btn-outline">🔁 Loop</button>
<button id="close-video-preview-btn" class="btn btn-small btn-outline">✕ Close</button>
</div>
</div>
</div>
</div>
<!-- Video Settings Section -->
<div class="video-settings-section">
<h4>⚙️ Video Player Settings</h4>
<div class="settings-grid">
<div class="setting-group">
<label class="switch-label">
<input type="checkbox" id="enable-video-player" checked>
<span class="switch"></span>
Enable Video Player
</label>
</div>
<div class="setting-group">
<label class="switch-label">
<input type="checkbox" id="enable-background-videos" checked>
<span class="switch"></span>
Background Videos
</label>
</div>
<div class="setting-group">
<label class="switch-label">
<input type="checkbox" id="enable-task-videos" checked>
<span class="switch"></span>
Task Videos
</label>
</div>
<div class="setting-group volume-setting">
<label for="video-volume">Video Volume:</label>
<div class="volume-control">
<input type="range" id="video-volume" min="0" max="100" value="30" class="volume-slider">
<span id="video-volume-display">30%</span>
</div>
</div>
<div class="setting-group">
<label class="switch-label">
<input type="checkbox" id="video-autoplay" checked>
<span class="switch"></span>
Autoplay Videos
</label>
</div>
<div class="setting-group">
<label class="switch-label">
<input type="checkbox" id="video-show-controls">
<span class="switch"></span>
Show Video Controls
</label>
</div>
<div class="setting-group">
<label class="switch-label">
<input type="checkbox" id="video-fade-transitions" checked>
<span class="switch"></span>
Fade Transitions
</label>
</div>
</div>
<div class="test-buttons">
<button id="test-background-video" class="btn btn-info">Test Background Video</button>
<button id="test-overlay-video" class="btn btn-primary">Test Overlay Video</button>
<button id="stop-all-videos" class="btn btn-danger">Stop All Videos</button>
</div>
</div>
<div class="management-buttons">
<button id="back-to-start-from-video-btn" class="btn btn-secondary">Back to Start</button>
</div>
</div>
<!-- Photo Gallery Screen -->
<div id="photo-gallery-screen" class="screen">
<h2>📸 Photo Gallery Management</h2>
<p>View and manage your captured photos from photography sessions</p>
<!-- Upload Section -->
<div class="upload-section">
<h3>📸 Photo Management</h3>
<div class="upload-controls">
<button id="download-all-photos-btn" class="btn btn-primary">📥 Download All Photos</button>
<button id="download-selected-photos-btn" class="btn btn-info" disabled>📥 Download Selected</button>
<button id="clear-all-photos-btn" class="btn btn-danger">🗑️ Clear All Photos</button>
<button id="photo-storage-settings-btn" class="btn btn-secondary">⚙️ Storage Settings</button>
</div>
<div class="upload-info desktop-feature">
<span>📷 Desktop: Photos automatically saved from sessions • JPEG, PNG formats</span>
</div>
<div class="directory-controls">
<div class="photo-stats">
<span id="photo-count-display">Loading photos...</span>
<span id="storage-size-display"></span>
</div>
<span class="scan-info">📷 Photos saved from gameplay sessions</span>
</div>
</div>
<!-- Photo Gallery Section -->
<div class="gallery-section">
<div class="gallery-header">
<h3>📷 Photo Collection</h3>
<div class="gallery-controls">
<button id="select-all-photos-btn" class="btn btn-small btn-outline">Select All</button>
<button id="deselect-all-photos-btn" class="btn btn-small btn-outline">Deselect All</button>
<div class="filter-controls">
<label>Filter by Session:
<select id="photo-session-filter" class="filter-select">
<option value="all">All Photos</option>
<option value="dress-up-photo">Dress-up Photos</option>
<option value="photography-studio">Photography Studio</option>
<option value="custom">Custom Sessions</option>
</select>
</label>
<label>Sort by:
<select id="photo-sort-order" class="filter-select">
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="session">By Session Type</option>
</select>
</label>
</div>
</div>
</div>
<!-- Photo Tabs -->
<div class="photo-tabs">
<button id="all-photos-tab" class="tab-btn active">📸 All Photos</button>
<button id="dress-up-photos-tab" class="tab-btn">👗 Dress-up</button>
<button id="studio-photos-tab" class="tab-btn">🏠 Studio</button>
<button id="custom-photos-tab" class="tab-btn">🎨 Custom</button>
</div>
<div class="photo-galleries-container">
<div id="all-photos-gallery" class="photo-gallery active">
<div id="photo-grid" class="photo-grid">
<!-- All photos will be populated here -->
</div>
</div>
<div id="dress-up-photos-gallery" class="photo-gallery">
<div class="photo-grid">
<!-- Dress-up photos will be populated here -->
</div>
</div>
<div id="studio-photos-gallery" class="photo-gallery">
<div class="photo-grid">
<!-- Studio photos will be populated here -->
</div>
</div>
<div id="custom-photos-gallery" class="photo-gallery">
<div class="photo-grid">
<!-- Custom photos will be populated here -->
</div>
</div>
<div id="no-photos-message" class="no-photos-message" style="display: none;">
<h4>📷 No Photos Yet</h4>
<p>Take some photos during photography sessions and they'll appear here!</p>
<p>You can enable photo saving in the settings when you take your first photo.</p>
</div>
</div>
</div>
<!-- Photo Preview Section -->
<div class="photo-preview-section" id="photo-preview-section" style="display: none;">
<h4>📸 Photo Preview</h4>
<div class="preview-controls">
<div class="photo-preview-container">
<img id="photo-preview-image" src="" alt="Photo Preview" style="max-width: 100%; max-height: 600px; border-radius: 8px;">
</div>
<div class="preview-info">
<span id="preview-photo-name">No photo selected</span>
<div class="preview-buttons">
<button id="download-photo-btn" class="btn btn-small btn-primary">💾 Download</button>
<button id="close-photo-preview-btn" class="btn btn-small btn-outline">✕ Close</button>
</div>
</div>
</div>
</div>
<!-- Photo Detail Modal -->
<div id="photo-detail-modal" class="modal photo-modal" style="display: none;">
<div class="modal-content photo-detail-content">
<div class="modal-header">
<h3 id="photo-detail-title">Photo Details</h3>
<span class="close" id="close-photo-detail">&times;</span>
</div>
<div class="modal-body photo-detail-body">
<div class="photo-display">
<img id="photo-detail-image" src="" alt="Photo" />
</div>
<div class="photo-info">
<div class="info-row">
<label>Captured:</label>
<span id="photo-detail-date"></span>
</div>
<div class="info-row">
<label>Session:</label>
<span id="photo-detail-session"></span>
</div>
<div class="info-row">
<label>Task:</label>
<span id="photo-detail-task"></span>
</div>
<div class="info-row">
<label>Size:</label>
<span id="photo-detail-size"></span>
</div>
</div>
<div class="photo-actions">
<button id="download-photo-btn" class="btn btn-primary">📥 Download</button>
<button id="delete-photo-btn" class="btn btn-danger">🗑️ Delete</button>
</div>
</div>
</div>
</div>
<!-- Photo Storage Settings Modal -->
<div id="photo-settings-modal" class="modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3>📸 Photo Storage Settings</h3>
<span class="close" id="close-photo-settings">&times;</span>
</div>
<div class="modal-body">
<div class="setting-group">
<h4>Storage Consent</h4>
<div class="consent-controls">
<label>
<input type="radio" name="photo-consent" value="true" id="consent-enable">
Enable photo storage (save photos for later viewing)
</label>
<label>
<input type="radio" name="photo-consent" value="false" id="consent-disable">
Disable photo storage (session only)
</label>
</div>
<p class="help-text">
Photos are stored locally on your device only. No photos are sent to any servers.
</p>
</div>
<div class="setting-group">
<h4>Storage Information</h4>
<div class="storage-info">
<div class="info-item">
<span class="info-label">Total Photos:</span>
<span id="settings-photo-count">0</span>
</div>
<div class="info-item">
<span class="info-label">Storage Used:</span>
<span id="settings-storage-size">0 KB</span>
</div>
<div class="info-item">
<span class="info-label">Oldest Photo:</span>
<span id="settings-oldest-photo">None</span>
</div>
</div>
</div>
<div class="modal-actions">
<button id="save-photo-settings-btn" class="btn btn-primary">Save Settings</button>
</div>
</div>
</div>
</div>
<div class="management-buttons">
<button id="back-to-start-from-photos-btn" class="btn btn-secondary">← Back to Start</button>
</div>
</div>
</main>
</div>
<!-- Game Data System -->
<script src="src/data/modes/mainGameData.js"></script>
<script src="src/data/modes/humiliationGameData.js"></script>
<script src="src/data/modes/trainingGameData.js"></script>
<script src="src/data/modes/enduranceGameData.js"></script>
<script src="src/data/modes/dressUpGameData.js"></script>
<script src="src/data/gameDataManager.js"></script>
<!-- Backup System -->
<script src="src/utils/backupManager.js"></script>
<!-- Theme Manager -->
<script src="src/utils/themeManager.js"></script>
<!-- Legacy Data (will be phased out) -->
<script src="src/data/gameData.js"></script>
<!-- Statistics Modal -->
<div id="stats-modal" class="modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h2>📊 Game Statistics</h2>
<span class="close" id="close-stats">&times;</span>
</div>
<div class="modal-body">
<div class="stats-grid">
<div class="stat-card">
<h3>Games Played</h3>
<div class="stat-value" id="stat-games">0</div>
</div>
<div class="stat-card">
<h3>Tasks Completed</h3>
<div class="stat-value" id="stat-completed">0</div>
</div>
<div class="stat-card">
<h3>Best Score</h3>
<div class="stat-value" id="stat-score">0</div>
</div>
<div class="stat-card">
<h3>Current Streak</h3>
<div class="stat-value" id="stat-streak">0</div>
</div>
<div class="stat-card">
<h3>Completion Rate</h3>
<div class="stat-value" id="stat-rate">0%</div>
</div>
<div class="stat-card">
<h3>Hours Played</h3>
<div class="stat-value" id="stat-hours">0.0</div>
</div>
</div>
<div class="stats-actions">
<button id="reset-stats-btn" class="btn btn-warning">Reset Statistics</button>
<button id="export-stats-btn" class="btn btn-secondary">
<span class="btn-text">Export Stats Only</span>
<span class="btn-loading" style="display: none;">⏳ Exporting...</span>
</button>
</div>
</div>
</div>
</div>
<!-- Help Menu Modal -->
<div id="help-modal" class="modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h2>⌨️ Keyboard Shortcuts & Help</h2>
<span class="close" id="close-help">&times;</span>
</div>
<div class="modal-body">
<div class="help-section">
<h3>🎮 Game Controls</h3>
<div class="shortcut-list">
<div class="shortcut-item">
<span class="shortcut-key">Enter</span>
<span class="shortcut-action">Complete Current Task</span>
</div>
<div class="shortcut-item">
<span class="shortcut-key">Ctrl</span>
<span class="shortcut-action">Skip Task (get consequence task)</span>
</div>
<div class="shortcut-item">
<span class="shortcut-key">Shift+Ctrl</span>
<span class="shortcut-action">Mercy Skip (spend points to avoid consequence)</span>
</div>
<div class="shortcut-item">
<span class="shortcut-key">Space</span>
<span class="shortcut-action">Pause/Resume Game</span>
</div>
<div class="shortcut-item">
<span class="shortcut-key">P</span>
<span class="shortcut-action">Pause/Resume Game</span>
</div>
</div>
</div>
<div class="help-section">
<h3>🎵 Music Controls</h3>
<div class="shortcut-list">
<div class="shortcut-item">
<span class="shortcut-key">M</span>
<span class="shortcut-action">Toggle Music On/Off</span>
</div>
</div>
</div>
<div class="help-section">
<h3>🧭 Navigation</h3>
<div class="shortcut-list">
<div class="shortcut-item">
<span class="shortcut-key">Escape</span>
<span class="shortcut-action">Close Modals / Back / Pause Game</span>
</div>
<div class="shortcut-item">
<span class="shortcut-key">H</span>
<span class="shortcut-action">Show/Hide This Help Menu</span>
</div>
</div>
</div>
<div class="help-section">
<h3>💡 Tips</h3>
<ul class="help-tips">
<li>Use Enter and Ctrl for lightning-fast task progression</li>
<li>Ctrl gives you a consequence task - complete it to save points!</li>
<li>Shift+Ctrl spends points for mercy skip - use strategically</li>
<li>Choose your skipping strategy: face the consequence or pay points</li>
<li>Shortcuts are disabled when typing in input fields</li>
<li>The game auto-saves when paused and offers to resume</li>
<li>Export your progress regularly to avoid data loss</li>
</ul>
</div>
</div>
</div>
</div>
</body>
<!-- Script Loading Order -->
<script src="src/features/ui/flashMessageManager.js"></script>
<script src="src/features/images/popupImageManager.js"></script>
<script src="src/features/audio/audioManager.js"></script>
<script src="src/features/stats/playerStats.js"></script>
<script src="src/core/gameModeManager.js"></script>
<script src="src/features/webcam/webcamManager.js"></script>
<script src="src/features/tts/voiceManager.js"></script>
<script src="src/features/media/baseVideoPlayer.js"></script>
<script src="src/features/media/focusVideoPlayer.js"></script>
<script src="src/features/media/overlayVideoPlayer.js"></script>
<script src="src/features/media/quadVideoPlayer.js"></script>
<script src="src/features/tasks/interactiveTaskManager.js"></script>
<script src="src/features/video/videoPlayerManager.js"></script>
<script src="src/utils/desktop-file-manager.js"></script>
<script src="src/core/game.js"></script>
<script>
// Theme switching functionality
async function applyTheme(theme) {
console.log('Applying theme:', theme);
const characterSides = document.querySelectorAll('.character-side');
// Remove any existing theme classes
document.body.classList.remove('library-theme');
if (theme === 'none') {
characterSides.forEach(element => {
element.style.display = 'none';
});
localStorage.setItem('selectedTheme', theme);
return;
}
if (theme === 'library') {
// Add library theme class for CSS targeting
document.body.classList.add('library-theme');
// Use random images from user's library
await applyLibraryTheme(characterSides);
localStorage.setItem('selectedTheme', theme);
return;
}
// Handle asset themes (hentai, pornstars, BBC, feet) - scan directories dynamically
await applyAssetTheme(theme, characterSides);
// Save theme preference
localStorage.setItem('selectedTheme', theme);
}
async function applyAssetTheme(theme, characterSides) {
console.log(`Applying ${theme} theme with dynamic image scanning...`);
try {
let allImages = [];
// Try to scan the assets folder if we have Electron API
if (window.electronAPI && window.electronAPI.readDirectory) {
const themePath = `assets/${theme}`;
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
try {
let files = [];
const filesPromise = window.electronAPI.readDirectory(themePath);
if (filesPromise && typeof filesPromise.then === 'function') {
files = await filesPromise;
} else if (Array.isArray(filesPromise)) {
files = filesPromise;
}
if (files && files.length > 0) {
allImages = files
.filter(file => {
const fileName = typeof file === 'object' ? file.name : file;
return imageExtensions.test(fileName);
})
.map(file => {
const fileName = typeof file === 'object' ? file.name : file;
return {
path: `${themePath}/${fileName}`,
name: fileName
};
});
}
} catch (error) {
console.warn(`Could not scan ${themePath} directory:`, error);
}
}
console.log(`📂 Found ${allImages.length} images in ${theme} theme`);
if (allImages.length === 0) {
// Fallback to hardcoded defaults if scanning failed
console.log('Using fallback hardcoded images');
applyFallbackTheme(theme, characterSides);
return;
}
// Apply dynamic layout like library theme
document.body.classList.add('library-theme');
const FIXED_WIDTH = 150;
const MIN_HEIGHT = 100;
const MAX_HEIGHT = 300;
const MARGIN = 0;
// Separate left and right columns
const leftElements = [];
const rightElements = [];
characterSides.forEach((element, index) => {
if (element.classList.contains('left')) {
leftElements.push(element);
} else if (element.classList.contains('right')) {
rightElements.push(element);
}
});
// Function to load and position images dynamically
const loadColumnImages = async (elements, isLeft) => {
let currentTop = 0;
const usedImages = new Set(); // Track used images to prevent duplicates
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
// Get a random unused image
let randomImage;
let attempts = 0;
do {
const randomIndex = Math.floor(Math.random() * allImages.length);
randomImage = allImages[randomIndex];
attempts++;
} while (usedImages.has(randomImage.path) && attempts < allImages.length * 2);
if (randomImage) {
usedImages.add(randomImage.path); // Mark as used
const imageUrl = randomImage.path;
// Load image to get dimensions
const img = new Image();
await new Promise((resolve) => {
img.onload = () => {
// Calculate height based on aspect ratio
const aspectRatio = img.height / img.width;
let calculatedHeight = FIXED_WIDTH * aspectRatio;
// Clamp height to min/max bounds
calculatedHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, calculatedHeight));
// Apply styles to element
element.style.display = 'block';
element.style.width = `${FIXED_WIDTH}px`;
element.style.height = `${calculatedHeight}px`;
element.style.top = `${currentTop}px`;
element.style.backgroundImage = `url("${imageUrl}")`;
element.style.backgroundSize = 'cover';
element.style.backgroundPosition = 'center';
// Position left or right
if (isLeft) {
element.style.left = '1px';
element.style.right = 'auto';
} else {
element.style.right = '1px';
element.style.left = 'auto';
}
// Update currentTop for next image
currentTop += calculatedHeight + MARGIN;
console.log(`📸 ${isLeft ? 'Left' : 'Right'} Panel ${i + 1}: ${randomImage.name} (${FIXED_WIDTH}x${calculatedHeight}px)`);
resolve();
};
img.onerror = () => {
console.warn(`Failed to load image: ${randomImage.path}`);
element.style.display = 'block';
element.style.width = `${FIXED_WIDTH}px`;
element.style.height = `${MIN_HEIGHT}px`;
element.style.top = `${currentTop}px`;
if (isLeft) {
element.style.left = '1px';
element.style.right = 'auto';
} else {
element.style.right = '1px';
element.style.left = 'auto';
}
currentTop += MIN_HEIGHT + MARGIN;
resolve();
};
img.src = imageUrl;
});
}
}
};
// Load both columns
await Promise.all([
loadColumnImages(leftElements, true),
loadColumnImages(rightElements, false)
]);
console.log('✅ Dynamic theme layout complete');
} catch (error) {
console.error('Error applying asset theme:', error);
applyFallbackTheme(theme, characterSides);
}
}
function applyFallbackTheme(theme, characterSides) {
// Fallback to original hardcoded approach
characterSides.forEach((element, index) => {
element.style.display = 'block';
let imageNames;
if (theme === 'hentai' || theme === 'pornstars') {
const imageNumbers = [1, 11, 3, 4, 5, 6, 7, 8, 9, 10];
imageNames = imageNumbers.map(num => `${num}.png`);
} else if (theme === 'BBC') {
imageNames = [
'18808856_035_d374.png',
'19086746_049_ba0d.png',
'23515083_025_f794.png',
'24163996_041_81d6.png',
'31015308_126_3792.png',
'44141198_113_f31a.png',
'60016759_151_ea2b.png',
'85835876_037_0c84.png',
'99106223_020_819b.png',
'99769898_038_0e86.png'
];
} else if (theme === 'feet') {
imageNames = [
'20931683_001_c8f7.png',
'25674071_097_2016.png',
'29553632_079_53ec.png',
'30818412_002_8cbf.png',
'40352681_083_bec8.png',
'46780885_029_b8a2.png',
'47594084_126_51a7.png',
'67258313_065_60f1.png',
'79150090_035_3c99.png',
'80350346_043_3e8a.png'
];
}
if (imageNames && imageNames[index]) {
const imagePath = `assets/${theme}/${imageNames[index]}`;
element.style.backgroundImage = `url('${imagePath}')`;
}
});
}
async function applyLibraryTheme(characterSides) {
console.log('Applying library theme with random images...');
try {
// Get images from user's linked directories (same way as lib-image-gallery)
const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
let allImages = [];
if (window.electronAPI && linkedDirs.length > 0) {
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
// Load images from all linked directories
for (const dir of linkedDirs) {
try {
let files = [];
if (window.electronAPI.readDirectory) {
const filesPromise = window.electronAPI.readDirectory(dir.path);
if (filesPromise && typeof filesPromise.then === 'function') {
files = await filesPromise;
} else if (Array.isArray(filesPromise)) {
files = filesPromise;
}
if (files && files.length > 0) {
const imageFiles = files.filter(file => {
const fileName = typeof file === 'object' ? file.name : file;
return imageExtensions.test(fileName);
});
const dirImages = imageFiles.map(file => {
if (typeof file === 'object' && file.name && file.path) {
return { path: file.path, name: file.name };
} else {
const fileName = typeof file === 'object' ? file.name : file;
const fullPath = window.electronAPI.pathJoin ?
window.electronAPI.pathJoin(dir.path, fileName) :
`${dir.path}\\${fileName}`;
return { path: fullPath, name: fileName };
}
});
allImages = allImages.concat(dirImages);
}
}
} catch (error) {
console.warn(`Error loading images from ${dir.path}:`, error);
}
}
}
console.log(`📂 Found ${allImages.length} images in user library`);
if (allImages.length === 0) {
// No images available - show placeholder or fall back to hentai theme
console.log('No library images found, falling back to hentai theme');
characterSides.forEach((element, index) => {
element.style.display = 'block';
// Fallback to hentai theme
const imageNumbers = [1, 11, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17];
const imageNumber = imageNumbers[index];
if (imageNumber) {
const imagePath = `assets/hentai/${imageNumber}.png`;
element.style.backgroundImage = `url('${imagePath}')`;
}
});
return;
}
// Dynamic layout variables
const FIXED_WIDTH = 150; // Fixed width for all images
const MIN_HEIGHT = 100; // Minimum height
const MAX_HEIGHT = 300; // Maximum height
const MARGIN = 0; // No gap between images
// Separate left and right columns
const leftElements = [];
const rightElements = [];
characterSides.forEach((element, index) => {
if (element.classList.contains('left')) {
leftElements.push(element);
} else if (element.classList.contains('right')) {
rightElements.push(element);
}
});
// Function to load and position images dynamically
const loadColumnImages = async (elements, isLeft) => {
let currentTop = 0;
const usedImages = new Set(); // Track used images to prevent duplicates
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
// Get a random unused image
let randomImage;
let attempts = 0;
do {
const randomIndex = Math.floor(Math.random() * allImages.length);
randomImage = allImages[randomIndex];
attempts++;
} while (usedImages.has(randomImage.path) && attempts < allImages.length * 2);
if (randomImage) {
usedImages.add(randomImage.path); // Mark as used
// Properly encode the file path for CSS url()
let imageUrl;
if (window.electronAPI) {
const normalizedPath = randomImage.path.replace(/\\/g, '/');
imageUrl = `file:///${encodeURI(normalizedPath)}`;
} else {
const cleanPath = randomImage.path.replace(/\\/g, '/');
imageUrl = `file:///${encodeURI(cleanPath)}`;
}
// Load image to get dimensions
const img = new Image();
await new Promise((resolve) => {
img.onload = () => {
// Calculate height based on aspect ratio
const aspectRatio = img.height / img.width;
let calculatedHeight = FIXED_WIDTH * aspectRatio;
// Clamp height to min/max bounds
calculatedHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, calculatedHeight));
// Apply styles to element
element.style.display = 'block';
element.style.width = `${FIXED_WIDTH}px`;
element.style.height = `${calculatedHeight}px`;
element.style.top = `${currentTop}px`;
element.style.backgroundImage = `url("${imageUrl}")`;
element.style.backgroundSize = 'cover';
element.style.backgroundPosition = 'center';
// Position left or right
if (isLeft) {
element.style.left = '1px';
element.style.right = 'auto';
} else {
element.style.right = '1px';
element.style.left = 'auto';
}
// Update currentTop for next image (small gap)
currentTop += calculatedHeight + MARGIN;
console.log(`📸 ${isLeft ? 'Left' : 'Right'} Panel ${i + 1}: ${randomImage.name} (${FIXED_WIDTH}x${calculatedHeight}px)`);
resolve();
};
img.onerror = () => {
console.warn(`Failed to load image: ${randomImage.path}`);
// Use default height on error
element.style.display = 'block';
element.style.width = `${FIXED_WIDTH}px`;
element.style.height = `${MIN_HEIGHT}px`;
element.style.top = `${currentTop}px`;
if (isLeft) {
element.style.left = '1px';
element.style.right = 'auto';
} else {
element.style.right = '1px';
element.style.left = 'auto';
}
currentTop += MIN_HEIGHT + MARGIN;
resolve();
};
img.src = imageUrl;
});
}
}
};
// Load both columns in parallel
await Promise.all([
loadColumnImages(leftElements, true),
loadColumnImages(rightElements, false)
]);
console.log('📸 Library theme applied with dynamic sizing');
} catch (error) {
console.error('Error applying library theme:', error);
// Fallback to hentai theme on error
characterSides.forEach((element, index) => {
element.style.display = 'block';
const imageNumbers = [1, 11, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17];
const imageNumber = imageNumbers[index];
if (imageNumber) {
const imagePath = `assets/hentai/${imageNumber}.png`;
element.style.backgroundImage = `url('${imagePath}')`;
}
});
}
}
// Initialize backup system
function initializeBackupSystem() {
if (typeof window.BackupManager !== 'undefined') {
window.backupManager = new window.BackupManager();
// Setup backup button handlers
setupBackupHandlers();
// Create initial backup with retry logic
try {
window.backupManager.createBackup(false);
console.log('🛡️ Initial backup created on startup');
} catch (error) {
console.warn('⚠️ Initial backup failed, attempting cleanup and retry:', error);
try {
// Perform emergency cleanup and try minimal backup
window.backupManager.performEmergencyCleanup();
const minimalBackup = window.backupManager.createMinimalBackup(false);
const backupKey = `${window.backupManager.backupPrefix}${Date.now()}`;
localStorage.setItem(backupKey, JSON.stringify(minimalBackup));
console.log('🛡️ Minimal backup created after cleanup');
} catch (retryError) {
console.error('❌ Even minimal backup failed:', retryError);
}
}
} else {
console.warn('⚠️ BackupManager not available');
}
}
function setupBackupHandlers() {
// Create manual backup
const createBackupBtn = document.getElementById('create-backup-btn');
if (createBackupBtn) {
createBackupBtn.addEventListener('click', async () => {
try {
createBackupBtn.disabled = true;
createBackupBtn.textContent = '📦 Creating...';
const backupKey = window.backupManager.createBackup(false);
alert(`✅ Backup created successfully!\n\nBackup ID: ${backupKey}\n\nThis backup contains all your settings, linked directories, custom content, and player data.`);
} catch (error) {
alert(`❌ Backup creation failed:\n${error.message}`);
} finally {
createBackupBtn.disabled = false;
createBackupBtn.textContent = '📦 Create Backup';
}
});
}
// Restore backup
const restoreBackupBtn = document.getElementById('restore-backup-btn');
if (restoreBackupBtn) {
restoreBackupBtn.addEventListener('click', () => {
showBackupRestoreDialog();
});
}
// Export backup
const exportBackupBtn = document.getElementById('export-backup-btn');
if (exportBackupBtn) {
exportBackupBtn.addEventListener('click', () => {
showBackupExportDialog();
});
}
// Import backup
const importBackupBtn = document.getElementById('import-backup-btn');
if (importBackupBtn) {
importBackupBtn.addEventListener('click', () => {
showBackupImportDialog();
});
}
// Backup info
const backupInfoBtn = document.getElementById('backup-info-btn');
if (backupInfoBtn) {
backupInfoBtn.addEventListener('click', () => {
showBackupInfo();
});
}
}
function showBackupRestoreDialog() {
const backups = window.backupManager.listBackups();
if (backups.length === 0) {
alert('No backups available to restore.');
return;
}
let html = `
<div class="backup-dialog">
<h3>🔄 Restore Backup</h3>
<p>Select a backup to restore. This will replace all current data!</p>
<div class="backup-list">
`;
backups.forEach(backup => {
const date = new Date(backup.timestamp).toLocaleString();
const type = backup.isAutomatic ? '🤖 Auto' : '👤 Manual';
const size = window.backupManager.formatBytes(backup.size);
html += `
<div class="backup-item" data-key="${backup.key}">
<div class="backup-header">
<strong>${type}</strong> - ${date}
</div>
<div class="backup-details">
${backup.totalItems} items • ${size}
</div>
<button onclick="restoreSpecificBackup('${backup.key}', '${date}')" class="btn btn-warning btn-small">
Restore This Backup
</button>
</div>
`;
});
html += `
</div>
<button onclick="closeBackupDialog()" class="btn btn-secondary">Cancel</button>
</div>
`;
showModal(html);
}
function showBackupExportDialog() {
const backups = window.backupManager.listBackups();
if (backups.length === 0) {
alert('No backups available to export.');
return;
}
let html = `
<div class="backup-dialog">
<h3>📁 Export Backup</h3>
<p>Select a backup to export to file:</p>
<div class="backup-list">
`;
backups.forEach(backup => {
const date = new Date(backup.timestamp).toLocaleString();
const type = backup.isAutomatic ? '🤖 Auto' : '👤 Manual';
const size = window.backupManager.formatBytes(backup.size);
html += `
<div class="backup-item" data-key="${backup.key}">
<div class="backup-header">
<strong>${type}</strong> - ${date}
</div>
<div class="backup-details">
${backup.totalItems} items • ${size}
</div>
<button onclick="exportSpecificBackup('${backup.key}')" class="btn btn-info btn-small">
Export This Backup
</button>
</div>
`;
});
html += `
</div>
<button onclick="closeBackupDialog()" class="btn btn-secondary">Cancel</button>
</div>
`;
showModal(html);
}
function showBackupImportDialog() {
const html = `
<div class="backup-dialog">
<h3>📂 Import Backup</h3>
<p>Select a backup file (.json) to import:</p>
<div class="import-area">
<input type="file" id="backup-file-input" accept=".json" />
<button onclick="processBackupImport()" class="btn btn-success">Import Backup</button>
</div>
<div class="import-note">
<small>⚠️ This will add the backup to your backup list. Use the restore function to apply it.</small>
</div>
<button onclick="closeBackupDialog()" class="btn btn-secondary">Cancel</button>
</div>
`;
showModal(html);
}
function showBackupInfo() {
const info = window.backupManager.getBackupStorageInfo();
let html = `
<div class="backup-info-dialog">
<h3>🛡️ Backup System Status</h3>
<div class="backup-stats">
<div class="stat-row">
<span>Total Backups:</span>
<span>${info.totalBackups}</span>
</div>
<div class="stat-row">
<span>Automatic Backups:</span>
<span>${info.autoBackups}</span>
</div>
<div class="stat-row">
<span>Manual Backups:</span>
<span>${info.manualBackups}</span>
</div>
<div class="stat-row">
<span>Storage Used:</span>
<span>${info.formattedSize}</span>
</div>
<div class="stat-row">
<span>Auto-backup Frequency:</span>
<span>Every 30 minutes</span>
</div>
<div class="stat-row">
<span>Max Auto-backups:</span>
<span>5 (oldest auto-deleted)</span>
</div>
</div>
<div class="backup-features">
<h4>🔧 Features</h4>
<ul>
<li>✅ Automatic backups every 30 minutes</li>
<li>✅ Manual backup creation</li>
<li>✅ Selective restore options</li>
<li>✅ Export backups to files</li>
<li>✅ Import backups from files</li>
<li>✅ Emergency backup before risky operations</li>
</ul>
</div>
<button onclick="closeBackupDialog()" class="btn btn-primary">Close</button>
</div>
`;
showModal(html);
}
window.restoreSpecificBackup = async function(backupKey, date) {
if (!confirm(`⚠️ This will replace ALL current data with the backup from:\n\n${date}\n\nAre you sure you want to continue?\n\nCurrent data will be backed up first as a safety measure.`)) {
return;
}
try {
const result = window.backupManager.restoreBackup(backupKey);
closeBackupDialog();
alert(`✅ Backup restored successfully!\n\n${result.restoredItems} items restored from ${result.backupTimestamp}\n\nSafety backup created: ${result.restorePointKey}\n\nPlease restart the application to fully apply changes.`);
} catch (error) {
alert(`❌ Backup restore failed:\n${error.message}`);
}
};
window.exportSpecificBackup = async function(backupKey) {
try {
const result = await window.backupManager.exportBackup(backupKey);
closeBackupDialog();
alert(`✅ Backup exported successfully!\n\nFile: ${result.filename}\n\nYou can now store this file safely and import it later if needed.`);
} catch (error) {
alert(`❌ Backup export failed:\n${error.message}`);
}
};
window.processBackupImport = async function() {
const fileInput = document.getElementById('backup-file-input');
const file = fileInput.files[0];
if (!file) {
alert('Please select a backup file first.');
return;
}
try {
const result = await window.backupManager.importBackup(file);
closeBackupDialog();
alert(`✅ Backup imported successfully!\n\nBackup from: ${result.timestamp}\nItems: ${result.totalItems}\n\nThe backup is now available in the restore list.`);
} catch (error) {
alert(`❌ Backup import failed:\n${error.message}`);
}
};
function showModal(content) {
// Remove existing modal if any
const existingModal = document.querySelector('.backup-modal');
if (existingModal) {
existingModal.remove();
}
const modal = document.createElement('div');
modal.className = 'backup-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
`;
const modalContent = document.createElement('div');
modalContent.style.cssText = `
background: var(--bg-secondary);
padding: 30px;
border-radius: 10px;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
color: white;
`;
modalContent.innerHTML = content;
modal.appendChild(modalContent);
document.body.appendChild(modal);
}
window.closeBackupDialog = function() {
const modal = document.querySelector('.backup-modal');
if (modal) {
modal.remove();
}
};
// Initialize theme dropdown functionality
async function initializeThemeDropdown() {
const themeDropdown = document.getElementById('theme-dropdown');
if (themeDropdown) {
// Load saved theme preference
const savedTheme = localStorage.getItem('selectedTheme') || 'hentai';
themeDropdown.value = savedTheme;
// Apply the saved theme (refreshes library theme with new random images)
await applyTheme(savedTheme);
// Handle theme changes
themeDropdown.addEventListener('change', async (e) => {
await applyTheme(e.target.value);
});
}
// Initialize color theme dropdown
const colorThemeDropdown = document.getElementById('color-theme-dropdown');
if (colorThemeDropdown && window.themeManager) {
// Load current theme
const currentTheme = window.themeManager.getCurrentTheme();
colorThemeDropdown.value = currentTheme;
// Handle color theme changes
colorThemeDropdown.addEventListener('change', (e) => {
window.themeManager.applyTheme(e.target.value);
});
}
}
// Game Guide Toggle
function initializeGameGuide() {
const guideBtn = document.getElementById('game-guide-btn');
const guidePanel = document.getElementById('game-guide');
if (guideBtn && guidePanel) {
guideBtn.addEventListener('click', () => {
const isVisible = guidePanel.style.display !== 'none';
if (isVisible) {
guidePanel.style.display = 'none';
guideBtn.textContent = '📋 Game Guide';
} else {
guidePanel.style.display = 'block';
guideBtn.textContent = '📋 Hide Guide';
}
});
}
}
// Initialize application when page loads
window.addEventListener('load', () => {
// Initialize theme dropdown
initializeThemeDropdown();
// Initialize game guide
initializeGameGuide();
setTimeout(() => {
if (window.forceFixGame) {
window.forceFixGame();
}
}, 2000);
});
// Video management UI handlers
let videoPlayerManager = null;
// Initialize video player when available
function initializeVideoPlayer() {
if (window.VideoPlayerManager && !videoPlayerManager) {
videoPlayerManager = new VideoPlayerManager();
window.videoPlayerManager = videoPlayerManager; // Expose globally
console.log('✅ Video player initialized');
}
}
// Video management screen handlers
let videoHandlersAttached = false;
function setupVideoManagementHandlers() {
// Prevent multiple attachments
if (videoHandlersAttached) {
console.log('Video handlers already attached, skipping...');
return;
}
console.log('Setting up video management handlers...');
videoHandlersAttached = true;
// Back button
const backBtn = document.getElementById('back-to-start-from-video-btn');
if (backBtn) {
backBtn.addEventListener('click', () => {
if (window.game && typeof window.game.showScreen === 'function') {
window.game.showScreen('start-screen');
}
});
}
function showVideoStorageInfo() {
const categories = ['background', 'task', 'reward', 'punishment'];
let totalVideos = 0;
let totalSize = 0;
let invalidCount = 0;
const details = categories.map(category => {
const videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
const validVideos = videos.filter(v => !v.url.startsWith('blob:'));
const invalid = videos.length - validVideos.length;
const categorySize = validVideos.reduce((sum, v) => sum + (v.size || 0), 0);
totalVideos += validVideos.length;
totalSize += categorySize;
invalidCount += invalid;
return `${category}: ${validVideos.length} videos (${formatFileSize(categorySize)})${invalid > 0 ? ` + ${invalid} invalid` : ''}`;
}).join('\n');
// Calculate current localStorage usage
const currentStorageSize = new Blob([JSON.stringify(localStorage)]).size;
const storageLimit = 5 * 1024 * 1024; // 5MB conservative estimate
const usagePercent = Math.round((currentStorageSize / storageLimit) * 100);
const storageWarning = currentStorageSize > storageLimit * 0.8 ?
'\n⚠ Warning: Storage nearly full!' : '';
alert(`Video Storage Info:\n\n${details}\n\nTotal: ${totalVideos} videos (${formatFileSize(totalSize)})${invalidCount > 0 ? `\nInvalid entries: ${invalidCount}` : ''}\n\nLocalStorage Usage: ${formatFileSize(currentStorageSize)} / ~${formatFileSize(storageLimit)} (${usagePercent}%)${storageWarning}\n\nTip: Use videos under 10MB for best performance.`);
}
function manualCleanupInvalidVideos() {
const categories = ['background', 'task', 'reward', 'punishment'];
let totalCleaned = 0;
categories.forEach(category => {
const videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
const validVideos = videos.filter(video => !video.url.startsWith('blob:'));
const cleaned = videos.length - validVideos.length;
if (cleaned > 0) {
localStorage.setItem(`${category}-videos`, JSON.stringify(validVideos));
totalCleaned += cleaned;
// Refresh the gallery if this category is currently active
// [REMOVED] loadVideoGalleryContent call - part of unified video gallery cleanup
}
});
if (totalCleaned > 0) {
if (window.game && window.game.showNotification) {
window.game.showNotification(`🧹 Cleaned up ${totalCleaned} invalid video entries`, 'success');
}
// Refresh current gallery
const activeTab = document.querySelector('.video-tabs .tab-btn.active');
if (activeTab) {
const category = activeTab.id.replace('-video-tab', '');
// [REMOVED] loadVideoGalleryContent call - part of unified video gallery cleanup
}
} else {
if (window.game && window.game.showNotification) {
window.game.showNotification('No invalid videos found to clean up', 'info');
}
}
}
function clearAllVideos() {
const categories = ['background', 'task', 'reward', 'punishment'];
let totalCleared = 0;
categories.forEach(category => {
const videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
totalCleared += videos.length;
localStorage.removeItem(`${category}-videos`);
// Refresh the gallery if this category is currently active
// [REMOVED] loadVideoGalleryContent call - part of unified video gallery cleanup
});
if (window.game && window.game.showNotification) {
window.game.showNotification(`🗑️ Cleared ${totalCleared} videos and freed up storage space`, 'success');
}
// Refresh current gallery
const activeTab = document.querySelector('.video-tabs .tab-btn.active');
if (activeTab) {
const category = activeTab.id.replace('-video-tab', '');
// [REMOVED] loadVideoGalleryContent call - part of unified video gallery cleanup
}
}
// Directory management buttons
const addDirectoryBtn = document.getElementById('add-video-directory-btn');
if (addDirectoryBtn) {
addDirectoryBtn.addEventListener('click', () => {
handleAddVideoDirectory();
});
}
const refreshAllBtn = document.getElementById('refresh-all-directories-btn');
if (refreshAllBtn) {
refreshAllBtn.addEventListener('click', () => {
handleRefreshAllDirectories();
});
}
const clearAllDirectoriesBtn = document.getElementById('clear-all-directories-btn');
if (clearAllDirectoriesBtn) {
clearAllDirectoriesBtn.addEventListener('click', () => {
handleClearAllDirectories();
});
}
// [REMOVED] Gallery control buttons - lib-video-gallery uses direct preview instead of selection system
// Refresh videos button
const refreshVideosBtn = document.getElementById('refresh-videos-btn');
if (refreshVideosBtn) {
refreshVideosBtn.addEventListener('click', () => {
refreshVideoLibrary();
});
}
// Preview close button
const closePreviewBtn = document.getElementById('close-video-preview-btn');
if (closePreviewBtn) {
closePreviewBtn.addEventListener('click', () => {
closeVideoPreview();
});
}
// Video loop toggle button
const toggleLoopBtn = document.getElementById('toggle-video-loop-btn');
if (toggleLoopBtn) {
toggleLoopBtn.addEventListener('click', () => {
toggleVideoLoop();
});
}
// Test buttons
const testBackgroundBtn = document.getElementById('test-background-video');
if (testBackgroundBtn) {
testBackgroundBtn.addEventListener('click', () => {
testVideoPlayback('background');
});
}
const testOverlayBtn = document.getElementById('test-overlay-video');
if (testOverlayBtn) {
testOverlayBtn.addEventListener('click', () => {
testVideoPlayback('overlay');
});
}
const stopAllBtn = document.getElementById('stop-all-videos');
if (stopAllBtn) {
stopAllBtn.addEventListener('click', () => {
stopAllVideos();
});
}
// Storage info button
const storageInfoBtn = document.getElementById('video-storage-info-btn');
if (storageInfoBtn) {
storageInfoBtn.addEventListener('click', () => {
showVideoStorageInfo();
});
}
// Cleanup button
const cleanupBtn = document.getElementById('cleanup-invalid-videos-btn');
if (cleanupBtn) {
cleanupBtn.addEventListener('click', () => {
if (confirm('Clean up all invalid video entries? This will remove broken video references but keep valid videos.')) {
manualCleanupInvalidVideos();
}
});
}
// Clear all videos button
const clearAllBtn = document.getElementById('clear-all-videos-btn');
if (clearAllBtn) {
clearAllBtn.addEventListener('click', () => {
if (confirm('⚠️ Delete ALL videos from ALL categories? This cannot be undone!')) {
clearAllVideos();
}
});
}
// Settings handlers
setupVideoSettingsHandlers();
// Initialize the unified video system
if (window.electronAPI && window.desktopFileManager) {
console.log('🔍 Initializing unified video library...');
// The file manager will automatically load linked directories
// No need to scan old category directories anymore
setTimeout(() => {
updateDirectoryList();
updateVideoStats();
// Video gallery loading moved to lib-video-gallery system
}, 500);
} else {
// Browser fallback - video gallery loading moved to lib-video-gallery system
}
// Clean up any existing invalid blob URLs from previous sessions
cleanupInvalidVideoData();
}
function cleanupInvalidVideoData() {
const categories = ['background', 'task', 'reward', 'punishment'];
let totalCleaned = 0;
categories.forEach(category => {
const videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
const validVideos = videos.filter(video => {
if (video.url && video.url.startsWith('blob:')) {
totalCleaned++;
return false;
}
return true;
});
if (validVideos.length !== videos.length) {
localStorage.setItem(`${category}-videos`, JSON.stringify(validVideos));
}
});
if (totalCleaned > 0) {
console.log(`🧹 Cleaned up ${totalCleaned} invalid blob URL videos from localStorage`);
if (window.game && window.game.showNotification) {
window.game.showNotification(`🧹 Cleaned up ${totalCleaned} invalid video entries`, 'info');
}
}
}
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();
// Video gallery loading moved to lib-video-gallery system
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();
// Video gallery loading moved to lib-video-gallery system
}).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();
// Video gallery loading moved to lib-video-gallery system
if (window.game && window.game.showNotification) {
window.game.showNotification('<27> 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 = '<p style="color: var(--text-dim); text-align: center; padding: 20px;">No directories linked yet. Click "Add Directory" to get started.</p>';
return;
}
listContainer.innerHTML = directories.map(dir => `
<div class="directory-item" style="display: flex; justify-content: space-between; align-items: center; padding: 8px; margin-bottom: 5px; background: var(--bg-tertiary); border-radius: 4px;">
<div>
<strong>${dir.name}</strong>
<br><small style="color: var(--text-muted);">${dir.path}</small>
<br><small style="color: var(--text-dim);">${dir.videoCount} videos</small>
</div>
<button class="btn btn-small btn-outline" onclick="removeDirectory(${dir.id})" style="color: var(--color-error);">Remove</button>
</div>
`).join('');
}
}
function removeDirectory(directoryId) {
if (window.electronAPI && window.desktopFileManager) {
window.desktopFileManager.removeVideoDirectory(directoryId).then((success) => {
if (success) {
updateDirectoryList();
updateVideoStats();
// Video gallery loading moved to lib-video-gallery system
}
});
}
}
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';
}
}
// loadUnifiedVideoGallery() function removed - using lib-video-gallery instead
// loadStandardVideoGallery() function removed - using lib-video-gallery instead
// loadLargeVideoGallery() function removed - using lib-video-gallery instead
function createVideoItem(video, index) {
return `
<div class="video-item" data-video-index="${index}" data-video-name="${video.name}" data-video-url="${video.url}">
<div class="video-thumbnail">
<video preload="metadata" muted style="width: 100%; height: 100%; object-fit: cover;"
onloadedmetadata="this.currentTime = 2">
<source src="${video.url}" type="${video.type || 'video/mp4'}">
</video>
</div>
<div class="video-info">
<input type="checkbox" class="video-checkbox" data-video-index="${index}">
<div class="video-details">
<div class="video-title" title="${video.title}">${video.title}</div>
<div class="video-meta">
<small style="color: var(--text-dim);">📁 ${video.directoryName || 'Unknown'}</small>
${video.size ? `<small style="color: var(--text-dim);"> • ${formatFileSize(video.size)}</small>` : ''}
</div>
<button class="btn btn-small" onclick="previewVideo('${video.url}', '${video.title}')" style="margin-top: 8px;">▶️ Preview</button>
</div>
</div>
</div>
`;
}
function createVideoItemLazy(video, index) {
return `
<div class="video-item" data-video-index="${index}" data-video-name="${video.name}" data-video-url="${video.url}">
<div class="video-thumbnail lazy-thumbnail" data-video-url="${video.url}" data-video-type="${video.type || 'video/mp4'}">
<div class="thumbnail-placeholder" style="width: 100%; height: 100%; background: var(--bg-tertiary); display: flex; align-items: center; justify-content: center; color: var(--text-dim);">
<span>🎬</span>
</div>
</div>
<div class="video-info">
<input type="checkbox" class="video-checkbox" data-video-index="${index}">
<div class="video-details">
<div class="video-title" title="${video.title}">${video.title}</div>
<div class="video-meta">
<small style="color: var(--text-dim);">📁 ${video.directoryName || 'Unknown'}</small>
${video.size ? `<small style="color: var(--text-dim);"> • ${formatFileSize(video.size)}</small>` : ''}
</div>
<button class="btn btn-small" onclick="previewVideo('${video.url}', '${video.title}')" style="margin-top: 8px;background: var(--color-primary) !important;color: #ffffff !important;border: 1px solid var(--color-primary) !important;">▶️ Preview</button>
</div>
</div>
</div>
`;
}
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 = `
<video preload="metadata" muted style="width: 100%; height: 100%; object-fit: cover;" loading="lazy"
onloadedmetadata="this.currentTime = 2">
<source src="${videoUrl}" type="${videoType}">
</video>
`;
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);
});
}
// [REMOVED] setupVideoItemHandlers function - part of unified video gallery cleanup
// 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();
// Video gallery loading moved to lib-video-gallery system
}
});
}
};
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...`);
if (window.electronAPI && window.desktopFileManager) {
// Desktop version - use desktop file manager with native file dialog
window.desktopFileManager.selectAndImportVideos(category).then((importedVideos) => {
// [REMOVED] loadVideoGalleryContent call - part of unified video gallery cleanup
if (window.game && window.game.showNotification && importedVideos.length > 0) {
window.game.showNotification(`${importedVideos.length} ${category} videos imported successfully!`, 'success');
}
}).catch(error => {
console.error('Video import failed:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Video import failed', 'error');
}
});
} else {
// Web version - use file input and process files
const input = document.createElement('input');
input.type = 'file';
input.accept = 'video/*';
input.multiple = true;
input.multiple = true;
input.onchange = async (e) => {
const files = Array.from(e.target.files);
console.log(`Selected ${files.length} ${category} video files`);
if (files.length === 0) return;
// Show progress notification
if (window.game && window.game.showNotification) {
window.game.showNotification(`Processing ${files.length} video files...`, 'info');
}
// Process each file
let processedCount = 0;
const videoList = [];
for (const file of files) {
try {
// Check file size (limit to 50MB for localStorage compatibility)
const maxSize = 50 * 1024 * 1024; // 50MB (will become ~67MB as base64)
if (file.size > maxSize) {
console.warn(`⚠️ Skipping ${file.name}: File too large (${formatFileSize(file.size)}). Maximum size: 50MB`);
if (window.game && window.game.showNotification) {
window.game.showNotification(`⚠️ ${file.name} is too large (max 50MB)`, 'warning');
}
continue;
}
// Check available localStorage space
const currentStorageSize = new Blob([JSON.stringify(localStorage)]).size;
const estimatedVideoSize = file.size * 1.37; // Base64 overhead estimate
const storageLimit = 5 * 1024 * 1024; // Conservative 5MB limit
if (currentStorageSize + estimatedVideoSize > storageLimit) {
console.warn(`⚠️ Skipping ${file.name}: Would exceed storage quota. Current: ${formatFileSize(currentStorageSize)}, Video: ${formatFileSize(estimatedVideoSize)}`);
if (window.game && window.game.showNotification) {
window.game.showNotification(`⚠️ Storage quota exceeded. Try smaller videos or clear some data.`, 'error');
}
continue;
}
console.log(`Processing video: ${file.name} (${formatFileSize(file.size)})`);
// Convert file to data URL for reliable storage and playback
const videoDataURL = await fileToDataURL(file);
const videoData = {
name: file.name,
url: videoDataURL,
size: file.size,
type: file.type,
category: category,
addedDate: new Date().toISOString()
};
videoList.push(videoData);
processedCount++;
console.log(`✅ Processed ${category} video: ${file.name}`);
} catch (error) {
console.error(`❌ Failed to process video ${file.name}:`, error);
}
}
if (processedCount > 0) {
try {
// Save videos to localStorage
const existingVideos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
const updatedVideos = [...existingVideos, ...videoList];
// Try to save and handle quota exceeded error
localStorage.setItem(`${category}-videos`, JSON.stringify(updatedVideos));
// Refresh the gallery
// [REMOVED] loadVideoGalleryContent call - part of unified video gallery cleanup
if (window.game && window.game.showNotification) {
window.game.showNotification(`${processedCount} ${category} videos uploaded successfully!`, 'success');
}
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.error('❌ LocalStorage quota exceeded:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Storage quota exceeded. Try smaller videos or clear browser data.', 'error');
}
} else {
console.error('❌ Failed to save videos:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to save videos', 'error');
}
}
}
}
};
input.click();
}
}
// [REMOVED] loadVideoGalleryContent function - part of unified video gallery cleanup
function formatFileSize(bytes) {
if (!bytes || 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(2)) + ' ' + sizes[i];
}
function fileToDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
console.log(`✅ Converted ${file.name} to data URL`);
resolve(e.target.result);
};
reader.onerror = (e) => {
console.error(`❌ Failed to read ${file.name}:`, e);
reject(e);
};
reader.onprogress = (e) => {
if (e.lengthComputable) {
const progress = Math.round((e.loaded / e.total) * 100);
if (progress % 25 === 0) { // Log every 25%
console.log(`📖 Reading ${file.name}: ${progress}%`);
}
}
};
reader.readAsDataURL(file);
});
}
function previewVideo(videoUrl, videoName) {
const previewSection = document.getElementById('video-preview-section');
const previewPlayer = document.getElementById('video-preview-player');
const previewNameElement = document.getElementById('preview-video-name');
const toggleLoopBtn = document.getElementById('toggle-video-loop-btn');
if (previewSection && previewPlayer && previewNameElement) {
// Stop any currently playing video
previewPlayer.pause();
previewPlayer.currentTime = 0;
// Reset loop state for new video
previewPlayer.loop = false;
if (toggleLoopBtn) {
toggleLoopBtn.textContent = '🔁 Loop';
toggleLoopBtn.classList.remove('btn-active');
}
// Set new video source
previewPlayer.src = videoUrl;
previewNameElement.textContent = videoName;
previewSection.style.display = 'block';
previewSection.scrollIntoView({ behavior: 'smooth' });
// Autoplay the video
previewPlayer.play().catch(error => {
console.warn('Autoplay failed:', error);
});
console.log(`🎬 Previewing video: ${videoName}`);
}
}
// [REMOVED] deleteVideo function - part of unified video gallery cleanup, was only used by removed category galleries
// [REMOVED] selectAllVideos and deselectAllVideos functions - lib-video-gallery doesn't use checkbox selection
// [REMOVED] deleteSelectedVideos function - lib-video-gallery doesn't use checkbox selection system
function refreshVideoLibrary() {
if (window.electronAPI && window.desktopFileManager) {
if (window.game && window.game.showNotification) {
window.game.showNotification('🔄 Refreshing video library...', 'info');
}
// Use the new unified system - refresh all linked directories
window.desktopFileManager.refreshAllDirectories().then(() => {
// Update UI
updateDirectoryList();
updateVideoStats();
// Video gallery loading moved to lib-video-gallery system
if (window.game && window.game.showNotification) {
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);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to refresh video library', 'error');
}
});
} else {
if (window.game && window.game.showNotification) {
window.game.showNotification('Refresh is only available in desktop mode', 'warning');
}
}
}
function closeVideoPreview() {
const previewSection = document.getElementById('video-preview-section');
const previewPlayer = document.getElementById('video-preview-player');
if (previewSection && previewPlayer) {
// Stop playback and clear source
previewPlayer.pause();
previewPlayer.currentTime = 0;
previewPlayer.src = '';
previewSection.style.display = 'none';
console.log('🔇 Video preview closed');
}
}
function toggleVideoLoop() {
const previewPlayer = document.getElementById('video-preview-player');
const toggleLoopBtn = document.getElementById('toggle-video-loop-btn');
if (previewPlayer && toggleLoopBtn) {
// Toggle the loop attribute
previewPlayer.loop = !previewPlayer.loop;
// Update button appearance and text
if (previewPlayer.loop) {
toggleLoopBtn.textContent = '🔁 Loop ON';
toggleLoopBtn.classList.add('btn-active');
console.log('🔁 Video loop enabled');
} else {
toggleLoopBtn.textContent = '🔁 Loop';
toggleLoopBtn.classList.remove('btn-active');
console.log('🔁 Video loop disabled');
}
}
}
async function testVideoPlayback(type) {
console.log(`Testing ${type} video playback`);
if (type === 'overlay') {
try {
// Show overlay video player with random video
const overlayPlayer = await OverlayVideoPlayer.showOverlay({
showQuality: false,
showSpeed: false,
minimal: false
});
if (window.game && window.game.showNotification) {
window.game.showNotification('🎬 Overlay video player opened!', 'success');
}
} catch (error) {
console.error('Error showing overlay video player:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('Error opening overlay video player', 'error');
}
}
} else {
if (window.game && window.game.showNotification) {
window.game.showNotification(`${type} video test - feature coming soon!`, 'info');
}
}
}
function stopAllVideos() {
console.log('Stopping all video playback');
if (window.game && window.game.showNotification) {
window.game.showNotification('All videos stopped', 'info');
}
}
function setupVideoSettingsHandlers() {
const settings = [
'enable-video-player',
'enable-background-videos',
'enable-task-videos',
'video-autoplay',
'video-show-controls',
'video-fade-transitions'
];
settings.forEach(settingId => {
const element = document.getElementById(settingId);
if (element && element.type === 'checkbox') {
element.addEventListener('change', () => {
localStorage.setItem(settingId, element.checked);
console.log(`Video setting ${settingId}: ${element.checked}`);
});
// Load saved setting
const saved = localStorage.getItem(settingId);
if (saved !== null) {
element.checked = saved === 'true';
}
}
});
// Volume slider
const volumeSlider = document.getElementById('video-volume');
const volumeDisplay = document.getElementById('video-volume-display');
if (volumeSlider && volumeDisplay) {
volumeSlider.addEventListener('input', () => {
const volume = volumeSlider.value;
volumeDisplay.textContent = `${volume}%`;
localStorage.setItem('video-volume', volume);
});
// Load saved volume
const savedVolume = localStorage.getItem('video-volume') || '30';
volumeSlider.value = savedVolume;
volumeDisplay.textContent = `${savedVolume}%`;
}
}
// ===== LEVEL CALCULATION SYSTEM =====
// Exponential XP scaling: starts at 10 XP for level 2, then grows exponentially
// Formula: XP = Math.floor(10 * Math.pow(1.5, level - 2)) for levels 2+
const levelData = [
{ level: 1, name: "Virgin", xpRequired: 0 },
{ level: 2, name: "Curious", xpRequired: 10 }, // 10 XP
{ level: 3, name: "Eager", xpRequired: 25 }, // 15 more (25 total)
{ level: 4, name: "Aroused", xpRequired: 48 }, // 23 more (48 total)
{ level: 5, name: "Lustful", xpRequired: 82 }, // 34 more (82 total)
{ level: 6, name: "Passionate", xpRequired: 133 }, // 51 more (133 total)
{ level: 7, name: "Addicted", xpRequired: 209 }, // 76 more (209 total)
{ level: 8, name: "Obsessed", xpRequired: 323 }, // 114 more (323 total)
{ level: 9, name: "Deviant", xpRequired: 494 }, // 171 more (494 total)
{ level: 10, name: "Kinky", xpRequired: 751 }, // 257 more (751 total)
{ level: 11, name: "Perverted", xpRequired: 1137 }, // 386 more (1137 total)
{ level: 12, name: "Depraved", xpRequired: 1716 }, // 579 more (1716 total)
{ level: 13, name: "Dominant", xpRequired: 2585 }, // 869 more (2585 total)
{ level: 14, name: "Submissive", xpRequired: 3889 }, // 1304 more (3889 total)
{ level: 15, name: "Hedonist", xpRequired: 5844 }, // 1955 more (5844 total)
{ level: 16, name: "Insatiable", xpRequired: 8777 }, // 2933 more (8777 total)
{ level: 17, name: "Transcendent", xpRequired: 13176 }, // 4399 more (13176 total)
{ level: 18, name: "Enlightened", xpRequired: 19775 }, // 6599 more (19775 total)
{ level: 19, name: "Godlike", xpRequired: 29673 }, // 9898 more (29673 total)
{ level: 20, name: "Omnipotent", xpRequired: 44520 } // 14847 more (44520 total)
];
function calculateLevel(totalXP) {
// Find the highest level that the user has reached
let currentLevel = levelData[0];
for (let i = levelData.length - 1; i >= 0; i--) {
if (totalXP >= levelData[i].xpRequired) {
currentLevel = levelData[i];
break;
}
}
// Calculate progress toward next level
const nextLevel = levelData.find(l => l.level === currentLevel.level + 1);
const currentLevelXP = totalXP - currentLevel.xpRequired;
const xpNeededForNext = nextLevel ? nextLevel.xpRequired - currentLevel.xpRequired : 100;
const progressPercent = nextLevel ? (currentLevelXP / xpNeededForNext) * 100 : 100;
return {
level: currentLevel.level,
name: currentLevel.name,
totalXP: totalXP,
currentLevelXP: currentLevelXP,
xpNeededForNext: xpNeededForNext,
progressPercent: Math.min(progressPercent, 100),
nextLevelName: nextLevel ? nextLevel.name : 'Max Level'
};
}
function updateLevelDisplay() {
// Get total XP from PlayerStats if available
let totalXP = 0;
if (window.playerStats && window.playerStats.stats) {
totalXP = window.playerStats.stats.totalXP || 0;
} else {
// Fallback to localStorage
const savedStats = localStorage.getItem('playerStats');
if (savedStats) {
try {
const stats = JSON.parse(savedStats);
totalXP = stats.totalXP || 0;
} catch (e) {
console.warn('Could not parse saved stats for level display');
}
}
}
const levelInfo = calculateLevel(totalXP);
// Update the display elements
const levelNameEl = document.getElementById('current-level-name');
const levelNumberEl = document.getElementById('current-level-number');
const levelXPEl = document.getElementById('level-xp-header');
const progressEl = document.getElementById('level-xp-progress');
const progressCurrentEl = document.getElementById('level-progress-current');
const progressNextEl = document.getElementById('level-progress-next');
if (levelNameEl) levelNameEl.textContent = levelInfo.name;
if (levelNumberEl) levelNumberEl.textContent = levelInfo.level;
if (levelXPEl) levelXPEl.textContent = levelInfo.totalXP.toLocaleString();
if (progressEl) progressEl.style.width = levelInfo.progressPercent + '%';
if (progressCurrentEl) progressCurrentEl.textContent = levelInfo.currentLevelXP;
if (progressNextEl) progressNextEl.textContent = levelInfo.xpNeededForNext;
console.log(`📊 Level Display Updated: ${levelInfo.name} (Level ${levelInfo.level}) - ${levelInfo.totalXP} XP`);
}
// Update level display when page loads and periodically
function initializeLevelDisplay() {
updateLevelDisplay();
// Update every 5 seconds to catch XP changes
setInterval(updateLevelDisplay, 5000);
// Also update when PlayerStats changes (if available)
if (window.addEventListener) {
window.addEventListener('playerStatsUpdated', updateLevelDisplay);
// Update when page gains focus (returning from other pages)
window.addEventListener('focus', () => {
console.log('📊 Page gained focus, refreshing XP display');
setTimeout(updateLevelDisplay, 500); // Small delay to ensure any stats updates are saved
});
// Update when page becomes visible (tab switching)
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
console.log('📊 Page became visible, refreshing XP display');
setTimeout(updateLevelDisplay, 500);
}
});
}
}
// ===== VIDEO BILLBOARD FUNCTIONALITY =====
let billboardVideoSystem = {
videos: [],
currentVideoIndex: 0,
isPlaying: true,
isMuted: true,
playTimeout: null,
async initialize() {
console.log('🎬 Initializing Video Billboard...');
// Try to load videos directly using the same method as Quick Play
await this.loadVideosDirectly();
// If no videos found, try fallback methods
if (this.videos.length === 0) {
await this.tryFallbackVideos();
}
if (this.videos.length === 0) {
console.warn('⚠️ No videos available for billboard');
return;
}
console.log(`🎬 Billboard loaded ${this.videos.length} videos`);
this.setupVideoElement();
this.setupControls();
this.startVideoRotation();
},
async loadVideosDirectly() {
try {
// Use the same video loading logic as Quick Play
console.log('🎬 Loading videos using Quick Play method...');
// Get linked directories from localStorage (same as Quick Play)
let linkedDirs;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
} catch (e) {
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
linkedDirs = [];
}
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, ${linkedIndividualVideos.length} individual videos`);
// Load videos from linked directories using Electron API (same as Quick Play)
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) {
files = await window.electronAPI.readVideoDirectory(dir.path);
} else if (window.electronAPI.readVideoDirectoryRecursive) {
files = await window.electronAPI.readVideoDirectoryRecursive(dir.path);
} else if (window.electronAPI.readDirectory) {
const allFiles = await window.electronAPI.readDirectory(dir.path);
files = allFiles.filter(file => videoExtensions.test(file.name));
}
if (files && files.length > 0) {
console.log(`🎬 Found ${files.length} video files in ${dir.path}`);
files.forEach(file => {
this.videos.push({
name: file.name,
path: file.path,
filePath: file.path,
size: file.size || 0,
duration: file.duration || 0,
directory: dir.path
});
});
} else {
console.log(`🎬 No video files found in ${dir.path}`);
}
} catch (error) {
console.error(`❌ Error loading videos from directory ${dir.path}:`, error);
continue;
}
}
}
// Add individual linked video files
linkedIndividualVideos.forEach(video => {
this.videos.push({
name: video.name || 'Unknown Video',
path: video.path,
filePath: video.path,
size: video.size || 0,
directory: 'individual'
});
});
console.log(`🎬 Billboard total videos loaded: ${this.videos.length}`);
} catch (error) {
console.error('❌ Error loading videos directly:', error);
}
},
async tryFallbackVideos() {
// Try the same fallback methods as Quick Play
console.log('🎬 Trying fallback video discovery methods...');
// Try unified storage (Quick Play method)
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
let allVideos = unifiedData.allVideos || [];
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
// Try legacy storage (Quick Play method)
if (allVideos.length === 0) {
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
allVideos = Object.values(storedVideos).flat();
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
}
// Try VideoLibrary localStorage (Quick Play method)
if (allVideos.length === 0) {
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
}
// Try to access the global VideoLibrary if available
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
allVideos = window.VideoLibrary.videos;
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
}
// Try desktop file manager
if (allVideos.length === 0 && window.desktopFileManager) {
try {
if (typeof window.desktopFileManager.getAllVideos === 'function') {
allVideos = window.desktopFileManager.getAllVideos();
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
}
} catch (dmError) {
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
}
}
if (allVideos.length > 0) {
this.videos = allVideos.slice(0, 50); // Limit to first 50 for performance
console.log(`🎬 Loaded ${this.videos.length} videos from fallback methods`);
return;
}
console.log('🎬 No video sources found, creating demo placeholder');
this.createDemoVideo();
},
async getVideosFromLibrarySystem() {
try {
// Try to access the global video library data that's being loaded
if (window.game && window.game.fileManager && window.game.fileManager.allLinkedVideos) {
const linkedVideos = window.game.fileManager.allLinkedVideos;
if (linkedVideos && linkedVideos.length > 0) {
console.log(`🎬 Found ${linkedVideos.length} videos from game fileManager`);
return linkedVideos;
}
}
// Try desktop file manager
if (window.desktopFileManager && window.desktopFileManager.allLinkedVideos) {
const linkedVideos = window.desktopFileManager.allLinkedVideos;
if (linkedVideos && linkedVideos.length > 0) {
console.log(`🎬 Found ${linkedVideos.length} videos from desktop fileManager`);
return linkedVideos;
}
}
// Try to check if we can find any active video data being processed
const libraryTab = document.querySelector('#lib-video-gallery');
if (libraryTab && libraryTab.children.length > 0) {
console.log('🎬 Found videos in library gallery, attempting to extract data...');
return this.extractVideosFromGallery();
}
return null;
} catch (error) {
console.warn('⚠️ Error accessing library system videos:', error);
return null;
}
},
extractVideosFromGallery() {
const videos = [];
const galleryItems = document.querySelectorAll('#lib-video-gallery .video-item');
galleryItems.forEach((item, index) => {
const videoElement = item.querySelector('video');
const titleElement = item.querySelector('.video-title');
if (videoElement && videoElement.src) {
let src = videoElement.src;
// Remove the file:// prefix if present
if (src.startsWith('file://')) {
src = src.substring(7);
}
videos.push({
name: titleElement ? titleElement.textContent : `Video ${index + 1}`,
path: src,
filePath: src
});
}
});
console.log(`🎬 Extracted ${videos.length} videos from gallery`);
return videos;
},
createDemoVideo() {
// Create a demo placeholder that shows the billboard is working
const demoContainer = document.querySelector('.billboard-frame');
if (demoContainer) {
const video = document.getElementById('billboard-video');
if (video) {
video.style.display = 'none';
}
// Create demo content
const demoDiv = document.createElement('div');
demoDiv.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--color-background-gradient);
background-size: 400% 400%;
animation: gradientShift 3s ease infinite;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: 'Audiowide', monospace;
text-align: center;
font-size: 14px;
z-index: 1;
`;
demoDiv.innerHTML = `
<div>
<div style="font-size: 18px; margin-bottom: 10px;">🎬</div>
<div>CYBERPUNK BILLBOARD</div>
<div style="font-size: 10px; margin-top: 5px; opacity: 0.7;">Awaiting Video Library</div>
</div>
`;
// Add animation keyframes if not already present
if (!document.querySelector('#billboard-demo-styles')) {
const style = document.createElement('style');
style.id = 'billboard-demo-styles';
style.textContent = `
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
`;
document.head.appendChild(style);
}
demoContainer.appendChild(demoDiv);
console.log('🎬 Created demo billboard content');
}
},
setupVideoElement() {
const video = document.getElementById('billboard-video');
if (!video) return;
// Set performance optimizations (like Quick Play)
video.muted = this.isMuted;
video.volume = 0.3;
video.playsInline = true;
video.preload = 'metadata';
// Add performance CSS properties via JavaScript
video.style.backfaceVisibility = 'hidden';
video.style.transform = 'translateZ(0)';
video.style.willChange = 'opacity';
// Set up event listeners for smooth playback
video.addEventListener('loadstart', () => {
console.log('🎬 Billboard video loading started');
});
video.addEventListener('loadedmetadata', () => {
console.log('🎬 Billboard metadata loaded');
});
video.addEventListener('canplay', () => {
console.log('🎬 Billboard video ready to play');
});
video.addEventListener('error', (e) => {
console.error('❌ Billboard video error:', e);
this.loadRandomVideo();
});
video.addEventListener('ended', () => {
// Immediately load next video when current one ends
this.loadRandomVideo();
});
// Handle stalled playback
video.addEventListener('stalled', () => {
console.warn('⚠️ Billboard video stalled, reloading...');
setTimeout(() => this.loadRandomVideo(), 1000);
});
// Handle waiting/buffering
video.addEventListener('waiting', () => {
console.log('🎬 Billboard video buffering...');
});
console.log('🎬 Billboard video element configured for smooth playback');
},
setupControls() {
const muteBtn = document.getElementById('billboard-mute');
const pauseBtn = document.getElementById('billboard-pause');
if (muteBtn) {
muteBtn.addEventListener('click', () => this.toggleMute());
}
if (pauseBtn) {
pauseBtn.addEventListener('click', () => this.togglePause());
}
this.updateControlButtons();
},
updateControlButtons() {
const muteBtn = document.getElementById('billboard-mute');
const pauseBtn = document.getElementById('billboard-pause');
if (muteBtn) {
muteBtn.textContent = this.isMuted ? '🔇' : '🔊';
muteBtn.title = this.isMuted ? 'Unmute' : 'Mute';
}
if (pauseBtn) {
pauseBtn.textContent = this.isPlaying ? '⏸️' : '▶️';
pauseBtn.title = this.isPlaying ? 'Pause' : 'Play';
}
},
async startVideoRotation() {
if (this.videos.length === 0) return;
this.loadRandomVideo();
},
async loadRandomVideo() {
if (this.videos.length === 0) return;
// Clear any existing timeout
if (this.playTimeout) {
clearTimeout(this.playTimeout);
}
// Select random video
this.currentVideoIndex = Math.floor(Math.random() * this.videos.length);
const selectedVideo = this.videos[this.currentVideoIndex];
const video = document.getElementById('billboard-video');
const source = document.getElementById('billboard-video-source');
if (!video || !source || !selectedVideo) return;
try {
// Fade out current video smoothly
video.style.opacity = '0';
// Wait for fade out, then load new video
setTimeout(() => {
const videoPath = selectedVideo.path || selectedVideo.filePath;
if (videoPath) {
console.log(`🎬 Billboard loading: ${selectedVideo.name || selectedVideo.title || 'Unknown'}`);
// Use source element for better loading (like Quick Play)
source.src = `file://${videoPath}`;
video.load(); // Force reload with new source
// Handle metadata loaded event
video.onloadedmetadata = () => {
// Set random start time for variety
const duration = video.duration;
if (duration > 10) {
const maxStart = Math.max(0, duration - 10); // Ensure we have at least 10 seconds to play
const randomStart = Math.random() * maxStart;
video.currentTime = randomStart;
}
};
// Handle when video can start playing
video.oncanplay = () => {
// Fade in smoothly
video.style.opacity = '1';
if (this.isPlaying) {
// Use a small delay to ensure smooth playback
setTimeout(() => {
video.play().catch(e => {
console.warn('Billboard autoplay prevented:', e);
});
}, 100);
}
// Set timeout for next video (8 seconds)
this.playTimeout = setTimeout(() => {
this.loadRandomVideo();
}, 8000);
};
// Handle loading errors
video.onerror = () => {
console.warn('Billboard video loading error, trying next video');
setTimeout(() => this.loadRandomVideo(), 1000);
};
}
}, 500); // Wait for fade out
} catch (error) {
console.error('❌ Error loading billboard video:', error);
// Try next video after a delay
setTimeout(() => this.loadRandomVideo(), 2000);
}
},
toggleMute() {
const video = document.getElementById('billboard-video');
if (!video) return;
this.isMuted = !this.isMuted;
video.muted = this.isMuted;
this.updateControlButtons();
console.log(`🎬 Billboard ${this.isMuted ? 'muted' : 'unmuted'}`);
},
togglePause() {
const video = document.getElementById('billboard-video');
if (!video) return;
this.isPlaying = !this.isPlaying;
if (this.isPlaying) {
video.play().catch(e => console.warn('Billboard play prevented:', e));
// Resume rotation
if (!this.playTimeout) {
this.playTimeout = setTimeout(() => {
this.loadRandomVideo();
}, 8000);
}
} else {
video.pause();
// Stop rotation
if (this.playTimeout) {
clearTimeout(this.playTimeout);
this.playTimeout = null;
}
}
this.updateControlButtons();
console.log(`🎬 Billboard ${this.isPlaying ? 'resumed' : 'paused'}`);
},
// Method to refresh videos when they become available
async refreshVideos() {
console.log('🎬 Refreshing billboard videos...');
const videos = await this.getVideosFromLibrarySystem();
if (videos && videos.length > 0) {
this.videos = videos.slice(0, 50);
console.log(`🎬 Billboard refreshed with ${this.videos.length} videos`);
// Remove demo content if it exists
const demoContent = document.querySelector('.billboard-frame > div');
if (demoContent && demoContent.innerHTML.includes('CYBERPUNK BILLBOARD')) {
demoContent.remove();
const video = document.getElementById('billboard-video');
if (video) {
video.style.display = 'block';
}
}
// Start playing if we weren't before
if (this.videos.length > 0) {
this.startVideoRotation();
return true;
}
}
return false;
}
};
// Global function to refresh billboard when videos become available
window.refreshBillboard = function() {
if (billboardVideoSystem) {
billboardVideoSystem.refreshVideos();
}
};
function initializeBillboardVideo() {
// Wait a bit for other systems to load, then try multiple times
let attempts = 0;
const maxAttempts = 5;
const tryInitialize = async () => {
attempts++;
console.log(`🎬 Billboard initialization attempt ${attempts}/${maxAttempts}`);
await billboardVideoSystem.initialize();
// If we still have no videos and haven't reached max attempts, try again
if (billboardVideoSystem.videos.length === 0 && attempts < maxAttempts) {
console.log(`🎬 No videos found on attempt ${attempts}, retrying in 3 seconds...`);
setTimeout(tryInitialize, 3000);
} else if (billboardVideoSystem.videos.length > 0) {
console.log(`🎬 Billboard successfully initialized with ${billboardVideoSystem.videos.length} videos`);
} else {
console.log('🎬 Billboard initialized with demo content after all attempts');
}
};
// Start the first attempt after a delay
setTimeout(tryInitialize, 5000);
}
// Initialize everything when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
// Initialize PlayerStats first
if (typeof PlayerStats !== 'undefined' && !window.playerStats) {
window.playerStats = new PlayerStats();
console.log('📊 PlayerStats initialized on home page');
}
initializeVideoPlayer();
// Initialize desktop file manager if in Electron environment
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();
// Video gallery loading moved to lib-video-gallery system
}, 1000);
// Initialize backup system
initializeBackupSystem();
} // Set up video management button (only once)
const videoManageBtn = document.getElementById('manage-video-btn');
if (videoManageBtn && !videoManageBtn.hasAttribute('data-handler-attached')) {
videoManageBtn.setAttribute('data-handler-attached', 'true');
videoManageBtn.addEventListener('click', () => {
if (window.game && typeof window.game.showScreen === 'function') {
window.game.showScreen('video-management-screen');
// Set up handlers when screen is shown (with delay to ensure DOM is ready)
setTimeout(() => {
setupVideoManagementHandlers();
}, 100);
} else {
console.error('Game instance not available for video management');
}
});
}
// Set up quick play button (only once)
const quickPlayBtn = document.getElementById('quick-play-btn');
if (quickPlayBtn && !quickPlayBtn.hasAttribute('data-handler-attached')) {
quickPlayBtn.setAttribute('data-handler-attached', 'true');
quickPlayBtn.addEventListener('click', () => {
console.log('⚡ Opening Quick Play...');
window.location.href = 'quick-play.html';
});
}
// Set up training academy button (only once)
const trainingAcademyBtn = document.getElementById('training-academy-btn');
if (trainingAcademyBtn && !trainingAcademyBtn.hasAttribute('data-handler-attached')) {
trainingAcademyBtn.setAttribute('data-handler-attached', 'true');
trainingAcademyBtn.addEventListener('click', () => {
console.log('🎓 Opening Training Academy...');
window.location.href = 'training-academy.html';
});
}
// Set up porn cinema button (only once)
const pornCinemaBtn = document.getElementById('porn-cinema-btn');
if (pornCinemaBtn && !pornCinemaBtn.hasAttribute('data-handler-attached')) {
pornCinemaBtn.setAttribute('data-handler-attached', 'true');
pornCinemaBtn.addEventListener('click', () => {
console.log('🎬 Opening Porn Cinema...');
window.location.href = 'porn-cinema.html';
});
}
// Set up hypno gallery button (only once)
const hypnoGalleryBtn = document.getElementById('hypno-gallery-btn');
if (hypnoGalleryBtn && !hypnoGalleryBtn.hasAttribute('data-handler-attached')) {
hypnoGalleryBtn.setAttribute('data-handler-attached', 'true');
hypnoGalleryBtn.addEventListener('click', () => {
console.log('🌀 Opening Hypno Gallery...');
window.location.href = 'hypno-gallery.html';
});
}
// Set up user profile button (only once)
const userProfileBtn = document.getElementById('user-profile-btn');
if (userProfileBtn && !userProfileBtn.hasAttribute('data-handler-attached')) {
userProfileBtn.setAttribute('data-handler-attached', 'true');
userProfileBtn.addEventListener('click', () => {
console.log('👤 Opening User Profile...');
window.location.href = 'user-profile.html';
});
}
// Set up library button (only once)
const libraryBtn = document.getElementById('library-btn');
console.log('🔍 Library button found:', !!libraryBtn);
if (libraryBtn && !libraryBtn.hasAttribute('data-handler-attached')) {
console.log('🔧 Attaching library button handler...');
libraryBtn.setAttribute('data-handler-attached', 'true');
libraryBtn.addEventListener('click', () => {
console.log('📚 Library button clicked - navigating to library.html');
window.location.href = 'library.html';
});
console.log('✅ Library button handler attached successfully');
} else if (libraryBtn && libraryBtn.hasAttribute('data-handler-attached')) {
console.log('⚠️ Library button handler already attached');
} else {
console.error('❌ Library button not found in DOM');
}
// Set up back to start from library button
const backToStartFromLibraryBtn = document.getElementById('back-to-start-from-library-btn');
if (backToStartFromLibraryBtn && !backToStartFromLibraryBtn.hasAttribute('data-handler-attached')) {
backToStartFromLibraryBtn.setAttribute('data-handler-attached', 'true');
backToStartFromLibraryBtn.addEventListener('click', () => {
if (window.game && typeof window.game.showScreen === 'function') {
window.game.showScreen('start-screen');
}
});
}
// Set up clear overall XP button (debug tool)
const clearXpBtn = document.getElementById('clear-overall-xp-btn');
if (clearXpBtn && !clearXpBtn.hasAttribute('data-handler-attached')) {
clearXpBtn.setAttribute('data-handler-attached', 'true');
clearXpBtn.addEventListener('click', () => {
if (confirm('Are you sure you want to reset your Overall XP to 0? This cannot be undone.')) {
if (window.game && window.game.dataManager) {
window.game.dataManager.set('overallXp', 0);
window.game.updateOverallXpDisplay(); // Update header display
alert('Overall XP has been reset to 0!');
console.log('🔄 Overall XP reset to 0 for testing');
} else {
alert('Game not initialized yet. Please try again after the game loads.');
}
}
});
}
// Initialize Video Billboard
initializeBillboardVideo();
// Initialize Level Display
initializeLevelDisplay();
// Set up a listener for when videos are loaded in the library
setTimeout(() => {
if (window.refreshBillboard) {
window.refreshBillboard();
}
}, 10000); // Check again after 10 seconds for loaded videos
}, 1000);
});
// Setup Library Tab Handlers
function setupLibraryHandlers() {
console.log('Setting up library handlers...');
// Set up library tab switching
const libraryTabs = document.querySelectorAll('.library-tab');
const libraryContents = document.querySelectorAll('.library-content');
libraryTabs.forEach(tab => {
tab.addEventListener('click', () => {
const targetTab = tab.getAttribute('data-tab');
// Remove active class from all tabs and contents
libraryTabs.forEach(t => t.classList.remove('active'));
libraryContents.forEach(c => c.classList.remove('active'));
// Add active class to clicked tab and corresponding content
tab.classList.add('active');
const targetContent = document.getElementById(`library-${targetTab}-content`);
if (targetContent) {
targetContent.classList.add('active');
// Initialize specific functionality for each tab
switch(targetTab) {
case 'images':
setupLibraryImagesTab();
break;
case 'audio':
setupLibraryAudioTab();
break;
case 'video':
setupLibraryVideoTab();
break;
case 'gallery':
setupLibraryGalleryTab();
break;
}
}
console.log(`Switched to library tab: ${targetTab}`);
});
});
// Set up gallery category switching for gallery tab
const galleryCategoryBtns = document.querySelectorAll('.gallery-category-btn');
const photoGalleries = document.querySelectorAll('.photo-gallery');
galleryCategoryBtns.forEach(btn => {
btn.addEventListener('click', () => {
const targetCategory = btn.getAttribute('data-category');
// Remove active class from all category buttons and galleries
galleryCategoryBtns.forEach(b => b.classList.remove('active'));
photoGalleries.forEach(g => g.classList.remove('active'));
// Add active class to clicked button and corresponding gallery
btn.classList.add('active');
const targetGallery = document.getElementById(`lib-${targetCategory}-photos-gallery`);
if (targetGallery) {
targetGallery.classList.add('active');
}
console.log(`Switched to gallery category: ${targetCategory}`);
});
});
// Set up refresh library button
const refreshLibraryBtn = document.getElementById('refresh-library-btn');
if (refreshLibraryBtn) {
refreshLibraryBtn.addEventListener('click', () => {
console.log('Refreshing library...');
refreshAllLibraryContent();
});
}
// Initialize the default tab (images) after a delay to ensure game data is loaded
setTimeout(() => {
setupLibraryImagesTab();
}, 1000);
// Listen for game ready event to refresh content
window.addEventListener('gameReady', () => {
console.log('🎮 Game ready event received, refreshing library content...');
refreshAllLibraryContent();
});
console.log('Library handlers setup complete');
}
// Library button click handler setup
function setupLibraryButtonHandler() {
const libraryBtn = document.getElementById('library-btn');
if (libraryBtn) {
libraryBtn.onclick = function(e) {
e.preventDefault();
console.log('📚 Library button clicked - navigating to library.html');
window.location.href = 'library.html';
};
}
}
// Setup library button handler after page load
setTimeout(setupLibraryButtonHandler, 2000);
// Setup individual library tab functionality
async function setupLibraryImagesTab(retryCount = 0) {
console.log('Setting up images tab functionality...');
// Wait for game to be available (max 10 retries)
if (!window.game && retryCount < 10) {
console.log(`⏳ Game not ready yet, retrying in 500ms... (attempt ${retryCount + 1}/10)`);
setTimeout(() => setupLibraryImagesTab(retryCount + 1), 500);
return;
}
if (!window.game) {
console.error('❌ Game not available after 10 retries, aborting image tab setup');
return;
}
// Get current image stats from multiple possible locations
let taskImages = [];
let consequenceImages = [];
// Try dataManager first (most likely location based on console output)
if (window.game.dataManager && window.game.dataManager.gameData) {
taskImages = window.game.dataManager.gameData.taskImages || [];
consequenceImages = window.game.dataManager.gameData.consequenceImages || [];
}
// Try gameData directly
if (taskImages.length === 0 && window.game.gameData) {
taskImages = window.game.gameData.taskImages || [];
consequenceImages = window.game.gameData.consequenceImages || [];
}
// Try fileManager (desktop file manager)
if (taskImages.length === 0 && window.game.fileManager) {
taskImages = window.game.fileManager.taskImages || [];
consequenceImages = window.game.fileManager.consequenceImages || [];
// Try desktop file manager methods if available
if (taskImages.length === 0 && window.desktopFileManager) {
console.log('📍 Trying desktop file manager...');
try {
// Desktop file manager might have different method names
if (window.desktopFileManager.getTaskImages) {
taskImages = window.desktopFileManager.getTaskImages() || [];
}
if (window.desktopFileManager.getConsequenceImages) {
consequenceImages = window.desktopFileManager.getConsequenceImages() || [];
}
console.log(`📍 Desktop file manager found: ${taskImages.length} task, ${consequenceImages.length} consequence images`);
} catch (e) {
console.log('📍 Desktop file manager error:', e.message);
}
}
}
// Try taskImagePaths and consequenceImagePaths
if (taskImages.length === 0 && window.game.taskImagePaths) {
taskImages = window.game.taskImagePaths;
}
if (consequenceImages.length === 0 && window.game.consequenceImagePaths) {
consequenceImages = window.game.consequenceImagePaths;
}
// Try other possible locations
if (taskImages.length === 0) {
taskImages = window.game.discoveredTaskImages || window.game.taskImages || [];
// Try accessing images through the discoverImages function
if (taskImages.length === 0 && window.game.discoverImages) {
console.log('📍 Trying to call discoverImages function...');
try {
// The discoverImages function might populate image arrays
const discovered = window.game.discoverImages();
if (discovered && discovered.taskImages) {
taskImages = discovered.taskImages;
}
if (discovered && discovered.consequenceImages) {
consequenceImages = discovered.consequenceImages;
}
console.log(`📍 discoverImages function returned: ${taskImages.length} task, ${consequenceImages.length} consequence images`);
} catch (e) {
console.log('📍 discoverImages function error:', e.message);
}
}
// Try accessing the image discovery result directly from game properties
if (taskImages.length === 0) {
console.log('📍 Checking for image discovery results in game properties...');
// Look for properties that might contain the discovered images
const gameProps = Object.keys(window.game);
const imageProps = gameProps.filter(prop => prop.toLowerCase().includes('image') || prop.toLowerCase().includes('task') || prop.toLowerCase().includes('consequence'));
console.log('📍 Image-related properties:', imageProps);
// Try common property names where images might be stored
const possibleTaskProps = ['taskImageList', 'allTaskImages', 'loadedTaskImages', 'imageList', 'taskImageCache'];
const possibleConsProps = ['consequenceImageList', 'allConsequenceImages', 'loadedConsequenceImages', 'consequenceImageCache'];
for (const prop of possibleTaskProps) {
if (window.game[prop] && Array.isArray(window.game[prop])) {
console.log(`📍 Found task images in ${prop}:`, window.game[prop].length);
taskImages = window.game[prop];
break;
}
}
for (const prop of possibleConsProps) {
if (window.game[prop] && Array.isArray(window.game[prop])) {
console.log(`📍 Found consequence images in ${prop}:`, window.game[prop].length);
consequenceImages = window.game[prop];
break;
}
}
}
}
if (consequenceImages.length === 0) {
consequenceImages = window.game.discoveredConsequenceImages || window.game.consequenceImages || [];
}
console.log(`📊 Found ${taskImages.length} task images, ${consequenceImages.length} consequence images`);
console.log('📍 Game data structure:', Object.keys(window.game));
console.log('📍 DataManager structure:', window.game.dataManager ? Object.keys(window.game.dataManager) : 'not available');
console.log('📍 FileManager structure:', window.game.fileManager ? Object.keys(window.game.fileManager) : 'not available');
console.log('📍 Checking specific image properties:');
console.log(' - gameData:', !!window.game.gameData);
console.log(' - dataManager.gameData:', !!(window.game.dataManager && window.game.dataManager.gameData));
console.log(' - fileManager:', !!window.game.fileManager);
console.log(' - taskImagePaths:', !!window.game.taskImagePaths);
console.log(' - consequenceImagePaths:', !!window.game.consequenceImagePaths);
console.log(' - discoveredTaskImages:', !!window.game.discoveredTaskImages);
console.log(' - discoveredConsequenceImages:', !!window.game.discoveredConsequenceImages);
// Detailed debugging of available image data
if (window.game.dataManager && window.game.dataManager.gameData) {
console.log('📍 DataManager gameData keys:', Object.keys(window.game.dataManager.gameData));
console.log('📍 DataManager task images:', window.game.dataManager.gameData.taskImages?.length || 0);
console.log('📍 DataManager consequence images:', window.game.dataManager.gameData.consequenceImages?.length || 0);
}
if (window.game.fileManager) {
console.log('📍 FileManager properties:', Object.keys(window.game.fileManager));
if (window.game.fileManager.imageDirectories) {
console.log('📍 FileManager image directories:', window.game.fileManager.imageDirectories);
// Debug available methods
console.log('📍 Available electronAPI methods:', window.electronAPI ? Object.keys(window.electronAPI) : 'electronAPI not available');
console.log('📍 Available desktopFileManager methods:', window.desktopFileManager ? Object.keys(window.desktopFileManager) : 'desktopFileManager not available');
console.log('📍 Available fileManager methods:', Object.keys(window.game.fileManager));
// Try to directly scan the directories if possible
if (taskImages.length === 0) {
console.log('📍 Attempting direct directory scan...');
try {
const taskDir = window.game.fileManager.imageDirectories.tasks;
const consDir = window.game.fileManager.imageDirectories.consequences;
console.log('📍 Task directory:', taskDir);
console.log('📍 Consequence directory:', consDir);
// Try different methods to scan directories
if (taskDir) {
// Method 1: electronAPI.readDirectory (correct method name)
if (window.electronAPI && window.electronAPI.readDirectory) {
try {
const taskFilesPromise = window.electronAPI.readDirectory(taskDir);
console.log('📍 Raw task files from readDirectory:', taskFilesPromise);
// Handle async result
if (taskFilesPromise && typeof taskFilesPromise.then === 'function') {
const taskFiles = await taskFilesPromise;
console.log('📍 Resolved task files:', taskFiles);
if (taskFiles && taskFiles.length > 0) {
// Filter for image files - check if it's an object with name property or just a string
const imageFiles = taskFiles.filter(file => {
const fileName = typeof file === 'object' ? file.name : file;
return /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(fileName);
});
console.log('📍 Filtered task image files:', imageFiles);
if (imageFiles.length > 0) {
taskImages = imageFiles.map(file => {
if (typeof file === 'object' && file.name && file.path) {
// Already has name and path
return { path: file.path, name: file.name };
} else {
// Just a filename string
const fileName = typeof file === 'object' ? file.name : file;
return {
path: window.electronAPI.pathJoin ? window.electronAPI.pathJoin(taskDir, fileName) : `${taskDir}\\${fileName}`,
name: fileName
};
}
});
console.log(`📍 ReadDirectory found ${taskImages.length} task images`);
}
}
} else if (taskFilesPromise && Array.isArray(taskFilesPromise)) {
// Handle sync result
const imageFiles = taskFilesPromise.filter(file =>
/\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(file)
);
if (imageFiles.length > 0) {
taskImages = imageFiles.map(file => ({
path: window.electronAPI.pathJoin ? window.electronAPI.pathJoin(taskDir, file) : `${taskDir}\\${file}`,
name: file
}));
console.log(`📍 ReadDirectory found ${taskImages.length} task images (sync)`);
}
}
} catch (e) {
console.log('📍 readDirectory error for tasks:', e.message);
}
}
}
if (consDir) {
// Method 1: electronAPI.readDirectory (correct method name)
if (window.electronAPI && window.electronAPI.readDirectory) {
try {
const consFilesPromise = window.electronAPI.readDirectory(consDir);
console.log('📍 Raw consequence files from readDirectory:', consFilesPromise);
// Handle async result
if (consFilesPromise && typeof consFilesPromise.then === 'function') {
const consFiles = await consFilesPromise;
console.log('📍 Resolved consequence files:', consFiles);
if (consFiles && consFiles.length > 0) {
// Filter for image files - check if it's an object with name property or just a string
const imageFiles = consFiles.filter(file => {
const fileName = typeof file === 'object' ? file.name : file;
return /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(fileName);
});
console.log('📍 Filtered consequence image files:', imageFiles);
if (imageFiles.length > 0) {
consequenceImages = imageFiles.map(file => {
if (typeof file === 'object' && file.name && file.path) {
// Already has name and path
return { path: file.path, name: file.name };
} else {
// Just a filename string
const fileName = typeof file === 'object' ? file.name : file;
return {
path: window.electronAPI.pathJoin ? window.electronAPI.pathJoin(consDir, fileName) : `${consDir}\\${fileName}`,
name: fileName
};
}
});
console.log(`📍 ReadDirectory found ${consequenceImages.length} consequence images`);
}
}
} else if (consFilesPromise && Array.isArray(consFilesPromise)) {
// Handle sync result
const imageFiles = consFilesPromise.filter(file =>
/\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(file)
);
if (imageFiles.length > 0) {
consequenceImages = imageFiles.map(file => ({
path: window.electronAPI.pathJoin ? window.electronAPI.pathJoin(consDir, file) : `${consDir}\\${file}`,
name: file
}));
console.log(`📍 ReadDirectory found ${consequenceImages.length} consequence images (sync)`);
}
}
} catch (e) {
console.log('📍 readDirectory error for consequences:', e.message);
}
}
}
} catch (e) {
console.log('📍 Direct directory scan error:', e.message);
}
}
}
if (window.game.fileManager.getTaskImages) {
console.log('📍 FileManager getTaskImages available');
try {
const taskImgs = window.game.fileManager.getTaskImages();
console.log('📍 FileManager task images:', taskImgs?.length || 0);
if (taskImgs && taskImgs.length > 0) {
taskImages = taskImgs;
}
} catch (e) {
console.log('📍 FileManager getTaskImages error:', e.message);
}
}
if (window.game.fileManager.getConsequenceImages) {
console.log('📍 FileManager getConsequenceImages available');
try {
const consImgs = window.game.fileManager.getConsequenceImages();
console.log('📍 FileManager consequence images:', consImgs?.length || 0);
if (consImgs && consImgs.length > 0) {
consequenceImages = consImgs;
}
} catch (e) {
console.log('📍 FileManager getConsequenceImages error:', e.message);
}
}
}
console.log(`📊 FINAL RESULT: ${taskImages.length} task images, ${consequenceImages.length} consequence images`);
// Update image count display
const imageCountElement = document.getElementById('lib-image-count');
if (imageCountElement) {
imageCountElement.textContent = `${taskImages.length + consequenceImages.length} images`;
}
// Populate image gallery
const imageGallery = document.getElementById('lib-image-gallery');
if (imageGallery && (taskImages.length > 0 || consequenceImages.length > 0)) {
imageGallery.innerHTML = '';
// Add task images
taskImages.forEach((image, index) => {
const imgElement = document.createElement('div');
imgElement.className = 'gallery-item';
imgElement.innerHTML = `
<img src="${image.path}" alt="Task Image ${index + 1}" style="width: 100%; height: 150px; object-fit: cover; border-radius: 8px;">
<div class="gallery-item-info">
<span class="gallery-item-type">Task</span>
<span class="gallery-item-name">${image.name}</span>
</div>
`;
imageGallery.appendChild(imgElement);
});
// Add consequence images
consequenceImages.forEach((image, index) => {
const imgElement = document.createElement('div');
imgElement.className = 'gallery-item';
imgElement.innerHTML = `
<img src="${image.path}" alt="Consequence Image ${index + 1}" style="width: 100%; height: 150px; object-fit: cover; border-radius: 8px;">
<div class="gallery-item-info">
<span class="gallery-item-type">Consequence</span>
<span class="gallery-item-name">${image.name}</span>
</div>
`;
imageGallery.appendChild(imgElement);
});
console.log(`✅ Created ${taskImages.length + consequenceImages.length} image gallery items`);
} else if (imageGallery) {
imageGallery.innerHTML = `
<div class="no-images-message">
<p>🗃️ No images found</p>
<p>Import images to get started</p>
</div>
`;
}
// Set up image directory management buttons
const addImageDirBtn = document.getElementById('lib-add-image-directory-btn');
const addIndividualImagesBtn = document.getElementById('lib-add-individual-images-btn');
const refreshImageDirBtn = document.getElementById('lib-refresh-image-directories-btn');
const clearImageDirBtn = document.getElementById('lib-clear-image-directories-btn');
if (addImageDirBtn) {
addImageDirBtn.onclick = () => {
console.log('Adding image directory...');
handleAddImageDirectory();
};
}
if (addIndividualImagesBtn) {
addIndividualImagesBtn.onclick = () => {
console.log('Adding individual images...');
handleAddIndividualImages();
};
}
if (refreshImageDirBtn) {
refreshImageDirBtn.onclick = () => {
console.log('Refreshing image directories...');
handleRefreshImageDirectories();
};
}
if (clearImageDirBtn) {
clearImageDirBtn.onclick = () => {
console.log('Clearing image directories...');
handleClearImageDirectories();
};
}
// Set up category filter dropdown
const categoryFilter = document.getElementById('lib-image-category-filter');
if (categoryFilter) {
categoryFilter.onchange = () => {
console.log('Filtering images by category:', categoryFilter.value);
// Use stored images and apply filter
if (window.allLinkedImages) {
const filteredImages = filterImagesByCategory(window.allLinkedImages, categoryFilter.value);
populateImageGallery(filteredImages);
// Update count display
const countElement = document.getElementById('lib-image-count');
if (countElement) {
countElement.textContent = `${filteredImages.length} images`;
}
}
};
}
// Initialize linked image directories display
updateImageDirectoriesList();
// Check if we have linked directories first
const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
if (linkedDirs.length > 0) {
console.log('📁 Using linked directories instead of built-in directories');
await loadLinkedImages();
return; // Skip built-in directory scanning
} else {
console.log('📁 No linked directories found, using built-in directories');
}
}
function setupLibraryAudioTab(retryCount = 0) {
console.log('Setting up audio tab functionality...');
// Wait for game to be available (max 10 retries)
if (!window.game && retryCount < 10) {
console.log(`⏳ Game not ready yet, retrying in 500ms... (attempt ${retryCount + 1}/10)`);
setTimeout(() => setupLibraryAudioTab(retryCount + 1), 500);
return;
}
if (!window.game) {
console.error('❌ Game not available after 10 retries, aborting audio tab setup');
return;
}
// Get current audio stats from multiple possible locations
let audioLibrary = {};
let backgroundTracks = [];
let ambientTracks = [];
// Try audioManager first
if (window.game.audioManager && window.game.audioManager.audioLibrary) {
audioLibrary = window.game.audioManager.audioLibrary;
backgroundTracks = audioLibrary.background || [];
ambientTracks = audioLibrary.ambient || [];
}
// If audioLibrary is empty, try checking if tracks are stored differently
if (backgroundTracks.length === 0 && window.game.audioManager) {
// Check if background tracks are stored in a different property
backgroundTracks = window.game.audioManager.backgroundTracks ||
window.game.audioManager.background || [];
}
if (ambientTracks.length === 0 && window.game.audioManager) {
// Check if ambient tracks are stored in a different property
ambientTracks = window.game.audioManager.ambientTracks ||
window.game.audioManager.ambient || [];
}
console.log(`📊 Found ${backgroundTracks.length} background tracks, ${ambientTracks.length} ambient tracks`);
console.log('📍 Audio manager available:', !!window.game.audioManager);
// Store audio tracks globally for filtering
window.allAudioTracks = { background: backgroundTracks, ambient: ambientTracks };
// Apply current filter and populate the gallery
const categoryFilter = document.getElementById('lib-audio-category-filter');
const selectedCategory = categoryFilter ? categoryFilter.value : 'all';
const filteredTracks = filterAudioByCategory(backgroundTracks, ambientTracks, selectedCategory);
populateAudioGallery(filteredTracks.background, filteredTracks.ambient);
// Update count display
const audioCountElement = document.getElementById('lib-audio-count');
if (audioCountElement) {
const totalCount = filteredTracks.background.length + filteredTracks.ambient.length;
audioCountElement.textContent = `${totalCount} files`;
}
// Set up category filter dropdown
if (categoryFilter) {
categoryFilter.onchange = () => {
console.log('Filtering audio by category:', categoryFilter.value);
// Use stored audio tracks and apply filter
if (window.allAudioTracks) {
const filtered = filterAudioByCategory(
window.allAudioTracks.background,
window.allAudioTracks.ambient,
categoryFilter.value
);
populateAudioGallery(filtered.background, filtered.ambient);
// Update count display
const totalCount = filtered.background.length + filtered.ambient.length;
audioCountElement.textContent = `${totalCount} files`;
}
};
}
}
function filterAudioByCategory(backgroundTracks, ambientTracks, category) {
switch (category) {
case 'background':
return { background: backgroundTracks, ambient: [] };
case 'ambient':
return { background: [], ambient: ambientTracks };
case 'all':
default:
return { background: backgroundTracks, ambient: ambientTracks };
}
}
function populateAudioGallery(backgroundTracks, ambientTracks) {
const audioGallery = document.getElementById('lib-audio-gallery');
if (!audioGallery) return;
if (backgroundTracks.length === 0 && ambientTracks.length === 0) {
audioGallery.innerHTML = `
<div class="no-audio-message">
<p>🎵 No audio files found</p>
<p>Import audio to get started</p>
</div>
`;
return;
}
audioGallery.innerHTML = '';
// Add background tracks
backgroundTracks.forEach((track, index) => {
const audioElement = document.createElement('div');
audioElement.className = 'gallery-item audio-item';
audioElement.innerHTML = `
<div class="audio-info">
<div class="audio-icon">🎵</div>
<div class="audio-name">${track.name || `Track ${index + 1}`}</div>
<div class="audio-type">Background</div>
</div>
`;
audioGallery.appendChild(audioElement);
});
// Add ambient tracks
ambientTracks.forEach((track, index) => {
const audioElement = document.createElement('div');
audioElement.className = 'gallery-item audio-item';
audioElement.innerHTML = `
<div class="audio-info">
<div class="audio-icon">🌊</div>
<div class="audio-name">${track.name || `Ambient ${index + 1}`}</div>
<div class="audio-type">Ambient</div>
</div>
`;
audioGallery.appendChild(audioElement);
});
console.log(`✅ Created ${backgroundTracks.length + ambientTracks.length} audio gallery items`);
}
async function setupLibraryVideoTab(retryCount = 0) {
console.log('Setting up video tab functionality...');
// Wait for game to be available (max 10 retries)
if (!window.game && retryCount < 10) {
console.log(`⏳ Game not ready yet, retrying in 500ms... (attempt ${retryCount + 1}/10)`);
setTimeout(() => setupLibraryVideoTab(retryCount + 1), 500);
return;
}
if (!window.game) {
console.error('❌ Game not available after 10 retries, aborting video tab setup');
return;
}
// Get current video stats from multiple possible locations
let videoLibrary = {};
let videoManager = null;
// Try different video manager locations
if (window.game.videoPlayerManager) {
videoManager = window.game.videoPlayerManager;
videoLibrary = videoManager.videoLibrary || {};
} else if (window.game.videoManager) {
videoManager = window.game.videoManager;
videoLibrary = videoManager.videoLibrary || {};
} else if (window.videoPlayerManager) {
// Check global videoPlayerManager
videoManager = window.videoPlayerManager;
videoLibrary = videoManager.videoLibrary || {};
}
// If still no video library found, try checking desktop file manager
if (Object.keys(videoLibrary).length === 0 && window.game.fileManager) {
// Desktop file manager might have video data
const fileManager = window.game.fileManager;
if (fileManager.getAllVideos) {
const allVideos = fileManager.getAllVideos();
// Convert to expected format
videoLibrary = {
background: allVideos.filter(v => v.category === 'background') || [],
task: allVideos.filter(v => v.category === 'task') || [],
reward: allVideos.filter(v => v.category === 'reward') || [],
punishment: allVideos.filter(v => v.category === 'punishment') || []
};
}
}
// Try using the desktop file manager video system directly
if (Object.keys(videoLibrary).length === 0 && window.desktopFileManager) {
console.log('📍 Trying desktop file manager for videos...');
try {
const allVideos = window.desktopFileManager.getAllVideos();
console.log(`📍 Desktop file manager found ${allVideos.length} videos`);
if (allVideos.length > 0) {
// Group videos by type/category if available
videoLibrary = {
background: allVideos.filter(v => v.type === 'background' || v.category === 'background') || [],
task: allVideos.filter(v => v.type === 'task' || v.category === 'task') || [],
reward: allVideos.filter(v => v.type === 'reward' || v.category === 'reward') || [],
punishment: allVideos.filter(v => v.type === 'punishment' || v.category === 'punishment') || [],
all: allVideos // Keep all videos as backup
};
console.log(`📍 Grouped videos: ${videoLibrary.background.length} bg, ${videoLibrary.task.length} task, ${videoLibrary.reward.length} reward, ${videoLibrary.punishment.length} punishment`);
}
} catch (e) {
console.log('📍 Desktop file manager video error:', e.message);
}
}
// Try accessing allLinkedVideos directly if no videos found yet
if (Object.keys(videoLibrary).length === 0 && window.game.fileManager.allLinkedVideos) {
console.log('📍 Trying allLinkedVideos from fileManager...');
const linkedVideos = window.game.fileManager.allLinkedVideos;
console.log(`📍 AllLinkedVideos found ${linkedVideos.length} videos`);
if (linkedVideos.length > 0) {
videoLibrary = {
background: linkedVideos.filter(v => v.type === 'background' || v.category === 'background') || [],
task: linkedVideos.filter(v => v.type === 'task' || v.category === 'task') || [],
reward: linkedVideos.filter(v => v.type === 'reward' || v.category === 'reward') || [],
punishment: linkedVideos.filter(v => v.type === 'punishment' || v.category === 'punishment') || [],
all: linkedVideos // Keep all videos as backup
};
console.log(`📍 AllLinkedVideos grouped: ${videoLibrary.background.length} bg, ${videoLibrary.task.length} task, ${videoLibrary.reward.length} reward, ${videoLibrary.punishment.length} punishment`);
}
}
const backgroundVideos = videoLibrary.background || [];
const taskVideos = videoLibrary.task || [];
const rewardVideos = videoLibrary.reward || [];
const punishmentVideos = videoLibrary.punishment || [];
const capturedVideos = JSON.parse(localStorage.getItem('capturedVideos') || '[]');
let totalVideos = backgroundVideos.length + taskVideos.length + rewardVideos.length + punishmentVideos.length + capturedVideos.length;
// If no categorized videos found but we have "all" videos, use those
let allVideos = [];
if (totalVideos === 0 && videoLibrary.all && Array.isArray(videoLibrary.all)) {
allVideos = videoLibrary.all;
totalVideos = allVideos.length;
console.log(`📍 Using ${totalVideos} uncategorized videos from 'all' array`);
}
console.log(`📊 Found ${backgroundVideos.length} background, ${taskVideos.length} task, ${rewardVideos.length} reward, ${punishmentVideos.length} punishment videos`);
console.log('📍 Video manager available:', !!videoManager);
console.log('📍 Video manager types:', Object.keys(window.game).filter(key => key.toLowerCase().includes('video')));
// Update video count display
const videoCountElement = document.getElementById('lib-video-count');
if (videoCountElement) {
videoCountElement.textContent = `${totalVideos} files`;
}
// Populate video gallery
const videoGallery = document.getElementById('lib-video-gallery');
if (videoGallery && totalVideos > 0) {
videoGallery.innerHTML = '';
// Add background videos
backgroundVideos.forEach((video, index) => {
const videoElement = document.createElement('div');
videoElement.className = 'gallery-item video-item';
videoElement.innerHTML = `
<div class="video-info">
<div class="video-icon">🎬</div>
<div class="video-name">${video.name || `Background ${index + 1}`}</div>
<div class="video-type">Background</div>
</div>
`;
videoGallery.appendChild(videoElement);
});
// Add task videos
taskVideos.forEach((video, index) => {
const videoElement = document.createElement('div');
videoElement.className = 'gallery-item video-item';
videoElement.innerHTML = `
<div class="video-info">
<div class="video-icon">📋</div>
<div class="video-name">${video.name || `Task ${index + 1}`}</div>
<div class="video-type">Task</div>
</div>
`;
videoGallery.appendChild(videoElement);
});
// Add reward videos
rewardVideos.forEach((video, index) => {
const videoElement = document.createElement('div');
videoElement.className = 'gallery-item video-item';
videoElement.innerHTML = `
<div class="video-info">
<div class="video-icon"><3E></div>
<div class="video-name">${video.name || `Reward ${index + 1}`}</div>
<div class="video-type">Reward</div>
</div>
`;
videoGallery.appendChild(videoElement);
});
// Add punishment videos
punishmentVideos.forEach((video, index) => {
const videoElement = document.createElement('div');
videoElement.className = 'gallery-item video-item';
videoElement.innerHTML = `
<div class="video-info">
<div class="video-icon">⚡</div>
<div class="video-name">${video.name || `Punishment ${index + 1}`}</div>
<div class="video-type">Punishment</div>
</div>
`;
videoGallery.appendChild(videoElement);
});
// If no categorized videos were added but we have uncategorized videos, add those
if (videoGallery.children.length === 0 && allVideos.length > 0) {
allVideos.forEach((video, index) => {
const videoElement = document.createElement('div');
videoElement.className = 'gallery-item video-item';
videoElement.innerHTML = `
<div class="video-info">
<div class="video-icon">🎬</div>
<div class="video-name">${video.name || video.fileName || `Video ${index + 1}`}</div>
<div class="video-type">${video.type || video.category || 'Video'}</div>
</div>
`;
videoGallery.appendChild(videoElement);
});
}
console.log(`✅ Created ${totalVideos} video gallery items`);
} else if (videoGallery) {
videoGallery.innerHTML = `
<div class="no-video-message">
<p>🎬 No video files found</p>
<p>Import videos to get started</p>
</div>
`;
}
// Set up video directory management buttons
const addVideoDirBtn = document.getElementById('lib-add-video-directory-btn');
const addIndividualVideosBtn = document.getElementById('lib-add-individual-videos-btn');
const refreshVideoDirBtn = document.getElementById('lib-refresh-video-directories-btn');
const clearVideoDirBtn = document.getElementById('lib-clear-video-directories-btn');
if (addVideoDirBtn) {
addVideoDirBtn.onclick = () => {
console.log('Adding video directory...');
handleAddVideoDirectory();
};
}
if (addIndividualVideosBtn) {
addIndividualVideosBtn.onclick = () => {
console.log('Adding individual videos...');
handleAddIndividualVideos();
};
}
if (refreshVideoDirBtn) {
refreshVideoDirBtn.onclick = () => {
console.log('Refreshing video directories...');
handleRefreshVideoDirectories();
};
}
if (clearVideoDirBtn) {
clearVideoDirBtn.onclick = () => {
console.log('Clearing video directories...');
handleClearVideoDirectories();
};
}
// Initialize linked video directories display
updateVideoDirectoriesList();
// Load linked videos if any exist
let linkedVideoDirs;
let individualVideos;
try {
linkedVideoDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedVideoDirs)) {
linkedVideoDirs = [];
}
individualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
if (!Array.isArray(individualVideos)) {
individualVideos = [];
}
} catch (e) {
console.log('Error parsing video directories, resetting to empty arrays:', e);
linkedVideoDirs = [];
individualVideos = [];
}
if (linkedVideoDirs.length > 0 || individualVideos.length > 0) {
console.log('📁 Loading linked video directories...');
loadLinkedVideos();
}
}
function setupLibraryGalleryTab() {
console.log('Setting up gallery tab functionality...');
// Load captured photos from localStorage
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
console.log(`📸 Found ${capturedPhotos.length} captured photos`);
// Load verification photos from localStorage
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
console.log(`📷 Found ${verificationPhotos.length} verification photos`);
const allPhotosGrid = document.getElementById('lib-all-photos-grid');
const allPhotosCount = document.getElementById('lib-all-photos-count');
if (allPhotosGrid) {
if (capturedPhotos.length === 0 && verificationPhotos.length === 0) {
allPhotosGrid.innerHTML = `
<div class="no-photos-message">
<p>📸 No photos found</p>
<p>Take some photos during gameplay to see them here</p>
</div>
`;
if (allPhotosCount) allPhotosCount.textContent = '0 photos';
} else {
// Create photo gallery grid - start with captured photos
let photosHtml = '';
capturedPhotos.forEach((photo, index) => {
const timestamp = new Date(photo.timestamp || Date.now()).toLocaleDateString();
const imageData = photo.imageData || photo.dataURL; // Support both formats
if (imageData) {
photosHtml += `
<div class="photo-item" data-index="${index}" data-type="captured">
<div class="photo-container">
<div class="photo-checkbox">
<input type="checkbox" id="photo-${index}" class="photo-select" data-index="${index}" onchange="updateSelectionCount()">
<label for="photo-${index}" class="checkbox-label"></label>
</div>
<img src="${imageData}" alt="Captured Photo ${index + 1}"
onclick="showPhotoPreview('${imageData}', 'Photo ${index + 1}')">
<div class="photo-actions">
<button class="photo-download-btn" onclick="downloadSinglePhoto(${index})" title="Download Photo">
📥
</button>
<button class="photo-delete-btn" onclick="deletePhoto(${index})" title="Delete Photo">
🗑️
</button>
</div>
<div class="photo-info">
<span class="photo-date">${timestamp}</span>
<span class="photo-type">${photo.sessionType || 'Training'}</span>
</div>
</div>
</div>
`;
}
});
// Add verification photos to the gallery
verificationPhotos.forEach((photo, index) => {
const timestamp = new Date(photo.timestamp || Date.now()).toLocaleDateString();
const photoType = photo.phase === 'start' ? '🟢 START Position' : '🔴 END Position';
const degradingMessage = photo.message || 'Position verification photo';
const verificationIndex = capturedPhotos.length + index; // Offset by captured photos length
const imageData = photo.data || photo.dataUrl; // Support both formats
if (imageData) {
photosHtml += `
<div class="photo-item verification-photo-item" data-index="${verificationIndex}" data-type="verification">
<div class="photo-container verification-photo-container">
<div class="photo-checkbox">
<input type="checkbox" id="verification-photo-${index}" class="photo-select verification-photo-select" data-index="${verificationIndex}" data-verification-index="${index}" onchange="updateSelectionCount()">
<label for="verification-photo-${index}" class="checkbox-label"></label>
</div>
<img src="${imageData}" alt="Verification Photo ${index + 1}"
onclick="showVerificationPhotoPreview('${imageData}', 'Verification Photo ${index + 1}', '${degradingMessage}', ${photo.phase === 'start'}, '${photo.timestamp}')">
<div class="photo-actions">
<button class="photo-download-btn" onclick="downloadVerificationPhoto(${index})" title="Download Verification Photo">
📥
</button>
<button class="photo-delete-btn" onclick="deleteVerificationPhoto(${index})" title="Delete Verification Photo">
🗑️
</button>
</div>
<div class="photo-info verification-photo-info">
<span class="photo-date">${timestamp}</span>
<span class="photo-type">${photoType}</span>
<span class="verification-message">"${degradingMessage}"</span>
</div>
</div>
</div>
`;
}
});
// Add captured videos to the gallery
const capturedVideos = JSON.parse(localStorage.getItem('capturedVideos') || '[]');
console.log(`📹 Found ${capturedVideos.length} captured videos`);
capturedVideos.forEach((video, index) => {
const timestamp = new Date(video.timestamp || Date.now()).toLocaleDateString();
const duration = formatVideoDuration(video.duration);
photosHtml += `
<div class="photo-item video-item" data-video-id="${video.id}">
<div class="photo-container">
<div class="photo-checkbox">
<input type="checkbox" id="video-${index}" class="video-select" data-video-id="${video.id}" onchange="updateSelectionCount()">
<label for="video-${index}" class="checkbox-label"></label>
</div>
<div class="video-thumbnail-wrapper" onclick="playCapturedVideo('${video.id}')"
style="position: relative; cursor: pointer;"
onmouseover="this.querySelector('.video-play-overlay').style.opacity='1'"
onmouseout="this.querySelector('.video-play-overlay').style.opacity='0'">
${video.thumbnail ?
`<img src="${video.thumbnail}" alt="Video Thumbnail" style="width: 100%; height: 120px; object-fit: cover; border-radius: 8px;">` :
`<div style="width: 100%; height: 120px; background: var(--bg-tertiary); display: flex; align-items: center; justify-content: center; border-radius: 8px; color: #fff;">📹</div>`
}
<div class="video-play-overlay" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.5); opacity: 0; transition: opacity 0.3s; border-radius: 8px;">
<div class="play-icon-small" style="color: white; font-size: 24px;">▶️</div>
</div>
</div>
<div class="photo-actions">
<button class="photo-download-btn" onclick="downloadCapturedVideo('${video.id}')" title="Download Video">
📥
</button>
<button class="photo-delete-btn" onclick="deleteCapturedVideoFromGallery('${video.id}')" title="Delete Video">
🗑️
</button>
</div>
<div class="photo-info">
<span class="photo-date">${timestamp}</span>
<span class="photo-type">Session Video • ${duration}</span>
</div>
</div>
</div>
`;
});
allPhotosGrid.innerHTML = photosHtml;
const totalPhotos = capturedPhotos.length + verificationPhotos.length;
const totalItems = totalPhotos + capturedVideos.length;
if (allPhotosCount) allPhotosCount.textContent = `${totalPhotos} photos, ${capturedVideos.length} videos`;
}
}
// Also populate dress-up photos if they exist
const dressUpPhotos = capturedPhotos.filter(photo =>
photo.sessionType && photo.sessionType.includes('dress-up'));
const dressUpGrid = document.getElementById('lib-dress-up-photos-grid');
const dressUpCount = document.getElementById('lib-dress-up-photos-count');
if (dressUpGrid && dressUpPhotos.length > 0) {
let dressUpHtml = '';
dressUpPhotos.forEach((photo, index) => {
const timestamp = new Date(photo.timestamp || Date.now()).toLocaleDateString();
const imageData = photo.imageData || photo.dataURL; // Support both formats
if (imageData) {
// Find the original index in the full capturedPhotos array
const originalIndex = capturedPhotos.findIndex(p => p.imageData === imageData || p.dataURL === imageData);
dressUpHtml += `
<div class="photo-item" data-index="${originalIndex}">
<div class="photo-container">
<div class="photo-checkbox">
<input type="checkbox" id="photo-${originalIndex}" class="photo-select" data-index="${originalIndex}" onchange="updateSelectionCount()">
<label for="photo-${originalIndex}" class="checkbox-label"></label>
</div>
<img src="${imageData}" alt="Dress Up Photo ${index + 1}"
onclick="showPhotoPreview('${imageData}', 'Dress Up Photo ${index + 1}')">
<div class="photo-actions">
<button class="photo-download-btn" onclick="downloadSinglePhoto(${originalIndex})" title="Download Photo">
📥
</button>
<button class="photo-delete-btn" onclick="deletePhoto(${originalIndex})" title="Delete Photo">
🗑️
</button>
</div>
<div class="photo-info">
<span class="photo-date">${timestamp}</span>
<span class="photo-type">${photo.sessionType}</span>
</div>
</div>
</div>
`;
}
});
dressUpGrid.innerHTML = dressUpHtml;
if (dressUpCount) dressUpCount.textContent = `${dressUpPhotos.length} photos`;
}
// Initialize bulk action event listeners
setTimeout(initializeBulkActions, 100);
}
// Delete a photo from the gallery
function deletePhoto(index) {
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
if (index < 0 || index >= capturedPhotos.length) {
console.error('Invalid photo index:', index);
return;
}
const photo = capturedPhotos[index];
const photoType = photo.sessionType || 'Training';
const photoDate = new Date(photo.timestamp || Date.now()).toLocaleDateString();
// Show confirmation dialog
const confirmed = confirm(`Are you sure you want to delete this photo?\n\nType: ${photoType}\nDate: ${photoDate}\n\nThis action cannot be undone.`);
if (confirmed) {
// Remove photo from array
capturedPhotos.splice(index, 1);
// Update localStorage
localStorage.setItem('capturedPhotos', JSON.stringify(capturedPhotos));
// Show success message
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📸 Photo deleted successfully!`, 'info');
}
// Refresh the photo galleries
setupLibraryGalleryTab();
console.log(`🗑️ Deleted photo ${index + 1} (${photoType})`);
}
}
// Update selection count and enable/disable bulk action buttons
function updateSelectionCount() {
const selectedCheckboxes = document.querySelectorAll('.photo-select:checked');
const count = selectedCheckboxes.length;
const selectedCountSpan = document.getElementById('selected-count');
const downloadBtn = document.getElementById('download-selected-photos');
const deleteBtn = document.getElementById('delete-selected-photos');
if (selectedCountSpan) selectedCountSpan.textContent = `${count} selected`;
if (downloadBtn) downloadBtn.disabled = count === 0;
if (deleteBtn) deleteBtn.disabled = count === 0;
}
// Select all photos
function selectAllPhotos() {
const checkboxes = document.querySelectorAll('.photo-select');
checkboxes.forEach(checkbox => {
checkbox.checked = true;
});
updateSelectionCount();
}
// Deselect all photos
function deselectAllPhotos() {
const checkboxes = document.querySelectorAll('.photo-select');
checkboxes.forEach(checkbox => {
checkbox.checked = false;
});
updateSelectionCount();
}
// Download single photo
function downloadSinglePhoto(index) {
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
if (index < 0 || index >= capturedPhotos.length) {
console.error('Invalid photo index:', index);
return;
}
const photo = capturedPhotos[index];
// Handle different photo data structures
let imageData;
if (photo.data) {
// Verification photos store in 'data' property
imageData = photo.data;
} else if (photo.dataURL) {
// Regular photos store in 'dataURL' property
imageData = photo.dataURL;
} else if (photo.imageData) {
// Fallback for other formats
imageData = photo.imageData;
} else {
console.error('Photo missing image data:', photo);
return;
}
const timestamp = new Date(photo.timestamp || Date.now()).toISOString().slice(0, 19).replace(/:/g, '-');
const filename = `photo-${timestamp}.png`;
// Create download link
const link = document.createElement('a');
link.href = imageData;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Photo downloaded: ${filename}`, 'info');
}
console.log(`📥 Downloaded photo: ${filename}`);
}
// Download single verification photo
function downloadSingleVerificationPhoto(index) {
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
if (index < 0 || index >= verificationPhotos.length) {
console.error('Invalid verification photo index:', index);
return;
}
const photo = verificationPhotos[index];
const imageData = photo.data || photo.dataUrl; // Verification photos store in 'data'
if (!imageData) {
console.error('Verification photo missing image data:', photo);
return;
}
const timestamp = new Date(photo.timestamp || Date.now()).toISOString().slice(0, 19).replace(/:/g, '-');
const phase = photo.phase || 'verification';
const filename = `verification-${phase}-${timestamp}.png`;
// Create download link
const link = document.createElement('a');
link.href = imageData;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Verification photo downloaded: ${filename}`, 'info');
}
console.log(`📥 Downloaded verification photo: ${filename}`);
}
// Download selected photos (zip if multiple)
async function downloadSelectedPhotos() {
const selectedCheckboxes = document.querySelectorAll('.photo-select:checked');
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
if (selectedCheckboxes.length === 0) {
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('⚠️ No photos selected for download', 'error');
}
return;
}
if (selectedCheckboxes.length === 1) {
// Single photo download
const checkbox = selectedCheckboxes[0];
const isVerification = checkbox.classList.contains('verification-photo-select');
if (isVerification) {
const verificationIndex = parseInt(checkbox.dataset.verificationIndex);
downloadSingleVerificationPhoto(verificationIndex);
} else {
const index = parseInt(checkbox.dataset.index);
downloadSinglePhoto(index);
}
return;
}
// Multiple photos - create zip
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('📦 Creating zip file...', 'info');
}
try {
// Create zip file (using JSZip if available, otherwise download individually)
if (typeof JSZip !== 'undefined') {
const zip = new JSZip();
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
selectedCheckboxes.forEach((checkbox, zipIndex) => {
const isVerification = checkbox.classList.contains('verification-photo-select');
let photo, imageData;
if (isVerification) {
// Handle verification photo
const verificationIndex = parseInt(checkbox.dataset.verificationIndex);
photo = verificationPhotos[verificationIndex];
if (!photo) {
console.warn(`Verification photo at index ${verificationIndex} not found. Available verification photos:`, verificationPhotos.length);
return; // Skip this photo
}
imageData = photo.data || photo.dataUrl; // Verification photos store in 'data'
} else {
// Handle regular photo
const index = parseInt(checkbox.dataset.index);
photo = capturedPhotos[index];
if (!photo) {
console.warn(`Regular photo at index ${index} not found. Available photos:`, capturedPhotos.length);
return; // Skip this photo
}
imageData = photo.dataURL || photo.data || photo.imageData; // Regular photos store in 'dataURL'
}
if (!imageData) {
console.warn('Photo missing image data:', photo);
return; // Skip this photo
}
const photoTimestamp = new Date(photo.timestamp || Date.now()).toISOString().slice(0, 19).replace(/:/g, '-');
// Convert base64 to blob
const base64Data = imageData.split(',')[1];
zip.file(`photo-${photoTimestamp}-${zipIndex + 1}.png`, base64Data, {base64: true});
});
const zipBlob = await zip.generateAsync({type: 'blob'});
const link = document.createElement('a');
link.href = URL.createObjectURL(zipBlob);
link.download = `photos-${timestamp}.zip`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Downloaded ${selectedCheckboxes.length} photos as zip file`, 'info');
}
} else {
// Fallback: download individually
selectedCheckboxes.forEach((checkbox, downloadIndex) => {
const isVerification = checkbox.classList.contains('verification-photo-select');
setTimeout(() => {
if (isVerification) {
const verificationIndex = parseInt(checkbox.dataset.verificationIndex);
downloadSingleVerificationPhoto(verificationIndex);
} else {
const index = parseInt(checkbox.dataset.index);
downloadSinglePhoto(index);
}
}, downloadIndex * 100); // Stagger downloads
});
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Downloading ${selectedCheckboxes.length} photos individually`, 'info');
}
}
} catch (error) {
console.error('Download error:', error);
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('❌ Error creating download', 'error');
}
}
}
// Delete selected photos
function deleteSelectedPhotos() {
const selectedCheckboxes = document.querySelectorAll('.photo-select:checked');
if (selectedCheckboxes.length === 0) {
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('⚠️ No photos selected for deletion', 'error');
}
return;
}
const confirmed = confirm(`Are you sure you want to delete ${selectedCheckboxes.length} selected photos?\n\nThis action cannot be undone.`);
if (confirmed) {
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
const indicesToDelete = Array.from(selectedCheckboxes).map(cb => parseInt(cb.dataset.index)).sort((a, b) => b - a);
// Delete in reverse order to maintain indices
indicesToDelete.forEach(index => {
if (index >= 0 && index < capturedPhotos.length) {
capturedPhotos.splice(index, 1);
}
});
// Update localStorage
localStorage.setItem('capturedPhotos', JSON.stringify(capturedPhotos));
// Show success message
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`🗑️ Successfully deleted ${indicesToDelete.length} photos!`, 'info');
}
// Refresh the photo galleries
setupLibraryGalleryTab();
console.log(`🗑️ Bulk deleted ${indicesToDelete.length} photos`);
}
}
// Captured Video Functions
function formatVideoDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}
// Captured video functions removed - videos now saved directly to user's selected directory
// Videos can be accessed from the file system at the location chosen in Quick Play setup
// Initialize bulk action event listeners
function initializeBulkActions() {
const selectAllBtn = document.getElementById('select-all-photos');
const deselectAllBtn = document.getElementById('deselect-all-photos');
const downloadSelectedBtn = document.getElementById('download-selected-photos');
const deleteSelectedBtn = document.getElementById('delete-selected-photos');
if (selectAllBtn) selectAllBtn.addEventListener('click', selectAllPhotos);
if (deselectAllBtn) deselectAllBtn.addEventListener('click', deselectAllPhotos);
if (downloadSelectedBtn) downloadSelectedBtn.addEventListener('click', downloadSelectedPhotos);
if (deleteSelectedBtn) deleteSelectedBtn.addEventListener('click', deleteSelectedPhotos);
}
// Verification Photo Functions
function showVerificationPhotoPreview(imageData, title, message, isStart, timestamp) {
const photoDate = timestamp ? new Date(timestamp).toLocaleString() : 'Unknown date';
const photoType = isStart ? '🟢 START Position' : '🔴 END Position';
// Create modal overlay
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
cursor: pointer;
`;
overlay.innerHTML = `
<div style="position: relative; max-width: 90%; max-height: 90%; padding: 20px; background: var(--bg-secondary); border-radius: 10px; border: 2px solid var(--color-error);">
<div style="text-align: center; margin-bottom: 15px;">
<h3 style="color: var(--color-error); margin: 0 0 10px 0;">${title}</h3>
<div style="display: flex; gap: 15px; justify-content: center; margin-bottom: 10px;">
<span style="color: var(--color-error); font-weight: bold;">${photoType}</span>
<span style="color: var(--text-muted); font-size: 0.9em;">${photoDate}</span>
</div>
<div style="background: var(--bg-primary-overlay-10); border: 1px solid var(--color-error); border-radius: 5px; padding: 10px; margin-bottom: 15px;">
<h4 style="color: var(--color-error); margin: 0 0 8px 0; font-size: 1.1em;">Degrading Message:</h4>
<p style="color: var(--color-error); font-style: italic; margin: 0; font-size: 1.1em;">"${message}"</p>
</div>
</div>
<img src="${imageData}" alt="${title}"
style="width: 100%; height: auto; max-width: 800px; max-height: 600px; object-fit: contain; border-radius: 5px;">
<button onclick="this.parentElement.parentElement.remove()"
style="position: absolute; top: 10px; right: 10px; background: var(--color-error); border: none; color: white;
width: 30px; height: 30px; border-radius: 50%; cursor: pointer; font-size: 18px;">✖</button>
</div>
`;
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.remove();
}
});
document.body.appendChild(overlay);
}
function downloadVerificationPhoto(index) {
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
if (index < 0 || index >= verificationPhotos.length) {
console.error('Invalid verification photo index:', index);
return;
}
const photo = verificationPhotos[index];
const timestamp = new Date(photo.timestamp || Date.now());
const dateStr = timestamp.toISOString().split('T')[0];
const timeStr = timestamp.toTimeString().split(' ')[0].replace(/:/g, '-');
const photoType = photo.phase === 'start' ? 'START' : 'END';
const imageData = photo.data || photo.dataUrl; // Support both formats
if (!imageData) {
console.error('No image data found for verification photo:', photo);
return;
}
// Create download link
const link = document.createElement('a');
link.href = imageData;
link.download = `verification-${photoType}-${dateStr}_${timeStr}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Downloaded verification photo: ${photoType}`, 'success');
}
}
function deleteVerificationPhoto(index) {
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
if (index < 0 || index >= verificationPhotos.length) {
console.error('Invalid verification photo index:', index);
return;
}
const photo = verificationPhotos[index];
const photoType = photo.phase === 'start' ? 'START' : 'END';
const confirmed = confirm(`Are you sure you want to delete this ${photoType} verification photo?\n\nThis action cannot be undone.`);
if (confirmed) {
// Remove the photo from the array
verificationPhotos.splice(index, 1);
// Update localStorage
localStorage.setItem('verificationPhotos', JSON.stringify(verificationPhotos));
// Refresh the gallery
setupLibraryGalleryTab();
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`🗑️ Deleted verification photo: ${photoType}`, 'info');
}
console.log(`🗑️ Deleted verification photo ${index} (${photoType})`);
}
}
// Show photo preview in modal
function showPhotoPreview(imageData, title) {
// Create modal overlay
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
cursor: pointer;
`;
// Create image element
const img = document.createElement('img');
img.src = imageData;
img.alt = title;
img.style.cssText = `
max-width: 90%;
max-height: 90%;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
`;
// Create title
const titleDiv = document.createElement('div');
titleDiv.textContent = title;
titleDiv.style.cssText = `
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 1.2em;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
`;
// Add elements to overlay
overlay.appendChild(img);
overlay.appendChild(titleDiv);
// Close on click
overlay.addEventListener('click', () => {
document.body.removeChild(overlay);
});
// Add to page
document.body.appendChild(overlay);
}
function refreshAllLibraryContent() {
console.log('Refreshing all library content...');
// Refresh based on currently active tab
const activeTab = document.querySelector('.library-tab.active');
if (activeTab) {
const tabType = activeTab.getAttribute('data-tab');
switch(tabType) {
case 'images':
setupLibraryImagesTab();
break;
case 'audio':
setupLibraryAudioTab();
break;
case 'video':
setupLibraryVideoTab();
break;
case 'gallery':
setupLibraryGalleryTab();
break;
}
}
console.log('Library content refreshed');
}
// Image Directory Management Functions
function handleAddImageDirectory() {
console.log('Adding new image directory...');
if (window.electronAPI && window.electronAPI.selectDirectory) {
// Use Electron's dialog to select a directory
console.log('📍 Calling electronAPI.selectDirectory...');
try {
const result = window.electronAPI.selectDirectory();
console.log('📍 selectDirectory returned:', result, typeof result);
// Handle both sync and async results
if (result && typeof result.then === 'function') {
// It's a promise
result.then((directoryResult) => {
console.log('Directory selection result (async):', directoryResult);
handleDirectoryResult(directoryResult);
}).catch(error => {
console.error('Error selecting image directory (async):', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to select directory', 'error');
}
});
} else {
// It's synchronous
console.log('Directory selection result (sync):', result);
handleDirectoryResult(result);
}
} catch (error) {
console.error('Error calling selectDirectory:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to open directory dialog', 'error');
}
}
} else {
console.log('❌ electronAPI.selectDirectory not available');
if (window.game && window.game.showNotification) {
window.game.showNotification('Directory linking is only available in desktop mode', 'warning');
}
}
}
function handleDirectoryResult(result) {
console.log('📍 Processing directory result:', result);
// Handle different possible result structures
let selectedPath = null;
if (result && !result.canceled) {
// Check for filePaths array (newer Electron API)
if (result.filePaths && result.filePaths.length > 0) {
selectedPath = result.filePaths[0];
}
// Check for filePath string (older API)
else if (result.filePath) {
selectedPath = result.filePath;
}
// Check if result is directly a path string
else if (typeof result === 'string') {
selectedPath = result;
}
// Check for paths array (alternative structure)
else if (result.paths && result.paths.length > 0) {
selectedPath = result.paths[0];
}
}
if (selectedPath) {
console.log('Selected image directory:', selectedPath);
// Add directory to linked directories
addImageDirectory(selectedPath);
} else {
console.log('No directory selected or selection was canceled');
}
}
function handleDirectoryResult(result) {
console.log('📍 Processing directory result:', result);
// Handle different possible result structures
let selectedPath = null;
if (result && !result.canceled) {
// Check for filePaths array (newer Electron API)
if (result.filePaths && result.filePaths.length > 0) {
selectedPath = result.filePaths[0];
}
// Check for filePath string (older API)
else if (result.filePath) {
selectedPath = result.filePath;
}
// Check if result is directly a path string
else if (typeof result === 'string') {
selectedPath = result;
}
// Check for paths array (alternative structure)
else if (result.paths && result.paths.length > 0) {
selectedPath = result.paths[0];
}
}
if (selectedPath) {
console.log('Selected image directory:', selectedPath);
// Add directory to linked directories
addImageDirectory(selectedPath);
} else {
console.log('No directory selected or selection was canceled');
}
}
function addImageDirectory(directoryPath) {
// Get existing linked directories
const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
// Check if directory is already linked
if (linkedDirs.some(dir => dir.path === directoryPath)) {
if (window.game && window.game.showNotification) {
window.game.showNotification('Directory already linked', 'warning');
}
return;
}
// Add new directory
const newDir = {
id: Date.now().toString(),
path: directoryPath,
name: directoryPath.split('\\').pop() || directoryPath.split('/').pop(),
addedAt: new Date().toISOString()
};
linkedDirs.push(newDir);
localStorage.setItem('linkedImageDirectories', JSON.stringify(linkedDirs));
console.log('Added image directory:', newDir);
// Refresh the display
updateImageDirectoriesList();
loadLinkedImages();
if (window.game && window.game.showNotification) {
window.game.showNotification(`✅ Added image directory: ${newDir.name}`, 'success');
}
}
// Individual Images Management Functions
function handleAddIndividualImages() {
console.log('Adding individual images...');
if (window.electronAPI && window.electronAPI.selectImages) {
// Use Electron's dialog to select multiple images
console.log('📍 Calling electronAPI.selectImages...');
try {
const result = window.electronAPI.selectImages();
console.log('📍 selectImages returned:', result, typeof result);
// Handle both sync and async results
if (result && typeof result.then === 'function') {
// It's a promise
result.then((imageResult) => {
console.log('Individual images selection result (async):', imageResult);
handleIndividualImagesResult(imageResult);
}).catch(error => {
console.error('Error selecting individual images (async):', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to select images', 'error');
}
});
} else {
// It's synchronous
console.log('Individual images selection result (sync):', result);
handleIndividualImagesResult(result);
}
} catch (error) {
console.error('Error calling selectImages:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to open image selection dialog', 'error');
}
}
} else {
console.log('❌ electronAPI.selectImages not available');
if (window.game && window.game.showNotification) {
window.game.showNotification('Individual image selection is only available in desktop mode', 'warning');
}
}
}
function handleIndividualImagesResult(result) {
console.log('📍 Processing individual images result:', result);
let selectedPaths = [];
if (result && !result.canceled) {
// Check for filePaths array (newer Electron API)
if (result.filePaths && result.filePaths.length > 0) {
selectedPaths = result.filePaths;
}
// Check for paths array (alternative structure)
else if (result.paths && result.paths.length > 0) {
selectedPaths = result.paths;
}
// Check if result is directly an array of paths
else if (Array.isArray(result)) {
selectedPaths = result;
}
}
if (selectedPaths.length > 0) {
console.log(`Selected ${selectedPaths.length} individual images:`, selectedPaths);
// Add individual images to collection
addIndividualImages(selectedPaths);
} else {
console.log('No images selected or selection was canceled');
}
}
function addIndividualImages(imagePaths) {
// Get existing individual images
const individualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
let addedCount = 0;
imagePaths.forEach(imagePath => {
// Check if image is already linked
if (!individualImages.some(img => img.path === imagePath)) {
const newImage = {
id: Date.now().toString() + '_' + Math.random().toString(36).substr(2, 9),
path: imagePath,
name: imagePath.split('\\').pop() || imagePath.split('/').pop(),
addedAt: new Date().toISOString(),
type: 'individual'
};
individualImages.push(newImage);
addedCount++;
console.log('Added individual image:', newImage);
}
});
if (addedCount > 0) {
localStorage.setItem('linkedIndividualImages', JSON.stringify(individualImages));
// Refresh the display
updateImageDirectoriesList();
loadLinkedImages();
if (window.game && window.game.showNotification) {
window.game.showNotification(`✅ Added ${addedCount} individual image(s)`, 'success');
}
} else {
if (window.game && window.game.showNotification) {
window.game.showNotification('All selected images were already linked', 'info');
}
}
}
function handleRefreshImageDirectories() {
console.log('Refreshing image directories...');
if (window.game && window.game.showNotification) {
window.game.showNotification('🔄 Refreshing image directories...', 'info');
}
// Reload images from all linked directories
loadLinkedImages();
if (window.game && window.game.showNotification) {
window.game.showNotification('✅ Image directories refreshed!', 'success');
}
}
function handleClearImageDirectories() {
if (!confirm('Are you sure you want to unlink all image directories and individual images? This will not delete your actual image files.')) {
return;
}
console.log('Clearing all image directories and individual images...');
// Clear from localStorage
localStorage.removeItem('linkedImageDirectories');
localStorage.removeItem('linkedIndividualImages');
// Update display
updateImageDirectoriesList();
loadLinkedImages();
if (window.game && window.game.showNotification) {
window.game.showNotification('🗑️ All image directories and individual images unlinked', 'info');
}
}
function removeImageDirectory(directoryId) {
const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
const updatedDirs = linkedDirs.filter(dir => dir.id !== directoryId);
localStorage.setItem('linkedImageDirectories', JSON.stringify(updatedDirs));
// Update display
updateImageDirectoriesList();
loadLinkedImages();
if (window.game && window.game.showNotification) {
window.game.showNotification('🗑️ Image directory unlinked', 'info');
}
}
function updateImageDirectoriesList() {
const listContainer = document.getElementById('linked-image-directories-list');
if (!listContainer) return;
const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
if (linkedDirs.length === 0) {
listContainer.innerHTML = '<div class="no-directories">No image directories linked yet</div>';
return;
}
listContainer.innerHTML = linkedDirs.map(dir => `
<div class="directory-item">
<div class="directory-info">
<div class="directory-name">${dir.name}</div>
<div class="directory-path">${dir.path}</div>
</div>
<button class="btn btn-small btn-danger" onclick="removeImageDirectory('${dir.id}')">
🗑️ Remove
</button>
</div>
`).join('');
}
async function loadLinkedImages() {
const linkedDirs = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
let allImages = [];
console.log(`📁 Loading images from ${linkedDirs.length} linked directories...`);
console.log('📁 Linked directories:', linkedDirs.map(d => d.path));
if (window.electronAPI && linkedDirs.length > 0) {
// Scan each linked directory for images
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp)$/i;
for (const dir of linkedDirs) {
console.log(`📁 Scanning directory: ${dir.path} (${dir.name})`);
try {
if (window.electronAPI.readDirectory) {
const filesPromise = window.electronAPI.readDirectory(dir.path);
console.log(`📁 ReadDirectory result for ${dir.path}:`, filesPromise);
// Handle async result
let files = [];
if (filesPromise && typeof filesPromise.then === 'function') {
files = await filesPromise;
console.log(`📁 Resolved files in ${dir.path}:`, files.length, 'files');
} else if (Array.isArray(filesPromise)) {
files = filesPromise;
console.log(`📁 Sync files in ${dir.path}:`, files.length, 'files');
}
if (files && files.length > 0) {
// Filter for image files
const imageFiles = files.filter(file => {
const fileName = typeof file === 'object' ? file.name : file;
return imageExtensions.test(fileName);
});
console.log(`📁 Found ${imageFiles.length} image files in ${dir.path}`);
if (imageFiles.length > 0) {
const dirImages = imageFiles.map(file => {
if (typeof file === 'object' && file.name && file.path) {
// Already has name and path
return {
path: file.path,
name: file.name,
directory: dir.name,
directoryId: dir.id
};
} else {
// Just a filename string
const fileName = typeof file === 'object' ? file.name : file;
const fullPath = window.electronAPI.pathJoin ? window.electronAPI.pathJoin(dir.path, fileName) : `${dir.path}\\${fileName}`;
return {
path: fullPath,
name: fileName,
directory: dir.name,
directoryId: dir.id
};
}
});
allImages = allImages.concat(dirImages);
console.log(`📸 Added ${dirImages.length} images from ${dir.name} to gallery`);
}
} else {
console.log(`📁 No files found in ${dir.path}`);
}
} else {
console.log('📁 electronAPI.readDirectory not available');
}
} catch (error) {
console.error(`📁 Error scanning directory ${dir.path}:`, error);
}
}
} else if (linkedDirs.length === 0) {
console.log('📁 No linked directories found in localStorage');
} else {
console.log('📁 electronAPI not available for directory scanning');
}
// Load individual images
const individualImages = JSON.parse(localStorage.getItem('linkedIndividualImages') || '[]');
if (individualImages.length > 0) {
console.log(`📸 Loading ${individualImages.length} individual images...`);
individualImages.forEach(image => {
// Verify the image still exists and add to gallery
allImages.push({
path: image.path,
name: image.name,
directory: 'Individual Images',
directoryId: 'individual',
type: 'individual'
});
});
console.log(`📸 Added ${individualImages.length} individual images to gallery`);
} else {
console.log('📸 No individual images found in localStorage');
}
// Load verification photos from localStorage
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
if (verificationPhotos.length > 0) {
console.log(`📷 Loading ${verificationPhotos.length} verification photos...`);
verificationPhotos.forEach((photo, index) => {
// Add verification photo to gallery with special metadata
const imageData = photo.data || photo.dataUrl; // Support both formats
if (imageData) {
allImages.push({
path: imageData,
name: `Verification Photo ${index + 1}`,
directory: 'Verification Photos',
directoryId: 'verification',
type: 'verification',
timestamp: photo.timestamp,
message: photo.message,
isStart: photo.phase === 'start'
});
}
});
console.log(`📷 Added ${verificationPhotos.length} verification photos to gallery`);
} else {
console.log('📷 No verification photos found in localStorage');
}
console.log(`📸 Total images found: ${allImages.length}`);
// Update the image count display
const imageCountElement = document.getElementById('lib-image-count');
if (imageCountElement) {
imageCountElement.textContent = `${allImages.length} images`;
}
// Update the directories count display
const dirCountElement = document.getElementById('lib-directories-count');
if (dirCountElement) {
dirCountElement.textContent = `${linkedDirs.length} directories linked`;
}
// Store all images globally for filtering
window.allLinkedImages = allImages;
// Apply current filter and populate the gallery
const categoryFilter = document.getElementById('lib-image-category-filter');
const selectedCategory = categoryFilter ? categoryFilter.value : 'all';
const filteredImages = filterImagesByCategory(allImages, selectedCategory);
populateImageGallery(filteredImages);
// Update count display
const countElement = document.getElementById('lib-image-count');
if (countElement) {
countElement.textContent = `${filteredImages.length} images`;
}
}
function filterImagesByCategory(images, category) {
if (category === 'all') {
return images;
}
// File type filtering
if (category === 'jpg' || category === 'png' || category === 'gif') {
return images.filter(image => {
const extension = image.path.toLowerCase().split('.').pop();
if (category === 'jpg') {
return extension === 'jpg' || extension === 'jpeg';
}
return extension === category;
});
}
// Category filtering based on directory names
return images.filter(image => {
const dirName = image.directory.toLowerCase();
switch (category) {
case 'tasks':
return dirName.includes('task');
case 'consequences':
return dirName.includes('consequence');
case 'rewards':
return dirName.includes('reward');
case 'verification':
return image.type === 'verification';
default:
return true;
}
});
}
function populateImageGallery(images) {
const imageGallery = document.getElementById('lib-image-gallery');
if (!imageGallery) {
console.error('❌ lib-image-gallery element not found!');
return;
}
console.log(`📸 Populating gallery with ${images.length} images`);
console.log('📸 Gallery element:', imageGallery);
if (images.length === 0) {
imageGallery.innerHTML = `
<div class="no-images-message">
<p>No images found in linked directories</p>
<p>Click "Add Directory" to link a folder containing images</p>
</div>
`;
return;
}
// Clear existing content
imageGallery.innerHTML = '';
console.log('📸 Cleared existing gallery content');
// Create image grid
images.forEach((image, index) => {
const imgElement = document.createElement('div');
imgElement.className = 'gallery-item image-item';
// Special formatting for verification photos
if (image.type === 'verification') {
const photoDate = image.timestamp ? new Date(image.timestamp).toLocaleString() : 'Unknown';
const photoType = image.isStart ? '🟢 START' : '🔴 END';
const degradingMessage = image.message || 'Position verification photo';
imgElement.innerHTML = `
<img src="${image.path}" alt="${image.name}" loading="lazy" />
<div class="image-info verification-photo-info">
<div class="image-name">${image.name}</div>
<div class="verification-type">${photoType} Photo</div>
<div class="verification-message">"${degradingMessage}"</div>
<div class="verification-timestamp">${photoDate}</div>
<div class="image-directory">${image.directory}</div>
</div>
`;
} else {
// Standard formatting for regular images
imgElement.innerHTML = `
<img src="${image.path}" alt="${image.name}" loading="lazy" />
<div class="image-info">
<div class="image-name">${image.name}</div>
<div class="image-directory">${image.directory}</div>
</div>
`;
}
// Add click handler for image preview
imgElement.addEventListener('click', function() {
if (image.type === 'verification') {
// Enhanced preview for verification photos with message
previewVerificationImage(image.path, image.name, image.message, image.isStart, image.timestamp);
} else {
previewImage(image.path, image.name);
}
});
imageGallery.appendChild(imgElement);
if (index < 5) {
console.log(`📸 Added image ${index + 1}: ${image.name}`);
}
});
console.log(`✅ Created ${images.length} linked image gallery items`);
console.log('📸 Gallery innerHTML length:', imageGallery.innerHTML.length);
}
function previewImage(imageUrl, imageName) {
// Create or show image preview modal
let modal = document.getElementById('image-preview-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'image-preview-modal';
modal.className = 'image-preview-modal';
modal.innerHTML = `
<div class="modal-backdrop" onclick="closeImagePreview()"></div>
<div class="modal-content">
<div class="modal-header">
<span id="preview-image-name">${imageName}</span>
<button class="close-btn" onclick="closeImagePreview()">✖️</button>
</div>
<div class="modal-body">
<img id="preview-image" src="${imageUrl}" alt="${imageName}" />
</div>
</div>
`;
document.body.appendChild(modal);
} else {
document.getElementById('preview-image-name').textContent = imageName;
document.getElementById('preview-image').src = imageUrl;
document.getElementById('preview-image').alt = imageName;
}
modal.style.display = 'flex';
document.body.style.overflow = 'hidden'; // Prevent background scrolling
}
function previewVerificationImage(imageUrl, imageName, message, isStart, timestamp) {
// Create or show enhanced verification image preview modal
let modal = document.getElementById('image-preview-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'image-preview-modal';
modal.className = 'image-preview-modal';
document.body.appendChild(modal);
}
const photoDate = timestamp ? new Date(timestamp).toLocaleString() : 'Unknown date';
const photoType = isStart ? '🟢 START Position' : '🔴 END Position';
const degradingMessage = message || 'Position verification photo';
modal.innerHTML = `
<div class="modal-backdrop" onclick="closeImagePreview()"></div>
<div class="modal-content verification-modal">
<div class="modal-header">
<div class="verification-header">
<span id="preview-image-name">${imageName}</span>
<div class="verification-details">
<span class="verification-type">${photoType}</span>
<span class="verification-timestamp">${photoDate}</span>
</div>
</div>
<button class="close-btn" onclick="closeImagePreview()">✖️</button>
</div>
<div class="modal-body">
<img id="preview-image" src="${imageUrl}" alt="${imageName}" />
<div class="verification-message-display">
<h4>Degrading Message:</h4>
<p>"${degradingMessage}"</p>
</div>
</div>
</div>
`;
modal.style.display = 'flex';
document.body.style.overflow = 'hidden'; // Prevent background scrolling
}
function closeImagePreview() {
const modal = document.getElementById('image-preview-modal');
if (modal) {
modal.style.display = 'none';
document.body.style.overflow = ''; // Restore scrolling
}
}
// Global functions for HTML onclick handlers
window.removeImageDirectory = removeImageDirectory;
window.closeImagePreview = closeImagePreview;
// Video Directory Management Functions
function handleAddVideoDirectory() {
console.log('Adding new video directory...');
if (window.electronAPI && window.electronAPI.selectDirectory) {
console.log('📍 Calling electronAPI.selectDirectory...');
try {
const result = window.electronAPI.selectDirectory();
console.log('📍 selectDirectory returned:', result, typeof result);
if (result && typeof result.then === 'function') {
result.then((directoryResult) => {
console.log('Video directory selection result (async):', directoryResult);
handleVideoDirectoryResult(directoryResult);
}).catch(error => {
console.error('Error selecting video directory (async):', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to select directory', 'error');
}
});
} else {
console.log('Video directory selection result (sync):', result);
handleVideoDirectoryResult(result);
}
} catch (error) {
console.error('Error calling selectDirectory:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to open directory selection dialog', 'error');
}
}
} else {
console.log('❌ electronAPI.selectDirectory not available');
if (window.game && window.game.showNotification) {
window.game.showNotification('Directory selection is only available in desktop mode', 'warning');
}
}
}
function handleVideoDirectoryResult(result) {
console.log('📍 Processing video directory result:', result);
let selectedPath = null;
if (result && !result.canceled) {
if (result.filePaths && result.filePaths.length > 0) {
selectedPath = result.filePaths[0];
} else if (typeof result === 'string') {
selectedPath = result;
} else if (result.path) {
selectedPath = result.path;
}
if (selectedPath) {
console.log('Selected video directory:', selectedPath);
addVideoDirectory(selectedPath);
} else {
console.log('No valid path found in result:', result);
}
} else {
console.log('Video directory selection was canceled');
}
}
function addVideoDirectory(directoryPath) {
let linkedDirs;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
} catch (e) {
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
linkedDirs = [];
}
if (linkedDirs.some(dir => dir.path === directoryPath)) {
if (window.game && window.game.showNotification) {
window.game.showNotification('Directory is already linked', 'info');
}
return;
}
const newDirectory = {
id: Date.now().toString() + '_' + Math.random().toString(36).substr(2, 9),
path: directoryPath,
name: directoryPath.split('\\').pop() || directoryPath.split('/').pop(),
addedAt: new Date().toISOString()
};
linkedDirs.push(newDirectory);
localStorage.setItem('linkedVideoDirectories', JSON.stringify(linkedDirs));
updateVideoDirectoriesList();
loadLinkedVideos();
if (window.game && window.game.showNotification) {
window.game.showNotification(`✅ Added video directory: ${newDirectory.name}`, 'success');
}
}
function handleAddIndividualVideos() {
console.log('Adding individual videos...');
if (window.electronAPI && window.electronAPI.selectVideos) {
console.log('📍 Calling electronAPI.selectVideos...');
try {
const result = window.electronAPI.selectVideos();
console.log('📍 selectVideos returned:', result, typeof result);
if (result && typeof result.then === 'function') {
result.then((videoResult) => {
console.log('Individual videos selection result (async):', videoResult);
handleIndividualVideosResult(videoResult);
}).catch(error => {
console.error('Error selecting individual videos (async):', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to select videos', 'error');
}
});
} else {
console.log('Individual videos selection result (sync):', result);
handleIndividualVideosResult(result);
}
} catch (error) {
console.error('Error calling selectVideos:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to open video selection dialog', 'error');
}
}
} else {
console.log('❌ electronAPI.selectVideos not available');
if (window.game && window.game.showNotification) {
window.game.showNotification('Individual video selection is only available in desktop mode', 'warning');
}
}
}
function handleIndividualVideosResult(result) {
console.log('📍 Processing individual videos result:', result);
let selectedPaths = [];
if (result && !result.canceled) {
if (result.filePaths && result.filePaths.length > 0) {
selectedPaths = result.filePaths;
} else if (Array.isArray(result)) {
selectedPaths = result;
} else if (typeof result === 'string') {
selectedPaths = [result];
}
console.log(`Selected ${selectedPaths.length} individual videos:`, selectedPaths);
addIndividualVideos(selectedPaths);
} else {
console.log('No videos selected or selection was canceled');
}
}
function addIndividualVideos(videoPaths) {
let individualVideos;
try {
individualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
if (!Array.isArray(individualVideos)) {
individualVideos = [];
}
} catch (e) {
console.log('Error parsing linkedIndividualVideos, resetting to empty array:', e);
individualVideos = [];
}
let addedCount = 0;
videoPaths.forEach(videoPath => {
if (!individualVideos.some(vid => vid.path === videoPath)) {
const newVideo = {
id: Date.now().toString() + '_' + Math.random().toString(36).substr(2, 9),
path: videoPath,
name: videoPath.split('\\').pop() || videoPath.split('/').pop(),
addedAt: new Date().toISOString(),
type: 'individual'
};
individualVideos.push(newVideo);
addedCount++;
console.log('Added individual video:', newVideo);
}
});
if (addedCount > 0) {
localStorage.setItem('linkedIndividualVideos', JSON.stringify(individualVideos));
updateVideoDirectoriesList();
loadLinkedVideos();
if (window.game && window.game.showNotification) {
window.game.showNotification(`✅ Added ${addedCount} individual video(s)`, 'success');
}
} else {
if (window.game && window.game.showNotification) {
window.game.showNotification('All selected videos were already linked', 'info');
}
}
}
function handleRefreshVideoDirectories() {
console.log('Refreshing video directories...');
if (window.game && window.game.showNotification) {
window.game.showNotification('🔄 Refreshing video directories...', 'info');
}
loadLinkedVideos();
if (window.game && window.game.showNotification) {
window.game.showNotification('✅ Video directories refreshed!', 'success');
}
}
function handleClearVideoDirectories() {
if (!confirm('Are you sure you want to unlink all video directories and individual videos? This will not delete your actual video files.')) {
return;
}
console.log('Clearing all video directories and individual videos...');
localStorage.removeItem('linkedVideoDirectories');
localStorage.removeItem('linkedIndividualVideos');
updateVideoDirectoriesList();
loadLinkedVideos();
if (window.game && window.game.showNotification) {
window.game.showNotification('🗑️ All video directories and individual videos unlinked', 'info');
}
}
function removeVideoDirectory(directoryId) {
let linkedDirs;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
} catch (e) {
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
linkedDirs = [];
}
const updatedDirs = linkedDirs.filter(dir => dir.id !== directoryId);
localStorage.setItem('linkedVideoDirectories', JSON.stringify(updatedDirs));
updateVideoDirectoriesList();
loadLinkedVideos();
if (window.game && window.game.showNotification) {
window.game.showNotification('🗑️ Video directory unlinked', 'info');
}
}
function updateVideoDirectoriesList() {
const listContainer = document.getElementById('linked-video-directories-list');
if (!listContainer) return;
let linkedDirs;
let individualVideos;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
individualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
if (!Array.isArray(individualVideos)) {
individualVideos = [];
}
} catch (e) {
console.log('Error parsing video directories, resetting to empty arrays:', e);
linkedDirs = [];
individualVideos = [];
}
const dirCountElement = document.getElementById('lib-video-directories-count');
if (dirCountElement) {
dirCountElement.textContent = `${linkedDirs.length} directories linked`;
}
if (linkedDirs.length === 0 && individualVideos.length === 0) {
listContainer.innerHTML = '<div class="no-directories">No video directories linked yet</div>';
return;
}
let html = '';
linkedDirs.forEach(dir => {
html += `
<div class="directory-item">
<div class="directory-info">
<div class="directory-name">📁 ${dir.name}</div>
<div class="directory-path">${dir.path}</div>
<div class="directory-meta">Added ${new Date(dir.addedAt).toLocaleDateString()}</div>
</div>
<button class="remove-directory-btn" onclick="removeVideoDirectory('${dir.id}')">🗑️</button>
</div>
`;
});
if (individualVideos.length > 0) {
html += `
<div class="directory-item individual-videos">
<div class="directory-info">
<div class="directory-name">🎬 Individual Videos</div>
<div class="directory-path">${individualVideos.length} video(s) selected individually</div>
<div class="directory-meta">Click "Add Individual Videos" to add more</div>
</div>
</div>
`;
}
listContainer.innerHTML = html;
}
async function loadLinkedVideos() {
console.log('🎬 Loading linked videos...');
let linkedDirs;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
} catch (e) {
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
linkedDirs = [];
}
const allVideos = [];
if (window.electronAPI && window.electronAPI.readDirectory) {
const videoExtensions = /\.(mp4|webm|avi|mov|mkv|wmv|flv|m4v)$/i;
for (const dir of linkedDirs) {
try {
console.log(`🎬 Scanning directory: ${dir.path}`);
console.log(`🎬 electronAPI available:`, !!window.electronAPI);
console.log(`🎬 readDirectory available:`, !!window.electronAPI.readDirectory);
if (!window.electronAPI.readDirectory) {
console.error(`❌ readDirectory function not available`);
continue;
}
// Try video-specific directory reading first
let filesPromise;
if (window.electronAPI.readVideoDirectory) {
console.log(`🎬 Using readVideoDirectory for ${dir.path}`);
filesPromise = window.electronAPI.readVideoDirectory(dir.path);
} else if (window.electronAPI.readVideoDirectoryRecursive) {
console.log(`🎬 Using readVideoDirectoryRecursive for ${dir.path}`);
filesPromise = window.electronAPI.readVideoDirectoryRecursive(dir.path);
} else {
console.log(`🎬 Using generic readDirectory for ${dir.path}`);
filesPromise = window.electronAPI.readDirectory(dir.path);
}
console.log(`🎬 ReadDirectory result for ${dir.path}:`, filesPromise);
console.log(`🎬 Result type:`, typeof filesPromise);
console.log(`🎬 Is promise:`, filesPromise && typeof filesPromise.then === 'function');
console.log(`🎬 Is array:`, Array.isArray(filesPromise));
// Handle async result
let files = [];
if (filesPromise && typeof filesPromise.then === 'function') {
try {
files = await filesPromise;
console.log(`🎬 Resolved files in ${dir.path}:`, files);
console.log(`🎬 Files length:`, files ? files.length : 'null/undefined');
console.log(`🎬 Files type:`, typeof files);
} catch (promiseError) {
console.error(`❌ Error resolving promise for ${dir.path}:`, promiseError);
continue;
}
} else if (Array.isArray(filesPromise)) {
files = filesPromise;
console.log(`🎬 Sync files in ${dir.path}:`, files.length, 'files');
} else {
console.error(`❌ Unexpected readDirectory result type for ${dir.path}:`, typeof filesPromise, filesPromise);
continue;
}
// If we got an empty array, try alternative methods
if (!files || files.length === 0) {
console.log(`🎬 Empty result from readDirectory, trying alternative approaches...`);
// Try with different path formats
const normalizedPath = dir.path.replace(/\//g, '\\');
console.log(`🎬 Trying normalized path: ${normalizedPath}`);
if (normalizedPath !== dir.path) {
try {
const altResult = await window.electronAPI.readDirectory(normalizedPath);
console.log(`🎬 Alternative path result:`, altResult);
if (altResult && altResult.length > 0) {
files = altResult;
console.log(`🎬 Success with normalized path! Found ${files.length} files`);
}
} catch (altError) {
console.log(`🎬 Alternative path also failed:`, altError);
}
}
// If still empty, check if there are other electron APIs we can use
if (!files || files.length === 0) {
console.log(`🎬 Available electronAPI methods:`, Object.keys(window.electronAPI));
// Try to list files using different methods if available
if (window.electronAPI.readVideoDirectory) {
try {
const videoResult = await window.electronAPI.readVideoDirectory(dir.path);
console.log(`🎬 readVideoDirectory result:`, videoResult);
if (videoResult && videoResult.length > 0) {
files = videoResult;
console.log(`🎬 Success with readVideoDirectory! Found ${files.length} files`);
}
} catch (videoError) {
console.log(`🎬 readVideoDirectory also failed:`, videoError);
}
}
if ((!files || files.length === 0) && window.electronAPI.readVideoDirectoryRecursive) {
try {
const recursiveResult = await window.electronAPI.readVideoDirectoryRecursive(dir.path);
console.log(`🎬 readVideoDirectoryRecursive result:`, recursiveResult);
if (recursiveResult && recursiveResult.length > 0) {
files = recursiveResult;
console.log(`🎬 Success with readVideoDirectoryRecursive! Found ${files.length} files`);
}
} catch (recursiveError) {
console.log(`🎬 readVideoDirectoryRecursive also failed:`, recursiveError);
}
}
if (window.electronAPI.listFiles) {
try {
const listResult = await window.electronAPI.listFiles(dir.path);
console.log(`🎬 listFiles result:`, listResult);
if (listResult && listResult.length > 0) {
files = listResult;
console.log(`🎬 Success with listFiles! Found ${files.length} files`);
}
} catch (listError) {
console.log(`🎬 listFiles also failed:`, listError);
}
}
if (window.electronAPI.scanDirectory) {
try {
const scanResult = await window.electronAPI.scanDirectory(dir.path);
console.log(`🎬 scanDirectory result:`, scanResult);
if (scanResult && scanResult.length > 0) {
files = scanResult;
console.log(`🎬 Success with scanDirectory! Found ${files.length} files`);
}
} catch (scanError) {
console.log(`🎬 scanDirectory also failed:`, scanError);
}
}
}
}
if (files && files.length > 0) {
console.log(`📍 Sample files:`, files.slice(0, 5));
// Filter for video files
const videoFiles = files.filter(file => {
const fileName = typeof file === 'object' ? file.name : file;
return videoExtensions.test(fileName);
});
console.log(`🎬 Found ${videoFiles.length} video files in ${dir.name}`);
console.log(`📍 Sample video files:`, videoFiles.slice(0, 5));
if (videoFiles.length > 0) {
const dirVideos = videoFiles.map(file => {
if (typeof file === 'object' && file.name && file.path) {
// Already has name and path
return {
path: file.path,
name: file.name,
directory: dir.name,
directoryId: dir.id,
type: 'directory'
};
} else {
// Just a filename string
const fileName = typeof file === 'object' ? file.name : file;
const fullPath = window.electronAPI.pathJoin ? window.electronAPI.pathJoin(dir.path, fileName) : `${dir.path}\\${fileName}`;
return {
path: fullPath,
name: fileName,
directory: dir.name,
directoryId: dir.id,
type: 'directory'
};
}
});
allVideos.push(...dirVideos);
console.log(`🎬 Added ${dirVideos.length} videos from ${dir.name}`);
}
} else {
console.log(`📍 No files found in ${dir.path}`);
}
} catch (error) {
console.error(`Error reading video directory ${dir.path}:`, error);
}
}
}
let individualVideos;
try {
individualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
if (!Array.isArray(individualVideos)) {
individualVideos = [];
}
} catch (e) {
console.log('Error parsing linkedIndividualVideos, resetting to empty array:', e);
individualVideos = [];
}
if (individualVideos.length > 0) {
console.log(`🎬 Loading ${individualVideos.length} individual videos...`);
individualVideos.forEach(video => {
allVideos.push({
path: video.path,
name: video.name,
directory: 'Individual Videos',
directoryId: 'individual',
type: 'individual'
});
});
console.log(`🎬 Added ${individualVideos.length} individual videos to gallery`);
}
console.log(`🎬 Total videos found: ${allVideos.length}`);
// Include captured videos in count
const capturedVideos = JSON.parse(localStorage.getItem('capturedVideos') || '[]');
const totalVideos = allVideos.length + capturedVideos.length;
const videoCountElement = document.getElementById('lib-video-count');
if (videoCountElement) {
videoCountElement.textContent = `${totalVideos} videos`;
console.log(`📊 Updated video count: ${totalVideos} (${allVideos.length} linked + ${capturedVideos.length} captured)`);
}
await populateVideoGallery(allVideos);
}
async function loadCapturedVideosFromDirectory(videoGallery, existingVideoCount) {
console.log('📹 Loading captured videos from directory...');
try {
// Get the selected recording directory
const savedDirectory = localStorage.getItem('webcamRecordingDirectory');
if (!savedDirectory) {
console.log('📹 No recording directory set, skipping captured videos');
return 0;
}
// Use Electron's IPC to read the video directory
if (window.electronAPI && window.electronAPI.readVideoDirectory) {
const videoFiles = await window.electronAPI.readVideoDirectory(savedDirectory);
const capturedVideoFiles = videoFiles.filter(file =>
file.name.startsWith('quick-play-session-') &&
(file.name.endsWith('.mp4') || file.name.endsWith('.webm'))
);
console.log(`📹 Found ${capturedVideoFiles.length} captured session videos`);
capturedVideoFiles.forEach((video, index) => {
const videoElement = document.createElement('div');
videoElement.className = 'gallery-item captured-video-item';
// Extract timestamp from filename
const timestampMatch = video.name.match(/quick-play-session-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})/);
const timestamp = timestampMatch ? timestampMatch[1].replace(/-/g, ':').replace('T', ' ') : 'Unknown';
videoElement.innerHTML = `
<div class="video-thumbnail-container">
<video class="video-thumbnail" preload="metadata" muted crossorigin="anonymous">
<source src="${video.url || video.path}#t=2" type="${video.type || 'video/mp4'}">
</video>
<div class="video-overlay">
<div class="play-icon">▶️</div>
</div>
<div class="video-fallback" style="display: none;">
<div class="video-icon">🎬</div>
</div>
</div>
<div class="video-info">
<div class="video-name">Session Recording ${index + 1}</div>
<div class="video-directory">Captured • ${timestamp}</div>
</div>
`;
// Handle video thumbnail loading
const videoThumb = videoElement.querySelector('.video-thumbnail');
const fallback = videoElement.querySelector('.video-fallback');
videoThumb.addEventListener('loadedmetadata', function() {
console.log(`✅ Captured video thumbnail loaded: Recording ${index + 1}`);
this.currentTime = 2;
});
videoThumb.addEventListener('error', function(e) {
console.log(`❌ Captured video thumbnail failed: Recording ${index + 1}`, e);
videoThumb.style.display = 'none';
fallback.style.display = 'flex';
});
videoElement.addEventListener('click', function() {
previewVideo(video.url || video.path, `Session Recording ${index + 1}`);
});
videoGallery.appendChild(videoElement);
});
return capturedVideoFiles.length;
} else {
console.log('📹 Electron API not available, showing directory info instead');
// Fallback: Show directory info
const infoElement = document.createElement('div');
infoElement.className = 'gallery-item info-item';
infoElement.innerHTML = `
<div class="video-thumbnail-container">
<div class="video-fallback" style="display: flex; width: 100%; height: 150px; background: var(--bg-secondary); align-items: center; justify-content: center;">
<div class="video-icon" style="font-size: 2em;">📁</div>
</div>
</div>
<div class="video-info">
<div class="video-name">Session Recordings</div>
<div class="video-directory">Saved to: ${savedDirectory}</div>
</div>
<div class="video-actions" style="text-align: center; padding: 10px;">
<small style="color: var(--text-dim);">Recorded videos are saved to your selected directory.</small>
</div>
`;
videoGallery.appendChild(infoElement);
return 1; // Return 1 for the info element
}
} catch (error) {
console.error('Error loading captured videos from directory:', error);
return 0;
}
}
async function populateVideoGallery(videos) {
const videoGallery = document.getElementById('lib-video-gallery');
if (!videoGallery) {
console.error('❌ lib-video-gallery element not found!');
return;
}
console.log(`🎬 Populating gallery with ${videos.length} videos`);
console.log(`🎬 Gallery element:`, videoGallery);
console.log(`🎬 Gallery innerHTML before clear:`, videoGallery.innerHTML.substring(0, 200));
if (videos.length === 0) {
videoGallery.innerHTML = `
<div class="no-videos-message">
<p>No videos found in linked directories</p>
<p>Click "Add Directory" to link a folder containing videos</p>
</div>
`;
return;
}
videoGallery.innerHTML = '';
console.log(`🎬 Gallery cleared, innerHTML after clear:`, videoGallery.innerHTML);
console.log(`🎬 Gallery element classes:`, videoGallery.className);
console.log(`🎬 Gallery element style:`, videoGallery.style.cssText);
videos.forEach((video, index) => {
const videoElement = document.createElement('div');
videoElement.className = 'gallery-item video-item';
videoElement.innerHTML = `
<div class="video-thumbnail-container">
<video class="video-thumbnail" preload="metadata" muted crossorigin="anonymous">
<source src="${video.path}#t=2" type="video/mp4">
</video>
<div class="video-overlay">
<div class="play-icon">▶️</div>
</div>
<div class="video-fallback" style="display: none;">
<div class="video-icon">🎬</div>
</div>
</div>
<div class="video-info">
<div class="video-name">${video.name}</div>
<div class="video-directory">${video.directory}</div>
</div>
`;
// Handle video thumbnail loading
const videoThumb = videoElement.querySelector('.video-thumbnail');
const fallback = videoElement.querySelector('.video-fallback');
videoThumb.addEventListener('loadedmetadata', function() {
console.log(`✅ Video thumbnail loaded for: ${video.name}`);
this.currentTime = 2; // Seek to 2 seconds
});
videoThumb.addEventListener('seeked', function() {
console.log(`📷 Video thumbnail ready for: ${video.name}`);
});
videoThumb.addEventListener('error', function(e) {
console.log(`❌ Video thumbnail failed for: ${video.name}`, e);
// Show fallback icon if video fails to load
videoThumb.style.display = 'none';
fallback.style.display = 'flex';
});
videoElement.addEventListener('click', function() {
previewVideo(video.path, video.name);
});
videoGallery.appendChild(videoElement);
if (index < 5) {
console.log(`🎬 Added video ${index + 1}: ${video.name}`);
console.log(`🎬 Video element HTML:`, videoElement.outerHTML.substring(0, 200));
}
});
console.log(`✅ Created ${videos.length} linked video gallery items`);
// Add captured session videos from directory
const capturedVideosCount = await loadCapturedVideosFromDirectory(videoGallery, videos.length);
const totalVideos = videos.length + capturedVideosCount;
console.log(`✅ Total videos in gallery: ${totalVideos} (${videos.length} linked + ${capturedVideos.length} captured)`);
console.log(`🎬 Final gallery innerHTML length:`, videoGallery.innerHTML.length);
console.log(`🎬 Gallery children count:`, videoGallery.children.length);
console.log(`🎬 Gallery computed style display:`, window.getComputedStyle(videoGallery).display);
// Add a watcher to see if the gallery gets cleared
setTimeout(() => {
console.log(`🎬 AFTER 5 seconds - Gallery children count:`, videoGallery.children.length);
console.log(`🎬 AFTER 5 seconds - Gallery innerHTML length:`, videoGallery.innerHTML.length);
if (videoGallery.children.length === 0) {
console.log(`❌ GALLERY WAS CLEARED! Something removed all video elements.`);
} else {
console.log(`✅ Gallery still has content after 5 seconds`);
}
}, 5000);
// Debug: Check each video item's computed styles
Array.from(videoGallery.children).forEach((child, index) => {
if (index < 3) { // Check first 3 items
const styles = window.getComputedStyle(child);
console.log(`🎬 Video item ${index + 1} styles:`, {
display: styles.display,
width: styles.width,
height: styles.height,
visibility: styles.visibility,
opacity: styles.opacity,
position: styles.position,
top: styles.top,
left: styles.left
});
const container = child.querySelector('.video-thumbnail-container');
if (container) {
const containerStyles = window.getComputedStyle(container);
console.log(`🎬 Container ${index + 1} styles:`, {
display: containerStyles.display,
width: containerStyles.width,
height: containerStyles.height,
visibility: containerStyles.visibility,
opacity: containerStyles.opacity
});
}
}
});
}
function previewVideo(videoUrl, videoName) {
let modal = document.getElementById('video-preview-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'video-preview-modal';
modal.className = 'video-preview-modal';
modal.innerHTML = `
<div class="modal-backdrop" onclick="closeVideoPreview()"></div>
<div class="modal-content">
<div class="modal-header">
<span id="preview-video-name">${videoName}</span>
<button class="close-btn" onclick="closeVideoPreview()">✖️</button>
</div>
<div class="modal-body">
<video id="preview-video" controls>
<source src="${videoUrl}" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
</div>
`;
document.body.appendChild(modal);
} else {
document.getElementById('preview-video-name').textContent = videoName;
const videoElement = document.getElementById('preview-video');
videoElement.src = videoUrl;
}
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeVideoPreview() {
const modal = document.getElementById('video-preview-modal');
if (modal) {
const videoElement = document.getElementById('preview-video');
if (videoElement) {
videoElement.pause();
videoElement.currentTime = 0;
}
modal.style.display = 'none';
document.body.style.overflow = '';
}
}
// Global functions for HTML onclick handlers
window.removeVideoDirectory = removeVideoDirectory;
window.closeVideoPreview = closeVideoPreview;
window.showVerificationPhotoPreview = showVerificationPhotoPreview;
window.downloadVerificationPhoto = downloadVerificationPhoto;
window.deleteVerificationPhoto = deleteVerificationPhoto;
// Test function for verification photos in main library
window.testVerificationPhotosInLibrary = function() {
console.log('🧪 Testing verification photos integration...');
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
console.log(`📷 Found ${verificationPhotos.length} verification photos in localStorage`);
if (verificationPhotos.length > 0) {
console.log('📷 First verification photo:', verificationPhotos[0]);
}
// Load the library to test integration
loadLinkedImages();
// Check if photos appear in "all" category
const allImages = window.allLinkedImages || [];
const verificationImagesInLibrary = allImages.filter(img => img.type === 'verification');
console.log(`📸 Found ${verificationImagesInLibrary.length} verification photos in main library`);
if (verificationImagesInLibrary.length > 0) {
console.log('✅ Verification photos successfully integrated into main library!');
console.log('📷 Sample verification photo in library:', verificationImagesInLibrary[0]);
} else {
console.log('⚠️ Verification photos not found in main library');
}
// Test filter
const categoryFilter = document.getElementById('lib-image-category-filter');
if (categoryFilter) {
categoryFilter.value = 'verification';
categoryFilter.dispatchEvent(new Event('change'));
console.log('🔍 Switched to verification filter');
}
// Test gallery tab
setupLibraryGalleryTab();
console.log('🖼️ Refreshed gallery tab to include verification photos');
return {
totalVerificationPhotos: verificationPhotos.length,
verificationPhotosInLibrary: verificationImagesInLibrary.length,
integrated: verificationImagesInLibrary.length > 0
};
};
// Test function specifically for the gallery tab
window.testGalleryIntegration = function() {
console.log('🧪 Testing gallery tab verification photos...');
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
const verificationPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]');
console.log(`📸 Found ${capturedPhotos.length} captured photos`);
console.log(`📷 Found ${verificationPhotos.length} verification photos`);
console.log(`📊 Total should be: ${capturedPhotos.length + verificationPhotos.length} photos`);
// Debug verification photo structure
if (verificationPhotos.length > 0) {
console.log('📷 First verification photo structure:', verificationPhotos[0]);
console.log('📷 Properties:', Object.keys(verificationPhotos[0]));
console.log('📷 Has data property:', !!verificationPhotos[0].data);
console.log('📷 Has dataUrl property:', !!verificationPhotos[0].dataUrl);
console.log('📷 Image data type:', typeof (verificationPhotos[0].data || verificationPhotos[0].dataUrl));
}
// Refresh the gallery
setupLibraryGalleryTab();
// Check the count display
const countElement = document.getElementById('lib-all-photos-count');
if (countElement) {
console.log('📊 Gallery count display:', countElement.textContent);
}
return {
capturedPhotos: capturedPhotos.length,
verificationPhotos: verificationPhotos.length,
expectedTotal: capturedPhotos.length + verificationPhotos.length,
verificationStructure: verificationPhotos.length > 0 ? verificationPhotos[0] : null
};
};
// Test function to check XP system on main page
window.testMainPageXP = function() {
console.log('🧪 Testing main page XP system...');
// Check PlayerStats availability
console.log('📊 PlayerStats available:', !!window.playerStats);
if (window.playerStats) {
const stats = window.playerStats.stats;
console.log('📊 Current total XP:', stats.totalXP);
console.log('📊 Current scenario XP:', stats.scenarioGameXP);
console.log('📊 Current quick play XP:', stats.quickPlayXP);
console.log('📊 Current porn cinema XP:', stats.pornCinemaXP);
}
// Check localStorage backup
const savedStats = localStorage.getItem('playerStats');
if (savedStats) {
try {
const stats = JSON.parse(savedStats);
console.log('💾 localStorage total XP:', stats.totalXP || 0);
console.log('💾 localStorage scenario XP:', stats.scenarioGameXP || 0);
} catch (e) {
console.log('❌ Error parsing localStorage stats');
}
}
// Force update the display
updateLevelDisplay();
// Test event system
if (window.playerStats) {
const beforeXP = window.playerStats.stats.totalXP;
window.playerStats.awardXP(1, 'test');
const afterXP = window.playerStats.stats.totalXP;
console.log(`🧪 Test XP award: ${beforeXP}${afterXP} (+${afterXP - beforeXP})`);
// Force another display update
setTimeout(() => {
updateLevelDisplay();
console.log('📊 Display updated after test XP award');
}, 100);
return {
success: true,
beforeXP: beforeXP,
afterXP: afterXP,
awarded: afterXP - beforeXP
};
}
return { success: false, reason: 'PlayerStats not available' };
};
// ===== ANIMATION EVENT LISTENERS =====
// Listen for level up events
window.addEventListener('levelUp', (event) => {
console.log('🎉 Level up event received:', event.detail);
updateLevelDisplay(); // Update the display immediately
});
// Listen for achievement events
window.addEventListener('achievementUnlocked', (event) => {
console.log('🏆 Achievement unlocked event received:', event.detail);
});
// Listen for player stats updates
window.addEventListener('playerStatsUpdated', (event) => {
console.log('📊 Player stats updated:', event.detail);
updateLevelDisplay(); // Keep display in sync
});
// Test function for animations
window.testLevelUpAnimation = function() {
const oldLevel = { level: 4, name: 'Aroused', icon: '🌿', description: 'Your arousal is building' };
const newLevel = { level: 5, name: 'Lustful', icon: '🌟', description: 'Consumed by lust and craving more' };
window.showLevelUpAnimation(oldLevel, newLevel, 25, 'test');
};
window.testAchievementAnimation = function() {
const achievement = {
id: 'test',
icon: '🏆',
title: 'Test Achievement',
description: 'This is a test achievement for demonstration'
};
window.showAchievementAnimation(achievement);
};
</script>
</html>