training-academy/index.html

6531 lines
339 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/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">
<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/1.png'); top: 15%;"></div>
<div class="character-side right" style="background-image: url('assets/11.png'); top: 20%;"></div>
<div class="character-side left" style="background-image: url('assets/3.png'); top: 35%; left: -40px;"></div>
<div class="character-side right" style="background-image: url('assets/4.png'); top: 40%; right: -40px;"></div>
<div class="character-side left" style="background-image: url('assets/5.png'); top: 55%; left: -30px;"></div>
<div class="character-side right" style="background-image: url('assets/6.png'); top: 60%; right: -35px;"></div>
<div class="character-side left" style="background-image: url('assets/7.png'); top: 75%; left: -35px;"></div>
<div class="character-side right" style="background-image: url('assets/8.png'); top: 80%; right: -30px;"></div>
<div class="character-side left" style="background-image: url('assets/9.png'); top: 90%; left: -25px;"></div>
<div class="character-side right" style="background-image: url('assets/10.png'); top: 95%; right: -25px;"></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="library-btn">
<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>
<button id="manage-annoyance-btn" class="btn btn-secondary">😈 Annoyance</button>
</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;">
<!-- Theme Selector -->
<div class="option-item theme-selector">
<label for="theme-dropdown" class="option-label">Choose Your Vibe:</label>
<select id="theme-dropdown" class="theme-dropdown">
<option value="balanced-purple" selected>💜 Balanced Purple (Default)</option>
</select>
</div>
<!-- Debug Controls -->
<div class="option-item debug-controls">
<label class="option-label">🛠️ Debug Tools</label>
<button id="clear-overall-xp-btn" class="btn btn-mini btn-danger">🔄 Reset Overall XP</button>
</div>
<!-- Periodic Popup Controls -->
<div class="option-item periodic-popup-controls">
<div class="periodic-popup-header">
<label class="option-label">💫 Periodic Image Popups</label>
<button id="test-periodic-popup" class="btn btn-mini">🔍 Test Popup</button>
</div>
<div class="periodic-popup-group">
<div class="periodic-control">
<label><input type="checkbox" id="enable-periodic-popups" checked> Enable Periodic Popups</label>
</div>
<div class="periodic-control">
<label for="popup-min-interval">Min Interval (seconds):</label>
<input type="number" id="popup-min-interval" min="10" max="300" value="30" class="number-input">
</div>
<div class="periodic-control">
<label for="popup-max-interval">Max Interval (seconds):</label>
<input type="number" id="popup-max-interval" min="30" max="600" value="120" class="number-input">
</div>
<div class="periodic-control">
<label for="popup-display-duration">Display Duration (seconds):</label>
<input type="number" id="popup-display-duration" min="2" max="30" value="5" class="number-input">
</div>
</div>
</div>
<!-- Audio Controls -->
<div class="option-item audio-controls">
<div class="audio-control-header">
<label class="option-label">🎵 Audio Settings</label>
<button id="debug-audio" class="btn btn-mini">🔧 Debug Audio</button>
</div>
<div class="audio-control-group">
<div class="audio-control">
<label for="master-volume">Master Volume:</label>
<input type="range" id="master-volume" min="0" max="100" value="70" class="volume-slider">
<span id="master-volume-display">70%</span>
</div>
<div class="audio-control">
<label for="task-audio-volume">Task Audio:</label>
<input type="range" id="task-audio-volume" min="0" max="100" value="70" class="volume-slider">
<span id="task-audio-volume-display">70%</span>
<button id="preview-task-audio" class="btn btn-mini">▶️</button>
</div>
<div class="audio-control">
<label for="punishment-audio-volume">Punishment Audio:</label>
<input type="range" id="punishment-audio-volume" min="0" max="100" value="80" class="volume-slider">
<span id="punishment-audio-volume-display">80%</span>
<button id="preview-punishment-audio" class="btn btn-mini">▶️</button>
</div>
<div class="audio-control">
<label for="reward-audio-volume">Reward Audio:</label>
<input type="range" id="reward-audio-volume" min="0" max="100" value="60" class="volume-slider">
<span id="reward-audio-volume-display">60%</span>
<button id="preview-reward-audio" class="btn btn-mini">▶️</button>
</div>
</div>
<div class="audio-toggles">
<label><input type="checkbox" id="enable-task-audio" checked> Enable Task Audio</label>
<label><input type="checkbox" id="enable-punishment-audio" checked> Enable Punishment Audio</label>
<label><input type="checkbox" id="enable-reward-audio" checked> Enable Reward Audio</label>
</div>
</div>
<!-- Data Management -->
<div class="option-item data-controls">
<button id="import-btn" class="btn btn-option" title="Import Save File">
<span class="btn-text"><EFBFBD> Import</span>
<span class="btn-loading" style="display: none;">⏳ Importing...</span>
</button>
<button id="export-btn" class="btn btn-option" title="Export Save File">
<span class="btn-text"><EFBFBD> Export</span>
<span class="btn-loading" style="display: none;">⏳ Exporting...</span>
</button>
</div>
<!-- Other Options -->
<div class="option-item other-controls">
<button id="help-btn" class="btn btn-option" title="Keyboard Shortcuts & Help">❓ Help</button>
</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">
<button id="select-all-images-btn" class="btn btn-small btn-outline">Select All</button>
<button id="deselect-all-images-btn" class="btn btn-small btn-outline">Deselect All</button>
<button id="delete-selected-btn" class="btn btn-danger btn-small">Delete Selected</button>
<button id="refresh-images-btn" class="btn btn-secondary btn-small">🔄 Refresh</button>
<span class="image-count">Loading images...</span>
</div>
</div>
<!-- Image Tabs -->
<div class="image-tabs">
<button id="task-images-tab" class="tab-btn active">🎯 Tasks</button>
<button id="consequence-images-tab" class="tab-btn">⚠️ Consequences</button>
</div>
<div class="image-galleries-container">
<div id="task-images-gallery" class="image-gallery active">
<!-- Task images will be populated here -->
</div>
<div id="consequence-images-gallery" class="image-gallery">
<!-- Consequence images will be populated here -->
</div>
</div>
</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">
<button id="select-all-audio-btn" class="btn btn-small btn-outline">Select All</button>
<button id="deselect-all-audio-btn" class="btn btn-small btn-outline">Deselect All</button>
<button id="delete-selected-audio-btn" class="btn btn-danger btn-small">Delete Selected</button>
<button id="preview-selected-audio-btn" class="btn btn-info btn-small">🎧 Preview</button>
<span class="audio-count">Loading audio files...</span>
</div>
</div>
<!-- Audio Tabs -->
<div class="audio-tabs">
<button id="background-audio-tab" class="tab-btn active">🎶 Background</button>
<button id="ambient-audio-tab" class="tab-btn">🌿 Ambient</button>
</div>
<div class="audio-galleries-container">
<div id="background-audio-gallery" class="audio-gallery active">
<!-- Background audio files will be populated here -->
</div>
<div id="ambient-audio-gallery" class="audio-gallery">
<!-- Ambient audio files will be populated here -->
</div>
</div>
</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: #ccc; 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: #2a2a3a; 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: #1a1a2a; border-radius: 4px; text-align: center;">
<strong id="total-video-count">0 videos total</strong>
<span id="directory-count" style="color: #aaa; 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">
<button id="select-all-video-btn" class="btn btn-small btn-outline">Select All</button>
<button id="deselect-all-video-btn" class="btn btn-small btn-outline">Deselect All</button>
<button id="delete-selected-video-btn" class="btn btn-danger btn-small">Remove Selected</button>
<button id="refresh-videos-btn" class="btn btn-secondary btn-small">🔄 Refresh</button>
<span class="video-count">Loading videos...</span>
</div>
</div>
<!-- Single unified video gallery (no categories) -->
<div class="video-galleries-container">
<div id="unified-video-gallery" class="video-gallery active">
<!-- All videos from all linked directories will be shown here -->
</div>
</div>
</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>
<!-- Annoyance Management Screen -->
<div id="annoyance-management-screen" class="screen">
<h2>😈 Annoyance Management</h2>
<p>Configure flash messages and motivational features to enhance your experience!</p>
<!-- Tab Navigation -->
<div class="annoyance-tabs">
<button id="messages-tab" class="annoyance-tab active">💬 Messages</button>
<button id="appearance-tab" class="annoyance-tab">🎨 Appearance</button>
<button id="behavior-tab" class="annoyance-tab">⚡ Behavior</button>
<button id="popup-images-tab" class="annoyance-tab">🖼️ Popup Images</button>
<button id="ai-tasks-tab" class="annoyance-tab">🤖 AI Tasks</button>
<button id="import-export-tab" class="annoyance-tab">📁 Import/Export</button>
</div>
<!-- Messages Tab -->
<div id="messages-tab-content" class="annoyance-tab-content active">
<div class="annoyance-section">
<div class="section-header">
<h3>💬 Message Management</h3>
<div class="header-controls">
<label>
<input type="checkbox" id="flash-messages-enabled" checked>
Enable Flash Messages
</label>
<button id="add-new-message-btn" class="btn btn-success btn-small">+ Add Message</button>
</div>
</div>
<!-- Message Editor -->
<div id="message-editor" class="message-editor" style="display: none;">
<div class="editor-header">
<h4 id="editor-title">Add New Message</h4>
<button id="close-editor-btn" class="btn btn-outline btn-small">✕ Close</button>
</div>
<div class="editor-form">
<div class="form-group">
<label>Message Text:</label>
<textarea id="message-text" placeholder="Enter your motivational message..." maxlength="200" rows="3"></textarea>
<div class="char-counter">
<span id="char-count">0</span>/200 characters
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Category:</label>
<select id="message-category">
<option value="motivational">💪 Motivational</option>
<option value="encouraging">🌟 Encouraging</option>
<option value="achievement">🏆 Achievement</option>
<option value="persistence">🔥 Persistence</option>
<option value="custom">✨ Custom</option>
</select>
</div>
<div class="form-group">
<label>Priority:</label>
<select id="message-priority">
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="low">Low</option>
</select>
</div>
</div>
<div class="editor-actions">
<button id="save-message-btn" class="btn btn-primary">Save Message</button>
<button id="preview-current-message-btn" class="btn btn-info">Preview</button>
<button id="cancel-edit-btn" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
<!-- Message List -->
<div class="message-list-section">
<div class="list-header">
<div class="list-filters">
<label>Filter by Category:
<select id="category-filter">
<option value="all">All Categories</option>
<option value="motivational">💪 Motivational</option>
<option value="encouraging">🌟 Encouraging</option>
<option value="achievement">🏆 Achievement</option>
<option value="persistence">🔥 Persistence</option>
<option value="custom">✨ Custom</option>
</select>
</label>
<label>
<input type="checkbox" id="show-disabled-messages"> Show Disabled
</label>
</div>
<div class="list-stats">
<span id="message-stats">20 messages (18 enabled, 2 disabled)</span>
</div>
</div>
<div id="message-list" class="message-list">
<!-- Messages will be populated here -->
</div>
</div>
</div>
</div>
<!-- Appearance Tab -->
<div id="appearance-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>🎨 Visual Appearance</h3>
<div class="appearance-controls">
<div class="control-row">
<div class="control-group">
<label>Position:</label>
<select id="message-position">
<option value="center">Center</option>
<option value="top-center">Top Center</option>
<option value="bottom-center">Bottom Center</option>
<option value="top-left">Top Left</option>
<option value="top-right">Top Right</option>
<option value="bottom-left">Bottom Left</option>
<option value="bottom-right">Bottom Right</option>
<option value="center-left">Center Left</option>
<option value="center-right">Center Right</option>
</select>
</div>
<div class="control-group">
<label>Animation:</label>
<select id="animation-style">
<option value="fade">Fade</option>
<option value="slide">Slide</option>
<option value="bounce">Bounce</option>
<option value="pulse">Pulse</option>
</select>
</div>
</div>
<div class="control-row">
<div class="control-group">
<label>Font Size: <span id="font-size-display">24px</span></label>
<input type="range" id="font-size" min="16" max="48" value="24" step="2">
</div>
<div class="control-group">
<label>Opacity: <span id="opacity-display">90%</span></label>
<input type="range" id="message-opacity" min="50" max="100" value="90" step="5">
</div>
</div>
<div class="control-row">
<div class="control-group">
<label>Text Color:</label>
<input type="color" id="text-color" value="#ffffff">
</div>
<div class="control-group">
<label>Background Color:</label>
<input type="color" id="background-color" value="#007bff">
</div>
</div>
<div class="control-group">
<button id="reset-appearance-btn" class="btn btn-outline">Reset to Defaults</button>
<button id="preview-appearance-btn" class="btn btn-info">Preview Style</button>
</div>
</div>
</div>
</div>
<!-- Behavior Tab -->
<div id="behavior-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>⚡ Behavior Settings</h3>
<div class="behavior-controls">
<div class="control-group">
<label>🧘 Focus Interruption Chance: <span id="focus-interruption-display">0%</span></label>
<input type="range" id="focus-interruption-chance" min="0" max="50" value="0" step="5">
<small class="help-text">Chance for focus-hold interruptions during scenario adventures (0% = disabled, max 50%)</small>
</div>
<div class="control-group">
<label>Display Duration: <span id="duration-display">3.0s</span></label>
<input type="range" id="display-duration" min="1000" max="10000" value="3000" step="500">
</div>
<div class="control-group">
<label>Interval Between Messages: <span id="interval-display">45s</span></label>
<input type="range" id="interval-delay" min="10000" max="300000" value="45000" step="5000">
</div>
<div class="control-group">
<label>Random Variation: <span id="variation-display">±5s</span></label>
<input type="range" id="time-variation" min="0" max="30000" value="5000" step="1000">
<small class="help-text">Adds random time variation to prevent predictability</small>
</div>
<div class="control-row">
<div class="control-group">
<label>
<input type="checkbox" id="event-based-messages" checked>
Enable Event-Based Messages
</label>
<small class="help-text">Show special messages for task completion, streaks, etc.</small>
</div>
<div class="control-group">
<label>
<input type="checkbox" id="pause-on-hover">
Pause Timer on Message Hover
</label>
<small class="help-text">Pause message fade when hovering (useful for reading)</small>
</div>
</div>
<div class="control-group">
<button id="test-behavior-btn" class="btn btn-success">Test Current Settings</button>
</div>
</div>
</div>
</div>
<!-- Popup Images Tab -->
<div id="popup-images-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>🖼️ Punishment Popups</h3>
<p class="help-text">Configure consequence images that appear when tasks are skipped</p>
<!-- Enable/Disable -->
<div class="control-section">
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-images-enabled" />
<span class="switch"></span>
Enable Punishment Popups
</label>
</div>
</div>
<!-- Image Count Settings -->
<div class="control-section">
<h4>📊 Number of Images</h4>
<div class="control-group">
<label for="popup-count-mode">Count Mode:</label>
<select id="popup-count-mode">
<option value="fixed">Fixed Amount</option>
<option value="random">Random (1-10)</option>
<option value="range">Custom Range</option>
</select>
</div>
<div id="popup-fixed-count" class="control-group">
<label for="popup-image-count">Number of Images:</label>
<input type="range" id="popup-image-count" min="1" max="40" value="3" />
<span id="popup-image-count-value">3</span>
</div>
<div id="popup-range-count" class="control-group" style="display: none;">
<div class="range-inputs">
<div>
<label for="popup-min-count">Minimum:</label>
<input type="number" id="popup-min-count" min="1" max="20" value="2" />
</div>
<div>
<label for="popup-max-count">Maximum:</label>
<input type="number" id="popup-max-count" min="2" max="40" value="5" />
</div>
</div>
</div>
</div>
<!-- Display Duration Settings -->
<div class="control-section">
<h4>⏱️ Display Duration</h4>
<div class="control-group">
<label for="popup-duration-mode">Duration Mode:</label>
<select id="popup-duration-mode">
<option value="fixed">Fixed Duration</option>
<option value="random">Random (5-15s)</option>
<option value="range">Custom Range</option>
</select>
</div>
<div id="popup-fixed-duration" class="control-group">
<label for="popup-display-duration">Duration (seconds):</label>
<input type="range" id="popup-display-duration" min="3" max="30" value="8" />
<span id="popup-display-duration-value">8s</span>
</div>
<div id="popup-range-duration" class="control-group" style="display: none;">
<div class="range-inputs">
<div>
<label for="popup-min-duration">Min (seconds):</label>
<input type="number" id="popup-min-duration" min="2" max="20" value="5" />
</div>
<div>
<label for="popup-max-duration">Max (seconds):</label>
<input type="number" id="popup-max-duration" min="5" max="60" value="15" />
</div>
</div>
</div>
</div>
<!-- Positioning & Appearance -->
<div class="control-section">
<h4>🎯 Positioning</h4>
<div class="control-group">
<label for="popup-positioning">Layout Style:</label>
<select id="popup-positioning">
<option value="random">Random Positions</option>
<option value="cascade">Cascading</option>
<option value="grid">Grid Layout</option>
<option value="center">Centered (stacked)</option>
</select>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-allow-overlap" />
<span class="switch"></span>
Allow Overlapping
</label>
</div>
</div>
<!-- Size Settings -->
<div class="control-section">
<h4>📏 Size Settings</h4>
<p class="help-text">Popups automatically size to match image proportions within these limits</p>
<div class="control-group">
<label for="popup-viewport-width">Max Viewport Width:</label>
<input type="range" id="popup-viewport-width" min="20" max="60" value="35" />
<span id="popup-viewport-width-value">35%</span>
</div>
<div class="control-group">
<label for="popup-viewport-height">Max Viewport Height:</label>
<input type="range" id="popup-viewport-height" min="20" max="60" value="40" />
<span id="popup-viewport-height-value">40%</span>
</div>
<div class="control-group">
<div class="range-inputs">
<div>
<label for="popup-min-width">Min Width (px):</label>
<input type="number" id="popup-min-width" min="150" max="400" value="200" />
</div>
<div>
<label for="popup-max-width">Max Width (px):</label>
<input type="number" id="popup-max-width" min="300" max="800" value="500" />
</div>
</div>
</div>
<div class="control-group">
<div class="range-inputs">
<div>
<label for="popup-min-height">Min Height (px):</label>
<input type="number" id="popup-min-height" min="100" max="300" value="150" />
</div>
<div>
<label for="popup-max-height">Max Height (px):</label>
<input type="number" id="popup-max-height" min="200" max="600" value="400" />
</div>
</div>
</div>
</div>
<!-- Visual Effects -->
<div class="control-section">
<h4>✨ Visual Effects</h4>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-fade-animation" />
<span class="switch"></span>
Fade In/Out Animation
</label>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-blur-background" />
<span class="switch"></span>
Blur Background
</label>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-show-timer" />
<span class="switch"></span>
Show Countdown Timer
</label>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-prevent-close" />
<span class="switch"></span>
Prevent Manual Close
</label>
</div>
</div>
<!-- Test & Preview -->
<div class="control-section">
<h4>🧪 Testing</h4>
<div class="test-buttons">
<button id="test-popup-single" class="btn btn-info">Test 1 Popup</button>
<button id="test-popup-multiple" class="btn btn-primary">Test Multiple</button>
<button id="clear-all-popups" class="btn btn-danger">Clear All</button>
</div>
<p class="help-text">Test your popup settings to see how they look</p>
<div id="popup-warning" class="warning-text" style="display: none;">
⚠️ High popup counts (>20) may impact performance and visibility
</div>
</div>
<!-- Status & Info -->
<div class="control-section">
<div class="info-display">
<div class="info-item">
<span class="info-label">Available Images:</span>
<span id="available-images-count">0</span>
</div>
<div class="info-item">
<span class="info-label">Active Popups:</span>
<span id="active-popups-count">0</span>
</div>
</div>
</div>
</div>
</div>
<!-- AI Tasks Tab -->
<div id="ai-tasks-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>🤖 AI Task Generation</h3>
<p class="help-text">Let AI create personalized edging tasks using your local Ollama installation</p>
<!-- Connection Status -->
<div class="control-section">
<h4>📡 Connection Status</h4>
<div class="ai-status-display">
<div class="status-item">
<span class="status-label">Ollama Service:</span>
<span id="ollama-status" class="status-value">Checking...</span>
</div>
<div class="status-item">
<span class="status-label">Available Models:</span>
<span id="models-count" class="status-value">0</span>
</div>
<div class="status-item">
<span class="status-label">Current Model:</span>
<span id="current-model" class="status-value">None</span>
</div>
</div>
<button id="test-ai-connection" class="btn btn-info">Test Connection</button>
</div>
<!-- AI Configuration -->
<div class="control-section">
<h4>⚙️ AI Configuration</h4>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="ai-tasks-enabled" />
<span class="switch"></span>
Enable AI Task Generation
</label>
</div>
<div class="control-group">
<label for="ai-model-select">Model Selection:</label>
<select id="ai-model-select">
<option value="">Select a model...</option>
</select>
</div>
<div class="control-group">
<label for="ai-temperature">Creativity (Temperature):</label>
<input type="range" id="ai-temperature" min="0.1" max="2.0" step="0.1" value="0.8" />
<span id="ai-temperature-value">0.8</span>
</div>
<div class="control-group">
<label for="ai-max-tokens">Max Response Length:</label>
<input type="range" id="ai-max-tokens" min="100" max="500" step="50" value="300" />
<span id="ai-max-tokens-value">300</span>
</div>
</div>
<!-- User Preferences -->
<div class="control-section">
<h4>👤 Your Preferences</h4>
<div class="control-group">
<label for="ai-experience">Experience Level:</label>
<select id="ai-experience">
<option value="beginner">Beginner</option>
<option value="intermediate" selected>Intermediate</option>
<option value="advanced">Advanced</option>
<option value="expert">Expert</option>
</select>
</div>
<div class="control-group">
<label for="ai-intensity">Default Intensity:</label>
<select id="ai-intensity">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="extreme">Extreme</option>
</select>
</div>
<div class="control-group">
<label for="ai-duration">Preferred Duration (minutes):</label>
<input type="range" id="ai-duration" min="3" max="30" value="5" />
<span id="ai-duration-value">5</span>
</div>
<div class="control-group">
<label for="ai-style">Instruction Style:</label>
<select id="ai-style">
<option value="instructional" selected>Instructional</option>
<option value="descriptive">Descriptive</option>
<option value="commanding">Commanding</option>
<option value="encouraging">Encouraging</option>
</select>
</div>
</div>
<!-- Testing & Preview -->
<div class="control-section">
<h4>🧪 Testing</h4>
<div class="ai-test-buttons">
<button id="generate-test-task" class="btn btn-primary">Generate Test Task</button>
<button id="generate-consequence-task" class="btn btn-danger">Generate Consequence</button>
<button id="clear-test-output" class="btn btn-secondary">Clear Output</button>
</div>
<div class="control-group">
<label for="test-task-output">Generated Task Preview:</label>
<div id="test-task-output" class="task-preview">
Click "Generate Test Task" to see AI-generated content...
</div>
</div>
</div>
<!-- Installation Help -->
<div class="control-section">
<h4>📚 Setup Help</h4>
<div class="help-content">
<p><strong>Need to install Ollama?</strong></p>
<ol>
<li>Download from <a href="https://ollama.ai" target="_blank">ollama.ai</a></li>
<li>Install recommended NSFW models:</li>
<ul>
<li><code>ollama pull dolphin-mistral:7b</code></li>
<li><code>ollama pull wizardlm-uncensored:7b</code></li>
</ul>
<li>Ensure Ollama service is running</li>
<li>Click "Test Connection" above</li>
</ol>
<p class="help-text">AI tasks are generated locally for complete privacy!</p>
</div>
</div>
</div>
</div>
<!-- Import/Export Tab -->
<div id="import-export-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3><EFBFBD> Import & Export</h3>
<div class="import-export-controls">
<div class="control-section">
<h4>💾 Export Messages</h4>
<div class="export-options">
<button id="export-all-messages-btn" class="btn btn-primary">Export All Messages</button>
<button id="export-enabled-messages-btn" class="btn btn-secondary">Export Enabled Only</button>
<button id="export-custom-messages-btn" class="btn btn-info">Export Custom Only</button>
</div>
<p class="help-text">Export your messages as a JSON file for backup or sharing</p>
</div>
<div class="control-section">
<h4><EFBFBD> Import Messages</h4>
<div class="import-options">
<button id="import-messages-btn" class="btn btn-success">Import Messages</button>
<input type="file" id="import-messages-file" accept=".json" style="display: none;">
<div class="import-mode">
<label>Import Mode:</label>
<div class="radio-group">
<label><input type="radio" name="importMode" value="merge" checked> Merge with existing</label>
<label><input type="radio" name="importMode" value="replace"> Replace all messages</label>
</div>
</div>
</div>
<p class="help-text">Import messages from a JSON file</p>
</div>
<div class="control-section">
<h4>🔄 Reset Options</h4>
<div class="reset-options">
<button id="reset-to-defaults-btn" class="btn btn-warning">Reset to Default Messages</button>
<button id="clear-all-messages-btn" class="btn btn-danger">Clear All Messages</button>
</div>
<p class="help-text danger">⚠️ Reset operations cannot be undone!</p>
</div>
</div>
</div>
</div>
<div class="management-buttons">
<button id="back-to-start-from-annoyance-btn" class="btn btn-secondary">Back to Start</button>
<button id="save-annoyance-settings" class="btn btn-primary">Save All Settings</button>
</div>
</div>
<!-- Library Screen -->
<div id="library-screen" class="screen">
<h2>📚 Media Library</h2>
<p>Manage all your media content in one place</p>
<!-- Library Navigation Tabs -->
<div class="library-tabs">
<button class="library-tab active" data-tab="images">🖼️ Images</button>
<button class="library-tab" data-tab="audio">🎵 Audio</button>
<button class="library-tab" data-tab="video">🎬 Video</button>
<button class="library-tab" data-tab="gallery">📸 Gallery</button>
</div>
<!-- Images Tab Content -->
<div id="library-images-content" class="library-content active">
<div class="content-section">
<h3>🖼️ Image Library Management</h3>
<p>Link directories from your computer to access image content</p>
<!-- Directory Management Section -->
<div class="directory-management-section">
<h4>📁 Linked Image Directories</h4>
<div class="directory-controls">
<button id="lib-add-image-directory-btn" class="btn btn-primary">📁 Add Directory</button>
<button id="lib-add-individual-images-btn" class="btn btn-primary">🖼️ Add Individual Images</button>
<button id="lib-refresh-image-directories-btn" class="btn btn-secondary">🔄 Refresh</button>
<button id="lib-clear-image-directories-btn" class="btn btn-danger">🗑️ Clear All</button>
<span id="lib-directories-count">0 directories linked</span>
</div>
<div class="linked-directories-container">
<div id="linked-image-directories-list">
<div class="no-directories">No image directories linked yet</div>
</div>
</div>
</div>
<!-- Image Gallery -->
<div class="gallery-section">
<div class="gallery-header">
<h4>🖼️ Current Image Library</h4>
<div class="gallery-controls">
<select id="lib-image-category-filter">
<option value="all">All Images</option>
<option value="jpg">JPEG Images</option>
<option value="png">PNG Images</option>
<option value="gif">GIF Images</option>
</select>
<span id="lib-image-count">0 images</span>
</div>
</div>
<div class="image-gallery active" id="lib-image-gallery">
<div class="no-images-message">
<p><EFBFBD> No images found in linked directories</p>
<p>Click "Add Directory" to link a folder containing images</p>
</div>
</div>
</div>
</div>
</div>
<!-- Audio Tab Content -->
<div id="library-audio-content" class="library-content">
<div class="content-section">
<h3>🎵 Audio Library Management</h3>
<p>Organize your background music and ambient sounds</p>
<!-- Audio Upload Section -->
<div class="upload-section">
<h4>🎵 Import Audio Files</h4>
<div class="upload-controls">
<button id="lib-import-background-music-btn" class="btn btn-primary">🎵 Background Music</button>
<button id="lib-import-ambient-audio-btn" class="btn btn-secondary">🌊 Ambient Sounds</button>
<input type="file" id="lib-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 formats</span>
</div>
<div class="directory-controls">
<button id="lib-audio-storage-info-btn" class="btn btn-outline">📊 Storage Info</button>
<button id="lib-cleanup-invalid-audio-btn" class="btn btn-warning">🧹 Cleanup</button>
<button id="lib-clear-all-audio-btn" class="btn btn-danger">🗑️ Clear All</button>
</div>
</div>
<!-- Audio Library -->
<div class="audio-gallery-section">
<div class="gallery-header">
<h4>🎵 Current Audio Library</h4>
<div class="gallery-controls">
<select id="lib-audio-category-filter">
<option value="all">All Categories</option>
<option value="background">Background Music</option>
<option value="ambient">Ambient Sounds</option>
</select>
<span id="lib-audio-count">0 files</span>
</div>
</div>
<div class="audio-gallery" id="lib-audio-gallery">
<div class="no-audio-message">
<p>🎵 No audio files found</p>
<p>Import audio to get started</p>
</div>
</div>
</div>
</div>
</div>
<!-- Video Tab Content -->
<div id="library-video-content" class="library-content">
<div class="content-section">
<h3>🎬 Video Library Management</h3>
<p>Manage your video content for enhanced training sessions</p>
<!-- Video Directory Management Section -->
<div class="directory-management-section">
<h4>📁 Linked Video Directories</h4>
<p>Link directories from your computer to access video content</p>
<div class="directory-controls">
<button id="lib-add-video-directory-btn" class="btn btn-primary">📁 Add Directory</button>
<button id="lib-add-individual-videos-btn" class="btn btn-primary">🎬 Add Individual Videos</button>
<button id="lib-refresh-video-directories-btn" class="btn btn-secondary">🔄 Refresh</button>
<button id="lib-clear-video-directories-btn" class="btn btn-danger">🗑️ Clear All</button>
<span id="lib-video-directories-count">0 directories linked</span>
</div>
<div class="linked-directories-container">
<div id="linked-video-directories-list">
<div class="no-directories">No video directories linked yet</div>
</div>
</div>
</div>
<!-- Video Library -->
<div class="video-gallery-section">
<div class="gallery-header">
<h4>🎬 Current Video Library</h4>
<div class="gallery-controls">
<select id="lib-video-category-filter">
<option value="all">All Categories</option>
<option value="training">Training Videos</option>
<option value="background">Background Videos</option>
</select>
<span id="lib-video-count">0 files</span>
</div>
</div>
<div class="video-gallery active" id="lib-video-gallery">
<div class="no-video-message">
<p>🎬 No video files found</p>
<p>Import videos to get started</p>
</div>
</div>
</div>
</div>
</div>
<!-- Gallery Tab Content -->
<div id="library-gallery-content" class="library-content">
<div class="content-section">
<h3>📸 Photo Gallery</h3>
<p>Browse and organize your photo collections</p>
<!-- Gallery Categories -->
<div class="gallery-categories">
<button class="gallery-category-btn active" data-category="all">All Photos</button>
<button class="gallery-category-btn" data-category="dress-up">Dress Up</button>
<button class="gallery-category-btn" data-category="studio">Studio</button>
<button class="gallery-category-btn" data-category="custom">Custom</button>
</div>
<!-- Photo Gallery Display -->
<div class="photo-galleries">
<div id="lib-all-photos-gallery" class="photo-gallery active">
<div class="gallery-header">
<h4>📸 All Photos</h4>
<span class="photo-count" id="lib-all-photos-count">0 photos</span>
</div>
<div class="bulk-actions">
<div class="selection-controls">
<button id="select-all-photos" class="btn btn-small">☑️ Select All</button>
<button id="deselect-all-photos" class="btn btn-small">☐ Deselect All</button>
<span id="selected-count" class="selected-count">0 selected</span>
</div>
<div class="action-buttons">
<button id="download-selected-photos" class="btn btn-success" disabled>📥 Download Selected</button>
<button id="delete-selected-photos" class="btn btn-danger" disabled>🗑️ Delete Selected</button>
</div>
</div>
<div class="photo-grid" id="lib-all-photos-grid">
<div class="no-photos-message">
<p>📸 No photos found</p>
<p>Take some photos during gameplay to see them here</p>
</div>
</div>
</div>
<div id="lib-dress-up-photos-gallery" class="photo-gallery">
<div class="gallery-header">
<h4>👗 Dress Up Photos</h4>
<span class="photo-count" id="lib-dress-up-photos-count">0 photos</span>
</div>
<div class="bulk-actions">
<div class="selection-controls">
<button id="select-all-dress-up" class="btn btn-small">☑️ Select All</button>
<button id="deselect-all-dress-up" class="btn btn-small">☐ Deselect All</button>
<span id="selected-dress-up-count" class="selected-count">0 selected</span>
</div>
<div class="action-buttons">
<button id="download-selected-dress-up" class="btn btn-success" disabled>📥 Download Selected</button>
<button id="delete-selected-dress-up" class="btn btn-danger" disabled>🗑️ Delete Selected</button>
</div>
</div>
<div class="photo-grid" id="lib-dress-up-photos-grid">
<div class="no-photos-message">
<p>👗 No dress up photos found</p>
</div>
</div>
</div>
<div id="lib-studio-photos-gallery" class="photo-gallery">
<div class="gallery-header">
<h4>🎬 Studio Photos</h4>
<span class="photo-count" id="lib-studio-photos-count">0 photos</span>
</div>
<div class="photo-grid" id="lib-studio-photos-grid">
<div class="no-photos-message">
<p>🎬 No studio photos found</p>
</div>
</div>
</div>
<div id="lib-custom-photos-gallery" class="photo-gallery">
<div class="gallery-header">
<h4>⭐ Custom Photos</h4>
<span class="photo-count" id="lib-custom-photos-count">0 photos</span>
</div>
<div class="photo-grid" id="lib-custom-photos-grid">
<div class="no-photos-message">
<p>⭐ No custom photos found</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Library Controls -->
<div class="management-buttons">
<button id="back-to-start-from-library-btn" class="btn btn-secondary">Back to Start</button>
<button id="refresh-library-btn" class="btn btn-primary">🔄 Refresh Library</button>
</div>
</div>
<!-- Game Screen -->
<div id="game-screen" class="screen">
<div class="task-container">
<div class="task-image-container">
<img id="task-image" src="" alt="Task Image" class="task-image">
</div>
<div class="task-text-container">
<h3 id="task-text" class="task-text">Loading task...</h3>
</div>
<div class="action-buttons">
<button id="complete-btn" class="btn btn-success">Complete</button>
<button id="skip-btn" class="btn btn-warning">Skip</button>
<button id="mercy-skip-btn" class="btn btn-danger" style="display: none;">
<span id="mercy-skip-text">Mercy Skip</span>
<span id="mercy-skip-cost" class="mercy-cost"></span>
</button>
<button id="pause-btn" class="btn btn-info">Pause</button>
</div>
</div>
<div class="game-stats">
<div class="stat">
<span class="stat-label">Session XP:</span>
<span id="xp" class="stat-value">0</span>
</div>
<div class="stat">
<span class="stat-label">Completed:</span>
<span id="completed-count" class="stat-value">0</span>
</div>
<div class="stat streak-stat">
<span class="stat-label">Streak:</span>
<span id="current-streak" class="stat-value">0</span>🔥
</div>
<div class="stat">
<span class="stat-label">Skipped:</span>
<span id="skipped-count" class="stat-value">0</span>
</div>
<div class="stat">
<span class="stat-label">Consequences:</span>
<span id="consequence-count" class="stat-value">0</span>
</div>
</div>
</div>
<!-- Paused Screen -->
<div id="paused-screen" class="screen">
<h2>Game Paused</h2>
<p>Take a break! Click resume when ready.</p>
<button id="resume-btn" class="btn btn-primary">Resume</button>
<button id="quit-btn" class="btn btn-secondary">Quit Game</button>
</div>
<!-- Game Over Screen -->
<div id="game-over-screen" class="screen">
<h2>Game Complete!</h2>
<p>Congratulations! You've completed all available tasks!</p>
<div id="final-stats" class="final-stats">
<p><strong>Game Mode:</strong> <span id="final-game-mode"></span></p>
<p>Final XP: <span id="final-xp"></span> XP</p>
<p>Final Time: <span id="final-time"></span></p>
<p>Tasks Completed: <span id="final-completed"></span></p>
<p>Tasks Skipped: <span id="final-skipped"></span></p>
<p>Consequence Tasks: <span id="final-consequences"></span></p>
<p>Best Streak: <span id="final-best-streak"></span> 🔥</p>
<p>Streak Bonus Points: <span id="final-streak-bonuses"></span></p>
</div>
<button id="play-again-btn" class="btn btn-primary">Play Again</button>
</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>
<!-- 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/tasks/aiTaskManager.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>
// Initialize application when page loads
window.addEventListener('load', () => {
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
loadVideoGalleryContent(category);
}
});
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', '');
loadVideoGalleryContent(category);
}
} 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
loadVideoGalleryContent(category);
});
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', '');
loadVideoGalleryContent(category);
}
}
// 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();
});
}
// Gallery control buttons
const selectAllBtn = document.getElementById('select-all-video-btn');
if (selectAllBtn) {
selectAllBtn.addEventListener('click', () => {
selectAllVideos();
});
}
const deselectAllBtn = document.getElementById('deselect-all-video-btn');
if (deselectAllBtn) {
deselectAllBtn.addEventListener('click', () => {
deselectAllVideos();
});
}
const deleteSelectedBtn = document.getElementById('delete-selected-video-btn');
if (deleteSelectedBtn) {
deleteSelectedBtn.addEventListener('click', () => {
deleteSelectedVideos();
});
}
// 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();
loadUnifiedVideoGallery();
}, 500);
} else {
// Browser fallback - try to load from unified storage
loadUnifiedVideoGallery();
}
// 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();
loadUnifiedVideoGallery();
console.log('Current video count:', window.desktopFileManager.getAllVideos().length);
if (window.game && window.game.showNotification) {
window.game.showNotification(`✅ Added directory: ${result.directory.name} (${result.videoCount} videos)`, 'success');
}
} else {
console.log('Directory addition returned null/failed');
}
}).catch(error => {
console.error('Directory addition failed:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to add directory', 'error');
}
});
} else {
if (window.game && window.game.showNotification) {
window.game.showNotification('⚠️ Directory management only available in desktop version', 'warning');
}
}
}
function handleRefreshAllDirectories() {
console.log('Refreshing all directories...');
if (window.electronAPI && window.desktopFileManager) {
window.desktopFileManager.refreshAllDirectories().then(() => {
updateDirectoryList();
updateVideoStats();
loadUnifiedVideoGallery();
}).catch(error => {
console.error('Directory refresh failed:', error);
if (window.game && window.game.showNotification) {
window.game.showNotification('❌ Failed to refresh directories', 'error');
}
});
}
}
function handleClearAllDirectories() {
if (!confirm('Are you sure you want to unlink all video directories? This will not delete your actual video files.')) {
return;
}
console.log('Clearing all directories...');
if (window.electronAPI && window.desktopFileManager) {
// Remove all directories
const directories = [...window.desktopFileManager.externalVideoDirectories];
Promise.all(directories.map(dir => window.desktopFileManager.removeVideoDirectory(dir.id)))
.then(() => {
updateDirectoryList();
updateVideoStats();
loadUnifiedVideoGallery();
if (window.game && window.game.showNotification) {
window.game.showNotification('<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: #666; 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: #333; border-radius: 4px;">
<div>
<strong>${dir.name}</strong>
<br><small style="color: #aaa;">${dir.path}</small>
<br><small style="color: #888;">${dir.videoCount} videos</small>
</div>
<button class="btn btn-small btn-outline" onclick="removeDirectory(${dir.id})" style="color: #ff6b6b;">Remove</button>
</div>
`).join('');
}
}
function removeDirectory(directoryId) {
if (window.electronAPI && window.desktopFileManager) {
window.desktopFileManager.removeVideoDirectory(directoryId).then((success) => {
if (success) {
updateDirectoryList();
updateVideoStats();
loadUnifiedVideoGallery();
}
});
}
}
function updateVideoStats() {
const totalCountEl = document.getElementById('total-video-count');
const directoryCountEl = document.getElementById('directory-count');
if (window.electronAPI && window.desktopFileManager) {
const allVideos = window.desktopFileManager.getAllVideos();
const directories = window.desktopFileManager.getDirectoriesInfo();
if (totalCountEl) totalCountEl.textContent = `${allVideos.length} videos total`;
if (directoryCountEl) directoryCountEl.textContent = `${directories.length} directories linked`;
} else {
if (totalCountEl) totalCountEl.textContent = '0 videos total';
if (directoryCountEl) directoryCountEl.textContent = '0 directories linked';
}
}
function loadUnifiedVideoGallery() {
const gallery = document.getElementById('unified-video-gallery');
console.log('Gallery element found:', !!gallery);
if (!gallery) {
console.error('❌ unified-video-gallery element not found!');
return;
}
// Show loading status for videos
const videoCount = document.querySelector('.video-count');
if (videoCount) {
videoCount.textContent = 'Loading videos...';
}
// Update main loading progress if still loading
if (window.game && !window.game.isInitialized) {
window.game.updateLoadingProgress(85, 'Loading video library...');
}
let videos = [];
if (window.electronAPI && window.desktopFileManager) {
videos = window.desktopFileManager.getAllVideos();
console.log(`Loading unified video gallery: ${videos.length} videos found`);
console.log('Sample video:', videos[0]);
} else {
// Fallback: try to load from unified storage
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
videos = unifiedData.allVideos || [];
console.log('Loaded from localStorage:', videos.length, 'videos');
}
// Update video count display
if (videoCount) {
videoCount.textContent = `${videos.length} videos found`;
console.log('Updated video count display');
}
// Update main loading progress if still loading
if (window.game && !window.game.isInitialized) {
window.game.updateLoadingProgress(95, 'Video library loaded...');
}
if (videos.length === 0) {
console.log('No videos found, showing empty message');
gallery.innerHTML = `
<div class="no-videos-message" style="text-align: center; padding: 40px; color: #666;">
<h4>📁 No videos found</h4>
<p>Link video directories above to build your library</p>
</div>
`;
return;
}
console.log('Proceeding to load gallery with', videos.length, 'videos');
// For large collections, use performance optimizations
if (videos.length > 100) {
console.log('Using large gallery loading');
loadLargeVideoGallery(gallery, videos);
} else {
console.log('Using standard gallery loading');
loadStandardVideoGallery(gallery, videos);
}
}
function loadStandardVideoGallery(gallery, videos) {
console.log('🎬 Loading standard gallery with', videos.length, 'videos');
// Standard loading for smaller collections
const videoHTML = videos.map((video, index) => createVideoItem(video, index)).join('');
gallery.innerHTML = videoHTML;
setupVideoItemHandlers(gallery);
}
function loadLargeVideoGallery(gallery, videos) {
console.log(`🚀 Loading large video gallery with ${videos.length} videos using performance optimizations`);
// Show loading message
gallery.innerHTML = `
<div class="loading-message" style="text-align: center; padding: 40px; color: #888;">
<h4>📼 Loading ${videos.length} videos...</h4>
<div class="progress-bar" style="width: 100%; height: 4px; background: #333; border-radius: 2px; margin: 10px 0;">
<div id="video-load-progress" style="width: 0%; height: 100%; background: #673ab7; border-radius: 2px; transition: width 0.3s;"></div>
</div>
<p>Processing in batches for optimal performance</p>
</div>
`;
// Process videos in chunks to prevent UI blocking
const chunkSize = 50; // Process 50 videos at a time
let currentChunk = 0;
const totalChunks = Math.ceil(videos.length / chunkSize);
function processNextChunk() {
const startIndex = currentChunk * chunkSize;
const endIndex = Math.min(startIndex + chunkSize, videos.length);
const chunk = videos.slice(startIndex, endIndex);
// Update progress
const progress = ((currentChunk + 1) / totalChunks) * 100;
const progressBar = document.getElementById('video-load-progress');
if (progressBar) {
progressBar.style.width = `${progress}%`;
}
if (currentChunk === 0) {
// First chunk - replace loading message with video grid
gallery.innerHTML = '<div class="video-grid-container"></div>';
}
const container = gallery.querySelector('.video-grid-container');
if (container) {
// Add this chunk's videos
const chunkHTML = chunk.map((video, localIndex) =>
createVideoItemLazy(video, startIndex + localIndex)
).join('');
container.insertAdjacentHTML('beforeend', chunkHTML);
}
currentChunk++;
if (currentChunk < totalChunks) {
// Process next chunk with a small delay to keep UI responsive
setTimeout(processNextChunk, 10);
} else {
// All chunks processed
console.log(`✅ Gallery loading complete: ${videos.length} videos rendered`);
setupVideoItemHandlers(gallery);
setupLazyThumbnailLoading();
}
}
// Start processing
setTimeout(processNextChunk, 100);
}
function createVideoItem(video, index) {
return `
<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: #888;">📁 ${video.directoryName || 'Unknown'}</small>
${video.size ? `<small style="color: #888;"> • ${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: #333; display: flex; align-items: center; justify-content: center; color: #666;">
<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: #888;">📁 ${video.directoryName || 'Unknown'}</small>
${video.size ? `<small style="color: #888;"> • ${formatFileSize(video.size)}</small>` : ''}
</div>
<button class="btn btn-small" onclick="previewVideo('${video.url}', '${video.title}')" style="margin-top: 8px;background: linear-gradient(135deg, #4a90e2, #357abd) !important;color: #ffffff !important;border: 1px solidabd !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);
});
}
function setupVideoItemHandlers(gallery) {
// Add click handlers for video selection
gallery.querySelectorAll('.video-item').forEach(item => {
item.addEventListener('click', (e) => {
if (e.target.type !== 'checkbox' && !e.target.closest('button')) {
const checkbox = item.querySelector('.video-checkbox');
checkbox.checked = !checkbox.checked;
item.classList.toggle('selected', checkbox.checked);
}
});
});
}
// Global function for removing directories (called from HTML onclick)
window.removeDirectory = function(directoryId) {
if (window.electronAPI && window.desktopFileManager) {
window.desktopFileManager.removeVideoDirectory(directoryId).then((success) => {
if (success) {
updateDirectoryList();
updateVideoStats();
loadUnifiedVideoGallery();
}
});
}
};
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function handleVideoImport(category) {
console.log(`Importing ${category} videos...`);
if (window.electronAPI && window.desktopFileManager) {
// Desktop version - use desktop file manager with native file dialog
window.desktopFileManager.selectAndImportVideos(category).then((importedVideos) => {
loadVideoGalleryContent(category);
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
loadVideoGalleryContent(category);
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();
}
}
function loadVideoGalleryContent(category) {
const gallery = document.getElementById(`${category}-video-gallery`);
if (!gallery) return;
let videos = [];
// Check if we're in Electron environment and can use file system
if (window.electronAPI && window.desktopFileManager) {
// Load from file system storage
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
const dirCategory = category === 'task' ? 'tasks' :
category === 'reward' ? 'rewards' :
category === 'punishment' ? 'punishments' :
category; // Keep cinema, background as-is
videos = storedVideos[dirCategory] || [];
console.log(`Loading ${category} videos:`, videos.length, 'videos found');
} else {
// Fallback to localStorage
videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
// Clean up any invalid blob URLs from previous uploads
const validVideos = videos.filter(video => {
if (video.url && video.url.startsWith('blob:')) {
console.warn(`🧹 Removing invalid blob URL video: ${video.name}`);
return false;
}
return true;
});
// Save cleaned data if we removed any invalid entries
if (validVideos.length !== videos.length) {
localStorage.setItem(`${category}-videos`, JSON.stringify(validVideos));
videos = validVideos;
console.log(`🧹 Cleaned up ${videos.length - validVideos.length} invalid video entries for ${category}`);
}
}
if (videos.length === 0) {
// Show placeholder content
gallery.innerHTML = `
<div class="video-placeholder">
<h4>📁 ${category.charAt(0).toUpperCase() + category.slice(1)} Videos</h4>
<p>No ${category} videos found. Use the import button above to add video files.</p>
<div class="video-instructions">
<h5>Supported formats:</h5>
<ul>
<li>MP4 (recommended)</li>
<li>WebM</li>
<li>OGV</li>
<li>MOV</li>
</ul>
<h5>Size limits:</h5>
<ul>
<li>Maximum: 50MB per video</li>
<li>Recommended: Under 10MB for best performance</li>
<li>Total storage: ~5MB across all videos</li>
</ul>
</div>
</div>
`;
} else {
// Show video grid
gallery.innerHTML = videos.map((video, index) => `
<div class="video-item" data-video-id="${video.name}">
<div class="video-thumbnail">
<video src="${video.url}" preload="metadata" muted>
Your browser does not support the video element.
</video>
</div>
<div class="video-info">
<div class="video-name">${video.name}</div>
<div class="video-meta">
<small>${formatFileSize(video.size || 0)}${video.type || 'video/mp4'}</small>
</div>
<div class="video-actions">
<button onclick="previewVideo('${video.url}', '${video.name}')" class="btn-small">🎬 Preview</button>
<button onclick="deleteVideo('${category}', '${video.name}')" class="btn-small btn-danger">🗑️ Delete</button>
<input type="checkbox" class="video-select" value="${video.name}">
</div>
</div>
</div>
`).join('');
// Add click handlers to video items for selection
setTimeout(() => {
gallery.querySelectorAll('.video-item').forEach(item => {
const videoName = item.dataset.videoId;
const checkbox = item.querySelector('.video-select');
// Click anywhere on item to select/deselect (like image gallery)
item.addEventListener('click', (e) => {
// Don't toggle selection if clicking on checkbox or buttons
if (e.target.type !== 'checkbox' && e.target.tagName !== 'BUTTON') {
const isCurrentlySelected = checkbox.checked;
checkbox.checked = !isCurrentlySelected;
// Toggle visual selection state
if (checkbox.checked) {
item.classList.add('selected');
} else {
item.classList.remove('selected');
}
}
});
// Checkbox click handler
checkbox.addEventListener('change', (e) => {
e.stopPropagation();
// Toggle visual selection state
if (checkbox.checked) {
item.classList.add('selected');
} else {
item.classList.remove('selected');
}
});
});
}, 0);
}
// Update video count
const videoCount = document.querySelector('.video-count');
if (videoCount) {
videoCount.textContent = `${category}: ${videos.length} videos`;
}
}
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}`);
}
}
function deleteVideo(category, videoName) {
if (confirm(`Delete video "${videoName}"?`)) {
// Check if we're in Electron environment with file system storage
if (window.electronAPI && window.desktopFileManager) {
// Get videos from file system storage
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
const dirCategory = category === 'task' ? 'tasks' :
category === 'reward' ? 'rewards' :
category === 'punishment' ? 'punishments' : category;
if (storedVideos[dirCategory]) {
// Find the video to get its file path
const videoToDelete = storedVideos[dirCategory].find(video => video.name === videoName);
if (videoToDelete && videoToDelete.path) {
// Delete the actual file
window.electronAPI.deleteVideo(videoToDelete.path).then(success => {
if (success) {
console.log(`Successfully deleted video file: ${videoToDelete.path}`);
} else {
console.warn(`Failed to delete video file: ${videoToDelete.path}`);
}
}).catch(error => {
console.error('Error deleting video file:', error);
});
}
// Remove from storage
storedVideos[dirCategory] = storedVideos[dirCategory].filter(video => video.name !== videoName);
localStorage.setItem('videoFiles', JSON.stringify(storedVideos));
}
} else {
// Browser environment - use old localStorage method
const videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
const updatedVideos = videos.filter(video => video.name !== videoName);
localStorage.setItem(`${category}-videos`, JSON.stringify(updatedVideos));
}
// Refresh the gallery
loadVideoGalleryContent(category);
if (window.game && window.game.showNotification) {
window.game.showNotification(`Video "${videoName}" deleted`, 'info');
}
}
}
function selectAllVideos() {
const activeGallery = document.querySelector('.video-gallery.active');
if (activeGallery) {
const checkboxes = activeGallery.querySelectorAll('.video-select');
checkboxes.forEach(checkbox => checkbox.checked = true);
console.log(`Selected ${checkboxes.length} videos in current tab`);
}
}
function deselectAllVideos() {
const activeGallery = document.querySelector('.video-gallery.active');
if (activeGallery) {
const checkboxes = activeGallery.querySelectorAll('.video-select');
checkboxes.forEach(checkbox => checkbox.checked = false);
console.log('Deselected all videos');
}
}
function deleteSelectedVideos() {
const activeGallery = document.querySelector('.video-gallery.active');
if (!activeGallery) return;
const selectedCheckboxes = activeGallery.querySelectorAll('.video-select:checked');
if (selectedCheckboxes.length === 0) {
if (window.game && window.game.showNotification) {
window.game.showNotification('No videos selected', 'warning');
}
return;
}
const videoNames = Array.from(selectedCheckboxes).map(cb => cb.value);
const category = activeGallery.id.replace('-video-gallery', '');
if (confirm(`Delete ${videoNames.length} selected videos?`)) {
// Check if we're in Electron environment with file system storage
if (window.electronAPI && window.desktopFileManager) {
// Get videos from file system storage
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
const dirCategory = category === 'task' ? 'tasks' :
category === 'reward' ? 'rewards' :
category === 'punishment' ? 'punishments' : category;
if (storedVideos[dirCategory]) {
// Find videos to delete and their file paths
const videosToDelete = storedVideos[dirCategory].filter(video => videoNames.includes(video.name));
// Delete actual files
videosToDelete.forEach(video => {
if (video.path) {
window.electronAPI.deleteVideo(video.path).then(success => {
if (success) {
console.log(`Successfully deleted video file: ${video.path}`);
} else {
console.warn(`Failed to delete video file: ${video.path}`);
}
}).catch(error => {
console.error('Error deleting video file:', error);
});
}
});
// Remove from storage
storedVideos[dirCategory] = storedVideos[dirCategory].filter(video => !videoNames.includes(video.name));
localStorage.setItem('videoFiles', JSON.stringify(storedVideos));
}
} else {
// Browser environment - use old localStorage method
const videos = JSON.parse(localStorage.getItem(`${category}-videos`) || '[]');
const updatedVideos = videos.filter(video => !videoNames.includes(video.name));
localStorage.setItem(`${category}-videos`, JSON.stringify(updatedVideos));
}
loadVideoGalleryContent(category);
if (window.game && window.game.showNotification) {
window.game.showNotification(`${videoNames.length} videos deleted`, 'success');
}
}
}
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();
loadUnifiedVideoGallery();
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 =====
const levelData = [
{ level: 1, name: "Virgin", xpRequired: 0 },
{ level: 2, name: "Curious", xpRequired: 100 },
{ level: 3, name: "Eager", xpRequired: 200 },
{ level: 4, name: "Aroused", xpRequired: 300 },
{ level: 5, name: "Lustful", xpRequired: 400 },
{ level: 6, name: "Passionate", xpRequired: 500 },
{ level: 7, name: "Addicted", xpRequired: 600 },
{ level: 8, name: "Obsessed", xpRequired: 700 },
{ level: 9, name: "Deviant", xpRequired: 800 },
{ level: 10, name: "Kinky", xpRequired: 900 },
{ level: 11, name: "Perverted", xpRequired: 1000 },
{ level: 12, name: "Depraved", xpRequired: 1100 },
{ level: 13, name: "Dominant", xpRequired: 1200 },
{ level: 14, name: "Submissive", xpRequired: 1300 },
{ level: 15, name: "Hedonist", xpRequired: 1400 },
{ level: 16, name: "Insatiable", xpRequired: 1500 },
{ level: 17, name: "Transcendent", xpRequired: 1600 },
{ level: 18, name: "Enlightened", xpRequired: 1700 },
{ level: 19, name: "Godlike", xpRequired: 1800 },
{ level: 20, name: "Omnipotent", xpRequired: 1900 }
];
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);
}
}
// ===== 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: linear-gradient(45deg, #1a0033, #330066, #8a2be2);
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();
loadUnifiedVideoGallery();
}, 1000);
}
// 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 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);
console.log('🔍 Library button has handler:', libraryBtn ? libraryBtn.hasAttribute('data-handler-attached') : 'button not found');
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');
console.log('🎮 Game instance available:', !!window.game);
console.log('🔧 showScreen method available:', !!(window.game && typeof window.game.showScreen === 'function'));
if (window.game && typeof window.game.showScreen === 'function') {
console.log('📺 Showing library screen...');
window.game.showScreen('library-screen');
// Set up library tab handlers when screen is shown
setTimeout(() => {
console.log('⚙️ Setting up library handlers...');
setupLibraryHandlers();
}, 100);
} else {
console.error('Game instance not available for library');
console.error('Available game methods:', window.game ? Object.keys(window.game) : 'No game object');
}
});
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();
// Show library screen directly
const libraryScreen = document.getElementById('library-screen');
const startScreen = document.getElementById('start-screen');
if (libraryScreen && startScreen) {
startScreen.classList.remove('active');
libraryScreen.classList.add('active');
// Set up library handlers
setTimeout(() => {
setupLibraryHandlers();
}, 100);
} else {
console.error('Could not find library or start screen');
}
};
}
}
// 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();
};
}
// 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);
// Update audio count display
const audioCountElement = document.getElementById('lib-audio-count');
if (audioCountElement) {
audioCountElement.textContent = `${backgroundTracks.length + ambientTracks.length} files`;
}
// Populate audio gallery
const audioGallery = document.getElementById('lib-audio-gallery');
if (audioGallery && (backgroundTracks.length > 0 || ambientTracks.length > 0)) {
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`);
} else if (audioGallery) {
audioGallery.innerHTML = `
<div class="no-audio-message">
<p>🎵 No audio files found</p>
<p>Import audio to get started</p>
</div>
`;
}
}
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 || [];
let totalVideos = backgroundVideos.length + taskVideos.length + rewardVideos.length + punishmentVideos.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`);
const allPhotosGrid = document.getElementById('lib-all-photos-grid');
const allPhotosCount = document.getElementById('lib-all-photos-count');
if (allPhotosGrid) {
if (capturedPhotos.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
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}">
<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>
`;
}
});
allPhotosGrid.innerHTML = photosHtml;
if (allPhotosCount) allPhotosCount.textContent = `${capturedPhotos.length} photos`;
}
}
// 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
showFlashMessage(`📸 Photo deleted successfully!`, 'success');
// 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];
const imageData = photo.imageData || photo.dataURL;
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);
showFlashMessage(`📥 Photo downloaded: ${filename}`, 'success');
console.log(`📥 Downloaded 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') || '[]');
if (selectedCheckboxes.length === 0) {
showFlashMessage('⚠️ No photos selected for download', 'warning');
return;
}
if (selectedCheckboxes.length === 1) {
// Single photo download
const index = parseInt(selectedCheckboxes[0].dataset.index);
downloadSinglePhoto(index);
return;
}
// Multiple photos - create zip
showFlashMessage('📦 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 index = parseInt(checkbox.dataset.index);
const photo = capturedPhotos[index];
const imageData = photo.imageData || photo.dataURL;
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);
showFlashMessage(`📥 Downloaded ${selectedCheckboxes.length} photos as zip file`, 'success');
} else {
// Fallback: download individually
selectedCheckboxes.forEach((checkbox, downloadIndex) => {
const index = parseInt(checkbox.dataset.index);
setTimeout(() => {
downloadSinglePhoto(index);
}, downloadIndex * 100); // Stagger downloads
});
showFlashMessage(`📥 Downloading ${selectedCheckboxes.length} photos individually`, 'info');
}
} catch (error) {
console.error('Download error:', error);
showFlashMessage('❌ Error creating download', 'error');
}
}
// Delete selected photos
function deleteSelectedPhotos() {
const selectedCheckboxes = document.querySelectorAll('.photo-select:checked');
if (selectedCheckboxes.length === 0) {
showFlashMessage('⚠️ No photos selected for deletion', 'warning');
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
showFlashMessage(`🗑️ Successfully deleted ${indicesToDelete.length} photos!`, 'success');
// Refresh the photo galleries
setupLibraryGalleryTab();
console.log(`🗑️ Bulk deleted ${indicesToDelete.length} photos`);
}
}
// 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);
}
// 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');
}
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`;
}
// Populate the image gallery
populateImageGallery(allImages);
}
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';
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() {
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 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}`);
const videoCountElement = document.getElementById('lib-video-count');
if (videoCountElement) {
videoCountElement.textContent = `${allVideos.length} videos`;
}
populateVideoGallery(allVideos);
}
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`);
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;
// Character images toggle functionality
let charactersVisible = true;
function toggleCharacterImages() {
charactersVisible = !charactersVisible;
const characterElements = document.querySelectorAll('.character-side');
const toggleBtn = document.getElementById('character-toggle-btn');
characterElements.forEach(element => {
element.style.display = charactersVisible ? 'block' : 'none';
});
toggleBtn.textContent = charactersVisible ? '👁️ Hide Images' : '👁️ Show Images';
// Save preference to localStorage
localStorage.setItem('charactersVisible', charactersVisible);
}
// Load saved preference on page load
document.addEventListener('DOMContentLoaded', function() {
const saved = localStorage.getItem('charactersVisible');
if (saved !== null) {
charactersVisible = saved === 'true';
if (!charactersVisible) {
toggleCharacterImages();
}
}
});
window.toggleCharacterImages = toggleCharacterImages;
</script>
<!-- Character Images Toggle Button -->
<button id="character-toggle-btn" class="character-toggle-btn" onclick="toggleCharacterImages()">
👁️ Hide Images
</button>
</html>