2623 lines
142 KiB
HTML
2623 lines
142 KiB
HTML
<!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 href="https://fonts.googleapis.com/css2?family=Audiowide&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">
|
||
<!-- Game Header -->
|
||
<header class="game-header">
|
||
<h1>Gooner Training Academy</h1>
|
||
<p class="tagline">Master Your Dedication</p>
|
||
|
||
<!-- Compact Timer (top-right corner) -->
|
||
<div class="timer-compact">
|
||
<span id="timer" class="timer">00:00</span>
|
||
<span id="timer-status" class="timer-status"></span>
|
||
</div>
|
||
|
||
<!-- Compact Music Controls (expandable) -->
|
||
<div class="music-controls-compact">
|
||
<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">
|
||
<h2>Ready to Start?</h2>
|
||
<p>Complete tasks, but beware of skipping - there are consequences!</p>
|
||
|
||
<!-- Main Action Buttons -->
|
||
<div class="main-actions">
|
||
<button id="start-btn" class="btn btn-primary">Start Game</button>
|
||
<button id="manage-tasks-btn" class="btn btn-secondary">Manage Tasks</button>
|
||
<button id="manage-images-btn" class="btn btn-secondary">Manage Images</button>
|
||
<button id="manage-audio-btn" class="btn btn-secondary">🎵 Manage Audio</button>
|
||
<button id="manage-video-btn" class="btn btn-secondary">🎬 Manage Video</button>
|
||
<button id="photo-gallery-btn" class="btn btn-secondary">📸 Photo Gallery</button>
|
||
<button id="manage-annoyance-btn" class="btn btn-secondary">😈 Annoyance</button>
|
||
</div>
|
||
|
||
<!-- Game Mode Selection -->
|
||
<div class="game-mode-selection">
|
||
<h3>Game Mode</h3>
|
||
<div class="game-mode-options">
|
||
<div class="game-mode-option">
|
||
<input type="radio" id="mode-complete-all" name="gameMode" value="complete-all" checked>
|
||
<label for="mode-complete-all">
|
||
<strong>🎯 Complete All Tasks</strong>
|
||
<p>Finish every task in the game</p>
|
||
</label>
|
||
</div>
|
||
<div class="game-mode-option">
|
||
<input type="radio" id="mode-timed" name="gameMode" value="timed">
|
||
<label for="mode-timed">
|
||
<strong>⏱️ Timed Challenge</strong>
|
||
<p>Complete as many tasks as possible within the time limit</p>
|
||
<div class="mode-config" id="timed-config" style="display: none;">
|
||
<label>Time Limit:
|
||
<select id="time-limit-select">
|
||
<option value="300">5 minutes</option>
|
||
<option value="600">10 minutes</option>
|
||
<option value="900">15 minutes</option>
|
||
<option value="1200">20 minutes</option>
|
||
<option value="1800">30 minutes</option>
|
||
<option value="custom">Custom...</option>
|
||
</select>
|
||
</label>
|
||
<div id="custom-time-input" style="display: none; margin-top: 10px;">
|
||
<label>Custom time (minutes):
|
||
<input type="number" id="custom-time-value" min="1" max="180" value="15" style="width: 60px;">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
<div class="game-mode-option">
|
||
<input type="radio" id="mode-xp-target" name="gameMode" value="xp-target">
|
||
<label for="mode-xp-target">
|
||
<strong>🏆 XP Target</strong>
|
||
<p>Reach the target XP to win</p>
|
||
<div class="mode-config" id="xp-target-config" style="display: none;">
|
||
<label>Target XP:
|
||
<select id="xp-target-select">
|
||
<option value="100">100 XP</option>
|
||
<option value="200">200 XP</option>
|
||
<option value="300">300 XP</option>
|
||
<option value="custom">Custom...</option>
|
||
</select>
|
||
</label>
|
||
<div id="custom-xp-input" style="display: none; margin-top: 10px;">
|
||
<label>Custom target XP:
|
||
<input type="number" id="custom-xp-value" min="50" max="10000" value="300" style="width: 80px;">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Options Menu -->
|
||
<div class="options-section">
|
||
<button id="options-menu-btn" class="btn btn-tertiary">⚙️ Options</button>
|
||
<div id="options-menu" class="options-menu" style="display: none;">
|
||
<!-- 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>
|
||
<option value="balanced-red">❤️ Balanced Red</option>
|
||
<option value="balanced-blue">💙 Balanced Blue</option>
|
||
<option value="balanced-green">💚 Balanced Forest Green</option>
|
||
</select>
|
||
</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="stats-btn" class="btn btn-option" title="View Statistics">📊 Stats</button>
|
||
<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>
|
||
|
||
<!-- Task Management Screen -->
|
||
<div id="task-management-screen" class="screen">
|
||
<h2>Manage Your Tasks</h2>
|
||
|
||
<!-- Add New Task Section -->
|
||
<div class="task-editor-section">
|
||
<h3>Add New Task</h3>
|
||
<div class="task-input-group">
|
||
<label for="new-task-text">Task Description:</label>
|
||
<textarea id="new-task-text" placeholder="Enter your task description..." rows="3"></textarea>
|
||
</div>
|
||
<div class="task-input-group">
|
||
<label for="new-task-type">Task Type:</label>
|
||
<select id="new-task-type">
|
||
<option value="main">Main Task</option>
|
||
<option value="consequence">Consequence Task</option>
|
||
</select>
|
||
</div>
|
||
<div class="task-input-group" id="difficulty-input-group">
|
||
<label for="new-task-difficulty">Difficulty:</label>
|
||
<select id="new-task-difficulty">
|
||
<option value="Easy">🟢 Easy (1 point)</option>
|
||
<option value="Medium" selected>🟡 Medium (3 points)</option>
|
||
<option value="Hard">🔴 Hard (5 points)</option>
|
||
</select>
|
||
</div>
|
||
<button id="add-task-btn" class="btn btn-success">Add Task</button>
|
||
</div>
|
||
|
||
<!-- Existing Tasks Section -->
|
||
<div class="task-list-section">
|
||
<h3>Your Tasks</h3>
|
||
<div class="task-tabs">
|
||
<button id="main-tasks-tab" class="tab-btn active">Main Tasks</button>
|
||
<button id="consequence-tasks-tab" class="tab-btn">Consequence Tasks</button>
|
||
</div>
|
||
|
||
<div id="main-tasks-list" class="task-list active">
|
||
<!-- Main tasks will be populated here -->
|
||
</div>
|
||
|
||
<div id="consequence-tasks-list" class="task-list">
|
||
<!-- Consequence tasks will be populated here -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="management-buttons">
|
||
<button id="back-to-start-btn" class="btn btn-secondary">Back to Start</button>
|
||
<button id="reset-tasks-btn" class="btn btn-warning">Reset to Defaults</button>
|
||
</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>🎥 Import Video Files</h3>
|
||
<div class="upload-controls">
|
||
<button id="import-background-video-btn" class="btn btn-primary">🌄 Background Videos</button>
|
||
<button id="import-task-video-btn" class="btn btn-success">🎯 Task Videos</button>
|
||
<button id="import-reward-video-btn" class="btn btn-info">🏆 Reward Videos</button>
|
||
<button id="import-punishment-video-btn" class="btn btn-warning">⚠️ Punishment Videos</button>
|
||
<input type="file" id="video-upload-input" accept="video/*" multiple style="display: none;">
|
||
</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>📹 Current 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">Delete Selected</button>
|
||
<button id="refresh-videos-btn" class="btn btn-secondary btn-small">🔄 Refresh</button>
|
||
<span class="video-count">Loading video files...</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Video Tabs -->
|
||
<div class="video-tabs">
|
||
<button id="background-video-tab" class="tab-btn active">🌄 Background</button>
|
||
<button id="task-video-tab" class="tab-btn">🎯 Tasks</button>
|
||
<button id="reward-video-tab" class="tab-btn">🏆 Rewards</button>
|
||
<button id="punishment-video-tab" class="tab-btn">⚠️ Punishments</button>
|
||
</div>
|
||
|
||
<div class="video-galleries-container">
|
||
<div id="background-video-gallery" class="video-gallery active">
|
||
<!-- Background video files will be populated here -->
|
||
</div>
|
||
|
||
<div id="task-video-gallery" class="video-gallery">
|
||
<!-- Task video files will be populated here -->
|
||
</div>
|
||
|
||
<div id="reward-video-gallery" class="video-gallery">
|
||
<!-- Reward video files will be populated here -->
|
||
</div>
|
||
|
||
<div id="punishment-video-gallery" class="video-gallery">
|
||
<!-- Punishment video files will be populated 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">×</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">×</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>
|
||
|
||
<!-- 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">Total XP:</span>
|
||
<span id="overall-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">×</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">×</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/images/image-discovery-fix.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/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>
|
||
// Force apply emergency fix
|
||
window.addEventListener('load', () => {
|
||
console.log('🚨 Page loaded - applying emergency fixes...');
|
||
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);
|
||
}
|
||
} // Video tab buttons
|
||
const videoTabs = [
|
||
{ id: 'background-video-tab', gallery: 'background-video-gallery', category: 'background' },
|
||
{ id: 'task-video-tab', gallery: 'task-video-gallery', category: 'task' },
|
||
{ id: 'reward-video-tab', gallery: 'reward-video-gallery', category: 'reward' },
|
||
{ id: 'punishment-video-tab', gallery: 'punishment-video-gallery', category: 'punishment' }
|
||
];
|
||
|
||
videoTabs.forEach(tab => {
|
||
const tabBtn = document.getElementById(tab.id);
|
||
if (tabBtn) {
|
||
tabBtn.addEventListener('click', () => {
|
||
console.log(`Switching to ${tab.category} videos`);
|
||
|
||
// Update active tab
|
||
document.querySelectorAll('.video-tabs .tab-btn').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
tabBtn.classList.add('active');
|
||
|
||
// Hide all galleries first
|
||
document.querySelectorAll('.video-gallery').forEach(gallery => {
|
||
gallery.classList.remove('active');
|
||
gallery.style.display = 'none';
|
||
});
|
||
|
||
// Show the selected gallery
|
||
const targetGallery = document.getElementById(tab.gallery);
|
||
if (targetGallery) {
|
||
targetGallery.classList.add('active');
|
||
targetGallery.style.display = 'grid';
|
||
}
|
||
|
||
// Load videos for this category
|
||
loadVideoGalleryContent(tab.category);
|
||
});
|
||
}
|
||
});
|
||
|
||
// Import video buttons
|
||
const importButtons = [
|
||
{ id: 'import-background-video-btn', category: 'background' },
|
||
{ id: 'import-task-video-btn', category: 'task' },
|
||
{ id: 'import-reward-video-btn', category: 'reward' },
|
||
{ id: 'import-punishment-video-btn', category: 'punishment' }
|
||
];
|
||
|
||
importButtons.forEach(button => {
|
||
const btn = document.getElementById(button.id);
|
||
if (btn) {
|
||
btn.addEventListener('click', () => {
|
||
handleVideoImport(button.category);
|
||
});
|
||
}
|
||
});
|
||
|
||
// 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();
|
||
|
||
// Initial load - ensure only background tab is active
|
||
// Hide all galleries first
|
||
document.querySelectorAll('.video-gallery').forEach(gallery => {
|
||
gallery.classList.remove('active');
|
||
gallery.style.display = 'none';
|
||
});
|
||
|
||
// Show only background gallery
|
||
const backgroundGallery = document.getElementById('background-video-gallery');
|
||
if (backgroundGallery) {
|
||
backgroundGallery.classList.add('active');
|
||
backgroundGallery.style.display = 'grid';
|
||
}
|
||
|
||
// Scan for new videos if in desktop mode
|
||
if (window.electronAPI && window.desktopFileManager) {
|
||
console.log('🔍 Scanning for new videos...');
|
||
|
||
// Clear existing video storage to force fresh scan
|
||
localStorage.removeItem('videoFiles');
|
||
|
||
Promise.all([
|
||
window.desktopFileManager.scanDirectoryForVideos('background'),
|
||
window.desktopFileManager.scanDirectoryForVideos('tasks'),
|
||
window.desktopFileManager.scanDirectoryForVideos('rewards'),
|
||
window.desktopFileManager.scanDirectoryForVideos('punishments')
|
||
]).then(([backgroundVideos, taskVideos, rewardVideos, punishmentVideos]) => {
|
||
const allVideos = [...backgroundVideos, ...taskVideos, ...rewardVideos, ...punishmentVideos];
|
||
if (allVideos.length > 0) {
|
||
window.desktopFileManager.updateVideoStorage(allVideos);
|
||
console.log(`📹 Found ${allVideos.length} videos in directories`);
|
||
}
|
||
// Reload all galleries after scanning
|
||
loadVideoGalleryContent('background');
|
||
loadVideoGalleryContent('task');
|
||
loadVideoGalleryContent('reward');
|
||
loadVideoGalleryContent('punishment');
|
||
}).catch(error => {
|
||
console.error('Error scanning video directories:', error);
|
||
// Still load the galleries even if scanning fails
|
||
loadVideoGalleryContent('background');
|
||
});
|
||
} else {
|
||
loadVideoGalleryContent('background');
|
||
}
|
||
|
||
// 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 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;
|
||
videos = storedVideos[dirCategory] || [];
|
||
} 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) {
|
||
// Clear existing video storage
|
||
localStorage.removeItem('videoFiles');
|
||
|
||
if (window.game && window.game.showNotification) {
|
||
window.game.showNotification('🔄 Refreshing video library...', 'info');
|
||
}
|
||
|
||
// Rescan all directories
|
||
Promise.all([
|
||
window.desktopFileManager.scanDirectoryForVideos('background'),
|
||
window.desktopFileManager.scanDirectoryForVideos('tasks'),
|
||
window.desktopFileManager.scanDirectoryForVideos('rewards'),
|
||
window.desktopFileManager.scanDirectoryForVideos('punishments')
|
||
]).then(([backgroundVideos, taskVideos, rewardVideos, punishmentVideos]) => {
|
||
const allVideos = [...backgroundVideos, ...taskVideos, ...rewardVideos, ...punishmentVideos];
|
||
if (allVideos.length > 0) {
|
||
window.desktopFileManager.updateVideoStorage(allVideos);
|
||
}
|
||
|
||
// Reload all galleries
|
||
loadVideoGalleryContent('background');
|
||
loadVideoGalleryContent('task');
|
||
loadVideoGalleryContent('reward');
|
||
loadVideoGalleryContent('punishment');
|
||
|
||
if (window.game && window.game.showNotification) {
|
||
window.game.showNotification(`✅ Video library refreshed! Found ${allVideos.length} 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');
|
||
}
|
||
}
|
||
}
|
||
|
||
function testVideoPlayback(type) {
|
||
console.log(`Testing ${type} video playback`);
|
||
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}%`;
|
||
}
|
||
}
|
||
|
||
// Initialize everything when DOM is ready
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
setTimeout(() => {
|
||
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');
|
||
}
|
||
|
||
// 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');
|
||
}
|
||
});
|
||
}
|
||
}, 1000);
|
||
});
|
||
</script>
|
||
</html> |