10259 lines
432 KiB
HTML
10259 lines
432 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>Quick Play - Gooner Training Academy</title>
|
||
<link rel="stylesheet" href="src/styles/styles.css">
|
||
<link rel="stylesheet" href="src/styles/base-video-player.css">
|
||
<link rel="stylesheet" href="src/styles/overlay-video-player.css">
|
||
<link href="https://fonts.googleapis.com/css2?family=Audiowide&family=Michroma&family=Electrolize:wght@400&display=swap" rel="stylesheet">
|
||
<script src="https://unpkg.com/jszip@3.10.1/dist/jszip.min.js" crossorigin="anonymous"></script>
|
||
</head>
|
||
<body class="quick-play-mode">
|
||
<!-- Video toggle and mute buttons removed for streamlined interface -->
|
||
|
||
<!-- Loading Overlay -->
|
||
<div id="quick-play-loading" class="loading-overlay">
|
||
<div class="loading-content">
|
||
<div class="loading-spinner"></div>
|
||
<h2>⚡ Loading Quick Play...</h2>
|
||
<p class="loading-status" id="loading-status">Preparing your session...</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>
|
||
|
||
|
||
|
||
<!-- Quick Play Header -->
|
||
<header class="quick-play-header">
|
||
<div class="quick-play-nav">
|
||
<div class="nav-left">
|
||
<h1>⚡ Quick Play</h1>
|
||
</div>
|
||
<div class="nav-center game-status-bar" id="game-status-bar" style="display: none;">
|
||
<div class="status-item">
|
||
<span class="status-label">TIME:</span>
|
||
<span id="game-timer" class="status-value timer-display">00:02</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span class="status-label">TASKS:</span>
|
||
<span id="tasks-completed" class="status-value">0</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span class="status-label">XP:</span>
|
||
<span id="current-xp" class="status-value">0</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span class="status-label">SESSION:</span>
|
||
<span id="session-status" class="status-value">Ready</span>
|
||
</div>
|
||
</div>
|
||
<div class="nav-right quick-play-controls">
|
||
|
||
<button id="back-to-home" class="btn btn-secondary">🏠 Home</button>
|
||
<button id="quick-play-webcam-btn" class="btn btn-info" title="Take a photo with webcam">📸 Photo</button>
|
||
<button id="force-exit" class="btn btn-danger" title="Force close application">❌ Force Exit</button>
|
||
<button id="pause-game-btn" class="btn btn-secondary" style="display: none;">⏸️ Pause</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Quick Play Content -->
|
||
<main class="quick-play-main">
|
||
<!-- Welcome/Setup Screen -->
|
||
<div class="quick-play-setup" id="quick-play-setup">
|
||
<div class="setup-container">
|
||
<div class="setup-header">
|
||
<h2>⚡ Quick Play Setup</h2>
|
||
<p>Configure your session for optimal training</p>
|
||
</div>
|
||
|
||
<div class="setup-content">
|
||
<!-- Play Time Settings -->
|
||
<div class="setting-group">
|
||
<h3>🕒 Session Duration</h3>
|
||
<div class="setting-options">
|
||
<div class="time-preset" data-time="900">
|
||
<div class="preset-value">15 min</div>
|
||
<div class="preset-label">Quick Session</div>
|
||
</div>
|
||
<div class="time-preset active" data-time="1800">
|
||
<div class="preset-value">30 min</div>
|
||
<div class="preset-label">Standard</div>
|
||
</div>
|
||
<div class="time-preset" data-time="3600">
|
||
<div class="preset-value">1 hour</div>
|
||
<div class="preset-label">Extended</div>
|
||
</div>
|
||
<div class="time-preset custom" data-time="custom">
|
||
<div class="preset-value">Custom</div>
|
||
<div class="preset-label">
|
||
<input type="number" id="custom-time" min="1" max="480" value="45" placeholder="min">
|
||
</div>
|
||
</div>
|
||
<div class="time-preset" data-time="endless">
|
||
<div class="preset-value">∞</div>
|
||
<div class="preset-label">Endless</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Audio Settings -->
|
||
<div class="setting-group">
|
||
<h3>🔊 Audio Experience</h3>
|
||
<div class="audio-settings">
|
||
<div class="audio-option">
|
||
<input type="checkbox" id="enable-background-audio" checked>
|
||
<label for="enable-background-audio">
|
||
<span class="option-icon">🎵</span>
|
||
<span class="option-text">Background Music</span>
|
||
</label>
|
||
</div>
|
||
<div class="audio-option">
|
||
<input type="checkbox" id="enable-ambient-audio" checked>
|
||
<label for="enable-ambient-audio">
|
||
<span class="option-icon">🌊</span>
|
||
<span class="option-text">Ambient Sounds</span>
|
||
</label>
|
||
</div>
|
||
<div class="audio-option">
|
||
<input type="checkbox" id="enable-voice-commands">
|
||
<label for="enable-voice-commands">
|
||
<span class="option-icon">🎤</span>
|
||
<span class="option-text">Voice Commands</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Video Settings -->
|
||
<div class="setting-group">
|
||
<h3>🎬 Video Experience</h3>
|
||
<div class="video-settings">
|
||
<div class="setting-row">
|
||
<label for="video-mode">Video Mode:</label>
|
||
<select id="video-mode">
|
||
<option value="none">None - Focus Mode</option>
|
||
<option value="background" selected>Background Video</option>
|
||
<option value="popup">Popup Rewards</option>
|
||
<option value="multi-screen">Multi-Screen (Advanced)</option>
|
||
</select>
|
||
</div>
|
||
<div class="video-options" id="video-options">
|
||
<div class="video-option">
|
||
<input type="checkbox" id="enable-video-sound" checked>
|
||
<label for="enable-video-sound">
|
||
<span class="option-icon">🔊</span>
|
||
<span class="option-text">Video Audio</span>
|
||
</label>
|
||
</div>
|
||
<div class="video-option">
|
||
<input type="checkbox" id="enable-video-controls">
|
||
<label for="enable-video-controls">
|
||
<span class="option-icon">🎛️</span>
|
||
<span class="option-text">Show Video Controls</span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<label for="video-opacity">Video Opacity:</label>
|
||
<select id="video-opacity">
|
||
<option value="0.3">30% - Subtle</option>
|
||
<option value="0.5">50% - Balanced</option>
|
||
<option value="0.7" selected>70% - Prominent</option>
|
||
<option value="1.0">100% - Full</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Webcam Recording Settings -->
|
||
<div class="setting-group">
|
||
<h3>📹 Webcam Recording</h3>
|
||
<div class="webcam-settings">
|
||
<div class="webcam-option">
|
||
<input type="checkbox" id="enable-session-recording">
|
||
<label for="enable-session-recording">
|
||
<span class="option-icon">🎥</span>
|
||
<span class="option-text">Enable Session Recording</span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-description">
|
||
Records your session with a small webcam viewer. All recordings are stored locally on your device.
|
||
</div>
|
||
<div class="webcam-sub-options" id="webcam-sub-options" style="display: none;">
|
||
<div class="setting-row">
|
||
<label for="webcam-output-directory">Output Directory:</label>
|
||
<div class="directory-selector">
|
||
<input type="text" id="webcam-output-path" readonly placeholder="Select directory for saving recordings..." style="flex: 1; margin-right: 10px;">
|
||
<button type="button" id="select-webcam-directory" class="btn btn-secondary">📁 Browse</button>
|
||
</div>
|
||
<div class="setting-description">
|
||
Choose where session recordings will be automatically saved. Directory will be remembered for future sessions. If no directory is selected, recordings will download automatically.
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<label for="webcam-position">Viewer Position:</label>
|
||
<select id="webcam-position">
|
||
<option value="bottom-right" selected>Bottom Right</option>
|
||
<option value="bottom-left">Bottom Left</option>
|
||
<option value="top-right">Top Right</option>
|
||
<option value="top-left">Top Left</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<label for="webcam-size">Viewer Size:</label>
|
||
<select id="webcam-size">
|
||
<option value="small" selected>Small (150px)</option>
|
||
<option value="medium">Medium (200px)</option>
|
||
<option value="large">Large (250px)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Consequence Settings -->
|
||
<div class="setting-group">
|
||
<h3>🚨 Consequence System</h3>
|
||
<div class="consequence-settings">
|
||
<div class="setting-row">
|
||
<label for="consequence-chance">Skip Consequence Chance:</label>
|
||
<div class="slider-container">
|
||
<input type="range" id="consequence-chance" min="0" max="100" value="100" class="slider">
|
||
<span id="consequence-chance-value" class="slider-value">100%</span>
|
||
</div>
|
||
<div class="setting-description">
|
||
Percentage chance that skipping a task will trigger a consequence task
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Task Types -->
|
||
<div class="setting-group">
|
||
<h3>📋 Task Types</h3>
|
||
<div class="task-type-settings">
|
||
<div class="task-type-option">
|
||
<input type="checkbox" id="include-standard-tasks" checked>
|
||
<label for="include-standard-tasks">
|
||
<span class="option-icon">📝</span>
|
||
<span class="option-text">Standard Tasks</span>
|
||
<span class="option-desc">Quick individual tasks</span>
|
||
</label>
|
||
</div>
|
||
<div class="task-type-option">
|
||
<input type="checkbox" id="include-scenario-tasks">
|
||
<label for="include-scenario-tasks">
|
||
<span class="option-icon">🎭</span>
|
||
<span class="option-text">Scenario Adventures</span>
|
||
<span class="option-desc">Longer interactive storylines</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Duration Settings -->
|
||
<div class="setting-group">
|
||
<h3>⏱️ Task Duration</h3>
|
||
<div class="setting-row">
|
||
<label for="duration-setting">Duration Mode:</label>
|
||
<select id="duration-setting">
|
||
<option value="short">Short (1-5 minutes)</option>
|
||
<option value="medium" selected>Medium (5-10 minutes)</option>
|
||
<option value="long">Long (15-20 minutes)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tag Selector -->
|
||
<div class="setting-group">
|
||
<h3>🏷️ Include Tags</h3>
|
||
<div class="tag-selector" id="include-tags">
|
||
<div class="tag-category">
|
||
<h4>Content Types</h4>
|
||
<div class="tag-options">
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="bbc">
|
||
<span class="tag-name">BBC</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="cuckold">
|
||
<span class="tag-name">Cuckold</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="interracial">
|
||
<span class="tag-name">Interracial</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="gangbang">
|
||
<span class="tag-name">Gangbang</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="milf">
|
||
<span class="tag-name">MILF</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="trans">
|
||
<span class="tag-name">Trans</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="femdom">
|
||
<span class="tag-name">Femdom</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="compilation">
|
||
<span class="tag-name">Compilation</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="sissy">
|
||
<span class="tag-name">Sissy</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="hypno">
|
||
<span class="tag-name">Hypno</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="captions">
|
||
<span class="tag-name">Captions</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="tag-category">
|
||
<h4>Play Types</h4>
|
||
<div class="tag-options">
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="cbt">
|
||
<span class="tag-name">CBT</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="humiliation">
|
||
<span class="tag-name">Humiliation</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="verbal">
|
||
<span class="tag-name">Verbal</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="feminization">
|
||
<span class="tag-name">Feminization</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="pet-play">
|
||
<span class="tag-name">Pet Play</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="anal">
|
||
<span class="tag-name">Anal</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="foot-worship">
|
||
<span class="tag-name">Foot Worship</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="online-exposure">
|
||
<span class="tag-name">Online Exposure</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tag Excluder -->
|
||
<div class="setting-group">
|
||
<h3>🚫 Exclude Tags</h3>
|
||
<div class="tag-selector" id="exclude-tags">
|
||
<div class="tag-category">
|
||
<h4>Content Types</h4>
|
||
<div class="tag-options">
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="bbc">
|
||
<span class="tag-name">BBC</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="cuckold">
|
||
<span class="tag-name">Cuckold</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="interracial">
|
||
<span class="tag-name">Interracial</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="gangbang">
|
||
<span class="tag-name">Gangbang</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="milf">
|
||
<span class="tag-name">MILF</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="trans">
|
||
<span class="tag-name">Trans</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="femdom">
|
||
<span class="tag-name">Femdom</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="compilation">
|
||
<span class="tag-name">Compilation</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="sissy">
|
||
<span class="tag-name">Sissy</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="hypno">
|
||
<span class="tag-name">Hypno</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="captions">
|
||
<span class="tag-name">Captions</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="tag-category">
|
||
<h4>Play Types</h4>
|
||
<div class="tag-options">
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="cbt">
|
||
<span class="tag-name">CBT</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="humiliation">
|
||
<span class="tag-name">Humiliation</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="verbal">
|
||
<span class="tag-name">Verbal</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="feminization">
|
||
<span class="tag-name">Feminization</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="pet-play">
|
||
<span class="tag-name">Pet Play</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="anal">
|
||
<span class="tag-name">Anal</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="foot-worship">
|
||
<span class="tag-name">Foot Worship</span>
|
||
</label>
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="online-exposure">
|
||
<span class="tag-name">Online Exposure</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="setup-actions">
|
||
<button id="start-quick-play" class="btn btn-primary btn-large">
|
||
⚡ Start Quick Play Session
|
||
</button>
|
||
<button id="manage-tasks-btn" class="btn btn-secondary">
|
||
📝 Manage Tasks
|
||
</button>
|
||
<button id="manage-messages-btn" class="btn btn-secondary">
|
||
💬 Manage Messages
|
||
</button>
|
||
<button id="manage-popup-images-btn" class="btn btn-secondary">
|
||
🖼️ Manage Popup Images
|
||
</button>
|
||
<button id="load-preset" class="btn btn-secondary">
|
||
📁 Load Saved Preset
|
||
</button>
|
||
<button id="save-preset" class="btn btn-secondary">
|
||
💾 Save Current Preset
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Game Screen -->
|
||
<div class="quick-play-game" id="quick-play-game" style="display: none;">
|
||
<!-- Game Container (will contain the full game interface) -->
|
||
<div id="game-container-wrapper">
|
||
<!-- Game will be loaded here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Task Management Screen -->
|
||
<div class="quick-play-task-management" id="quick-play-task-management" style="display: none;">
|
||
<div class="task-management-container">
|
||
<div class="task-management-header">
|
||
<h2>📝 Quick Play Task Management</h2>
|
||
<p>View and manage your tasks and consequences with custom tags</p>
|
||
</div>
|
||
|
||
<!-- Task Type Tabs -->
|
||
<div class="task-type-tabs">
|
||
<button id="main-tasks-tab" class="task-tab-btn active" data-type="main">
|
||
📋 Main Tasks
|
||
</button>
|
||
<button id="consequence-tasks-tab" class="task-tab-btn" data-type="consequence">
|
||
🚨 Consequence Tasks
|
||
</button>
|
||
<button id="tag-management-tab" class="task-tab-btn" data-type="tags">
|
||
🏷️ Tag Management
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Main Tasks Section -->
|
||
<div id="main-tasks-section" class="task-section active">
|
||
<!-- Add New Main Task -->
|
||
<div class="add-task-section">
|
||
<h3>Add New Main Task</h3>
|
||
<div class="task-form">
|
||
<div class="form-row">
|
||
<label for="main-task-text">Task Description:</label>
|
||
<textarea id="main-task-text" placeholder="Enter task description..." rows="3"></textarea>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="main-task-tags">Tags:</label>
|
||
<div class="multi-select-container">
|
||
<select id="main-task-tags" multiple class="tag-dropdown" size="6">
|
||
<!-- Tags will be populated here -->
|
||
</select>
|
||
<small class="form-help">Hold Ctrl/Cmd to select multiple tags</small>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button id="add-main-task-btn" class="btn btn-success">+ Add Main Task</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Existing Main Tasks -->
|
||
<div class="existing-tasks-section">
|
||
<h3>Existing Main Tasks</h3>
|
||
<div class="task-filter">
|
||
<input type="text" id="main-task-search" placeholder="Search tasks..." class="search-input">
|
||
<select id="main-task-filter" class="filter-select">
|
||
<option value="">All Tasks</option>
|
||
<option value="preset">Preset Tasks</option>
|
||
<option value="custom">Custom Tasks</option>
|
||
</select>
|
||
</div>
|
||
<div id="main-tasks-list" class="task-display-list">
|
||
<!-- Tasks will be populated here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Consequence Tasks Section -->
|
||
<div id="consequence-tasks-section" class="task-section">
|
||
<!-- Add New Consequence Task -->
|
||
<div class="add-task-section">
|
||
<h3>Add New Consequence Task</h3>
|
||
<div class="task-form">
|
||
<div class="form-row">
|
||
<label for="consequence-task-text">Task Description:</label>
|
||
<textarea id="consequence-task-text" placeholder="Enter consequence task description..." rows="3"></textarea>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="consequence-task-tags">Tags:</label>
|
||
<div class="multi-select-container">
|
||
<select id="consequence-task-tags" multiple class="tag-dropdown" size="6">
|
||
<!-- Tags will be populated here -->
|
||
</select>
|
||
<small class="form-help">Hold Ctrl/Cmd to select multiple tags</small>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button id="add-consequence-task-btn" class="btn btn-success">+ Add Consequence Task</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Existing Consequence Tasks -->
|
||
<div class="existing-tasks-section">
|
||
<h3>Existing Consequence Tasks</h3>
|
||
<div class="task-filter">
|
||
<input type="text" id="consequence-task-search" placeholder="Search tasks..." class="search-input">
|
||
<select id="consequence-task-filter" class="filter-select">
|
||
<option value="">All Tasks</option>
|
||
<option value="preset">Preset Tasks</option>
|
||
<option value="custom">Custom Tasks</option>
|
||
</select>
|
||
</div>
|
||
<div id="consequence-tasks-list" class="task-display-list">
|
||
<!-- Tasks will be populated here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tag Management Section -->
|
||
<div id="tag-management-section" class="task-section">
|
||
<h3>🏷️ Tag Management</h3>
|
||
|
||
<!-- Add New Tag -->
|
||
<div class="add-tag-section">
|
||
<h4>Add New Tag</h4>
|
||
<div class="tag-form">
|
||
<div class="form-group">
|
||
<label for="new-tag-input">Tag Name:</label>
|
||
<input type="text" id="new-tag-input" placeholder="Enter new tag name" maxlength="20">
|
||
</div>
|
||
<button id="add-tag-btn" class="btn btn-primary">➕ Add Tag</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Existing Tags -->
|
||
<div class="existing-tags-section">
|
||
<h4>Existing Custom Tags</h4>
|
||
<div id="custom-tags-list" class="tags-list">
|
||
<!-- Custom tags will be populated here -->
|
||
</div>
|
||
<p class="tag-info">💡 Preset tags cannot be deleted. Only custom tags can be managed here.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Management Actions -->
|
||
<div class="task-management-actions">
|
||
<button id="export-tasks-btn" class="btn btn-secondary">📤 Export Tasks</button>
|
||
<button id="import-tasks-btn" class="btn btn-secondary">📥 Import Tasks</button>
|
||
<button id="reset-tasks-btn" class="btn btn-warning">🔄 Reset to Defaults</button>
|
||
<button id="back-to-setup-btn" class="btn btn-secondary">⬅️ Back to Setup</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Message Management Screen -->
|
||
<div class="quick-play-message-management" id="quick-play-message-management" style="display: none;">
|
||
<div class="message-management-container">
|
||
<div class="message-management-header">
|
||
<h2>💬 Quick Play Message Management</h2>
|
||
<p>Configure flash messages and appearance settings for your Quick Play sessions</p>
|
||
</div>
|
||
|
||
<!-- Message Type Tabs -->
|
||
<div class="message-type-tabs">
|
||
<button id="flash-messages-tab" class="message-tab-btn active" data-type="flash">
|
||
💫 Flash Messages
|
||
</button>
|
||
<button id="message-appearance-tab" class="message-tab-btn" data-type="appearance">
|
||
🎨 Appearance
|
||
</button>
|
||
<button id="import-export-tab" class="message-tab-btn" data-type="import-export">
|
||
📁 Import/Export
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Flash Messages Section -->
|
||
<div id="flash-messages-section" class="message-section active">
|
||
<div class="message-section-content">
|
||
<!-- Enable/Disable Flash Messages -->
|
||
<div class="control-section">
|
||
<div class="control-group">
|
||
<label class="switch-label">
|
||
<input type="checkbox" id="flash-messages-enabled" checked />
|
||
<span class="switch"></span>
|
||
Enable Flash Messages
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Message Behavior Settings -->
|
||
<div class="control-section">
|
||
<h4>⚡ Behavior Settings</h4>
|
||
<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>
|
||
|
||
<!-- Message Management -->
|
||
<div class="control-section">
|
||
<h4>💬 Message Management</h4>
|
||
|
||
<!-- Add New Message -->
|
||
<div class="add-message-section">
|
||
<h5>Add New Message</h5>
|
||
<div class="message-form">
|
||
<div class="form-row">
|
||
<label for="new-message-text">Message Text:</label>
|
||
<textarea id="new-message-text" placeholder="Enter your motivational message..." rows="3" maxlength="200"></textarea>
|
||
<div class="char-counter">
|
||
<span id="message-char-count">0</span>/200 characters
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="message-category">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 for="message-priority">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="form-actions">
|
||
<button id="add-message-btn" class="btn btn-success">+ Add Message</button>
|
||
<button id="preview-message-btn" class="btn btn-info">Preview</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="message-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">Loading messages...</span>
|
||
</div>
|
||
</div>
|
||
<div id="message-list" class="message-list">
|
||
<!-- Messages will be populated here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Test Controls -->
|
||
<div class="control-section">
|
||
<h4>🧪 Testing</h4>
|
||
<div class="test-buttons">
|
||
<button id="test-flash-message" class="btn btn-info">Test Flash Message</button>
|
||
<button id="test-behavior-settings" class="btn btn-primary">Test Behavior</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Appearance Section -->
|
||
<div id="message-appearance-section" class="message-section">
|
||
<div class="message-section-content">
|
||
<div class="control-section">
|
||
<h4>🎨 Visual Appearance</h4>
|
||
<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-row">
|
||
<div class="control-group">
|
||
<label>
|
||
<input type="checkbox" id="remove-background"> Remove Background
|
||
</label>
|
||
<small class="help-text">Show text only without background box</small>
|
||
</div>
|
||
<div class="control-group">
|
||
<label>
|
||
<input type="checkbox" id="text-stroke"> Add Text Stroke
|
||
</label>
|
||
<small class="help-text">Add outline to text for better visibility</small>
|
||
</div>
|
||
</div>
|
||
<div class="control-row" id="stroke-options" style="display: none;">
|
||
<div class="control-group">
|
||
<label>Stroke Color:</label>
|
||
<input type="color" id="stroke-color" value="#000000">
|
||
</div>
|
||
<div class="control-group">
|
||
<label>Stroke Width: <span id="stroke-width-display">2px</span></label>
|
||
<input type="range" id="stroke-width" min="1" max="5" value="2" step="1">
|
||
</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>
|
||
</div>
|
||
|
||
<!-- Import/Export Section -->
|
||
<div id="import-export-section" class="message-section">
|
||
<div class="message-section-content">
|
||
<div class="control-section">
|
||
<h4>📁 Import & Export Messages</h4>
|
||
<p class="section-description">Backup, share, or restore your flash message collections</p>
|
||
|
||
<div class="import-export-controls">
|
||
<div class="export-options">
|
||
<h5>📤 Export Messages</h5>
|
||
<div class="button-group">
|
||
<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>
|
||
</div>
|
||
<p class="help-text">Save your messages as a JSON file for backup or sharing</p>
|
||
</div>
|
||
|
||
<div class="import-options">
|
||
<h5>📥 Import Messages</h5>
|
||
<button id="import-messages-btn" class="btn btn-success">Choose File to Import</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>
|
||
<p class="help-text">Import messages from a previously exported JSON file</p>
|
||
</div>
|
||
|
||
<div class="reset-options">
|
||
<h5>🔄 Reset Messages</h5>
|
||
<button id="reset-to-defaults-btn" class="btn btn-warning">Reset to Default Messages</button>
|
||
<p class="help-text">Restore the original set of motivational messages</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Management Actions -->
|
||
<div class="message-management-actions">
|
||
<button id="save-message-settings-btn" class="btn btn-primary">💾 Save All Settings</button>
|
||
<button id="back-to-setup-from-messages-btn" class="btn btn-secondary" onclick="backToQuickPlay()">⬅️ Back to Quick Play</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Popup Image Management Screen -->
|
||
<div class="popup-image-management" id="popup-image-management" style="display: none;">
|
||
<div class="management-container">
|
||
<div class="management-header">
|
||
<h2>🖼️ Popup Image Management</h2>
|
||
<p>Configure random popup images that appear during your Quick Play sessions</p>
|
||
</div>
|
||
|
||
<div class="management-content">
|
||
<!-- Enable/Disable -->
|
||
<div class="control-section">
|
||
<div class="control-group">
|
||
<label class="switch-label">
|
||
<input type="checkbox" id="popup-images-enabled-main" />
|
||
<span class="switch"></span>
|
||
Enable Random Popup Images
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Frequency Settings -->
|
||
<div class="control-section">
|
||
<h4>⏰ Popup Frequency</h4>
|
||
<div class="control-group">
|
||
<label for="popup-frequency-main">Popup Frequency:</label>
|
||
<select id="popup-frequency-main">
|
||
<option value="low">Low (Every 3-5 min)</option>
|
||
<option value="medium">Medium (Every 2-3 min)</option>
|
||
<option value="high">High (Every 1-2 min)</option>
|
||
<option value="constant">Constant (Every 30-60s)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label>Custom Frequency Range (seconds):</label>
|
||
<div class="range-inputs">
|
||
<div>
|
||
<label for="popup-min-interval-main">Min Interval:</label>
|
||
<input type="number" id="popup-min-interval-main" min="10" max="600" value="30" />
|
||
</div>
|
||
<div>
|
||
<label for="popup-max-interval-main">Max Interval:</label>
|
||
<input type="number" id="popup-max-interval-main" min="30" max="1200" value="120" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Image Count Settings -->
|
||
<div class="control-section">
|
||
<h4>📊 Number of Images</h4>
|
||
<div class="control-group">
|
||
<label for="popup-count-mode-main">Count Mode:</label>
|
||
<select id="popup-count-mode-main">
|
||
<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-main" class="control-group">
|
||
<label for="popup-image-count-main">Number of Images:</label>
|
||
<input type="range" id="popup-image-count-main" min="1" max="40" value="3" />
|
||
<span id="popup-image-count-value-main">3</span>
|
||
</div>
|
||
|
||
<div id="popup-range-count-main" class="control-group" style="display: none;">
|
||
<div class="range-inputs">
|
||
<div>
|
||
<label for="popup-min-count-main">Minimum:</label>
|
||
<input type="number" id="popup-min-count-main" min="1" max="20" value="2" />
|
||
</div>
|
||
<div>
|
||
<label for="popup-max-count-main">Maximum:</label>
|
||
<input type="number" id="popup-max-count-main" 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-main">Duration Mode:</label>
|
||
<select id="popup-duration-mode-main">
|
||
<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-main" class="control-group">
|
||
<label for="popup-display-duration-main">Duration (seconds):</label>
|
||
<input type="range" id="popup-display-duration-main" min="3" max="30" value="8" />
|
||
<span id="popup-display-duration-value-main">8s</span>
|
||
</div>
|
||
|
||
<div id="popup-range-duration-main" class="control-group" style="display: none;">
|
||
<div class="range-inputs">
|
||
<div>
|
||
<label for="popup-min-duration-main">Min (seconds):</label>
|
||
<input type="number" id="popup-min-duration-main" min="2" max="20" value="5" />
|
||
</div>
|
||
<div>
|
||
<label for="popup-max-duration-main">Max (seconds):</label>
|
||
<input type="number" id="popup-max-duration-main" 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-main">Layout Style:</label>
|
||
<select id="popup-positioning-main">
|
||
<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-main" />
|
||
<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-main">Max Viewport Width:</label>
|
||
<input type="range" id="popup-viewport-width-main" min="20" max="60" value="35" />
|
||
<span id="popup-viewport-width-value-main">35%</span>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="popup-viewport-height-main">Max Viewport Height:</label>
|
||
<input type="range" id="popup-viewport-height-main" min="20" max="60" value="40" />
|
||
<span id="popup-viewport-height-value-main">40%</span>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<div class="range-inputs">
|
||
<div>
|
||
<label for="popup-min-width-main">Min Width (px):</label>
|
||
<input type="number" id="popup-min-width-main" min="150" max="400" value="200" />
|
||
</div>
|
||
<div>
|
||
<label for="popup-max-width-main">Max Width (px):</label>
|
||
<input type="number" id="popup-max-width-main" min="300" max="800" value="500" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<div class="range-inputs">
|
||
<div>
|
||
<label for="popup-min-height-main">Min Height (px):</label>
|
||
<input type="number" id="popup-min-height-main" min="100" max="300" value="150" />
|
||
</div>
|
||
<div>
|
||
<label for="popup-max-height-main">Max Height (px):</label>
|
||
<input type="number" id="popup-max-height-main" 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-main" />
|
||
<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-main" />
|
||
<span class="switch"></span>
|
||
Blur Background
|
||
</label>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="switch-label">
|
||
<input type="checkbox" id="popup-show-timer-main" />
|
||
<span class="switch"></span>
|
||
Show Countdown Timer
|
||
</label>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="switch-label">
|
||
<input type="checkbox" id="popup-prevent-close-main" />
|
||
<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-main" class="btn btn-info">Test 1 Popup</button>
|
||
<button id="test-popup-multiple-main" class="btn btn-primary">Test Multiple</button>
|
||
<button id="clear-all-popups-main" 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-main" 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-main">0</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">Last Updated:</span>
|
||
<span id="popup-settings-updated-main">Never</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Management Actions -->
|
||
<div class="management-actions">
|
||
<button id="save-popup-settings-btn" class="btn btn-primary">💾 Save Settings</button>
|
||
<button id="back-to-setup-from-popup-images-btn" class="btn btn-secondary" onclick="backToQuickPlay()">⬅️ Back to Quick Play</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Results Screen -->
|
||
<div class="quick-play-results" id="quick-play-results" style="display: none;">
|
||
<div class="results-container">
|
||
<div class="results-header">
|
||
<h2>🏆 Session Complete!</h2>
|
||
<p>Your training session results</p>
|
||
</div>
|
||
|
||
<div class="results-content">
|
||
<div class="results-stats">
|
||
<div class="stat-item">
|
||
<div class="stat-icon">⏱️</div>
|
||
<div class="stat-label">Session Time</div>
|
||
<div class="stat-value" id="final-session-time">--:--</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-icon">✅</div>
|
||
<div class="stat-label">Tasks Completed</div>
|
||
<div class="stat-value" id="final-tasks-count">--</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-icon">🏆</div>
|
||
<div class="stat-label">XP Earned</div>
|
||
<div class="stat-value" id="final-xp-earned">--</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-icon">📊</div>
|
||
<div class="stat-label">Performance</div>
|
||
<div class="stat-value" id="final-performance">--</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="results-achievements" id="results-achievements">
|
||
<!-- Achievements will be populated here -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="results-actions">
|
||
<button id="play-again" class="btn btn-primary">
|
||
🔄 Play Again
|
||
</button>
|
||
<button id="new-settings" class="btn btn-secondary">
|
||
⚙️ Change Settings
|
||
</button>
|
||
<button id="back-to-home-results" class="btn btn-secondary">
|
||
🏠 Back to Home
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</main>
|
||
|
||
<!-- Scripts -->
|
||
<script src="src/data/gameDataManager.js"></script>
|
||
<script src="src/data/gameData.js"></script>
|
||
<script src="src/data/modes/mainGameData.js"></script>
|
||
<script src="src/utils/desktop-file-manager.js"></script>
|
||
<script src="src/features/stats/playerStats.js"></script>
|
||
<script src="src/features/ui/flashMessageManager.js"></script>
|
||
<script src="src/features/images/popupImageManager.js"></script>
|
||
<script src="src/features/audio/audioManager.js"></script>
|
||
<script src="src/features/media/baseVideoPlayer.js"></script>
|
||
<script src="src/features/media/overlayVideoPlayer.js"></script>
|
||
<script src="src/features/media/quadVideoPlayer.js"></script>
|
||
<script src="src/features/media/videoLibrary.js"></script>
|
||
<script src="src/features/video/videoPlayerManager.js"></script>
|
||
<script src="src/features/webcam/webcamManager.js"></script>
|
||
<script src="src/features/tasks/aiTaskManager.js"></script>
|
||
<script src="src/features/tasks/interactiveTaskManager.js"></script>
|
||
<script src="src/features/tts/voiceManager.js"></script>
|
||
<script src="src/core/gameModeManager.js"></script>
|
||
<script src="src/core/game.js"></script>
|
||
|
||
<script>
|
||
// Quick Play specific initialization
|
||
let quickPlaySettings = {
|
||
playTime: 1800, // 30 minutes default
|
||
enableBackgroundAudio: true,
|
||
enableAmbientAudio: true,
|
||
enableVoiceCommands: false,
|
||
popupFrequency: 'medium',
|
||
popupDuration: 'medium',
|
||
enableFlashMessages: true,
|
||
duration: 'medium', // Task duration setting
|
||
includeTags: [], // Tags to include
|
||
excludeTags: [], // Tags to exclude
|
||
includeStandardTasks: true,
|
||
includeScenarioTasks: false, // Scenarios disabled by default
|
||
consequenceChance: 100, // 100% chance by default
|
||
// Video settings
|
||
videoMode: 'background',
|
||
enableVideoSound: true,
|
||
enableVideoControls: false,
|
||
videoOpacity: 0.7,
|
||
// Webcam recording settings
|
||
enableSessionRecording: false,
|
||
webcamOutputDirectory: '',
|
||
webcamPosition: 'bottom-right',
|
||
webcamSize: 'small',
|
||
// Task management
|
||
disabledTasks: {
|
||
main: [],
|
||
consequence: []
|
||
},
|
||
customTasks: {
|
||
main: [],
|
||
consequence: []
|
||
}
|
||
};
|
||
|
||
let gameInstance = null;
|
||
let isGameRunning = false;
|
||
let currentTask = null;
|
||
let isConsequenceTask = false;
|
||
let recentlyPlayedVideos = []; // Track recently played videos to avoid repeats
|
||
let sessionStats = {
|
||
started: Date.now(),
|
||
completed: 0,
|
||
skipped: 0,
|
||
xp: 0
|
||
};
|
||
|
||
// Check MP4 codec support for Electron
|
||
function checkMP4Support() {
|
||
console.log('🎥 Checking MP4 codec support...');
|
||
|
||
const mp4Codecs = [
|
||
'video/mp4;codecs=avc1.42E01E',
|
||
'video/mp4;codecs=avc1.4D401E',
|
||
'video/mp4;codecs=avc1.64001E',
|
||
'video/mp4'
|
||
];
|
||
|
||
const supportedCodecs = mp4Codecs.filter(codec => MediaRecorder.isTypeSupported(codec));
|
||
|
||
if (supportedCodecs.length > 0) {
|
||
console.log('✅ MP4 codec support confirmed:', supportedCodecs);
|
||
return true;
|
||
} else {
|
||
console.error('❌ No MP4 codecs supported. Available types:');
|
||
console.error('WebM VP8:', MediaRecorder.isTypeSupported('video/webm;codecs=vp8'));
|
||
console.error('WebM VP9:', MediaRecorder.isTypeSupported('video/webm;codecs=vp9'));
|
||
console.error('WebM:', MediaRecorder.isTypeSupported('video/webm'));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Initialize Quick Play when page loads
|
||
document.addEventListener('DOMContentLoaded', async function() {
|
||
console.log('⚡ Initializing Quick Play...');
|
||
|
||
try {
|
||
// Check MP4 support first
|
||
checkMP4Support();
|
||
|
||
// Load saved settings
|
||
await loadSavedSettings();
|
||
console.log('✅ Settings loaded');
|
||
|
||
// Setup event listeners
|
||
setupEventListeners();
|
||
console.log('✅ Event listeners setup');
|
||
|
||
// Initialize player stats
|
||
if (typeof PlayerStats !== 'undefined') {
|
||
window.playerStats = new PlayerStats();
|
||
console.log('✅ Player stats initialized');
|
||
}
|
||
|
||
// Initialize desktop file manager if in Electron environment
|
||
await initializeFileManager();
|
||
console.log('✅ File manager initialized');
|
||
|
||
// Add window unload handler with better cleanup
|
||
let unloadHandlerAdded = false;
|
||
const handleBeforeUnload = (e) => {
|
||
if (window.isForceExiting) {
|
||
// If force exiting, don't prevent and don't show dialog
|
||
return;
|
||
}
|
||
|
||
if (isGameRunning && !unloadHandlerAdded) {
|
||
e.preventDefault();
|
||
e.returnValue = 'Game in progress. Are you sure you want to leave?';
|
||
return e.returnValue;
|
||
}
|
||
};
|
||
|
||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||
unloadHandlerAdded = true;
|
||
|
||
// Add global click handler for game buttons as backup
|
||
document.addEventListener('click', (e) => {
|
||
if (e.target && e.target.id === 'end-game') {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
console.log('End Session button clicked via global handler');
|
||
endGame();
|
||
}
|
||
if (e.target && e.target.id === 'complete-task') {
|
||
console.log('Complete task button clicked via global handler - Quick Play handles this with specific listeners');
|
||
// Don't call anything here - Quick Play uses its own specific event listeners
|
||
return;
|
||
}
|
||
if (e.target && e.target.id === 'skip-task') {
|
||
console.log('Skip task button clicked via global handler');
|
||
if (currentTask) {
|
||
quickPlaySkipTask(currentTask);
|
||
}
|
||
}
|
||
});
|
||
console.log('✅ Global click handlers added');
|
||
|
||
// Initialize tag selectors with any existing custom tags
|
||
initializeTagSelectors();
|
||
console.log('✅ Tag selectors initialized');
|
||
|
||
// Add webcam photo session completion listener
|
||
document.addEventListener('photoSessionComplete', (event) => {
|
||
console.log('📸 Photo session completed in Quick Play', event.detail);
|
||
|
||
// Award bonus XP for completing photo session (2 XP)
|
||
const bonusXP = 2;
|
||
if (sessionStats) {
|
||
sessionStats.xp += bonusXP;
|
||
updateSessionDisplay();
|
||
}
|
||
|
||
// Update game state if available
|
||
if (gameInstance && gameInstance.gameState) {
|
||
gameInstance.gameState.xp = (gameInstance.gameState.xp || 0) + bonusXP;
|
||
updateGameStatus({ xp: gameInstance.gameState.xp });
|
||
}
|
||
|
||
console.log(`📸 Awarded ${bonusXP} bonus XP for completing photo session`);
|
||
});
|
||
|
||
// Hide loading overlay
|
||
setTimeout(() => {
|
||
const loadingOverlay = document.getElementById('quick-play-loading');
|
||
if (loadingOverlay) {
|
||
loadingOverlay.style.display = 'none';
|
||
}
|
||
console.log('✅ Quick Play initialization complete');
|
||
|
||
|
||
}, 1000);
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error during Quick Play initialization:', error);
|
||
}
|
||
});
|
||
|
||
// Directory handle persistence functions
|
||
async function storeDirectoryHandle(directoryHandle) {
|
||
// Modern browsers with Origin Private File System API
|
||
if ('storage' in navigator && 'persist' in navigator.storage) {
|
||
try {
|
||
const opfsRoot = await navigator.storage.getDirectory();
|
||
const handleFile = await opfsRoot.getFileHandle('webcam-directory-handle', { create: true });
|
||
const writable = await handleFile.createWritable();
|
||
await writable.write(JSON.stringify({ name: directoryHandle.name }));
|
||
await writable.close();
|
||
return 'opfs-stored';
|
||
} catch (error) {
|
||
console.log('OPFS storage failed:', error);
|
||
}
|
||
}
|
||
|
||
// Fallback - just return a reference ID
|
||
return `handle-${Date.now()}`;
|
||
}
|
||
|
||
async function loadStoredDirectory() {
|
||
try {
|
||
// Try to load saved directory name
|
||
const savedDirectory = localStorage.getItem('webcamRecordingDirectory');
|
||
if (savedDirectory) {
|
||
quickPlaySettings.webcamOutputDirectory = savedDirectory;
|
||
document.getElementById('webcam-output-path').value = savedDirectory;
|
||
console.log('📁 Restored recording directory:', savedDirectory);
|
||
return true;
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading stored directory:', error);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
async function loadSavedSettings() {
|
||
try {
|
||
const saved = localStorage.getItem('quickPlaySettings');
|
||
if (saved) {
|
||
console.log('📂 Loading saved settings from localStorage');
|
||
const savedSettings = JSON.parse(saved);
|
||
console.log('📂 Raw saved settings:', JSON.stringify(savedSettings));
|
||
quickPlaySettings = { ...quickPlaySettings, ...savedSettings };
|
||
console.log('📂 Merged settings:', JSON.stringify(quickPlaySettings));
|
||
applySettingsToUI();
|
||
} else {
|
||
console.log('📂 No saved settings found, using defaults');
|
||
}
|
||
|
||
// Also load saved directory
|
||
await loadStoredDirectory();
|
||
|
||
} catch (error) {
|
||
console.warn('Failed to load saved settings:', error);
|
||
}
|
||
}
|
||
|
||
function applySettingsToUI() {
|
||
// Apply time preset
|
||
document.querySelectorAll('.time-preset').forEach(preset => {
|
||
preset.classList.remove('active');
|
||
if (preset.dataset.time == quickPlaySettings.playTime ||
|
||
(preset.dataset.time === 'endless' && quickPlaySettings.playTime === -1)) {
|
||
preset.classList.add('active');
|
||
}
|
||
});
|
||
|
||
// Apply audio settings
|
||
document.getElementById('enable-background-audio').checked = quickPlaySettings.enableBackgroundAudio;
|
||
document.getElementById('enable-ambient-audio').checked = quickPlaySettings.enableAmbientAudio;
|
||
document.getElementById('enable-voice-commands').checked = quickPlaySettings.enableVoiceCommands;
|
||
|
||
// Apply video settings
|
||
document.getElementById('video-mode').value = quickPlaySettings.videoMode;
|
||
document.getElementById('enable-video-sound').checked = quickPlaySettings.enableVideoSound;
|
||
document.getElementById('enable-video-controls').checked = quickPlaySettings.enableVideoControls;
|
||
document.getElementById('video-opacity').value = quickPlaySettings.videoOpacity;
|
||
|
||
// Apply task type settings
|
||
document.getElementById('include-standard-tasks').checked = quickPlaySettings.includeStandardTasks;
|
||
document.getElementById('include-scenario-tasks').checked = quickPlaySettings.includeScenarioTasks;
|
||
|
||
// Visual settings now managed by popup image management screen
|
||
|
||
// Apply consequence settings
|
||
document.getElementById('consequence-chance').value = quickPlaySettings.consequenceChance;
|
||
document.getElementById('consequence-chance-value').textContent = quickPlaySettings.consequenceChance + '%';
|
||
|
||
// Apply duration setting
|
||
document.getElementById('duration-setting').value = quickPlaySettings.duration;
|
||
|
||
// Apply include tags
|
||
document.querySelectorAll('#include-tags input[type="checkbox"]').forEach(checkbox => {
|
||
checkbox.checked = quickPlaySettings.includeTags.includes(checkbox.value);
|
||
});
|
||
|
||
// Apply exclude tags
|
||
document.querySelectorAll('#exclude-tags input[type="checkbox"]').forEach(checkbox => {
|
||
checkbox.checked = quickPlaySettings.excludeTags.includes(checkbox.value);
|
||
});
|
||
|
||
// Show/hide video options based on mode
|
||
updateVideoOptionsVisibility();
|
||
}
|
||
|
||
|
||
async function initializeBackgroundVideo() {
|
||
try {
|
||
console.log('🎬 Initializing background video player...');
|
||
|
||
// Simple, smooth video implementation (similar to training academy)
|
||
// Setup background video specific event listeners
|
||
setupBackgroundVideoListeners();
|
||
|
||
// Initialize video visibility controls
|
||
initializeVideoToggle();
|
||
|
||
// Load random video
|
||
await loadRandomBackgroundVideo();
|
||
|
||
console.log('✅ Background video player initialized');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error initializing background video:', error);
|
||
}
|
||
}
|
||
|
||
async function loadRandomBackgroundVideo() {
|
||
try {
|
||
// Get all available videos using the same logic as other video players
|
||
let allVideos = [];
|
||
|
||
// First check if we have a video library instance
|
||
if (window.videoLibrary && typeof window.videoLibrary.getAllVideos === 'function') {
|
||
allVideos = window.videoLibrary.getAllVideos();
|
||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary instance`);
|
||
}
|
||
|
||
// NEW: Try to access VideoLibrary videos directly if it exists
|
||
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
|
||
allVideos = window.VideoLibrary.videos;
|
||
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
|
||
}
|
||
|
||
// Try desktop file manager
|
||
if (allVideos.length === 0 && window.desktopFileManager) {
|
||
try {
|
||
if (typeof window.desktopFileManager.getAllVideos === 'function') {
|
||
allVideos = window.desktopFileManager.getAllVideos();
|
||
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
|
||
} else {
|
||
console.log('🎬 DesktopFileManager.getAllVideos not available');
|
||
}
|
||
} catch (dmError) {
|
||
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
|
||
}
|
||
}
|
||
|
||
// Fallback to unified storage
|
||
if (allVideos.length === 0) {
|
||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||
allVideos = unifiedData.allVideos || [];
|
||
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
|
||
}
|
||
|
||
// Fallback to legacy storage
|
||
if (allVideos.length === 0) {
|
||
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
|
||
allVideos = Object.values(storedVideos).flat();
|
||
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
|
||
}
|
||
|
||
// NEW: Try accessing VideoLibrary legacy storage directly
|
||
if (allVideos.length === 0) {
|
||
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
|
||
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
|
||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
|
||
}
|
||
|
||
// If still no videos, try to initialize video library
|
||
if (allVideos.length === 0) {
|
||
console.log('🎬 No videos found, attempting to initialize video library...');
|
||
const initSuccess = await initializeVideoLibrary();
|
||
|
||
if (initSuccess) {
|
||
// Try again after initialization
|
||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||
allVideos = unifiedData.allVideos || [];
|
||
console.log(`🎬 After initialization: ${allVideos.length} videos available`);
|
||
}
|
||
}
|
||
|
||
if (allVideos.length === 0) {
|
||
console.warn('⚠️ No videos available for background playback');
|
||
showVideoSetupNotification();
|
||
return;
|
||
}
|
||
|
||
await playRandomVideoFromArray(allVideos);
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error loading background video:', error);
|
||
}
|
||
}
|
||
|
||
// Helper function to play a random video from an array
|
||
async function playRandomVideoFromArray(videos) {
|
||
try {
|
||
// Filter out recently played videos to avoid immediate repeats
|
||
let availableVideos = videos.filter(video => {
|
||
const videoId = video.path || video.filePath || video.name;
|
||
return !recentlyPlayedVideos.includes(videoId);
|
||
});
|
||
|
||
// If all videos have been played recently, reset the list but keep the last 3
|
||
if (availableVideos.length === 0) {
|
||
console.log('🔄 All videos recently played, resetting with exclusion of last 3');
|
||
const lastThree = recentlyPlayedVideos.slice(-3);
|
||
recentlyPlayedVideos = lastThree;
|
||
availableVideos = videos.filter(video => {
|
||
const videoId = video.path || video.filePath || video.name;
|
||
return !lastThree.includes(videoId);
|
||
});
|
||
|
||
// If still no available videos (very small library), use all
|
||
if (availableVideos.length === 0) {
|
||
availableVideos = videos;
|
||
recentlyPlayedVideos = [];
|
||
}
|
||
}
|
||
|
||
// Select random video from available ones
|
||
const randomIndex = Math.floor(Math.random() * availableVideos.length);
|
||
const randomVideo = availableVideos[randomIndex];
|
||
const videoId = randomVideo.path || randomVideo.filePath || randomVideo.name;
|
||
|
||
// Add to recently played list (keep last 10)
|
||
recentlyPlayedVideos.push(videoId);
|
||
if (recentlyPlayedVideos.length > 10) {
|
||
recentlyPlayedVideos = recentlyPlayedVideos.slice(-10);
|
||
}
|
||
|
||
console.log(`🎬 Loading background video: ${randomVideo.name || randomVideo.title}`);
|
||
console.log(`🎬 Recently played: ${recentlyPlayedVideos.length}/10`);
|
||
|
||
// Use simple, smooth video approach (like training academy)
|
||
const videoContainer = document.getElementById('background-video-container');
|
||
const videoOpacity = quickPlaySettings.videoOpacity || 0.3;
|
||
const videoVolume = quickPlaySettings.enableVideoSound ? 0.3 : 0;
|
||
|
||
videoContainer.innerHTML = `
|
||
<video id="background-video" autoplay muted style="opacity: ${videoOpacity};">
|
||
<source src="file://${randomVideo.path || randomVideo.filePath}" type="video/mp4">
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
`;
|
||
|
||
const video = document.getElementById('background-video');
|
||
|
||
// Video starts with settings-based mute state (no external mute button)
|
||
|
||
// Get volume from slider if it exists, otherwise use default
|
||
const volumeSlider = document.getElementById('video-volume');
|
||
let targetVolume = videoVolume;
|
||
if (volumeSlider) {
|
||
targetVolume = (volumeSlider.value / 100) * (quickPlaySettings.enableVideoSound ? 1 : 0);
|
||
console.log(`🔊 Using volume from slider: ${volumeSlider.value}% -> ${targetVolume}`);
|
||
}
|
||
|
||
// Set volume and mute state based on settings
|
||
video.volume = targetVolume;
|
||
video.muted = !quickPlaySettings.enableVideoSound || targetVolume === 0;
|
||
|
||
video.onloadstart = () => {
|
||
console.log('🎬 Video loading started');
|
||
};
|
||
|
||
video.onloadeddata = () => {
|
||
console.log('🎬 Video data loaded');
|
||
};
|
||
|
||
video.onloadedmetadata = () => {
|
||
console.log('🎬 Video metadata loaded');
|
||
updateVideoInfo();
|
||
updateVideoProgress();
|
||
|
||
// Sync volume control with video
|
||
const volumeSlider = document.getElementById('video-volume');
|
||
const volumeDisplay = document.getElementById('video-volume-display');
|
||
if (volumeSlider && volumeDisplay) {
|
||
const currentVolume = Math.round(video.volume * 100);
|
||
volumeSlider.value = currentVolume;
|
||
volumeDisplay.textContent = currentVolume + '%';
|
||
console.log(`🔊 Synced volume control to ${currentVolume}%`);
|
||
}
|
||
|
||
// Dispatch custom event for volume control initialization
|
||
document.dispatchEvent(new CustomEvent('videoLoaded'));
|
||
};
|
||
|
||
video.ontimeupdate = () => {
|
||
updateVideoProgress();
|
||
};
|
||
|
||
video.onerror = (e) => {
|
||
console.error('❌ Video error:', e);
|
||
// Try loading a different video after error
|
||
setTimeout(() => {
|
||
loadRandomBackgroundVideo();
|
||
}, 2000);
|
||
};
|
||
|
||
// Auto-load next video when current one ends
|
||
video.onended = () => {
|
||
setTimeout(() => {
|
||
loadRandomBackgroundVideo();
|
||
}, 1000);
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error playing random video:', error);
|
||
}
|
||
}
|
||
|
||
function setupBackgroundVideoListeners() {
|
||
// Set up a function to initialize controls when video container is ready
|
||
const initControlsWhenReady = () => {
|
||
// Random video button
|
||
const randomBtn = document.querySelector('.random-video-btn');
|
||
if (randomBtn) {
|
||
randomBtn.addEventListener('click', () => {
|
||
loadRandomBackgroundVideo();
|
||
});
|
||
}
|
||
|
||
// Background video controls volume slider
|
||
const backgroundVolumeSlider = document.querySelector('.background-video-controls .volume-slider');
|
||
if (backgroundVolumeSlider) {
|
||
backgroundVolumeSlider.addEventListener('input', (e) => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (currentVideo) {
|
||
const volume = e.target.value / 100;
|
||
currentVideo.volume = volume;
|
||
console.log(`🔊 Background controls volume set to ${e.target.value}% (${volume})`);
|
||
|
||
// Sync with main volume control
|
||
const mainVolumeSlider = document.getElementById('video-volume');
|
||
const mainVolumeDisplay = document.getElementById('video-volume-display');
|
||
if (mainVolumeSlider && mainVolumeDisplay) {
|
||
mainVolumeSlider.value = e.target.value;
|
||
mainVolumeDisplay.textContent = e.target.value + '%';
|
||
}
|
||
|
||
// Update mute states
|
||
const backgroundMuteBtn = document.querySelector('.background-video-controls .mute-btn');
|
||
if (volume === 0) {
|
||
currentVideo.muted = true;
|
||
if (backgroundMuteBtn) {
|
||
backgroundMuteBtn.textContent = '🔇';
|
||
}
|
||
} else if (currentVideo.muted) {
|
||
currentVideo.muted = false;
|
||
if (backgroundMuteBtn) {
|
||
backgroundMuteBtn.textContent = '🔊';
|
||
}
|
||
}
|
||
}
|
||
});
|
||
console.log('✅ Background video volume slider listener set up');
|
||
}
|
||
|
||
// Background video mute button
|
||
const backgroundMuteBtn = document.querySelector('.background-video-controls .mute-btn');
|
||
if (backgroundMuteBtn) {
|
||
backgroundMuteBtn.addEventListener('click', () => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (currentVideo) {
|
||
if (currentVideo.muted) {
|
||
currentVideo.muted = false;
|
||
backgroundMuteBtn.textContent = '🔊';
|
||
console.log('🔊 Background video unmuted');
|
||
} else {
|
||
currentVideo.muted = true;
|
||
backgroundMuteBtn.textContent = '🔇';
|
||
console.log('🔇 Background video muted');
|
||
}
|
||
|
||
// Main mute button removed for streamlined interface
|
||
}
|
||
});
|
||
console.log('✅ Background video mute button listener set up');
|
||
}
|
||
|
||
// Background video play/pause button
|
||
const backgroundPlayPauseBtn = document.querySelector('.background-video-controls .play-pause-btn');
|
||
if (backgroundPlayPauseBtn) {
|
||
backgroundPlayPauseBtn.addEventListener('click', async () => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (currentVideo) {
|
||
try {
|
||
if (currentVideo.paused) {
|
||
await currentVideo.play();
|
||
backgroundPlayPauseBtn.textContent = '⏸️';
|
||
console.log('▶️ Background video resumed');
|
||
} else {
|
||
currentVideo.pause();
|
||
backgroundPlayPauseBtn.textContent = '▶️';
|
||
console.log('⏸️ Background video paused');
|
||
}
|
||
} catch (error) {
|
||
console.error('Background video playback error:', error);
|
||
}
|
||
}
|
||
});
|
||
console.log('✅ Background video play/pause button listener set up');
|
||
}
|
||
};
|
||
|
||
// Try to initialize now and also set up for later
|
||
initControlsWhenReady();
|
||
|
||
// Also set up when video loads
|
||
document.addEventListener('videoLoaded', initControlsWhenReady);
|
||
}
|
||
|
||
function updateVideoOptionsVisibility() {
|
||
const videoOptions = document.getElementById('video-options');
|
||
const videoMode = document.getElementById('video-mode').value;
|
||
|
||
if (videoMode === 'none') {
|
||
videoOptions.style.display = 'none';
|
||
} else {
|
||
videoOptions.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
function updateWebcamOptionsVisibility() {
|
||
const webcamSubOptions = document.getElementById('webcam-sub-options');
|
||
const enableRecording = document.getElementById('enable-session-recording').checked;
|
||
|
||
if (enableRecording) {
|
||
webcamSubOptions.style.display = 'block';
|
||
} else {
|
||
webcamSubOptions.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
async function initializeVideoLibrary() {
|
||
try {
|
||
console.log('🎬 Attempting to initialize video library for Quick Play...');
|
||
|
||
// Use the same video loading logic as VideoLibrary class
|
||
let linkedDirs;
|
||
try {
|
||
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
|
||
if (!Array.isArray(linkedDirs)) {
|
||
linkedDirs = [];
|
||
}
|
||
} catch (e) {
|
||
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
|
||
linkedDirs = [];
|
||
}
|
||
|
||
let linkedIndividualVideos;
|
||
try {
|
||
linkedIndividualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
|
||
if (!Array.isArray(linkedIndividualVideos)) {
|
||
linkedIndividualVideos = [];
|
||
}
|
||
} catch (e) {
|
||
console.log('Error parsing linkedIndividualVideos, resetting to empty array:', e);
|
||
linkedIndividualVideos = [];
|
||
}
|
||
|
||
const allVideos = [];
|
||
|
||
console.log(`🎬 Found ${linkedDirs.length} linked directories, scanning...`);
|
||
|
||
// Load videos from linked directories using Electron API (same as VideoLibrary)
|
||
if (window.electronAPI && linkedDirs.length > 0) {
|
||
const videoExtensions = /\.(mp4|webm|avi|mov|mkv|wmv|flv|m4v)$/i;
|
||
|
||
for (const dir of linkedDirs) {
|
||
try {
|
||
console.log(`🎬 Scanning directory: ${dir.path}`);
|
||
|
||
// Use video-specific directory reading for better results (same as VideoLibrary)
|
||
let files = [];
|
||
if (window.electronAPI.readVideoDirectory) {
|
||
files = await window.electronAPI.readVideoDirectory(dir.path);
|
||
} else if (window.electronAPI.readVideoDirectoryRecursive) {
|
||
files = await window.electronAPI.readVideoDirectoryRecursive(dir.path);
|
||
} else if (window.electronAPI.readDirectory) {
|
||
const allFiles = await window.electronAPI.readDirectory(dir.path);
|
||
files = allFiles.filter(file => videoExtensions.test(file.name));
|
||
}
|
||
|
||
if (files && files.length > 0) {
|
||
console.log(`🎬 Found ${files.length} video files in ${dir.path}`);
|
||
files.forEach(file => {
|
||
allVideos.push({
|
||
name: file.name,
|
||
path: file.path,
|
||
size: file.size || 0,
|
||
duration: file.duration || 0,
|
||
format: getFormatFromPath(file.path),
|
||
dateAdded: new Date().toISOString(),
|
||
category: 'directory',
|
||
directory: dir.path
|
||
});
|
||
});
|
||
} else {
|
||
console.log(`🎬 No video files found in ${dir.path}`);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Error loading videos from directory ${dir.path}:`, error);
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Add individual linked video files (same as VideoLibrary)
|
||
linkedIndividualVideos.forEach(video => {
|
||
allVideos.push({
|
||
name: video.name || 'Unknown Video',
|
||
path: video.path,
|
||
size: video.size || 0,
|
||
duration: video.duration || 0,
|
||
format: video.format || getFormatFromPath(video.path),
|
||
dateAdded: video.dateAdded || new Date().toISOString(),
|
||
category: 'individual',
|
||
directory: 'Individual Videos'
|
||
});
|
||
});
|
||
|
||
console.log(`🎬 Building unified video library...`);
|
||
console.log(`🎬 Added ${allVideos.length} videos from VideoLibrary-style scanning`);
|
||
|
||
// Save to unified storage (same as VideoLibrary)
|
||
if (allVideos.length > 0) {
|
||
try {
|
||
const unifiedData = {
|
||
allVideos: allVideos.map(video => ({
|
||
name: video.name,
|
||
path: video.path,
|
||
size: video.size,
|
||
duration: video.duration,
|
||
format: video.format,
|
||
dateAdded: video.dateAdded,
|
||
source: video.category === 'individual' ? 'individual' : 'directory',
|
||
directory: video.directory,
|
||
thumbnail: video.thumbnail,
|
||
resolution: video.resolution || 'Unknown',
|
||
bitrate: video.bitrate || 0
|
||
})),
|
||
lastUpdated: new Date().toISOString(),
|
||
source: 'QuickPlay'
|
||
};
|
||
|
||
localStorage.setItem('unifiedVideoLibrary', JSON.stringify(unifiedData));
|
||
console.log(`🎬 ✅ Saved ${allVideos.length} videos to unified storage`);
|
||
} catch (error) {
|
||
console.warn('🎬 ⚠️ Failed to save to unified storage:', error);
|
||
}
|
||
}
|
||
|
||
console.log(`🎬 Total videos collected: ${allVideos.length}`);
|
||
return allVideos.length > 0;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error initializing video library:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Helper function to get file format from path (same as VideoLibrary)
|
||
function getFormatFromPath(path) {
|
||
if (!path) return 'mp4';
|
||
const extension = path.toLowerCase().split('.').pop();
|
||
return extension || 'mp4';
|
||
}
|
||
|
||
// Video visibility control functions
|
||
let currentVideoOpacity = 'normal'; // normal, dim, bright, hidden
|
||
|
||
function cycleVideoOpacity() {
|
||
const video = document.querySelector('.background-video');
|
||
const btn = document.getElementById('videoOpacityBtn');
|
||
if (!video || !btn) return;
|
||
|
||
// Remove all opacity classes
|
||
video.classList.remove('video-hidden', 'video-dim', 'video-normal', 'video-bright');
|
||
|
||
// Cycle through opacity states
|
||
switch (currentVideoOpacity) {
|
||
case 'normal':
|
||
currentVideoOpacity = 'bright';
|
||
video.classList.add('video-bright');
|
||
btn.textContent = '🔆';
|
||
btn.title = 'Video: Bright';
|
||
break;
|
||
case 'bright':
|
||
currentVideoOpacity = 'dim';
|
||
video.classList.add('video-dim');
|
||
btn.textContent = '🔅';
|
||
btn.title = 'Video: Dim';
|
||
break;
|
||
case 'dim':
|
||
currentVideoOpacity = 'hidden';
|
||
video.classList.add('video-hidden');
|
||
btn.textContent = '🌑';
|
||
btn.title = 'Video: Hidden';
|
||
break;
|
||
case 'hidden':
|
||
currentVideoOpacity = 'normal';
|
||
video.classList.add('video-normal');
|
||
btn.textContent = '🔆';
|
||
btn.title = 'Video: Normal';
|
||
break;
|
||
}
|
||
}
|
||
|
||
function initializeVideoToggle() {
|
||
// Video toggle and mute buttons removed for streamlined interface
|
||
// Video controls are now available through the main video control panel
|
||
console.log('<27> Video control buttons removed for streamlined interface');
|
||
}
|
||
|
||
function showVideoSetupNotification() {
|
||
// Show a user-friendly notification about setting up videos
|
||
const taskTitle = document.getElementById('task-title');
|
||
const taskText = document.getElementById('task-text');
|
||
|
||
if (taskTitle && taskText) {
|
||
// Temporarily show setup message
|
||
const originalTitle = taskTitle.textContent;
|
||
const originalText = taskText.textContent;
|
||
|
||
taskTitle.textContent = '🎬 Background Video Setup';
|
||
taskText.innerHTML = `
|
||
No videos found for background playback.<br>
|
||
To enable background videos:<br>
|
||
1. Go to the main menu → Porn Cinema<br>
|
||
2. Add video directories or files<br>
|
||
3. Return to Quick Play<br><br>
|
||
<small>Continuing without background video...</small>
|
||
`;
|
||
|
||
// Restore original content after 8 seconds
|
||
setTimeout(() => {
|
||
if (taskTitle && taskText) {
|
||
taskTitle.textContent = originalTitle;
|
||
taskText.textContent = originalText;
|
||
}
|
||
}, 8000);
|
||
}
|
||
|
||
console.log('📋 Video setup notification shown to user');
|
||
}
|
||
|
||
// Recording Overlay System - Always enabled for webcam recording
|
||
let recordingOverlayEnabled = true;
|
||
let recordingStartTime = Date.now();
|
||
let recordingTimerInterval = null;
|
||
|
||
function toggleRecordingOverlay() {
|
||
const webcamViewer = document.getElementById('webcam-viewer');
|
||
const button = document.getElementById('toggle-recording-overlay');
|
||
|
||
console.log('🎬 Toggle recording overlay called. Current state:', recordingOverlayEnabled);
|
||
console.log('🎬 Webcam viewer found:', !!webcamViewer);
|
||
console.log('🎬 Button element found:', !!button);
|
||
|
||
if (!recordingOverlayEnabled) {
|
||
// Enable webcam task overlay
|
||
recordingOverlayEnabled = true;
|
||
recordingStartTime = Date.now();
|
||
|
||
// Add recording class to webcam viewer to show task overlay
|
||
if (webcamViewer) {
|
||
webcamViewer.classList.add('recording');
|
||
console.log('🎬 Added recording class to webcam viewer');
|
||
}
|
||
|
||
button.textContent = '🔴 Task Overlay';
|
||
button.classList.remove('btn-info');
|
||
button.classList.add('btn-danger');
|
||
|
||
// Start timer
|
||
recordingTimerInterval = setInterval(updateRecordingTimer, 1000);
|
||
|
||
// Update with current task
|
||
updateRecordingOverlay();
|
||
|
||
console.log('🎬 Webcam task overlay enabled');
|
||
} else {
|
||
// Disable webcam task overlay
|
||
recordingOverlayEnabled = false;
|
||
recordingStartTime = null;
|
||
|
||
// Remove recording class from webcam viewer to hide task overlay
|
||
if (webcamViewer) {
|
||
webcamViewer.classList.remove('recording');
|
||
console.log('🎬 Removed recording class from webcam viewer');
|
||
}
|
||
|
||
button.textContent = '🎬 Task Overlay';
|
||
button.classList.remove('btn-danger');
|
||
button.classList.add('btn-info');
|
||
|
||
// Stop timer
|
||
if (recordingTimerInterval) {
|
||
clearInterval(recordingTimerInterval);
|
||
recordingTimerInterval = null;
|
||
}
|
||
|
||
console.log('🎬 Webcam task overlay disabled');
|
||
}
|
||
}
|
||
|
||
function updateRecordingTimer() {
|
||
if (!recordingStartTime) return;
|
||
|
||
const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
|
||
const minutes = Math.floor(elapsed / 60);
|
||
const seconds = elapsed % 60;
|
||
const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||
|
||
// Update both screen overlay and webcam overlay timestamps
|
||
const timestampElement = document.getElementById('recording-timestamp');
|
||
if (timestampElement) {
|
||
timestampElement.textContent = timeString;
|
||
}
|
||
|
||
// Update webcam overlay timer if no task timer is active
|
||
const webcamTimer = document.getElementById('webcam-task-timer');
|
||
const taskTimer = document.getElementById('task-timer');
|
||
if (webcamTimer && (!taskTimer || !taskTimer.textContent || taskTimer.textContent === '0:00')) {
|
||
webcamTimer.textContent = timeString;
|
||
}
|
||
}
|
||
|
||
function updateRecordingOverlay() {
|
||
if (!recordingOverlayEnabled) {
|
||
console.log('🎬 updateRecordingOverlay called but recording not enabled');
|
||
return;
|
||
}
|
||
|
||
console.log('🎬 Updating recording overlay content');
|
||
|
||
// Try multiple selectors to find task elements
|
||
let taskText = document.getElementById('task-text');
|
||
let taskTitle = document.getElementById('task-title');
|
||
let taskTimer = document.getElementById('task-timer');
|
||
|
||
// Fallback selectors if main ones not found
|
||
if (!taskText) taskText = document.querySelector('.task-text, [data-task-text]');
|
||
if (!taskTitle) taskTitle = document.querySelector('.task-title, [data-task-title], .current-task-title');
|
||
if (!taskTimer) taskTimer = document.querySelector('.task-timer, [data-task-timer], .timer-display');
|
||
|
||
console.log('🎬 Main task elements found:', {
|
||
taskText: !!taskText,
|
||
taskTitle: !!taskTitle,
|
||
taskTimer: !!taskTimer
|
||
});
|
||
|
||
const overlayTaskType = document.getElementById('webcam-task-type');
|
||
const overlayTaskText = document.getElementById('webcam-task-text');
|
||
const overlayTimer = document.getElementById('webcam-task-timer');
|
||
|
||
console.log('🎬 Webcam overlay elements found:', {
|
||
overlayTaskType: !!overlayTaskType,
|
||
overlayTaskText: !!overlayTaskText,
|
||
overlayTimer: !!overlayTimer
|
||
});
|
||
|
||
// Get task information from DOM or game state
|
||
let taskTitleText = '';
|
||
let taskTextContent = '';
|
||
let taskTimerContent = '';
|
||
|
||
if (taskTitle) {
|
||
taskTitleText = taskTitle.textContent || '';
|
||
} else if (window.gameInstance && window.gameInstance.gameState && window.gameInstance.gameState.currentTask) {
|
||
taskTitleText = window.gameInstance.gameState.currentTask.title || window.gameInstance.gameState.currentTask.text || '';
|
||
}
|
||
|
||
// Get actual task text - prefer title over description for actual task content
|
||
if (taskTitle) {
|
||
taskTextContent = taskTitle.textContent || '';
|
||
} else if (taskText) {
|
||
taskTextContent = taskText.textContent || '';
|
||
} else if (window.gameInstance && window.gameInstance.gameState && window.gameInstance.gameState.currentTask) {
|
||
taskTextContent = window.gameInstance.gameState.currentTask.title || window.gameInstance.gameState.currentTask.text || 'Training session in progress...';
|
||
}
|
||
|
||
if (taskTimer) {
|
||
taskTimerContent = taskTimer.textContent || '';
|
||
}
|
||
|
||
// Update webcam overlay with task information
|
||
if (overlayTaskType) {
|
||
console.log('🎬 Task title content:', taskTitleText);
|
||
if (taskTitleText.toLowerCase().includes('consequence')) {
|
||
overlayTaskType.textContent = 'CONSEQUENCE';
|
||
overlayTaskType.style.color = '#ff4757';
|
||
console.log('🎬 Set webcam overlay type to CONSEQUENCE');
|
||
} else if (taskTitleText.toLowerCase().includes('task')) {
|
||
overlayTaskType.textContent = 'TASK';
|
||
overlayTaskType.style.color = '#00d4ff';
|
||
console.log('🎬 Set webcam overlay type to TASK');
|
||
} else if (taskTitleText) {
|
||
overlayTaskType.textContent = 'SESSION';
|
||
overlayTaskType.style.color = '#ffd700';
|
||
console.log('🎬 Set webcam overlay type to SESSION');
|
||
} else {
|
||
overlayTaskType.textContent = 'TRAINING';
|
||
overlayTaskType.style.color = '#00ff00';
|
||
console.log('🎬 Set webcam overlay type to TRAINING (fallback)');
|
||
}
|
||
}
|
||
|
||
if (overlayTaskText) {
|
||
const text = taskTextContent || taskTitleText || 'Training session in progress...';
|
||
console.log('🎬 Updating webcam overlay text to:', text);
|
||
overlayTaskText.textContent = text;
|
||
}
|
||
|
||
if (overlayTimer) {
|
||
console.log('🎬 Task timer content:', taskTimerContent);
|
||
if (taskTimerContent && taskTimerContent !== '0:00' && taskTimerContent !== '') {
|
||
overlayTimer.textContent = taskTimerContent;
|
||
overlayTimer.style.display = 'inline';
|
||
console.log('🎬 Showing timer in webcam overlay:', taskTimerContent);
|
||
} else {
|
||
overlayTimer.textContent = '00:00';
|
||
overlayTimer.style.display = 'inline';
|
||
console.log('🎬 Set default timer in webcam overlay');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Call this function whenever task content changes
|
||
function syncTaskWithOverlay() {
|
||
console.log('🎬 syncTaskWithOverlay called, recordingOverlayEnabled:', recordingOverlayEnabled);
|
||
if (recordingOverlayEnabled) {
|
||
updateRecordingOverlay();
|
||
|
||
// Also update webcam overlay elements directly for immediate display
|
||
updateWebcamOverlayElements();
|
||
}
|
||
}
|
||
|
||
function updateWebcamOverlayElements() {
|
||
const taskTitleElement = document.getElementById('task-title');
|
||
const webcamTaskText = document.getElementById('webcam-task-text');
|
||
const webcamTaskType = document.getElementById('webcam-task-type');
|
||
|
||
if (taskTitleElement && webcamTaskText && taskTitleElement.textContent.trim()) {
|
||
webcamTaskText.textContent = taskTitleElement.textContent.trim();
|
||
console.log('🎬 Updated webcam overlay with actual task:', taskTitleElement.textContent.trim());
|
||
}
|
||
|
||
if (webcamTaskType) {
|
||
const title = taskTitleElement?.textContent || '';
|
||
if (title.toLowerCase().includes('consequence')) {
|
||
webcamTaskType.textContent = 'CONSEQUENCE';
|
||
webcamTaskType.style.color = '#ff4757';
|
||
} else if (title.toLowerCase().includes('task')) {
|
||
webcamTaskType.textContent = 'TASK';
|
||
webcamTaskType.style.color = '#00d4ff';
|
||
} else if (title) {
|
||
webcamTaskType.textContent = 'SESSION';
|
||
webcamTaskType.style.color = '#ffd700';
|
||
} else {
|
||
webcamTaskType.textContent = 'TRAINING';
|
||
webcamTaskType.style.color = '#00ff00';
|
||
}
|
||
}
|
||
}
|
||
|
||
function setupEventListeners() {
|
||
console.log('Setting up event listeners...');
|
||
|
||
|
||
|
||
|
||
|
||
// Navigation buttons with error handling
|
||
const backToHomeBtn = document.getElementById('back-to-home');
|
||
if (backToHomeBtn) {
|
||
backToHomeBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Home button clicked');
|
||
showExitDialog();
|
||
});
|
||
} else {
|
||
console.warn('back-to-home button not found');
|
||
}
|
||
|
||
const backToHomeResultsBtn = document.getElementById('back-to-home-results');
|
||
if (backToHomeResultsBtn) {
|
||
backToHomeResultsBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Results home button clicked');
|
||
exitToHome();
|
||
});
|
||
}
|
||
|
||
|
||
|
||
// Force exit button
|
||
const forceExitBtn = document.getElementById('force-exit');
|
||
if (forceExitBtn) {
|
||
forceExitBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Force exit button clicked');
|
||
if (confirm('⚠️ Force close the application? This will close the entire window.')) {
|
||
forceExit();
|
||
}
|
||
});
|
||
}
|
||
|
||
// Webcam photo button
|
||
const webcamBtn = document.getElementById('quick-play-webcam-btn');
|
||
if (webcamBtn) {
|
||
webcamBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Webcam photo button clicked');
|
||
openQuickPlayWebcam();
|
||
});
|
||
}
|
||
|
||
// Setup screen interactions
|
||
setupSetupScreenListeners();
|
||
|
||
// Setup video controls
|
||
setupVideoControlListeners();
|
||
|
||
// Game control buttons
|
||
const pauseBtn = document.getElementById('pause-game-btn');
|
||
if (pauseBtn) {
|
||
pauseBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Pause button clicked');
|
||
toggleGamePause();
|
||
});
|
||
}
|
||
|
||
// Results screen buttons
|
||
const playAgainBtn = document.getElementById('play-again');
|
||
if (playAgainBtn) {
|
||
playAgainBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Play again button clicked');
|
||
playAgain();
|
||
});
|
||
}
|
||
|
||
const newSettingsBtn = document.getElementById('new-settings');
|
||
if (newSettingsBtn) {
|
||
newSettingsBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('New settings button clicked');
|
||
showSetupScreen();
|
||
});
|
||
}
|
||
|
||
console.log('Event listeners setup complete');
|
||
}
|
||
|
||
function setupSetupScreenListeners() {
|
||
// Time presets
|
||
document.querySelectorAll('.time-preset').forEach(preset => {
|
||
preset.addEventListener('click', () => {
|
||
document.querySelectorAll('.time-preset').forEach(p => p.classList.remove('active'));
|
||
preset.classList.add('active');
|
||
|
||
if (preset.dataset.time === 'custom') {
|
||
quickPlaySettings.playTime = parseInt(document.getElementById('custom-time').value) * 60;
|
||
} else if (preset.dataset.time === 'endless') {
|
||
quickPlaySettings.playTime = -1; // -1 indicates endless mode
|
||
} else {
|
||
quickPlaySettings.playTime = parseInt(preset.dataset.time);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Custom time input
|
||
document.getElementById('custom-time').addEventListener('input', (e) => {
|
||
if (document.querySelector('.time-preset.custom').classList.contains('active')) {
|
||
quickPlaySettings.playTime = parseInt(e.target.value) * 60;
|
||
}
|
||
});
|
||
|
||
// Difficulty presets
|
||
document.getElementById('duration-setting').addEventListener('change', (e) => {
|
||
quickPlaySettings.duration = e.target.value;
|
||
});
|
||
|
||
// Include tags
|
||
document.querySelectorAll('#include-tags input[type="checkbox"]').forEach(checkbox => {
|
||
checkbox.addEventListener('change', (e) => {
|
||
if (e.target.checked) {
|
||
if (!quickPlaySettings.includeTags.includes(e.target.value)) {
|
||
quickPlaySettings.includeTags.push(e.target.value);
|
||
}
|
||
} else {
|
||
quickPlaySettings.includeTags = quickPlaySettings.includeTags.filter(tag => tag !== e.target.value);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Exclude tags
|
||
document.querySelectorAll('#exclude-tags input[type="checkbox"]').forEach(checkbox => {
|
||
checkbox.addEventListener('change', (e) => {
|
||
if (e.target.checked) {
|
||
if (!quickPlaySettings.excludeTags.includes(e.target.value)) {
|
||
quickPlaySettings.excludeTags.push(e.target.value);
|
||
}
|
||
} else {
|
||
quickPlaySettings.excludeTags = quickPlaySettings.excludeTags.filter(tag => tag !== e.target.value);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Audio checkboxes
|
||
document.getElementById('enable-background-audio').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableBackgroundAudio = e.target.checked;
|
||
});
|
||
document.getElementById('enable-ambient-audio').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableAmbientAudio = e.target.checked;
|
||
});
|
||
document.getElementById('enable-voice-commands').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableVoiceCommands = e.target.checked;
|
||
});
|
||
|
||
// Video settings
|
||
document.getElementById('video-mode').addEventListener('change', (e) => {
|
||
quickPlaySettings.videoMode = e.target.value;
|
||
updateVideoOptionsVisibility();
|
||
});
|
||
document.getElementById('enable-video-sound').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableVideoSound = e.target.checked;
|
||
});
|
||
document.getElementById('enable-video-controls').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableVideoControls = e.target.checked;
|
||
});
|
||
document.getElementById('video-opacity').addEventListener('change', (e) => {
|
||
quickPlaySettings.videoOpacity = parseFloat(e.target.value);
|
||
});
|
||
|
||
// Webcam recording settings
|
||
document.getElementById('enable-session-recording').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableSessionRecording = e.target.checked;
|
||
updateWebcamOptionsVisibility();
|
||
});
|
||
document.getElementById('select-webcam-directory').addEventListener('click', async () => {
|
||
try {
|
||
const directoryHandle = await window.showDirectoryPicker();
|
||
const directoryPath = directoryHandle.name;
|
||
|
||
// Save directory persistently
|
||
quickPlaySettings.webcamOutputDirectory = directoryPath;
|
||
quickPlaySettings.webcamDirectoryHandle = directoryHandle;
|
||
|
||
// Save to localStorage for persistence across sessions
|
||
localStorage.setItem('selectedVideoDirectory', JSON.stringify({
|
||
name: directoryPath,
|
||
timestamp: Date.now()
|
||
}));
|
||
|
||
// Also save to more persistent storage
|
||
localStorage.setItem('webcamRecordingDirectory', directoryPath);
|
||
|
||
// Try to store directory handle reference (where supported)
|
||
try {
|
||
const handleId = await storeDirectoryHandle(directoryHandle);
|
||
localStorage.setItem('webcamDirectoryHandleId', handleId);
|
||
} catch (handleError) {
|
||
console.log('📁 Directory handle storage not supported, will use fallback download');
|
||
}
|
||
|
||
document.getElementById('webcam-output-path').value = directoryPath;
|
||
console.log('📁 Webcam output directory selected and saved:', directoryPath);
|
||
|
||
if (window.flashMessageManager) {
|
||
window.flashMessageManager.show(`📁 Recording directory set to: ${directoryPath}`, 'positive');
|
||
}
|
||
} catch (error) {
|
||
console.log('📁 Directory selection cancelled or failed:', error);
|
||
}
|
||
});
|
||
document.getElementById('webcam-position').addEventListener('change', (e) => {
|
||
quickPlaySettings.webcamPosition = e.target.value;
|
||
});
|
||
document.getElementById('webcam-size').addEventListener('change', (e) => {
|
||
quickPlaySettings.webcamSize = e.target.value;
|
||
});
|
||
|
||
// Task type checkboxes
|
||
document.getElementById('include-standard-tasks').addEventListener('change', (e) => {
|
||
quickPlaySettings.includeStandardTasks = e.target.checked;
|
||
});
|
||
document.getElementById('include-scenario-tasks').addEventListener('change', (e) => {
|
||
quickPlaySettings.includeScenarioTasks = e.target.checked;
|
||
});
|
||
|
||
// Visual settings
|
||
// Visual settings now managed by popup image management screen
|
||
|
||
// Consequence settings
|
||
document.getElementById('consequence-chance').addEventListener('input', (e) => {
|
||
quickPlaySettings.consequenceChance = parseInt(e.target.value);
|
||
document.getElementById('consequence-chance-value').textContent = e.target.value + '%';
|
||
});
|
||
|
||
// Action buttons
|
||
document.getElementById('start-quick-play').addEventListener('click', startQuickPlaySession);
|
||
document.getElementById('save-preset').addEventListener('click', saveCurrentPreset);
|
||
document.getElementById('load-preset').addEventListener('click', loadPresetDialog);
|
||
|
||
// Task management buttons
|
||
document.getElementById('manage-tasks-btn').addEventListener('click', showTaskManagement);
|
||
document.getElementById('back-to-setup-btn').addEventListener('click', backToSetup);
|
||
|
||
// Message management buttons
|
||
document.getElementById('manage-messages-btn').addEventListener('click', showMessageManagement);
|
||
document.getElementById('back-to-setup-from-messages-btn').addEventListener('click', backToSetup);
|
||
|
||
// Popup image management buttons
|
||
document.getElementById('manage-popup-images-btn').addEventListener('click', showPopupImageManagement);
|
||
document.getElementById('back-to-setup-from-popup-images-btn').addEventListener('click', backToSetup);
|
||
|
||
// Task management functionality
|
||
document.getElementById('main-tasks-tab').addEventListener('click', () => showTaskTab('main'));
|
||
document.getElementById('consequence-tasks-tab').addEventListener('click', () => showTaskTab('consequence'));
|
||
document.getElementById('tag-management-tab').addEventListener('click', () => {
|
||
showTaskTab('tags');
|
||
// Set up tag management event listeners when tab is first accessed
|
||
setupTagManagementEventListeners();
|
||
});
|
||
document.getElementById('add-main-task-btn').addEventListener('click', () => addNewTask('main'));
|
||
document.getElementById('add-consequence-task-btn').addEventListener('click', () => addNewTask('consequence'));
|
||
|
||
// Tag management event listeners will be set up when the tab is accessed
|
||
|
||
// Tag suggestions
|
||
document.querySelectorAll('.tag-suggestion').forEach(tag => {
|
||
tag.addEventListener('click', (e) => {
|
||
const tagValue = e.target.dataset.tag;
|
||
const isMainTask = e.target.closest('#main-tasks-section');
|
||
const inputId = isMainTask ? 'main-task-tags' : 'consequence-task-tags';
|
||
const input = document.getElementById(inputId);
|
||
|
||
if (input) {
|
||
const currentValue = input.value.trim();
|
||
const tags = currentValue ? currentValue.split(',').map(t => t.trim()) : [];
|
||
if (!tags.includes(tagValue)) {
|
||
tags.push(tagValue);
|
||
input.value = tags.join(', ');
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
async function initializeFileManager() {
|
||
if (window.electronAPI && typeof DesktopFileManager !== 'undefined') {
|
||
const minimalDataManager = {
|
||
get: (key) => {
|
||
try {
|
||
return JSON.parse(localStorage.getItem(key));
|
||
} catch {
|
||
return null;
|
||
}
|
||
},
|
||
set: (key, value) => {
|
||
localStorage.setItem(key, JSON.stringify(value));
|
||
}
|
||
};
|
||
|
||
window.desktopFileManager = new DesktopFileManager(minimalDataManager);
|
||
console.log('🖥️ Desktop File Manager initialized for Quick Play');
|
||
}
|
||
}
|
||
|
||
function startQuickPlaySession() {
|
||
console.log('⚡ Starting Quick Play session with settings:', quickPlaySettings);
|
||
|
||
// Show loading overlay during session startup
|
||
const loadingOverlay = document.getElementById('quick-play-loading');
|
||
const loadingStatus = document.getElementById('loading-status');
|
||
const loadingProgress = document.getElementById('loading-progress-fill');
|
||
const loadingPercentage = document.getElementById('loading-percentage');
|
||
|
||
if (loadingOverlay) {
|
||
loadingOverlay.style.display = 'flex';
|
||
if (loadingStatus) loadingStatus.textContent = 'Initializing your session...';
|
||
if (loadingProgress) loadingProgress.style.width = '0%';
|
||
if (loadingPercentage) loadingPercentage.textContent = '0%';
|
||
}
|
||
|
||
// Reset session stats for new session
|
||
sessionStats = {
|
||
started: Date.now(),
|
||
completed: 0,
|
||
skipped: 0,
|
||
xp: 0
|
||
};
|
||
console.log('📊 Session stats reset:', sessionStats);
|
||
|
||
// Start periodic XP display updates (every minute)
|
||
if (window.xpDisplayInterval) {
|
||
clearInterval(window.xpDisplayInterval);
|
||
}
|
||
window.xpDisplayInterval = setInterval(() => {
|
||
updateSessionDisplay();
|
||
}, 60000); // Update every minute
|
||
|
||
// Initialize webcam recording if enabled
|
||
if (quickPlaySettings.enableSessionRecording) {
|
||
initializeWebcamRecording();
|
||
}
|
||
|
||
// Save current settings
|
||
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
|
||
|
||
// Simulate loading progress
|
||
let progress = 0;
|
||
const progressInterval = setInterval(() => {
|
||
progress += Math.random() * 20 + 10; // Random progress between 10-30% per step
|
||
if (progress >= 100) {
|
||
progress = 100;
|
||
clearInterval(progressInterval);
|
||
}
|
||
|
||
if (loadingProgress) loadingProgress.style.width = progress + '%';
|
||
if (loadingPercentage) loadingPercentage.textContent = Math.round(progress) + '%';
|
||
|
||
// Update status messages
|
||
if (loadingStatus) {
|
||
if (progress < 30) {
|
||
loadingStatus.textContent = 'Loading game configuration...';
|
||
} else if (progress < 60) {
|
||
loadingStatus.textContent = 'Preparing video systems...';
|
||
} else if (progress < 90) {
|
||
loadingStatus.textContent = 'Setting up task system...';
|
||
} else {
|
||
loadingStatus.textContent = 'Almost ready...';
|
||
}
|
||
}
|
||
|
||
if (progress >= 100) {
|
||
setTimeout(() => {
|
||
// Hide setup screen, show game screen
|
||
document.getElementById('quick-play-setup').style.display = 'none';
|
||
document.getElementById('quick-play-game').style.display = 'block';
|
||
document.getElementById('game-status-bar').style.display = 'flex';
|
||
document.getElementById('pause-game-btn').style.display = 'inline-block';
|
||
|
||
// Hide loading overlay
|
||
if (loadingOverlay) {
|
||
loadingOverlay.style.display = 'none';
|
||
}
|
||
|
||
// Initialize and start the game
|
||
initializeGameInstance();
|
||
}, 500);
|
||
}
|
||
}, 200); // Update every 200ms
|
||
}
|
||
|
||
function initializeGameInstance() {
|
||
const gameContainer = document.getElementById('game-container-wrapper');
|
||
|
||
// Create the game interface structure with optional background video
|
||
const hasBackgroundVideo = quickPlaySettings.videoMode === 'background';
|
||
|
||
gameContainer.innerHTML = `
|
||
<div class="game-container">
|
||
${hasBackgroundVideo ? `
|
||
<!-- Background Video Container -->
|
||
<div id="background-video-container" class="background-video-container">
|
||
<video id="background-video" class="background-video" preload="metadata" loop>
|
||
<source id="background-video-source" src="" type="video/mp4">
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
|
||
<!-- Video Controls (optional) -->
|
||
<div class="video-controls background-video-controls" style="display: ${quickPlaySettings.enableVideoControls ? 'flex' : 'none'};">
|
||
<div class="controls-row">
|
||
<button class="control-btn play-pause-btn" title="Play/Pause">▶</button>
|
||
<div class="volume-control">
|
||
<button class="control-btn mute-btn" title="Mute">🔊</button>
|
||
<input type="range" class="volume-slider" min="0" max="100" value="70">
|
||
</div>
|
||
<button class="control-btn" id="videoOpacityBtn" onclick="cycleVideoOpacity()" title="Video Opacity">🔆</button>
|
||
<button class="control-btn random-video-btn" title="Random Video">🎲</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Video Loading Indicator -->
|
||
<div class="video-loading" id="background-video-loading" style="display: none;">
|
||
<div class="loading-spinner"></div>
|
||
<p>Loading video...</p>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
|
||
<!-- Include main game interface elements here -->
|
||
<div class="game-content">
|
||
<div class="main-content-area">
|
||
<div class="task-display-container">
|
||
<div class="task-text-container">
|
||
<h3 id="task-title">Preparing your session...</h3>
|
||
<p id="task-text">Get ready for your Quick Play training session</p>
|
||
<div id="task-duration-container" class="task-duration-container" style="display: none;">
|
||
<span class="duration-label">Duration:</span>
|
||
<span id="task-duration" class="task-duration">0:00</span>
|
||
<span class="duration-remaining">Remaining:</span>
|
||
<span id="task-timer" class="task-timer">0:00</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-sidebar">
|
||
<!-- Video Controls Panel -->
|
||
<div id="video-control-panel" class="video-control-panel">
|
||
<div class="video-control-header" onclick="toggleVideoControls()">
|
||
<span class="video-control-icon">🎬</span>
|
||
<span class="video-control-title">Video Controls</span>
|
||
<span id="video-control-toggle" class="video-control-toggle collapsed">▶</span>
|
||
</div>
|
||
<div id="video-control-content" class="video-control-content collapsed">
|
||
<!-- Playback Controls -->
|
||
<div class="control-group">
|
||
<div class="control-row">
|
||
<button id="video-rewind" class="control-btn" title="Rewind 10s">⏪</button>
|
||
<button id="video-play-pause" class="control-btn" title="Play/Pause">⏸️</button>
|
||
<button id="video-forward" class="control-btn" title="Forward 10s">⏩</button>
|
||
<button id="video-skip" class="control-btn" title="Skip Video">⏭️</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Volume Control -->
|
||
<div class="control-group">
|
||
<label class="control-label">🔊 Volume</label>
|
||
<div class="volume-control">
|
||
<input type="range" id="video-volume" class="volume-slider" min="0" max="100" value="50">
|
||
<span id="video-volume-display" class="volume-display">50%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Playlist Selection -->
|
||
<div class="control-group">
|
||
<label class="control-label">📂 Source</label>
|
||
<select id="video-playlist-select" class="playlist-select">
|
||
<option value="random">🎲 Random Videos</option>
|
||
<option value="playlist1">📝 Playlist 1</option>
|
||
<option value="playlist2">📝 Playlist 2</option>
|
||
<option value="playlist3">📝 Playlist 3</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Video Info -->
|
||
<div class="control-group">
|
||
<div class="video-info">
|
||
<div id="current-video-name" class="current-video-name">No video loaded</div>
|
||
<div id="video-progress" class="video-progress">
|
||
<div class="progress-bar">
|
||
<div id="progress-fill" class="progress-fill"></div>
|
||
</div>
|
||
<div id="video-time" class="video-time">00:00 / 00:00</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
<button id="complete-task" class="btn btn-success" style="display: none;">Complete Task</button>
|
||
<button id="skip-task" class="btn btn-warning" style="display: none;">Skip Task</button>
|
||
<button id="end-game" class="btn btn-danger" style="display: none;">End Session</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Set up event listeners for the newly created buttons
|
||
setTimeout(() => {
|
||
setupGameButtonListeners();
|
||
setupVideoControlListeners();
|
||
console.log('Game button listeners and video controls set up after DOM creation');
|
||
}, 100);
|
||
|
||
// Initialize background video if enabled
|
||
if (hasBackgroundVideo) {
|
||
setTimeout(() => {
|
||
initializeBackgroundVideo();
|
||
}, 200);
|
||
} else {
|
||
console.log('🎬 Background video disabled - video mode:', quickPlaySettings.videoMode);
|
||
}
|
||
|
||
// Initialize the game instance with Quick Play specific settings
|
||
setTimeout(() => {
|
||
initializeFullGame();
|
||
}, 500);
|
||
}
|
||
|
||
function setupGameButtonListeners() {
|
||
console.log('Setting up game button listeners...');
|
||
|
||
const completeBtn = document.getElementById('complete-task');
|
||
const skipBtn = document.getElementById('skip-task');
|
||
const endBtn = document.getElementById('end-game');
|
||
|
||
if (completeBtn) {
|
||
completeBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
console.log('Complete task button clicked');
|
||
// Will be handled by displayTask function
|
||
});
|
||
console.log('Complete button listener attached');
|
||
}
|
||
|
||
if (skipBtn) {
|
||
skipBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
console.log('Skip task button clicked');
|
||
if (currentTask) {
|
||
quickPlaySkipTask(currentTask);
|
||
}
|
||
});
|
||
console.log('Skip button listener attached');
|
||
}
|
||
|
||
if (endBtn) {
|
||
endBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
console.log('End Session button clicked from setupGameButtonListeners');
|
||
endGame();
|
||
});
|
||
console.log('End button listener attached');
|
||
} else {
|
||
console.warn('End game button not found during setup');
|
||
}
|
||
}
|
||
|
||
// Video Control Functions
|
||
function toggleVideoControls() {
|
||
const content = document.getElementById('video-control-content');
|
||
const toggle = document.getElementById('video-control-toggle');
|
||
|
||
if (content.classList.contains('collapsed')) {
|
||
content.classList.remove('collapsed');
|
||
toggle.classList.remove('collapsed');
|
||
toggle.textContent = '▼';
|
||
} else {
|
||
content.classList.add('collapsed');
|
||
toggle.classList.add('collapsed');
|
||
toggle.textContent = '▶';
|
||
}
|
||
}
|
||
|
||
function setupVideoControlListeners() {
|
||
const video = document.getElementById('background-video');
|
||
if (!video) {
|
||
console.log('⚠️ Background video element not found during control setup');
|
||
return;
|
||
}
|
||
|
||
console.log('🎮 Setting up video control listeners...');
|
||
|
||
// Play/Pause control
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
if (playPauseBtn) {
|
||
playPauseBtn.addEventListener('click', async () => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (!currentVideo) {
|
||
console.log('No video element found');
|
||
return;
|
||
}
|
||
|
||
const source = currentVideo.querySelector('source');
|
||
if (!source || !source.src) {
|
||
console.log('No video source loaded');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (currentVideo.paused) {
|
||
await currentVideo.play();
|
||
playPauseBtn.textContent = '⏸️';
|
||
playPauseBtn.title = 'Pause';
|
||
console.log('▶️ Video resumed');
|
||
} else {
|
||
currentVideo.pause();
|
||
playPauseBtn.textContent = '▶️';
|
||
playPauseBtn.title = 'Play';
|
||
console.log('⏸️ Video paused');
|
||
}
|
||
} catch (error) {
|
||
console.error('Video playback error:', error);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Rewind 10 seconds
|
||
const rewindBtn = document.getElementById('video-rewind');
|
||
if (rewindBtn) {
|
||
rewindBtn.addEventListener('click', () => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (currentVideo && currentVideo.duration && isFinite(currentVideo.duration)) {
|
||
currentVideo.currentTime = Math.max(0, currentVideo.currentTime - 10);
|
||
console.log('⏪ Rewound 10 seconds');
|
||
} else {
|
||
console.log('Cannot rewind: video not ready');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Forward 10 seconds
|
||
const forwardBtn = document.getElementById('video-forward');
|
||
if (forwardBtn) {
|
||
forwardBtn.addEventListener('click', () => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (currentVideo && currentVideo.duration && isFinite(currentVideo.duration)) {
|
||
currentVideo.currentTime = Math.min(currentVideo.duration, currentVideo.currentTime + 10);
|
||
console.log('⏩ Fast forwarded 10 seconds');
|
||
} else {
|
||
console.log('Cannot fast forward: video not ready');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Skip video
|
||
const skipBtn = document.getElementById('video-skip');
|
||
if (skipBtn) {
|
||
skipBtn.addEventListener('click', () => {
|
||
loadRandomVideo();
|
||
});
|
||
}
|
||
|
||
// Volume control
|
||
const volumeSlider = document.getElementById('video-volume');
|
||
const volumeDisplay = document.getElementById('video-volume-display');
|
||
if (volumeSlider && volumeDisplay) {
|
||
volumeSlider.addEventListener('input', (e) => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (currentVideo) {
|
||
const volume = e.target.value / 100;
|
||
currentVideo.volume = volume;
|
||
volumeDisplay.textContent = e.target.value + '%';
|
||
console.log(`🔊 Volume set to ${e.target.value}% (${volume})`);
|
||
|
||
// Auto-mute when volume is 0
|
||
if (volume === 0) {
|
||
currentVideo.muted = true;
|
||
} else if (currentVideo.muted && volume > 0) {
|
||
currentVideo.muted = false;
|
||
}
|
||
} else {
|
||
console.warn('⚠️ No background video element found for volume control');
|
||
}
|
||
});
|
||
|
||
// Set initial volume when video loads
|
||
const initializeVolume = () => {
|
||
const currentVideo = document.getElementById('background-video');
|
||
if (currentVideo) {
|
||
currentVideo.volume = volumeSlider.value / 100;
|
||
volumeDisplay.textContent = volumeSlider.value + '%';
|
||
console.log(`🔊 Initial volume set to ${volumeSlider.value}%`);
|
||
}
|
||
};
|
||
|
||
// Try to set initial volume now and also when video loads
|
||
initializeVolume();
|
||
|
||
// Also set up a listener for when new videos are loaded
|
||
document.addEventListener('videoLoaded', initializeVolume);
|
||
}
|
||
|
||
// Playlist selection
|
||
const playlistSelect = document.getElementById('video-playlist-select');
|
||
if (playlistSelect) {
|
||
playlistSelect.addEventListener('change', (e) => {
|
||
const selectedPlaylist = e.target.value;
|
||
console.log('🎵 Playlist changed to:', selectedPlaylist);
|
||
// TODO: Implement playlist switching logic
|
||
loadRandomVideo(); // For now, just load a random video
|
||
});
|
||
}
|
||
|
||
// Video progress and time updates
|
||
video.addEventListener('timeupdate', updateVideoProgress);
|
||
video.addEventListener('loadedmetadata', updateVideoInfo);
|
||
video.addEventListener('play', () => {
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
if (playPauseBtn) {
|
||
playPauseBtn.textContent = '⏸️';
|
||
playPauseBtn.title = 'Pause';
|
||
}
|
||
});
|
||
video.addEventListener('pause', () => {
|
||
const playPauseBtn = document.getElementById('video-play-pause');
|
||
if (playPauseBtn) {
|
||
playPauseBtn.textContent = '▶️';
|
||
playPauseBtn.title = 'Play';
|
||
}
|
||
});
|
||
|
||
// Initial updates
|
||
setTimeout(() => {
|
||
updateVideoInfo();
|
||
updateVideoProgress();
|
||
}, 100);
|
||
|
||
console.log('✅ Video control listeners set up successfully');
|
||
}
|
||
|
||
function updateVideoProgress() {
|
||
const video = document.getElementById('background-video');
|
||
const progressFill = document.getElementById('progress-fill');
|
||
const videoTime = document.getElementById('video-time');
|
||
|
||
if (video && progressFill && videoTime && video.duration && isFinite(video.duration)) {
|
||
const progress = (video.currentTime / video.duration) * 100;
|
||
progressFill.style.width = isNaN(progress) ? '0%' : progress + '%';
|
||
|
||
const currentMinutes = Math.floor(video.currentTime / 60);
|
||
const currentSeconds = Math.floor(video.currentTime % 60);
|
||
const durationMinutes = Math.floor(video.duration / 60);
|
||
const durationSeconds = Math.floor(video.duration % 60);
|
||
|
||
videoTime.textContent = `${currentMinutes.toString().padStart(2, '0')}:${currentSeconds.toString().padStart(2, '0')} / ${durationMinutes.toString().padStart(2, '0')}:${durationSeconds.toString().padStart(2, '0')}`;
|
||
} else if (videoTime) {
|
||
videoTime.textContent = '00:00 / 00:00';
|
||
}
|
||
}
|
||
|
||
function updateVideoInfo() {
|
||
const video = document.getElementById('background-video');
|
||
const videoName = document.getElementById('current-video-name');
|
||
|
||
if (video && videoName) {
|
||
const source = video.querySelector('source');
|
||
if (source && source.src) {
|
||
const fileName = source.src.split('/').pop().split('.')[0];
|
||
const formattedName = fileName.replace(/[-_]/g, ' ');
|
||
// Capitalize each word
|
||
const displayName = formattedName.split(' ').map(word =>
|
||
word.charAt(0).toUpperCase() + word.slice(1)
|
||
).join(' ');
|
||
videoName.textContent = displayName;
|
||
} else {
|
||
videoName.textContent = 'No video loaded';
|
||
}
|
||
}
|
||
}
|
||
|
||
function loadRandomVideo() {
|
||
console.log('🎲 Loading random video...');
|
||
// Use the existing background video loading logic
|
||
if (typeof loadRandomBackgroundVideo === 'function') {
|
||
loadRandomBackgroundVideo();
|
||
} else {
|
||
console.warn('loadRandomBackgroundVideo function not available');
|
||
}
|
||
}
|
||
|
||
async function initializeFullGame() {
|
||
try {
|
||
// Wait for all scripts to load
|
||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
|
||
// Set up the game environment with error handling
|
||
if (typeof TaskChallengeGame !== 'undefined') {
|
||
console.log('📋 Creating TaskChallengeGame instance...');
|
||
|
||
try {
|
||
gameInstance = new TaskChallengeGame(quickPlaySettings);
|
||
console.log('✅ TaskChallengeGame instance created');
|
||
|
||
// Initialize GameDataManager for Quick Play
|
||
if (typeof GameDataManager !== 'undefined' && !window.gameDataManager) {
|
||
window.gameDataManager = new GameDataManager();
|
||
console.log('🎮 Game Data Manager initialized for Quick Play');
|
||
}
|
||
} catch (constructorError) {
|
||
console.error('❌ Error creating TaskChallengeGame:', constructorError);
|
||
// Try a simpler approach if the full constructor fails
|
||
throw new Error('Game constructor failed: ' + constructorError.message);
|
||
}
|
||
|
||
// Wait for game initialization
|
||
if (gameInstance.initialize) {
|
||
try {
|
||
await gameInstance.initialize();
|
||
console.log('✅ Game initialization complete');
|
||
} catch (initError) {
|
||
console.warn('⚠️ Game initialization had issues:', initError);
|
||
// Continue anyway - we can still run a basic game
|
||
}
|
||
}
|
||
|
||
// Configure Quick Play mode
|
||
if (gameInstance.gameModeManager) {
|
||
gameInstance.gameModeManager.currentMode = 'quick-play';
|
||
console.log('✅ Set mode to quick-play');
|
||
}
|
||
|
||
// Explicitly disable standard game systems for Quick Play
|
||
if (gameInstance.gameState) {
|
||
gameInstance.gameState.mode = 'quick-play';
|
||
gameInstance.gameState.isQuickPlay = true;
|
||
}
|
||
|
||
// Apply Quick Play settings to game configuration
|
||
const gameConfig = {
|
||
gameMode: 'timed',
|
||
timeLimit: quickPlaySettings.playTime,
|
||
enableAudio: quickPlaySettings.enableBackgroundAudio,
|
||
enableAmbient: quickPlaySettings.enableAmbientAudio,
|
||
popupFrequency: quickPlaySettings.popupFrequency,
|
||
popupDuration: quickPlaySettings.popupDuration,
|
||
enableFlashMessages: quickPlaySettings.enableFlashMessages,
|
||
difficulty: quickPlaySettings.difficulty,
|
||
includeStandardTasks: quickPlaySettings.includeStandardTasks,
|
||
includeScenarioTasks: quickPlaySettings.includeScenarioTasks,
|
||
// Force standard tasks to be included for Quick Play
|
||
isQuickPlay: true
|
||
};
|
||
|
||
console.log('📋 Quick Play config:', gameConfig);
|
||
|
||
// Configure systems based on Quick Play settings
|
||
configureGameSystems(gameConfig);
|
||
|
||
// Start the game with Quick Play specific approach
|
||
console.log('📋 Starting Quick Play mode - using simplified game setup');
|
||
setupSimpleGame(gameConfig);
|
||
|
||
isGameRunning = true;
|
||
|
||
// Set up game event listeners
|
||
setupGameEventListeners();
|
||
|
||
// Show game control buttons once game is running
|
||
showGameButtons();
|
||
|
||
// Show status bar and hide loading overlay
|
||
document.getElementById('game-status-bar').style.display = 'flex';
|
||
const loadingOverlay = document.querySelector('.loading-overlay');
|
||
if (loadingOverlay) {
|
||
loadingOverlay.style.display = 'none';
|
||
}
|
||
|
||
// Set up periodic task display updates
|
||
setInterval(() => {
|
||
updateTaskDisplay();
|
||
}, 1000);
|
||
|
||
console.log('✅ Quick Play game initialized successfully');
|
||
} else {
|
||
throw new Error('TaskChallengeGame class not available');
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Failed to initialize Quick Play game:', error);
|
||
showErrorDialog('Failed to start game. Please try again. Error: ' + error.message);
|
||
}
|
||
}
|
||
|
||
function setupSimpleGame(config) {
|
||
// Basic game setup for Quick Play mode
|
||
gameInstance.gameState = {
|
||
isRunning: true,
|
||
isPaused: false,
|
||
timeLimit: config.timeLimit,
|
||
tasksCompleted: 0,
|
||
xp: 0,
|
||
mode: 'quick-play',
|
||
isQuickPlay: true
|
||
};
|
||
|
||
// Ensure game data is available - try multiple approaches for Quick Play
|
||
if (!gameInstance.gameData) {
|
||
try {
|
||
// First try the GameDataManager
|
||
if (window.gameDataManager) {
|
||
gameInstance.gameData = window.gameDataManager.getDataForMode('standard');
|
||
console.log('📋 Loaded game data from GameDataManager:', gameInstance.gameData);
|
||
}
|
||
|
||
// If that didn't work, try direct access to mainGameData
|
||
if (!gameInstance.gameData || !gameInstance.gameData.mainTasks) {
|
||
if (window.mainGameData) {
|
||
gameInstance.gameData = window.mainGameData;
|
||
console.log('📋 Loaded game data directly from mainGameData:', gameInstance.gameData);
|
||
}
|
||
}
|
||
|
||
// Fallback: create basic tasks
|
||
if (!gameInstance.gameData || !gameInstance.gameData.mainTasks) {
|
||
gameInstance.gameData = {
|
||
mainTasks: [
|
||
{ id: 1, text: "Quick Play Task 1", description: "Complete this task", difficulty: "Easy" },
|
||
{ id: 2, text: "Quick Play Task 2", description: "Complete this task", difficulty: "Medium" },
|
||
{ id: 3, text: "Quick Play Task 3", description: "Complete this task", difficulty: "Easy" }
|
||
]
|
||
};
|
||
console.log('📋 Using fallback game data for Quick Play');
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Failed to load game data:', error);
|
||
}
|
||
}
|
||
|
||
// Initialize flash message system
|
||
initializeFlashMessages();
|
||
|
||
// Trigger welcome message
|
||
setTimeout(() => {
|
||
triggerEventFlashMessage('gameStart');
|
||
}, 3000); // Show after 3 seconds
|
||
|
||
// Start timer
|
||
startGameTimer(config.timeLimit);
|
||
|
||
// Load first task
|
||
loadNextTask();
|
||
}
|
||
|
||
function startGameTimer(timeLimit) {
|
||
// Handle endless mode
|
||
if (timeLimit === -1) {
|
||
console.log('🔄 Starting endless session - no time limit');
|
||
let timeElapsed = 0;
|
||
const timerInterval = setInterval(() => {
|
||
if (!isGameRunning || (gameInstance && gameInstance.gameState && gameInstance.gameState.isPaused)) {
|
||
return;
|
||
}
|
||
|
||
timeElapsed++;
|
||
updateGameStatus({ timer: formatTime(timeElapsed) + ' ∞' });
|
||
}, 1000);
|
||
return;
|
||
}
|
||
|
||
// Handle timed mode
|
||
let timeLeft = timeLimit;
|
||
const timerInterval = setInterval(() => {
|
||
if (!isGameRunning || (gameInstance && gameInstance.gameState && gameInstance.gameState.isPaused)) {
|
||
return;
|
||
}
|
||
|
||
timeLeft--;
|
||
updateGameStatus({ timer: formatTime(timeLeft) });
|
||
|
||
if (timeLeft <= 0) {
|
||
clearInterval(timerInterval);
|
||
endGame();
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
function formatTime(seconds) {
|
||
const mins = Math.floor(seconds / 60);
|
||
const secs = seconds % 60;
|
||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||
}
|
||
|
||
// ===== XP CALCULATION FUNCTIONS =====
|
||
|
||
function calculateTaskCompletionTime() {
|
||
if (gameInstance && gameInstance.gameState && gameInstance.gameState.currentTask && gameInstance.gameState.currentTask.startTime) {
|
||
const completionTime = Date.now() - gameInstance.gameState.currentTask.startTime;
|
||
return completionTime / (1000 * 60); // Convert to minutes
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
function calculateTaskXP(completionTimeMinutes) {
|
||
// Based on ROADMAP: 1 XP for <5min, 2 XP for 5-10min, 3 XP for >10min
|
||
if (completionTimeMinutes < 5) {
|
||
return 1;
|
||
} else if (completionTimeMinutes <= 10) {
|
||
return 2;
|
||
} else {
|
||
return 3;
|
||
}
|
||
}
|
||
|
||
function calculateCurrentTimeBonus() {
|
||
// Calculate current time bonus without awarding it (for display purposes)
|
||
const sessionDurationMinutes = (Date.now() - sessionStats.started) / (1000 * 60);
|
||
const complete15MinIntervals = Math.floor(sessionDurationMinutes / 15);
|
||
let timeBasedXP = complete15MinIntervals * 2; // 2 XP per complete 15-minute interval
|
||
|
||
// Add recording bonus if session recording is enabled
|
||
if (quickPlaySettings.enableSessionRecording) {
|
||
const recordingBonusXP = complete15MinIntervals * 1; // Additional 1 XP per interval when recording
|
||
timeBasedXP += recordingBonusXP;
|
||
}
|
||
|
||
return timeBasedXP;
|
||
}
|
||
|
||
function awardSessionTimeXP() {
|
||
// Award XP based on new system: 2 XP per complete 15-minute interval
|
||
const sessionDurationMinutes = (Date.now() - sessionStats.started) / (1000 * 60);
|
||
const complete15MinIntervals = Math.floor(sessionDurationMinutes / 15);
|
||
let timeBasedXP = complete15MinIntervals * 2; // 2 XP per complete 15-minute interval
|
||
|
||
// Add recording bonus if session recording is enabled
|
||
if (quickPlaySettings.enableSessionRecording) {
|
||
const recordingBonusXP = complete15MinIntervals * 1; // Additional 1 XP per interval when recording
|
||
timeBasedXP += recordingBonusXP;
|
||
console.log(`📹 Recording bonus: ${recordingBonusXP} XP for ${complete15MinIntervals} complete 15-minute intervals`);
|
||
}
|
||
|
||
if (timeBasedXP > 0) {
|
||
sessionStats.xp += timeBasedXP;
|
||
if (gameInstance && gameInstance.gameState) {
|
||
gameInstance.gameState.xp = (gameInstance.gameState.xp || 0) + timeBasedXP;
|
||
}
|
||
console.log(`⏰ Awarded ${timeBasedXP} XP for ${complete15MinIntervals} complete 15-minute intervals (${sessionDurationMinutes.toFixed(1)} minutes total)`);
|
||
}
|
||
|
||
return timeBasedXP;
|
||
}
|
||
|
||
function loadNextTask() {
|
||
// Simple task loading for Quick Play
|
||
try {
|
||
let tasks = null;
|
||
|
||
// Get all tasks including custom ones
|
||
tasks = getAllTasksForGame('main');
|
||
|
||
if (tasks && tasks.length > 0) {
|
||
// Apply tag filtering (disabled tasks already filtered out)
|
||
let filteredTasks = [...tasks];
|
||
|
||
// Step 1: If include tags are selected, filter to only those tasks
|
||
if (quickPlaySettings.includeTags && quickPlaySettings.includeTags.length > 0) {
|
||
filteredTasks = filteredTasks.filter(task => {
|
||
const taskTags = task.tags || [];
|
||
return quickPlaySettings.includeTags.some(includeTag =>
|
||
taskTags.includes(includeTag)
|
||
);
|
||
});
|
||
console.log(`🏷️ Filtered to ${filteredTasks.length} tasks with include tags:`, quickPlaySettings.includeTags);
|
||
}
|
||
|
||
// Step 2: Remove any tasks that have exclude tags (this overrides include)
|
||
if (quickPlaySettings.excludeTags && quickPlaySettings.excludeTags.length > 0) {
|
||
filteredTasks = filteredTasks.filter(task => {
|
||
const taskTags = task.tags || [];
|
||
return !quickPlaySettings.excludeTags.some(excludeTag =>
|
||
taskTags.includes(excludeTag)
|
||
);
|
||
});
|
||
console.log(`🚫 Filtered out tasks with exclude tags:`, quickPlaySettings.excludeTags, `- ${filteredTasks.length} tasks remaining`);
|
||
}
|
||
|
||
if (filteredTasks.length > 0) {
|
||
const randomTask = filteredTasks[Math.floor(Math.random() * filteredTasks.length)];
|
||
console.log('📋 Loading filtered task:', randomTask.text || randomTask.description, 'Tags:', randomTask.tags);
|
||
|
||
displayTask(randomTask);
|
||
} else {
|
||
console.log('⚠️ No tasks match the current filters or all tasks are disabled - showing fallback task');
|
||
displayTask({
|
||
text: 'No Available Tasks',
|
||
description: 'No tasks match your current filters or all tasks are disabled. Please adjust your settings.'
|
||
});
|
||
}
|
||
} else {
|
||
console.error('❌ No tasks available for Quick Play');
|
||
// Create a fallback task
|
||
displayTask({
|
||
text: 'Quick Play Task',
|
||
description: 'Complete this simple task to continue your Quick Play session.'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error loading task:', error);
|
||
// Fallback task in case of error
|
||
displayTask({
|
||
text: 'Basic Task',
|
||
description: 'Complete this task to continue.'
|
||
});
|
||
}
|
||
}
|
||
|
||
// Quick Play specific task completion functions
|
||
function quickPlayCompleteTask(task) {
|
||
console.log('✅ Quick Play task completed:', task.text);
|
||
|
||
// Check timer validation
|
||
if (!taskCompleteAllowed) {
|
||
console.log('⏱️ Task completion blocked - timer not finished');
|
||
|
||
// Show user feedback
|
||
const completeBtn = document.getElementById('complete-task');
|
||
if (completeBtn) {
|
||
const originalText = completeBtn.textContent;
|
||
completeBtn.textContent = 'Wait for Timer!';
|
||
completeBtn.style.background = '#ff4444';
|
||
|
||
setTimeout(() => {
|
||
if (completeBtn.textContent === 'Wait for Timer!') {
|
||
completeBtn.textContent = originalText;
|
||
completeBtn.style.background = '';
|
||
}
|
||
}, 1500);
|
||
}
|
||
|
||
return; // Exit early - don't complete the task
|
||
}
|
||
|
||
// Stop the timer
|
||
stopTaskTimer();
|
||
|
||
// Trigger achievement flash message
|
||
triggerEventFlashMessage('taskComplete');
|
||
|
||
// Check if this was a consequence task
|
||
if (isConsequenceTask || task.isConsequence) {
|
||
console.log('✅ Consequence task completed - returning to normal tasks');
|
||
isConsequenceTask = false;
|
||
|
||
// Reset task styling
|
||
const taskTitle = document.getElementById('task-title');
|
||
const taskText = document.getElementById('task-text');
|
||
const taskDurationContainer = document.getElementById('task-duration-container');
|
||
const skipBtn = document.getElementById('skip-task');
|
||
const completeBtn = document.getElementById('complete-task');
|
||
|
||
if (taskTitle) {
|
||
taskTitle.style.color = '';
|
||
}
|
||
if (taskText) {
|
||
taskText.style.color = '';
|
||
}
|
||
if (taskDurationContainer) {
|
||
taskDurationContainer.style.display = 'none';
|
||
}
|
||
if (skipBtn) {
|
||
skipBtn.style.display = 'inline-block';
|
||
}
|
||
if (completeBtn) {
|
||
completeBtn.style.background = '';
|
||
completeBtn.textContent = 'Complete Task';
|
||
completeBtn.disabled = false;
|
||
completeBtn.style.opacity = '1';
|
||
}
|
||
|
||
// Update game state
|
||
if (gameInstance && gameInstance.gameState) {
|
||
gameInstance.gameState.isConsequenceTask = false;
|
||
}
|
||
|
||
console.log('🔄 Consequence completed - loading next normal task');
|
||
} else {
|
||
// Normal task completion with time-based XP
|
||
console.log('✅ Normal task completed');
|
||
|
||
// Calculate task completion time and XP
|
||
const taskCompletionTime = calculateTaskCompletionTime();
|
||
const xpEarned = calculateTaskXP(taskCompletionTime);
|
||
|
||
// Update session stats
|
||
sessionStats.completed++;
|
||
sessionStats.xp += xpEarned;
|
||
|
||
// Update game state
|
||
if (gameInstance && gameInstance.gameState) {
|
||
gameInstance.gameState.tasksCompleted = (gameInstance.gameState.tasksCompleted || 0) + 1;
|
||
gameInstance.gameState.xp = (gameInstance.gameState.xp || 0) + xpEarned;
|
||
}
|
||
|
||
console.log(`✅ Task completed in ${taskCompletionTime.toFixed(1)} minutes, earned ${xpEarned} XP`);
|
||
|
||
// Track XP in player stats
|
||
if (window.playerStats) {
|
||
window.playerStats.onTaskCompleted({
|
||
xpEarned: xpEarned,
|
||
completionTime: taskCompletionTime,
|
||
source: 'quickplay'
|
||
});
|
||
}
|
||
|
||
// Update UI displays
|
||
updateSessionDisplay();
|
||
updateGameStatus({
|
||
tasksCompleted: gameInstance.gameState.tasksCompleted,
|
||
xp: gameInstance.gameState.xp
|
||
});
|
||
}
|
||
|
||
// Load next task
|
||
loadNextTask();
|
||
}
|
||
|
||
function quickPlaySkipTask(task) {
|
||
console.log('⏭️ Quick Play task skipped:', task.text);
|
||
|
||
// Trigger persistence flash message
|
||
triggerEventFlashMessage('taskSkip');
|
||
|
||
// Check if this is already a consequence task
|
||
if (isConsequenceTask) {
|
||
console.log('❌ Cannot skip consequence task - must be completed');
|
||
showRudeSkipMessage();
|
||
return;
|
||
}
|
||
|
||
// Check consequence chance
|
||
const consequenceChance = quickPlaySettings.consequenceChance || 100;
|
||
const roll = Math.random() * 100;
|
||
const triggersConsequence = roll < consequenceChance;
|
||
|
||
console.log(`🎲 Consequence roll: ${roll.toFixed(1)}% (threshold: ${consequenceChance}%) - ${triggersConsequence ? 'CONSEQUENCE!' : 'lucky escape'}`);
|
||
|
||
if (triggersConsequence) {
|
||
// Don't stop timer here - let consequence task handle its own timer
|
||
// Set consequence flag and trigger consequence
|
||
isConsequenceTask = true;
|
||
console.log('🚨 Task skipped - loading consequence task');
|
||
|
||
// Update session stats
|
||
if (sessionStats) {
|
||
sessionStats.skipped++;
|
||
updateSessionDisplay();
|
||
}
|
||
|
||
// Load a consequence task
|
||
loadConsequenceTask();
|
||
} else {
|
||
// Lucky escape - stop timer and load next normal task
|
||
stopTaskTimer();
|
||
console.log('😈 Lucky escape - no consequence this time');
|
||
|
||
// Update session stats (still count as skipped)
|
||
if (sessionStats) {
|
||
sessionStats.skipped++;
|
||
updateSessionDisplay();
|
||
}
|
||
|
||
// Load next normal task
|
||
loadNextTask();
|
||
}
|
||
}
|
||
|
||
function loadConsequenceTask() {
|
||
console.log('🚨 Loading consequence task for skipping');
|
||
|
||
// Get all consequence tasks including custom ones (disabled tasks already filtered)
|
||
let consequenceTasks = getAllTasksForGame('consequence');
|
||
console.log('📋 Got consequence tasks (including custom) - enabled count:', consequenceTasks.length);
|
||
|
||
if (consequenceTasks.length === 0) {
|
||
console.warn('⚠️ No consequence tasks available, using fallback');
|
||
// Fallback consequence task
|
||
const fallbackTask = {
|
||
id: 'fallback-consequence-' + Date.now(),
|
||
text: 'Punishment: 60 Second Timeout',
|
||
description: 'You skipped a task and must complete this timeout as punishment. Focus on the screen and do not look away for 60 seconds.',
|
||
type: 'consequence',
|
||
duration: 60
|
||
};
|
||
displayConsequenceTask(fallbackTask);
|
||
return;
|
||
}
|
||
|
||
// Select a random consequence task
|
||
const randomIndex = Math.floor(Math.random() * consequenceTasks.length);
|
||
let consequenceTask = { ...consequenceTasks[randomIndex] };
|
||
|
||
console.log('🚨 Selected consequence task:', consequenceTask.text);
|
||
displayConsequenceTask(consequenceTask);
|
||
}
|
||
|
||
function displayConsequenceTask(task) {
|
||
console.log('🚨 Displaying consequence task:', task.text);
|
||
|
||
// Update current task
|
||
currentTask = {
|
||
...task,
|
||
isConsequence: true
|
||
};
|
||
|
||
// Display the consequence task with special styling
|
||
const taskTitle = document.getElementById('task-title');
|
||
const taskText = document.getElementById('task-text');
|
||
const taskDurationContainer = document.getElementById('task-duration-container');
|
||
const taskDuration = document.getElementById('task-duration');
|
||
const taskTimer = document.getElementById('task-timer');
|
||
const completeBtn = document.getElementById('complete-task');
|
||
const skipBtn = document.getElementById('skip-task');
|
||
|
||
if (taskTitle) {
|
||
taskTitle.textContent = task.text; // Remove consequence prefix, but keep red styling
|
||
taskTitle.style.color = '#ff4444'; // Red color for consequence tasks
|
||
}
|
||
|
||
if (taskText) {
|
||
taskText.textContent = ''; // Remove redundant description text
|
||
taskText.style.display = 'none'; // Hide the description for consequence tasks
|
||
}
|
||
|
||
// Update recording overlay with new task
|
||
syncTaskWithOverlay();
|
||
|
||
// Hide skip button for consequence tasks
|
||
if (skipBtn) {
|
||
skipBtn.style.display = 'none';
|
||
}
|
||
|
||
// Update complete button to match normal task styling
|
||
if (completeBtn) {
|
||
completeBtn.textContent = 'Complete Task'; // Match normal task button text
|
||
completeBtn.style.background = ''; // Reset to normal styling
|
||
}
|
||
|
||
// Ensure timer display elements are visible for consequence tasks
|
||
if (taskDurationContainer) {
|
||
taskDurationContainer.style.display = 'block';
|
||
}
|
||
if (taskDuration) {
|
||
taskDuration.style.display = 'block';
|
||
}
|
||
if (taskTimer) {
|
||
taskTimer.style.display = 'block';
|
||
taskTimer.textContent = 'Starting timer...';
|
||
}
|
||
|
||
// Store task in game state if available
|
||
if (gameInstance && gameInstance.gameState) {
|
||
gameInstance.gameState.currentTask = currentTask;
|
||
gameInstance.gameState.isConsequenceTask = true;
|
||
}
|
||
|
||
// Calculate duration using the same system as regular tasks
|
||
let calculatedDuration = 0;
|
||
if (task.baseDuration && window.mainGameData && window.mainGameData.config) {
|
||
calculatedDuration = window.mainGameData.config.calculateDuration(task.baseDuration, quickPlaySettings.duration);
|
||
console.log('⏰ Calculated consequence task duration using config:', calculatedDuration, 'minutes');
|
||
} else if (task.baseDuration) {
|
||
// Fallback if mainGameData config not available - use same multipliers as regular tasks
|
||
const multipliers = { short: 0.5, medium: 1.0, long: 2.5 };
|
||
calculatedDuration = Math.round(task.baseDuration * (multipliers[quickPlaySettings.duration] || 1.0));
|
||
console.log('⏰ Calculated consequence task duration using fallback:', calculatedDuration, 'minutes');
|
||
} else if (task.duration) {
|
||
// Legacy duration field (in seconds)
|
||
calculatedDuration = Math.round(task.duration / 60); // Convert to minutes
|
||
} else {
|
||
// Default consequence duration
|
||
calculatedDuration = 5;
|
||
}
|
||
|
||
// Update task with calculated duration
|
||
currentTask.calculatedDuration = calculatedDuration;
|
||
|
||
// Display the calculated duration
|
||
if (taskDuration) {
|
||
taskDuration.textContent = `${calculatedDuration} minute${calculatedDuration !== 1 ? 's' : ''}`;
|
||
}
|
||
|
||
// Get timer and button elements for consequence tasks
|
||
const timerElement = document.getElementById('task-timer');
|
||
const completeButton = document.getElementById('complete-task');
|
||
|
||
console.log('⏰ Timer elements found:', {
|
||
timerElement: !!timerElement,
|
||
completeButton: !!completeButton,
|
||
calculatedDuration: calculatedDuration
|
||
});
|
||
|
||
// Use the main timer system (same as regular tasks)
|
||
startTaskTimer(calculatedDuration, timerElement, completeButton);
|
||
|
||
console.log('⏰ Timer started for consequence task with duration:', calculatedDuration, 'minutes');
|
||
|
||
console.log('🚨 Consequence task displayed successfully');
|
||
}
|
||
|
||
function displayTask(task) {
|
||
console.log('Displaying task:', task);
|
||
|
||
const taskTitle = document.getElementById('task-title');
|
||
const taskText = document.getElementById('task-text');
|
||
const taskDurationContainer = document.getElementById('task-duration-container');
|
||
const taskDuration = document.getElementById('task-duration');
|
||
const taskTimer = document.getElementById('task-timer');
|
||
const completeBtn = document.getElementById('complete-task');
|
||
const skipBtn = document.getElementById('skip-task');
|
||
const endBtn = document.getElementById('end-game');
|
||
|
||
console.log('Task display elements found:', {
|
||
taskTitle: !!taskTitle,
|
||
taskText: !!taskText,
|
||
taskDurationContainer: !!taskDurationContainer
|
||
});
|
||
|
||
if (taskTitle) {
|
||
taskTitle.textContent = task.text || 'Complete the task';
|
||
console.log('Set task title to:', task.text);
|
||
} else {
|
||
console.error('task-title element not found!');
|
||
}
|
||
|
||
if (taskText) {
|
||
taskText.textContent = task.description || 'Follow the instructions to complete this task.';
|
||
console.log('Set task text to:', task.description || 'Follow the instructions to complete this task.');
|
||
} else {
|
||
console.error('task-text element not found!');
|
||
}
|
||
|
||
// Calculate and display task duration
|
||
let calculatedDuration = 0;
|
||
if (task.baseDuration && window.mainGameData && window.mainGameData.config) {
|
||
calculatedDuration = window.mainGameData.config.calculateDuration(task.baseDuration, quickPlaySettings.duration);
|
||
} else if (task.baseDuration) {
|
||
// Fallback if mainGameData not available
|
||
const multipliers = { short: 0.5, medium: 1.0, long: 2.5 };
|
||
calculatedDuration = Math.round(task.baseDuration * (multipliers[quickPlaySettings.duration] || 1.0));
|
||
} else {
|
||
calculatedDuration = 5; // Default 5 minutes
|
||
}
|
||
|
||
// Display duration info
|
||
if (taskDurationContainer && taskDuration && taskTimer) {
|
||
taskDurationContainer.style.display = 'block';
|
||
const minutes = Math.floor(calculatedDuration);
|
||
const seconds = (calculatedDuration % 1) * 60;
|
||
taskDuration.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||
|
||
// Timer will be started after button setup to ensure correct button reference
|
||
}
|
||
|
||
// Store the current task in game state so updateTaskDisplay doesn't overwrite it
|
||
if (gameInstance && gameInstance.gameState) {
|
||
gameInstance.gameState.currentTask = {
|
||
title: task.text,
|
||
text: task.description || 'Follow the instructions to complete this task.',
|
||
duration: calculatedDuration,
|
||
startTime: Date.now()
|
||
};
|
||
}
|
||
|
||
// Update recording overlay with new task
|
||
syncTaskWithOverlay();
|
||
|
||
// Set up button event handlers with proper error handling
|
||
if (completeBtn) {
|
||
completeBtn.style.display = 'inline-block';
|
||
// Remove existing listeners
|
||
completeBtn.replaceWith(completeBtn.cloneNode(true));
|
||
const newCompleteBtn = document.getElementById('complete-task');
|
||
newCompleteBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Complete task button clicked');
|
||
quickPlayCompleteTask(task);
|
||
});
|
||
|
||
// Start timer with the new button reference after replacement
|
||
if (taskDurationContainer && taskDuration && taskTimer) {
|
||
console.log('🕐 Starting task timer with new button reference');
|
||
// Get fresh references to timer elements
|
||
const currentTimerElement = document.getElementById('task-timer');
|
||
const currentCompleteButton = document.getElementById('complete-task');
|
||
|
||
if (calculatedDuration > 0) {
|
||
startTaskTimer(calculatedDuration, currentTimerElement, currentCompleteButton);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (skipBtn) {
|
||
skipBtn.style.display = 'inline-block';
|
||
// Remove existing listeners
|
||
skipBtn.replaceWith(skipBtn.cloneNode(true));
|
||
const newSkipBtn = document.getElementById('skip-task');
|
||
newSkipBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('Skip task button clicked');
|
||
quickPlaySkipTask(task);
|
||
});
|
||
}
|
||
|
||
if (endBtn) {
|
||
endBtn.style.display = 'inline-block';
|
||
// Remove existing listeners
|
||
endBtn.replaceWith(endBtn.cloneNode(true));
|
||
const newEndBtn = document.getElementById('end-game');
|
||
newEndBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
console.log('End game button clicked');
|
||
endGame();
|
||
});
|
||
}
|
||
}
|
||
|
||
// Global timer variables
|
||
let taskTimer = null;
|
||
let taskTimeRemaining = 0;
|
||
let taskCompleteAllowed = false;
|
||
|
||
function startTaskTimer(durationMinutes, timerElement, completeButton) {
|
||
// console.log('🔧 startTaskTimer called with:', {
|
||
// durationMinutes: durationMinutes,
|
||
// timerElement: !!timerElement,
|
||
// completeButton: !!completeButton,
|
||
// timerElementId: timerElement ? timerElement.id : 'null',
|
||
// completeButtonId: completeButton ? completeButton.id : 'null'
|
||
// });
|
||
|
||
// Clear any existing timer
|
||
if (taskTimer) {
|
||
clearInterval(taskTimer);
|
||
// console.log('🔧 Cleared existing timer');
|
||
}
|
||
|
||
// Convert minutes to seconds
|
||
taskTimeRemaining = durationMinutes * 60;
|
||
taskCompleteAllowed = false;
|
||
|
||
console.log('🔧 Timer initialized with', taskTimeRemaining, 'seconds');
|
||
|
||
// Disable complete button initially
|
||
if (completeButton) {
|
||
completeButton.disabled = true;
|
||
completeButton.textContent = 'Wait for Timer...';
|
||
completeButton.style.opacity = '0.5';
|
||
console.log('🔧 Complete button disabled');
|
||
}
|
||
|
||
// Update timer display immediately
|
||
updateTimerDisplay(timerElement);
|
||
console.log('🔧 Initial timer display updated');
|
||
|
||
// Start countdown
|
||
console.log('🔧 About to create timer interval...');
|
||
try {
|
||
taskTimer = setInterval(() => {
|
||
try {
|
||
// console.log('🔧 Timer callback fired, timeRemaining:', taskTimeRemaining);
|
||
taskTimeRemaining--;
|
||
updateTimerDisplay(timerElement);
|
||
|
||
// Debug only every 60 seconds to reduce spam
|
||
if (taskTimeRemaining % 60 === 0 && taskTimeRemaining > 0) {
|
||
console.log('🔧 Timer countdown:', taskTimeRemaining, 'seconds remaining');
|
||
}
|
||
|
||
// Check if timer completed
|
||
if (taskTimeRemaining <= 0) {
|
||
clearInterval(taskTimer);
|
||
taskCompleteAllowed = true;
|
||
console.log('✅ Task timer completed!');
|
||
|
||
// Get fresh reference to complete button to ensure we're updating the right element
|
||
const currentCompleteBtn = document.getElementById('complete-task');
|
||
if (currentCompleteBtn) {
|
||
currentCompleteBtn.disabled = false;
|
||
currentCompleteBtn.textContent = 'Complete Task';
|
||
currentCompleteBtn.style.opacity = '1';
|
||
console.log('✅ Complete button enabled');
|
||
} else {
|
||
console.error('❌ Complete button not found when timer finished');
|
||
}
|
||
|
||
// Flash notification
|
||
if (timerElement) {
|
||
timerElement.style.color = '#00ff00';
|
||
timerElement.textContent = 'COMPLETE!';
|
||
setTimeout(() => {
|
||
timerElement.style.color = '';
|
||
timerElement.textContent = '0:00';
|
||
}, 2000);
|
||
}
|
||
|
||
console.log('✅ Task timer completed - task can now be completed');
|
||
}
|
||
} catch (error) {
|
||
console.error('🔧 Error in timer callback:', error);
|
||
}
|
||
}, 1000);
|
||
|
||
console.log('🔧 Timer interval created with ID:', taskTimer);
|
||
|
||
// Test if the timer is actually working by checking in 2 seconds
|
||
setTimeout(() => {
|
||
console.log('🔧 Timer check after 2 seconds - timeRemaining should be ~178:', taskTimeRemaining);
|
||
}, 2000);
|
||
|
||
} catch (error) {
|
||
console.error('🔧 Error creating timer interval:', error);
|
||
}
|
||
}
|
||
|
||
function updateTimerDisplay(timerElement) {
|
||
// console.log('🔧 updateTimerDisplay called with timeRemaining:', taskTimeRemaining, 'element:', !!timerElement);
|
||
if (!timerElement) return;
|
||
|
||
const minutes = Math.floor(taskTimeRemaining / 60);
|
||
const seconds = taskTimeRemaining % 60;
|
||
timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||
// console.log('🔧 Timer display updated to:', timerElement.textContent);
|
||
|
||
// Color coding
|
||
if (taskTimeRemaining <= 0) {
|
||
timerElement.style.color = '#00ff00'; // Green when complete
|
||
// Also update button state here as a backup
|
||
const completeBtn = document.getElementById('complete-task');
|
||
if (completeBtn && !taskCompleteAllowed) {
|
||
console.log('✅ Task timer completed - enabling complete button');
|
||
completeBtn.disabled = false;
|
||
completeBtn.textContent = 'Complete Task';
|
||
completeBtn.style.opacity = '1';
|
||
taskCompleteAllowed = true;
|
||
}
|
||
} else if (taskTimeRemaining <= 30) {
|
||
timerElement.style.color = '#ffaa00'; // Orange when almost done
|
||
} else {
|
||
timerElement.style.color = '#00d4ff'; // Blue normally
|
||
}
|
||
|
||
// Update recording overlay when timer changes
|
||
syncTaskWithOverlay();
|
||
}
|
||
|
||
function stopTaskTimer() {
|
||
if (taskTimer) {
|
||
clearInterval(taskTimer);
|
||
taskTimer = null;
|
||
}
|
||
taskTimeRemaining = 0;
|
||
taskCompleteAllowed = true;
|
||
}
|
||
|
||
function completeTask(task) {
|
||
// Legacy function - disabled in Quick Play to prevent double XP
|
||
// Quick Play uses quickPlayCompleteTask() instead
|
||
console.warn('Legacy completeTask() called - this should not happen in Quick Play mode');
|
||
return;
|
||
}
|
||
|
||
function skipTask(task) {
|
||
// Load consequence or next task
|
||
loadNextTask();
|
||
}
|
||
|
||
function endGame() {
|
||
console.log('endGame called - starting game termination');
|
||
|
||
try {
|
||
// Clear XP display update interval
|
||
if (window.xpDisplayInterval) {
|
||
clearInterval(window.xpDisplayInterval);
|
||
window.xpDisplayInterval = null;
|
||
console.log('🔄 Cleared XP display update interval');
|
||
}
|
||
|
||
// Immediately mute all videos to stop audio instantly
|
||
const allVideos = document.querySelectorAll('video');
|
||
allVideos.forEach(video => {
|
||
video.muted = true;
|
||
video.pause();
|
||
});
|
||
console.log(`Immediately muted and paused ${allVideos.length} video elements`);
|
||
|
||
// Immediately set flag to prevent multiple calls
|
||
if (window.isEndingGame) {
|
||
console.log('Game already ending, ignoring duplicate call');
|
||
return;
|
||
}
|
||
window.isEndingGame = true;
|
||
|
||
console.log('Setting isGameRunning to false');
|
||
isGameRunning = false;
|
||
|
||
// Stop flash message system
|
||
stopFlashMessageSystem();
|
||
|
||
// Clean up game instance
|
||
if (gameInstance) {
|
||
console.log('Cleaning up game instance');
|
||
try {
|
||
if (gameInstance.pauseGame) {
|
||
gameInstance.pauseGame();
|
||
console.log('Game paused');
|
||
}
|
||
if (gameInstance.cleanup) {
|
||
gameInstance.cleanup();
|
||
console.log('Game cleanup called');
|
||
}
|
||
if (gameInstance.audioManager) {
|
||
gameInstance.audioManager.stopAll();
|
||
console.log('Audio stopped');
|
||
}
|
||
} catch (cleanupError) {
|
||
console.warn('Error during game cleanup:', cleanupError);
|
||
}
|
||
}
|
||
|
||
// Clean up background video (check if it exists first)
|
||
if (typeof backgroundVideoPlayer !== 'undefined' && backgroundVideoPlayer) {
|
||
console.log('Stopping background video via backgroundVideoPlayer');
|
||
try {
|
||
backgroundVideoPlayer.pause();
|
||
if (backgroundVideoPlayer.destroy) {
|
||
backgroundVideoPlayer.destroy();
|
||
}
|
||
backgroundVideoPlayer = null;
|
||
} catch (videoError) {
|
||
console.warn('Error stopping background video via backgroundVideoPlayer:', videoError);
|
||
}
|
||
} else {
|
||
console.log('No backgroundVideoPlayer found - checking for background video element directly');
|
||
}
|
||
|
||
// Quick Play specific background video cleanup
|
||
const backgroundVideo = document.getElementById('background-video');
|
||
if (backgroundVideo) {
|
||
console.log('Found Quick Play background video - stopping it');
|
||
try {
|
||
backgroundVideo.pause();
|
||
backgroundVideo.currentTime = 0;
|
||
backgroundVideo.src = '';
|
||
const source = backgroundVideo.querySelector('source');
|
||
if (source) {
|
||
source.src = '';
|
||
}
|
||
console.log('✅ Quick Play background video stopped and cleared');
|
||
} catch (bgVideoError) {
|
||
console.warn('Error stopping Quick Play background video:', bgVideoError);
|
||
}
|
||
} else {
|
||
console.log('No Quick Play background video element found');
|
||
}
|
||
|
||
// Also clean up the background video container
|
||
const backgroundVideoContainer = document.getElementById('background-video-container');
|
||
if (backgroundVideoContainer) {
|
||
console.log('Clearing background video container');
|
||
try {
|
||
backgroundVideoContainer.innerHTML = '<div style="opacity: 0;">Video stopped</div>';
|
||
console.log('✅ Background video container cleared');
|
||
} catch (containerError) {
|
||
console.warn('Error clearing background video container:', containerError);
|
||
}
|
||
}
|
||
|
||
// Clean up video player manager - THIS IS THE IMPORTANT ONE
|
||
if (window.videoPlayerManager) {
|
||
console.log('Stopping all videos via VideoPlayerManager');
|
||
try {
|
||
window.videoPlayerManager.stopAllVideos();
|
||
console.log('All videos stopped via VideoPlayerManager');
|
||
} catch (videoManagerError) {
|
||
console.warn('Error stopping videos via VideoPlayerManager:', videoManagerError);
|
||
}
|
||
} else {
|
||
console.log('VideoPlayerManager not found - trying manual video cleanup');
|
||
}
|
||
|
||
// Manual fallback - find and stop all video elements (do this regardless)
|
||
try {
|
||
const allVideos = document.querySelectorAll('video');
|
||
console.log(`Found ${allVideos.length} video elements to stop`);
|
||
allVideos.forEach((video, index) => {
|
||
const videoId = video.id || 'unknown';
|
||
const videoSrc = video.src || video.currentSrc || 'no source';
|
||
console.log(`Stopping video element ${index}: ID="${videoId}", src="${videoSrc}"`);
|
||
|
||
try {
|
||
video.pause();
|
||
video.currentTime = 0;
|
||
video.muted = true; // Mute first to stop audio immediately
|
||
video.src = '';
|
||
// Remove source elements too
|
||
const sources = video.querySelectorAll('source');
|
||
sources.forEach(source => source.src = '');
|
||
video.load(); // Force reload to clear the video
|
||
|
||
console.log(`✅ Stopped video ${index} (${videoId})`);
|
||
} catch (singleVideoError) {
|
||
console.warn(`❌ Error stopping video ${index} (${videoId}):`, singleVideoError);
|
||
}
|
||
});
|
||
console.log(`✅ Manual video cleanup complete - processed ${allVideos.length} videos`);
|
||
} catch (manualError) {
|
||
console.warn('❌ Error in manual video cleanup:', manualError);
|
||
}
|
||
|
||
// Clean up periodic popups and any active popups
|
||
if (gameInstance && gameInstance.popupImageManager) {
|
||
console.log('Stopping periodic popups and clearing active popups');
|
||
try {
|
||
gameInstance.popupImageManager.stopPeriodicPopups();
|
||
gameInstance.popupImageManager.clearAllPopups();
|
||
console.log('Popup systems cleaned up');
|
||
} catch (popupError) {
|
||
console.warn('Error cleaning up popup systems:', popupError);
|
||
}
|
||
}
|
||
|
||
// Award XP for session time (users still accrue XP for incomplete games)
|
||
const sessionTimeXP = awardSessionTimeXP();
|
||
|
||
// Collect results
|
||
const timer = document.getElementById('game-timer');
|
||
const tasksElement = document.getElementById('tasks-completed');
|
||
const xpElement = document.getElementById('current-xp');
|
||
|
||
// Calculate actual session time
|
||
const sessionDurationMs = Date.now() - sessionStats.started;
|
||
const sessionMinutes = Math.floor(sessionDurationMs / 60000);
|
||
const sessionSeconds = Math.floor((sessionDurationMs % 60000) / 1000);
|
||
const formattedSessionTime = `${sessionMinutes.toString().padStart(2, '0')}:${sessionSeconds.toString().padStart(2, '0')}`;
|
||
|
||
const results = {
|
||
sessionTime: formattedSessionTime,
|
||
tasksCompleted: sessionStats.completed || 0,
|
||
xpEarned: (sessionStats.xp || 0) + (sessionTimeXP || 0),
|
||
performance: 'Session Complete!',
|
||
sessionTimeXP: sessionTimeXP,
|
||
skipped: sessionStats.skipped || 0
|
||
};
|
||
|
||
console.log('📊 Final session stats used for results:', sessionStats);
|
||
console.log('📊 Session time calculation: duration=', sessionDurationMs, 'ms, formatted=', formattedSessionTime);
|
||
console.log('📊 Calculated results object:', results);
|
||
|
||
console.log('Game ended with results:', results);
|
||
|
||
// Track session completion in player stats
|
||
if (window.playerStats) {
|
||
window.playerStats.onQuickPlaySessionComplete({
|
||
tasksCompleted: results.tasksCompleted,
|
||
xpEarned: results.xpEarned,
|
||
timeSpent: Date.now() - sessionStats.started,
|
||
sessionDuration: results.sessionTime
|
||
});
|
||
}
|
||
|
||
// Final safety cleanup - double-check all videos are stopped
|
||
setTimeout(() => {
|
||
console.log('🔍 Final video cleanup check...');
|
||
const remainingVideos = document.querySelectorAll('video');
|
||
if (remainingVideos.length > 0) {
|
||
console.log(`⚠️ Found ${remainingVideos.length} remaining videos - forcing stop`);
|
||
remainingVideos.forEach((video, index) => {
|
||
if (!video.paused) {
|
||
console.log(`🛑 Force stopping video ${index}: ${video.id || 'unnamed'}`);
|
||
video.pause();
|
||
video.src = '';
|
||
video.load();
|
||
}
|
||
});
|
||
} else {
|
||
console.log('✅ No remaining videos found - cleanup successful');
|
||
}
|
||
}, 100);
|
||
|
||
// Reset the ending flag after a delay
|
||
setTimeout(() => {
|
||
window.isEndingGame = false;
|
||
}, 1000);
|
||
|
||
showResultsScreen(results);
|
||
|
||
} catch (error) {
|
||
console.error('Error ending game:', error);
|
||
// Reset the ending flag
|
||
window.isEndingGame = false;
|
||
|
||
// Fallback - just show results screen
|
||
showResultsScreen({
|
||
sessionTime: '00:00',
|
||
tasksCompleted: 0,
|
||
xpEarned: 0,
|
||
performance: 'Session Complete!'
|
||
});
|
||
}
|
||
}
|
||
|
||
function configureGameSystems(config) {
|
||
if (!gameInstance) return;
|
||
|
||
// Configure popup manager with new comprehensive settings
|
||
if (gameInstance.popupImageManager) {
|
||
const popupSettings = JSON.parse(localStorage.getItem('popupImageConfig') || '{}');
|
||
|
||
if (popupSettings.enabled !== false) {
|
||
// Configure with comprehensive settings
|
||
gameInstance.popupImageManager.updatePeriodicSettings({
|
||
minInterval: popupSettings.minInterval || 120,
|
||
maxInterval: popupSettings.maxInterval || 180,
|
||
displayDuration: popupSettings.displayDuration || 8
|
||
});
|
||
|
||
// Update full configuration
|
||
gameInstance.popupImageManager.updateConfig(popupSettings);
|
||
|
||
// Start the periodic popup system
|
||
gameInstance.popupImageManager.startPeriodicPopups();
|
||
console.log('🚀 Started enhanced popup system with settings:', popupSettings);
|
||
} else {
|
||
console.log('📷 Popup images disabled');
|
||
}
|
||
}
|
||
|
||
// Configure flash messages (managed through message management screen)
|
||
if (gameInstance.flashMessageManager) {
|
||
const flashSettings = JSON.parse(localStorage.getItem('flashMessageConfig') || '{}');
|
||
gameInstance.flashMessageManager.enabled = flashSettings.enabled !== false;
|
||
}
|
||
|
||
// Configure audio
|
||
if (gameInstance.audioManager) {
|
||
try {
|
||
// Use the correct method name
|
||
gameInstance.audioManager.setCategoryEnabled('background', config.enableAudio);
|
||
|
||
// Set master volume if background audio is enabled
|
||
if (config.enableAudio) {
|
||
gameInstance.audioManager.setMasterVolume(0.7);
|
||
}
|
||
} catch (audioError) {
|
||
console.warn('⚠️ Audio configuration failed:', audioError);
|
||
}
|
||
}
|
||
}
|
||
|
||
function setupGameEventListeners() {
|
||
if (!gameInstance) return;
|
||
|
||
// Listen for game events
|
||
document.addEventListener('gameStateChanged', (event) => {
|
||
updateGameStatus(event.detail);
|
||
});
|
||
|
||
document.addEventListener('taskCompleted', (event) => {
|
||
updateTaskProgress(event.detail);
|
||
});
|
||
|
||
document.addEventListener('gameEnded', (event) => {
|
||
showResultsScreen(event.detail);
|
||
});
|
||
}
|
||
|
||
function showGameButtons() {
|
||
console.log('showGameButtons called');
|
||
|
||
// Show the complete and skip buttons when game starts
|
||
const completeBtn = document.getElementById('complete-task');
|
||
const skipBtn = document.getElementById('skip-task');
|
||
const endBtn = document.getElementById('end-game');
|
||
|
||
if (completeBtn) {
|
||
completeBtn.style.display = 'block';
|
||
console.log('Complete button shown');
|
||
}
|
||
if (skipBtn) {
|
||
skipBtn.style.display = 'block';
|
||
console.log('Skip button shown');
|
||
}
|
||
if (endBtn) {
|
||
endBtn.style.display = 'block';
|
||
console.log('End game button shown');
|
||
|
||
// Ensure the end button has proper event listener
|
||
endBtn.replaceWith(endBtn.cloneNode(true));
|
||
const newEndBtn = document.getElementById('end-game');
|
||
newEndBtn.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
console.log('End Session button clicked from showGameButtons');
|
||
endGame();
|
||
});
|
||
console.log('End button event listener attached');
|
||
} else {
|
||
console.warn('End game button not found in showGameButtons');
|
||
}
|
||
|
||
// Update task display to show actual game content
|
||
updateTaskDisplay();
|
||
}
|
||
|
||
function updateTaskDisplay() {
|
||
if (!gameInstance) return;
|
||
|
||
const taskTitle = document.getElementById('task-title');
|
||
const taskText = document.getElementById('task-text');
|
||
const taskImage = document.getElementById('task-image');
|
||
|
||
// Try to get current task from game instance
|
||
if (gameInstance.gameState && gameInstance.gameState.currentTask) {
|
||
const currentTask = gameInstance.gameState.currentTask;
|
||
|
||
if (taskTitle) {
|
||
taskTitle.textContent = currentTask.title || currentTask.text || '';
|
||
}
|
||
if (taskText) {
|
||
taskText.textContent = currentTask.text || 'Complete your task';
|
||
}
|
||
if (taskImage && currentTask.image) {
|
||
taskImage.src = currentTask.image;
|
||
taskImage.style.display = 'block';
|
||
|
||
// Clear any previously set dimensions to let CSS handle sizing
|
||
taskImage.style.width = '';
|
||
taskImage.style.height = '';
|
||
taskImage.style.maxWidth = '100%';
|
||
taskImage.style.maxHeight = '100%';
|
||
}
|
||
} else {
|
||
// Fallback - clear loading message
|
||
if (taskTitle) {
|
||
taskTitle.textContent = 'Training Session Active';
|
||
}
|
||
if (taskText) {
|
||
taskText.textContent = 'Your task will appear here';
|
||
}
|
||
}
|
||
|
||
// Update recording overlay whenever task display changes
|
||
syncTaskWithOverlay();
|
||
}
|
||
|
||
function updateGameStatus(gameState) {
|
||
if (gameState.timer) {
|
||
document.getElementById('game-timer').textContent = gameState.timer;
|
||
}
|
||
if (gameState.tasksCompleted !== undefined) {
|
||
document.getElementById('tasks-completed').textContent = gameState.tasksCompleted;
|
||
}
|
||
if (gameState.xp !== undefined) {
|
||
document.getElementById('current-xp').textContent = gameState.xp;
|
||
}
|
||
if (gameState.status) {
|
||
document.getElementById('session-status').textContent = gameState.status;
|
||
}
|
||
}
|
||
|
||
function updateSessionDisplay() {
|
||
console.log('📊 Updating session display with stats:', sessionStats);
|
||
|
||
// Update session stats display
|
||
const statusElement = document.getElementById('session-status');
|
||
if (statusElement) {
|
||
const skippedText = sessionStats.skipped > 0 ? ` (${sessionStats.skipped} skipped)` : '';
|
||
statusElement.textContent = `${sessionStats.completed} completed${skippedText}`;
|
||
|
||
// Change color if tasks were skipped
|
||
if (sessionStats.skipped > 0) {
|
||
statusElement.style.color = '#ff9800';
|
||
} else {
|
||
statusElement.style.color = '';
|
||
}
|
||
}
|
||
|
||
// Update tasks completed
|
||
const tasksElement = document.getElementById('tasks-completed');
|
||
if (tasksElement) {
|
||
tasksElement.textContent = sessionStats.completed;
|
||
}
|
||
|
||
// Update XP display (include current time bonus)
|
||
const xpElement = document.getElementById('current-xp');
|
||
if (xpElement) {
|
||
const currentTimeBonus = calculateCurrentTimeBonus();
|
||
const totalCurrentXP = sessionStats.xp + currentTimeBonus;
|
||
xpElement.textContent = totalCurrentXP;
|
||
}
|
||
}
|
||
|
||
function updateTaskProgress(taskData) {
|
||
// Update progress indicators
|
||
console.log('Task completed:', taskData);
|
||
|
||
// Update task display after task completion
|
||
setTimeout(() => {
|
||
updateTaskDisplay();
|
||
}, 500);
|
||
}
|
||
|
||
function showResultsScreen(results) {
|
||
isGameRunning = false;
|
||
|
||
console.log('📊 Showing results screen with data:', results);
|
||
|
||
// Stop webcam recording if active
|
||
if (quickPlaySettings.enableSessionRecording) {
|
||
stopWebcamRecording();
|
||
}
|
||
|
||
// Hide game screen, show results
|
||
document.getElementById('quick-play-game').style.display = 'none';
|
||
document.getElementById('quick-play-results').style.display = 'block';
|
||
document.getElementById('game-status-bar').style.display = 'none';
|
||
document.getElementById('pause-game-btn').style.display = 'none';
|
||
|
||
// Populate results with detailed logging
|
||
if (results.sessionTime) {
|
||
document.getElementById('final-session-time').textContent = results.sessionTime;
|
||
console.log('✅ Set session time:', results.sessionTime);
|
||
}
|
||
if (results.tasksCompleted !== undefined) {
|
||
document.getElementById('final-tasks-count').textContent = results.tasksCompleted;
|
||
console.log('✅ Set tasks completed:', results.tasksCompleted);
|
||
}
|
||
if (results.xpEarned !== undefined) {
|
||
document.getElementById('final-xp-earned').textContent = results.xpEarned;
|
||
console.log('✅ Set XP earned:', results.xpEarned);
|
||
}
|
||
if (results.performance) {
|
||
let performanceText = results.performance;
|
||
if (results.skipped && results.skipped > 0) {
|
||
performanceText += ` (${results.skipped} skipped)`;
|
||
}
|
||
document.getElementById('final-performance').textContent = performanceText;
|
||
console.log('✅ Set performance:', performanceText);
|
||
}
|
||
}
|
||
|
||
function toggleGamePause() {
|
||
if (gameInstance && isGameRunning) {
|
||
if (gameInstance.gameState.isPaused) {
|
||
gameInstance.resumeGame();
|
||
resumeFlashMessageSystem();
|
||
document.getElementById('pause-game-btn').innerHTML = '⏸️ Pause';
|
||
} else {
|
||
gameInstance.pauseGame();
|
||
pauseFlashMessageSystem();
|
||
document.getElementById('pause-game-btn').innerHTML = '▶️ Resume';
|
||
}
|
||
}
|
||
}
|
||
|
||
function playAgain() {
|
||
// Reset to setup screen for new session
|
||
document.getElementById('quick-play-results').style.display = 'none';
|
||
startQuickPlaySession();
|
||
}
|
||
|
||
function showSetupScreen() {
|
||
// Return to setup for new configuration
|
||
document.getElementById('quick-play-results').style.display = 'none';
|
||
document.getElementById('quick-play-setup').style.display = 'block';
|
||
}
|
||
|
||
function showRudeSkipMessage() {
|
||
// Create a styled notification overlay
|
||
const overlay = document.createElement('div');
|
||
overlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 10000;
|
||
`;
|
||
|
||
const messageBox = document.createElement('div');
|
||
messageBox.style.cssText = `
|
||
background: linear-gradient(45deg, #8B0000, #A52A2A);
|
||
color: white;
|
||
padding: 25px;
|
||
border-radius: 10px;
|
||
border: 2px solid #ff6666;
|
||
text-align: center;
|
||
max-width: 400px;
|
||
font-family: Arial, sans-serif;
|
||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
|
||
`;
|
||
|
||
const messages = [
|
||
"🚫 What a pathetic little worm!<br><br>You can't skip your punishment, loser.<br>You earned this consequence for being weak.<br><br>Complete it like the submissive bitch you are.",
|
||
"❌ Nice try, you worthless slut!<br><br>Trying to escape your humiliation?<br>Too bad! You're going to do every second<br><br>of this degrading punishment.",
|
||
"🔒 Aww, is the little sissy scared?<br><br>You can't run away from your consequence.<br>This is what happens to pathetic losers<br><br>who can't handle their tasks.",
|
||
"🚨 Absolutely fucking not!<br><br>You don't get to escape your humiliation.<br>Complete your punishment like the<br><br>worthless piece of shit you are."
|
||
];
|
||
|
||
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
|
||
messageBox.innerHTML = `
|
||
<div style="margin-bottom: 20px; font-size: 16px; line-height: 1.4;">${randomMessage}</div>
|
||
<button id="dismissRudeMessage" style="
|
||
background: #ffffff;
|
||
color: #8B0000;
|
||
border: 1px solid #8B0000;
|
||
padding: 10px 20px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
">Understood</button>
|
||
`;
|
||
|
||
overlay.appendChild(messageBox);
|
||
document.body.appendChild(overlay);
|
||
|
||
// Close button functionality
|
||
document.getElementById('dismissRudeMessage').onclick = function() {
|
||
document.body.removeChild(overlay);
|
||
};
|
||
|
||
// Auto-close after 5 seconds
|
||
setTimeout(() => {
|
||
if (document.body.contains(overlay)) {
|
||
document.body.removeChild(overlay);
|
||
}
|
||
}, 5000);
|
||
}
|
||
|
||
function saveCurrentPreset() {
|
||
const presetName = prompt('Enter a name for this preset:');
|
||
if (presetName) {
|
||
const presets = JSON.parse(localStorage.getItem('quickPlayPresets') || '{}');
|
||
presets[presetName] = { ...quickPlaySettings };
|
||
localStorage.setItem('quickPlayPresets', JSON.stringify(presets));
|
||
alert(`Preset "${presetName}" saved successfully!`);
|
||
}
|
||
}
|
||
|
||
function loadPresetDialog() {
|
||
const presets = JSON.parse(localStorage.getItem('quickPlayPresets') || '{}');
|
||
const presetNames = Object.keys(presets);
|
||
|
||
if (presetNames.length === 0) {
|
||
alert('No saved presets found.');
|
||
return;
|
||
}
|
||
|
||
const selectedPreset = prompt(`Select a preset to load:\n${presetNames.map((name, i) => `${i + 1}. ${name}`).join('\n')}\n\nEnter the preset name:`);
|
||
|
||
if (selectedPreset && presets[selectedPreset]) {
|
||
quickPlaySettings = { ...presets[selectedPreset] };
|
||
applySettingsToUI();
|
||
alert(`Preset "${selectedPreset}" loaded successfully!`);
|
||
}
|
||
}
|
||
|
||
function showExitDialog() {
|
||
console.log('showExitDialog called, isGameRunning:', isGameRunning);
|
||
|
||
if (isGameRunning) {
|
||
if (confirm('⚠️ Game in progress! Are you sure you want to exit? Progress will be lost.')) {
|
||
exitToHome();
|
||
}
|
||
} else {
|
||
exitToHome();
|
||
}
|
||
}
|
||
|
||
function exitToHome() {
|
||
console.log('Attempting to exit to home...');
|
||
|
||
// Stop webcam recording if active
|
||
if (quickPlaySettings.enableSessionRecording) {
|
||
stopWebcamRecording();
|
||
}
|
||
|
||
// Stop any running game
|
||
if (gameInstance) {
|
||
try {
|
||
if (gameInstance.pauseGame) gameInstance.pauseGame();
|
||
if (gameInstance.cleanup) gameInstance.cleanup();
|
||
} catch (e) {
|
||
console.warn('Error cleaning up game:', e);
|
||
}
|
||
}
|
||
|
||
// Clear game state
|
||
isGameRunning = false;
|
||
gameInstance = null;
|
||
|
||
// Try different navigation methods
|
||
try {
|
||
// Check if we're in Electron
|
||
if (window.electronAPI || window.require) {
|
||
console.log('Detected Electron environment');
|
||
// Try Electron navigation
|
||
if (window.electronAPI && window.electronAPI.navigateToHome) {
|
||
window.electronAPI.navigateToHome();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Fallback to standard web navigation
|
||
console.log('Using standard navigation');
|
||
window.location.href = 'index.html';
|
||
|
||
} catch (error) {
|
||
console.error('Navigation failed:', error);
|
||
// Last resort - try to close the window
|
||
try {
|
||
window.close();
|
||
} catch (closeError) {
|
||
console.error('Could not close window:', closeError);
|
||
alert('Unable to navigate. Please manually close the window or navigate to the home page.');
|
||
}
|
||
}
|
||
}
|
||
|
||
function forceExit() {
|
||
console.log('Force exit called');
|
||
|
||
// Prevent multiple calls
|
||
if (window.isForceExiting) {
|
||
console.log('Force exit already in progress');
|
||
return;
|
||
}
|
||
window.isForceExiting = true;
|
||
|
||
// Clean up immediately to prevent loops
|
||
try {
|
||
// Stop the game and clear state
|
||
isGameRunning = false;
|
||
|
||
// Clean up game instance quickly
|
||
if (gameInstance) {
|
||
if (gameInstance.audioManager) {
|
||
gameInstance.audioManager.stopAll();
|
||
}
|
||
gameInstance = null;
|
||
}
|
||
|
||
// Remove event listeners to prevent loops
|
||
window.removeEventListener('beforeunload', arguments.callee);
|
||
|
||
} catch (error) {
|
||
console.warn('Error during cleanup:', error);
|
||
}
|
||
|
||
// Try multiple exit strategies with timeouts
|
||
setTimeout(() => {
|
||
try {
|
||
// Try Electron specific methods first
|
||
if (window.electronAPI) {
|
||
if (window.electronAPI.closeWindow) {
|
||
console.log('Attempting Electron closeWindow');
|
||
window.electronAPI.closeWindow();
|
||
return;
|
||
}
|
||
if (window.electronAPI.quit) {
|
||
console.log('Attempting Electron quit');
|
||
window.electronAPI.quit();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Try to access Electron's remote module if available
|
||
if (window.require) {
|
||
try {
|
||
const remote = window.require('@electron/remote');
|
||
if (remote) {
|
||
console.log('Using Electron remote to close window');
|
||
remote.getCurrentWindow().close();
|
||
return;
|
||
}
|
||
} catch (e) {
|
||
console.log('Remote module not available');
|
||
}
|
||
}
|
||
|
||
// Try standard window close
|
||
console.log('Attempting standard window close');
|
||
window.close();
|
||
|
||
} catch (error) {
|
||
console.error('All exit methods failed:', error);
|
||
|
||
// Final fallback - navigate to a blank page
|
||
setTimeout(() => {
|
||
window.location.href = 'about:blank';
|
||
}, 100);
|
||
}
|
||
}, 100); // Small delay to ensure cleanup completes
|
||
}
|
||
|
||
async function openQuickPlayWebcam() {
|
||
console.log('📸 Opening Quick Play webcam');
|
||
|
||
try {
|
||
// Initialize webcam manager if not already done
|
||
if (!window.webcamManager) {
|
||
if (typeof WebcamManager !== 'undefined') {
|
||
window.webcamManager = new WebcamManager(gameInstance);
|
||
await window.webcamManager.init();
|
||
} else {
|
||
console.warn('WebcamManager not available');
|
||
alert('📸 Webcam functionality not available. Please ensure camera permissions are granted.');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Create simple photo session for Quick Play
|
||
const sessionData = {
|
||
type: 'quick-play-photo',
|
||
requirements: {
|
||
count: 1,
|
||
description: 'Take a photo to document your Quick Play session'
|
||
}
|
||
};
|
||
|
||
// Start photo session
|
||
const success = await window.webcamManager.startPhotoSessionWithProgress('quick-play', sessionData);
|
||
|
||
if (success) {
|
||
console.log('✅ Webcam photo session started');
|
||
|
||
// Award XP for using webcam feature (1 XP)
|
||
if (sessionStats) {
|
||
sessionStats.xp += 1;
|
||
updateSessionDisplay();
|
||
}
|
||
|
||
// Update game state if available
|
||
if (gameInstance && gameInstance.gameState) {
|
||
gameInstance.gameState.xp = (gameInstance.gameState.xp || 0) + 1;
|
||
updateGameStatus({ xp: gameInstance.gameState.xp });
|
||
}
|
||
|
||
} else {
|
||
console.warn('Failed to start webcam photo session');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error opening webcam:', error);
|
||
alert('📸 Failed to open webcam. Please check camera permissions and try again.');
|
||
}
|
||
}
|
||
|
||
// Webcam Recording Functions
|
||
let mediaRecorder = null;
|
||
let recordedChunks = [];
|
||
let webcamStream = null;
|
||
let recordingCanvas = null;
|
||
let recordingCanvasContext = null;
|
||
let compositeStream = null;
|
||
let animationFrameId = null;
|
||
|
||
async function initializeWebcamRecording() {
|
||
console.log('🎥 Initializing webcam recording for session...');
|
||
|
||
try {
|
||
// Request camera access with high quality 16:9 settings
|
||
webcamStream = await navigator.mediaDevices.getUserMedia({
|
||
video: {
|
||
width: { ideal: 1920, min: 1280 },
|
||
height: { ideal: 1080, min: 720 },
|
||
aspectRatio: { ideal: 16/9 },
|
||
frameRate: { ideal: 30, min: 24 },
|
||
facingMode: 'user'
|
||
},
|
||
audio: false // Audio disabled for privacy
|
||
});
|
||
|
||
// Set up webcam viewer
|
||
const webcamViewer = document.getElementById('webcam-viewer');
|
||
const webcamPreview = document.getElementById('webcam-preview');
|
||
|
||
webcamPreview.srcObject = webcamStream;
|
||
|
||
// Apply user settings and enable recording overlay by default
|
||
webcamViewer.className = `webcam-viewer active recording ${quickPlaySettings.webcamSize} ${quickPlaySettings.webcamPosition}`;
|
||
|
||
// Set up recording - Force MP4 format for Electron
|
||
recordedChunks = [];
|
||
|
||
// Try various MP4 codec combinations for Electron compatibility
|
||
const mp4Codecs = [
|
||
'video/mp4;codecs=avc1.42E01E', // H.264 Baseline
|
||
'video/mp4;codecs=avc1.4D401E', // H.264 Main
|
||
'video/mp4;codecs=avc1.64001E', // H.264 High
|
||
'video/mp4', // Generic MP4
|
||
];
|
||
|
||
let mediaRecorderCreated = false;
|
||
|
||
// Set up canvas for compositing video with overlays
|
||
recordingCanvas = document.createElement('canvas');
|
||
recordingCanvas.width = 1920;
|
||
recordingCanvas.height = 1080;
|
||
recordingCanvasContext = recordingCanvas.getContext('2d');
|
||
|
||
// Create composite stream from canvas
|
||
compositeStream = recordingCanvas.captureStream(30); // 30 FPS
|
||
|
||
// Start compositing loop and recording timer
|
||
startVideoCompositing();
|
||
|
||
// Start recording timer for overlay
|
||
if (!recordingTimerInterval) {
|
||
recordingTimerInterval = setInterval(updateRecordingTimer, 1000);
|
||
}
|
||
|
||
// Initialize overlay with current task content
|
||
setTimeout(() => {
|
||
updateRecordingOverlay();
|
||
updateWebcamOverlayElements();
|
||
}, 1000);
|
||
|
||
// Try each MP4 codec until one works
|
||
for (const codec of mp4Codecs) {
|
||
if (MediaRecorder.isTypeSupported(codec)) {
|
||
try {
|
||
mediaRecorder = new MediaRecorder(compositeStream, {
|
||
mimeType: codec,
|
||
videoBitsPerSecond: 4000000, // 4 Mbps for high quality 1080p
|
||
bitsPerSecond: 4000000 // Fallback for older browsers
|
||
});
|
||
quickPlaySettings.recordingMimeType = codec;
|
||
quickPlaySettings.recordingExtension = 'mp4';
|
||
console.log('🎥 Successfully using MP4 codec with compositing:', codec);
|
||
mediaRecorderCreated = true;
|
||
break;
|
||
} catch (error) {
|
||
console.warn('Failed to create MediaRecorder with codec:', codec, error);
|
||
}
|
||
}
|
||
}
|
||
|
||
// If no MP4 codec worked, throw error since user wants MP4 only
|
||
if (!mediaRecorderCreated) {
|
||
throw new Error('❌ MP4 recording not supported in this Electron version. Please update Electron or Chrome engine.');
|
||
}
|
||
|
||
mediaRecorder.ondataavailable = (event) => {
|
||
if (event.data.size > 0) {
|
||
recordedChunks.push(event.data);
|
||
}
|
||
};
|
||
|
||
mediaRecorder.onstop = () => {
|
||
saveRecordedSession();
|
||
};
|
||
|
||
// Start recording with optimized timeslice for quality
|
||
mediaRecorder.start(1000); // Collect data every 1 second for better quality
|
||
console.log('✅ High-quality session recording started at 4 Mbps');
|
||
|
||
// Set up viewer controls
|
||
setupWebcamViewerControls();
|
||
|
||
} catch (error) {
|
||
console.error('❌ Failed to initialize webcam recording:', error);
|
||
// Don't show error to user - recording is optional
|
||
// Hide the viewer if it failed
|
||
const webcamViewer = document.getElementById('webcam-viewer');
|
||
webcamViewer.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function setupWebcamViewerControls() {
|
||
const toggleBtn = document.getElementById('toggle-webcam-viewer');
|
||
const stopBtn = document.getElementById('stop-recording');
|
||
const webcamViewer = document.getElementById('webcam-viewer');
|
||
|
||
// Toggle visibility
|
||
toggleBtn.addEventListener('click', () => {
|
||
const preview = document.getElementById('webcam-preview');
|
||
if (preview.style.display === 'none') {
|
||
preview.style.display = 'block';
|
||
toggleBtn.textContent = '👁️';
|
||
toggleBtn.title = 'Hide Webcam';
|
||
} else {
|
||
preview.style.display = 'none';
|
||
toggleBtn.textContent = '👁️🗨️';
|
||
toggleBtn.title = 'Show Webcam';
|
||
}
|
||
});
|
||
|
||
// Stop recording
|
||
stopBtn.addEventListener('click', () => {
|
||
stopWebcamRecording();
|
||
});
|
||
|
||
// Make viewer draggable
|
||
makeWebcamViewerDraggable();
|
||
}
|
||
|
||
function makeWebcamViewerDraggable() {
|
||
const webcamViewer = document.getElementById('webcam-viewer');
|
||
let isDragging = false;
|
||
let startX, startY, startLeft, startTop;
|
||
|
||
webcamViewer.addEventListener('mousedown', (e) => {
|
||
// Only drag when clicking on the viewer itself, not controls
|
||
if (e.target.classList.contains('control-btn')) return;
|
||
|
||
isDragging = true;
|
||
startX = e.clientX;
|
||
startY = e.clientY;
|
||
startLeft = webcamViewer.offsetLeft;
|
||
startTop = webcamViewer.offsetTop;
|
||
|
||
webcamViewer.style.position = 'fixed';
|
||
webcamViewer.style.left = startLeft + 'px';
|
||
webcamViewer.style.top = startTop + 'px';
|
||
webcamViewer.classList.remove('bottom-right', 'bottom-left', 'top-right', 'top-left');
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (!isDragging) return;
|
||
|
||
const deltaX = e.clientX - startX;
|
||
const deltaY = e.clientY - startY;
|
||
|
||
webcamViewer.style.left = (startLeft + deltaX) + 'px';
|
||
webcamViewer.style.top = (startTop + deltaY) + 'px';
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
isDragging = false;
|
||
});
|
||
}
|
||
|
||
function startVideoCompositing() {
|
||
console.log('🎨 Starting video compositing with task overlay...');
|
||
|
||
function drawFrame() {
|
||
if (!recordingCanvas || !recordingCanvasContext || !webcamStream) {
|
||
return;
|
||
}
|
||
|
||
const webcamPreview = document.getElementById('webcam-preview');
|
||
if (!webcamPreview || webcamPreview.videoWidth === 0) {
|
||
animationFrameId = requestAnimationFrame(drawFrame);
|
||
return;
|
||
}
|
||
|
||
// Clear canvas
|
||
recordingCanvasContext.clearRect(0, 0, recordingCanvas.width, recordingCanvas.height);
|
||
|
||
// Draw webcam video
|
||
recordingCanvasContext.drawImage(
|
||
webcamPreview,
|
||
0, 0,
|
||
recordingCanvas.width,
|
||
recordingCanvas.height
|
||
);
|
||
|
||
// Draw task overlay if enabled
|
||
if (recordingOverlayEnabled) {
|
||
drawTaskOverlayOnCanvas();
|
||
}
|
||
|
||
// Continue loop
|
||
animationFrameId = requestAnimationFrame(drawFrame);
|
||
}
|
||
|
||
drawFrame();
|
||
}
|
||
|
||
function drawTaskOverlayOnCanvas() {
|
||
const ctx = recordingCanvasContext;
|
||
if (!ctx) return;
|
||
|
||
// Get current task information - prioritize actual task title over generic description
|
||
const taskTitleElement = document.getElementById('task-title');
|
||
const taskTextElement = document.getElementById('task-text');
|
||
|
||
let actualTaskText = '';
|
||
if (taskTitleElement && taskTitleElement.textContent && taskTitleElement.textContent.trim()) {
|
||
actualTaskText = taskTitleElement.textContent.trim();
|
||
} else if (taskTextElement && taskTextElement.textContent && taskTextElement.textContent.trim()) {
|
||
actualTaskText = taskTextElement.textContent.trim();
|
||
} else {
|
||
actualTaskText = 'Training session in progress...';
|
||
}
|
||
|
||
const taskType = document.getElementById('webcam-task-type')?.textContent || 'SESSION';
|
||
const taskTimer = document.getElementById('webcam-task-timer')?.textContent || '00:00';
|
||
|
||
// Set up text styling
|
||
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
|
||
ctx.strokeStyle = '#00ff00';
|
||
ctx.lineWidth = 2;
|
||
|
||
// Calculate overlay position (bottom area)
|
||
const overlayHeight = 120;
|
||
const overlayY = recordingCanvas.height - overlayHeight - 20;
|
||
const overlayX = 20;
|
||
const overlayWidth = recordingCanvas.width - 40;
|
||
|
||
// Draw overlay background
|
||
ctx.fillRect(overlayX, overlayY, overlayWidth, overlayHeight);
|
||
ctx.strokeRect(overlayX, overlayY, overlayWidth, overlayHeight);
|
||
|
||
// Draw task type and timer header
|
||
ctx.fillStyle = '#00ff00';
|
||
ctx.font = 'bold 28px Arial';
|
||
ctx.fillText(taskType, overlayX + 15, overlayY + 35);
|
||
|
||
ctx.fillStyle = '#ffa500';
|
||
ctx.font = 'bold 24px Arial';
|
||
const timerWidth = ctx.measureText(taskTimer).width;
|
||
ctx.fillText(taskTimer, overlayX + overlayWidth - timerWidth - 15, overlayY + 35);
|
||
|
||
// Draw task text (with word wrapping)
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = '22px Arial';
|
||
|
||
const maxWidth = overlayWidth - 30;
|
||
const lineHeight = 30;
|
||
const words = actualTaskText.split(' ');
|
||
let line = '';
|
||
let y = overlayY + 70;
|
||
|
||
for (let n = 0; n < words.length; n++) {
|
||
const testLine = line + words[n] + ' ';
|
||
const metrics = ctx.measureText(testLine);
|
||
const testWidth = metrics.width;
|
||
|
||
if (testWidth > maxWidth && n > 0) {
|
||
ctx.fillText(line, overlayX + 15, y);
|
||
line = words[n] + ' ';
|
||
y += lineHeight;
|
||
|
||
// Prevent overflow
|
||
if (y > overlayY + overlayHeight - 10) break;
|
||
} else {
|
||
line = testLine;
|
||
}
|
||
}
|
||
ctx.fillText(line, overlayX + 15, y);
|
||
}
|
||
|
||
function stopVideoCompositing() {
|
||
console.log('⏹️ Stopping video compositing...');
|
||
|
||
if (animationFrameId) {
|
||
cancelAnimationFrame(animationFrameId);
|
||
animationFrameId = null;
|
||
}
|
||
|
||
if (compositeStream) {
|
||
const tracks = compositeStream.getTracks();
|
||
tracks.forEach(track => track.stop());
|
||
compositeStream = null;
|
||
}
|
||
|
||
recordingCanvas = null;
|
||
recordingCanvasContext = null;
|
||
}
|
||
|
||
function stopWebcamRecording() {
|
||
console.log('⏹️ Stopping webcam recording...');
|
||
|
||
// Stop video compositing
|
||
stopVideoCompositing();
|
||
|
||
if (mediaRecorder && mediaRecorder.state === 'recording') {
|
||
mediaRecorder.stop();
|
||
}
|
||
|
||
if (webcamStream) {
|
||
webcamStream.getTracks().forEach(track => track.stop());
|
||
webcamStream = null;
|
||
}
|
||
|
||
const webcamViewer = document.getElementById('webcam-viewer');
|
||
webcamViewer.classList.remove('active');
|
||
|
||
console.log('✅ Webcam recording stopped');
|
||
}
|
||
|
||
async function saveRecordedSession() {
|
||
console.log('💾 Automatically saving recorded session...');
|
||
|
||
if (recordedChunks.length === 0) {
|
||
console.warn('No recorded data to save');
|
||
return;
|
||
}
|
||
|
||
// Use the recording format that was selected during setup
|
||
const mimeType = quickPlaySettings.recordingMimeType || 'video/mp4';
|
||
const extension = quickPlaySettings.recordingExtension || 'mp4';
|
||
const blob = new Blob(recordedChunks, { type: mimeType });
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||
const filename = `quick-play-session-${timestamp}.${extension}`;
|
||
|
||
// Try to save to user-selected directory first (if available)
|
||
if (quickPlaySettings.webcamDirectoryHandle) {
|
||
try {
|
||
const fileHandle = await quickPlaySettings.webcamDirectoryHandle.getFileHandle(filename, { create: true });
|
||
const writable = await fileHandle.createWritable();
|
||
await writable.write(blob);
|
||
await writable.close();
|
||
|
||
console.log('✅ Session recording saved to directory:', filename);
|
||
|
||
if (window.flashMessageManager) {
|
||
window.flashMessageManager.show(`📹 Recording saved to directory: ${filename}`, 'positive');
|
||
}
|
||
|
||
recordedChunks = [];
|
||
return;
|
||
|
||
} catch (error) {
|
||
console.error('❌ Directory save failed, falling back to download:', error);
|
||
}
|
||
}
|
||
|
||
// Automatic download fallback (no prompts)
|
||
console.log('💾 Auto-downloading recording...');
|
||
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
a.style.display = 'none';
|
||
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
|
||
console.log('✅ Session recording auto-downloaded:', filename);
|
||
|
||
if (window.flashMessageManager) {
|
||
window.flashMessageManager.show(`📹 Recording downloaded: ${filename}`, 'positive');
|
||
}
|
||
|
||
// Clean up
|
||
recordedChunks = [];
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
function showErrorDialog(message) {
|
||
alert(`❌ Error: ${message}`);
|
||
}
|
||
|
||
// Keyboard shortcuts
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.target.tagName === 'INPUT') return; // Don't interfere with input fields
|
||
|
||
switch (e.key) {
|
||
case 'Escape':
|
||
if (isGameRunning) {
|
||
toggleGamePause();
|
||
} else {
|
||
showExitDialog();
|
||
}
|
||
break;
|
||
case ' ':
|
||
if (isGameRunning) {
|
||
e.preventDefault();
|
||
toggleGamePause();
|
||
}
|
||
break;
|
||
case 'F11':
|
||
e.preventDefault();
|
||
toggleFullscreen();
|
||
break;
|
||
case 'F4':
|
||
// Alt+F4 - Emergency force exit
|
||
if (e.altKey) {
|
||
e.preventDefault();
|
||
console.log('Alt+F4 detected - force exit');
|
||
forceExit();
|
||
}
|
||
break;
|
||
}
|
||
});
|
||
|
||
function toggleFullscreen() {
|
||
if (!document.fullscreenElement) {
|
||
document.documentElement.requestFullscreen();
|
||
} else {
|
||
document.exitFullscreen();
|
||
}
|
||
}
|
||
|
||
// Load available playlists into the selector
|
||
function loadPlaylistSelector() {
|
||
const selector = document.getElementById('playlist-selector');
|
||
if (!selector) return;
|
||
|
||
try {
|
||
// Get saved playlists from localStorage (same as VideoLibrary)
|
||
const saved = localStorage.getItem('pornCinema_savedPlaylists');
|
||
const playlists = saved ? JSON.parse(saved) : [];
|
||
|
||
console.log(`📺 Found ${playlists.length} saved playlists`);
|
||
|
||
// Clear existing options (except Random)
|
||
const randomOption = selector.querySelector('option[value="random"]');
|
||
selector.innerHTML = '';
|
||
selector.appendChild(randomOption);
|
||
|
||
// Add playlist options
|
||
if (playlists.length > 0) {
|
||
playlists.forEach(playlist => {
|
||
const option = document.createElement('option');
|
||
option.value = playlist.name;
|
||
option.textContent = `📋 ${playlist.name} (${playlist.videos.length} videos)`;
|
||
selector.appendChild(option);
|
||
});
|
||
} else {
|
||
const noPlaylistsOption = document.createElement('option');
|
||
noPlaylistsOption.value = '';
|
||
noPlaylistsOption.disabled = true;
|
||
noPlaylistsOption.textContent = '📝 No playlists created yet';
|
||
selector.appendChild(noPlaylistsOption);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.warn('Failed to load playlists:', error);
|
||
const errorOption = document.createElement('option');
|
||
errorOption.value = '';
|
||
errorOption.disabled = true;
|
||
errorOption.textContent = '⚠️ Error loading playlists';
|
||
selector.appendChild(errorOption);
|
||
}
|
||
}
|
||
|
||
// Load videos from a specific playlist
|
||
async function loadPlaylistVideos(playlistName) {
|
||
try {
|
||
const saved = localStorage.getItem('pornCinema_savedPlaylists');
|
||
const playlists = saved ? JSON.parse(saved) : [];
|
||
|
||
const playlist = playlists.find(p => p.name === playlistName);
|
||
if (!playlist) {
|
||
console.warn(`Playlist "${playlistName}" not found`);
|
||
return [];
|
||
}
|
||
|
||
console.log(`📋 Found playlist "${playlistName}" with ${playlist.videos.length} videos`);
|
||
return playlist.videos || [];
|
||
|
||
} catch (error) {
|
||
console.error('Failed to load playlist videos:', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// Get all available videos (same logic as background video loading)
|
||
async function getAllAvailableVideos() {
|
||
let allVideos = [];
|
||
|
||
// First check if we have a video library instance
|
||
if (window.videoLibrary && typeof window.videoLibrary.getAllVideos === 'function') {
|
||
allVideos = window.videoLibrary.getAllVideos();
|
||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary instance`);
|
||
}
|
||
|
||
// Try to access VideoLibrary videos directly if it exists
|
||
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
|
||
allVideos = window.VideoLibrary.videos;
|
||
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
|
||
}
|
||
|
||
// Try desktop file manager (this is likely where your videos are)
|
||
if (allVideos.length === 0 && window.desktopFileManager) {
|
||
try {
|
||
if (typeof window.desktopFileManager.getAllVideos === 'function') {
|
||
allVideos = window.desktopFileManager.getAllVideos();
|
||
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
|
||
} else {
|
||
console.log('🎬 DesktopFileManager.getAllVideos not available');
|
||
}
|
||
} catch (dmError) {
|
||
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
|
||
}
|
||
}
|
||
|
||
// Fallback to unified storage (use same logic as background video)
|
||
if (allVideos.length === 0) {
|
||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||
allVideos = unifiedData.allVideos || [];
|
||
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
|
||
}
|
||
|
||
// Fallback to legacy storage
|
||
if (allVideos.length === 0) {
|
||
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
|
||
allVideos = Object.values(storedVideos).flat();
|
||
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
|
||
}
|
||
|
||
// Try accessing VideoLibrary legacy storage directly
|
||
if (allVideos.length === 0) {
|
||
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
|
||
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
|
||
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
|
||
}
|
||
|
||
// If still no videos, try to initialize video library (same as background loading)
|
||
if (allVideos.length === 0) {
|
||
console.log('🎬 Overlay: No videos found, attempting to initialize video library...');
|
||
const initSuccess = await initializeVideoLibrary();
|
||
|
||
if (initSuccess) {
|
||
// Try again after initialization
|
||
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
||
allVideos = unifiedData.allVideos || [];
|
||
console.log(`🎬 Overlay: After initialization: ${allVideos.length} videos available`);
|
||
}
|
||
}
|
||
|
||
return allVideos;
|
||
}
|
||
|
||
// Open overlay video player
|
||
async function openOverlayVideoPlayer() {
|
||
console.log('📺 Opening overlay video player...');
|
||
|
||
try {
|
||
// Initialize overlay video player if not already done
|
||
if (typeof OverlayVideoPlayer === 'undefined') {
|
||
console.warn('⚠️ OverlayVideoPlayer not available');
|
||
alert('Overlay video player is not available. Please check that all scripts are loaded.');
|
||
return;
|
||
}
|
||
|
||
// Check if user selected a playlist
|
||
const playlistSelector = document.getElementById('playlist-selector');
|
||
const selectedPlaylist = playlistSelector ? playlistSelector.value : 'random';
|
||
|
||
let allVideos = [];
|
||
|
||
if (selectedPlaylist !== 'random') {
|
||
// Load specific playlist
|
||
console.log(`📋 Loading playlist: ${selectedPlaylist}`);
|
||
allVideos = await loadPlaylistVideos(selectedPlaylist);
|
||
|
||
if (allVideos.length === 0) {
|
||
alert(`Playlist "${selectedPlaylist}" is empty or could not be loaded.`);
|
||
return;
|
||
}
|
||
} else {
|
||
// Use random mode - get all available videos
|
||
console.log('🎲 Using random video mode');
|
||
allVideos = await getAllAvailableVideos();
|
||
}
|
||
|
||
if (allVideos.length === 0) {
|
||
console.warn('⚠️ No videos found for overlay player');
|
||
|
||
// Show setup instructions overlay instead of alert
|
||
const setupOverlay = document.createElement('div');
|
||
setupOverlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.9);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 10000;
|
||
color: white;
|
||
font-family: Arial, sans-serif;
|
||
`;
|
||
|
||
setupOverlay.innerHTML = `
|
||
<div style="text-align: center; max-width: 600px; padding: 40px;">
|
||
<h2 style="color: #ff6b35; margin-bottom: 20px;">📺 No Videos Available</h2>
|
||
<p style="font-size: 1.1em; margin-bottom: 20px;">
|
||
To use the overlay video player, you need to set up video directories first.
|
||
</p>
|
||
<div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin: 20px 0;">
|
||
<h3 style="color: #3a86ff; margin-bottom: 15px;">Setup Instructions:</h3>
|
||
<ol style="text-align: left; font-size: 1em; line-height: 1.6;">
|
||
<li>Go to the main menu (index.html)</li>
|
||
<li>Click on "Media Library" tab</li>
|
||
<li>Select "Video" section</li>
|
||
<li>Click "Add Directory" to link your video folders</li>
|
||
<li>Return to Quick Play to use overlay videos</li>
|
||
</ol>
|
||
</div>
|
||
<div style="margin-top: 30px;">
|
||
<button onclick="this.parentElement.parentElement.remove()"
|
||
style="padding: 12px 24px; background: #ff6b35; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 1em; margin-right: 10px;">
|
||
✅ Got It
|
||
</button>
|
||
<button onclick="window.open('index.html', '_blank')"
|
||
style="padding: 12px 24px; background: #3a86ff; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 1em;">
|
||
📂 Go to Media Library
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(setupOverlay);
|
||
return;
|
||
}
|
||
|
||
// Create or get existing overlay player instance
|
||
let overlayPlayer = window.quickPlayOverlayPlayer;
|
||
if (!overlayPlayer) {
|
||
overlayPlayer = new OverlayVideoPlayer();
|
||
window.quickPlayOverlayPlayer = overlayPlayer;
|
||
}
|
||
|
||
// Select a random video
|
||
const randomIndex = Math.floor(Math.random() * allVideos.length);
|
||
const selectedVideo = allVideos[randomIndex];
|
||
|
||
console.log(`🎬 Selected video for overlay: ${selectedVideo.name || selectedVideo.filename || selectedVideo.title}`);
|
||
console.log(`🎬 Video path: ${selectedVideo.path || selectedVideo.filePath}`);
|
||
|
||
// Show the overlay player first
|
||
await overlayPlayer.show();
|
||
|
||
// Then load the specific video
|
||
const videoPath = selectedVideo.path || selectedVideo.filePath;
|
||
if (videoPath) {
|
||
overlayPlayer.loadVideo(videoPath, true);
|
||
overlayPlayer.currentVideo = selectedVideo;
|
||
overlayPlayer.updateVideoInfo(selectedVideo);
|
||
} else {
|
||
console.error('❌ No valid video path found for selected video:', selectedVideo);
|
||
}
|
||
|
||
console.log('✅ Overlay video player opened');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Failed to open overlay video player:', error);
|
||
alert('Failed to open overlay video player. Check console for details.');
|
||
}
|
||
}
|
||
|
||
// Prevent accidental navigation
|
||
window.addEventListener('beforeunload', (e) => {
|
||
if (isGameRunning) {
|
||
e.preventDefault();
|
||
e.returnValue = '';
|
||
}
|
||
});
|
||
|
||
// Task Management Functions
|
||
// Helper function to generate consistent task IDs
|
||
function generateTaskId(task, index, type = 'main') {
|
||
// If task already has an ID, convert it to string and use it
|
||
if (task.id !== undefined && task.id !== null) {
|
||
return String(task.id);
|
||
}
|
||
// Otherwise generate ID based on index and text
|
||
return `${type}-preset-${index}-${(task.text || '').substring(0, 10).replace(/\s+/g, '')}`;
|
||
}
|
||
|
||
// Helper function to get tasks from consistent source
|
||
function getTasksFromSource(type = 'main') {
|
||
let tasks = [];
|
||
|
||
// Try multiple sources in priority order
|
||
if (window.mainGameData) {
|
||
const sourceTasks = type === 'main' ?
|
||
window.mainGameData.mainTasks || [] :
|
||
window.mainGameData.consequenceTasks || [];
|
||
|
||
tasks = sourceTasks.map((task, index) => ({
|
||
...task,
|
||
id: generateTaskId(task, index, type)
|
||
}));
|
||
} else if (gameInstance && gameInstance.gameData) {
|
||
const sourceTasks = type === 'main' ?
|
||
gameInstance.gameData.mainTasks || [] :
|
||
gameInstance.gameData.consequenceTasks || [];
|
||
|
||
tasks = sourceTasks.map((task, index) => ({
|
||
...task,
|
||
id: generateTaskId(task, index, type)
|
||
}));
|
||
}
|
||
|
||
return tasks;
|
||
}
|
||
|
||
function getAllTasksForGame(type = 'main') {
|
||
let tasks = [];
|
||
|
||
// Get preset tasks
|
||
const presetTasks = getTasksFromSource(type);
|
||
tasks = presetTasks.map(task => {
|
||
const isDisabled = quickPlaySettings.disabledTasks &&
|
||
quickPlaySettings.disabledTasks[type] &&
|
||
quickPlaySettings.disabledTasks[type].includes(task.id);
|
||
|
||
return {
|
||
...task,
|
||
isCustom: false,
|
||
type: type,
|
||
enabled: !isDisabled
|
||
};
|
||
});
|
||
|
||
// Add custom tasks
|
||
if (quickPlaySettings.customTasks && quickPlaySettings.customTasks[type]) {
|
||
const customTasks = quickPlaySettings.customTasks[type].map(task => {
|
||
const isDisabled = quickPlaySettings.disabledTasks &&
|
||
quickPlaySettings.disabledTasks[type] &&
|
||
quickPlaySettings.disabledTasks[type].includes(task.id);
|
||
|
||
return {
|
||
...task,
|
||
enabled: !isDisabled
|
||
};
|
||
});
|
||
|
||
tasks = tasks.concat(customTasks);
|
||
}
|
||
|
||
// Filter out disabled tasks
|
||
tasks = tasks.filter(task => task.enabled !== false);
|
||
|
||
return tasks;
|
||
}
|
||
|
||
function showTaskManagement() {
|
||
console.log('📝 Opening task management');
|
||
document.getElementById('quick-play-setup').style.display = 'none';
|
||
document.getElementById('quick-play-task-management').style.display = 'block';
|
||
loadExistingTasks();
|
||
}
|
||
|
||
function backToSetup() {
|
||
console.log('⬅️ Returning to setup');
|
||
document.getElementById('quick-play-task-management').style.display = 'none';
|
||
document.getElementById('quick-play-setup').style.display = 'block';
|
||
}
|
||
|
||
let tagEventListenersSetup = false;
|
||
function setupTagManagementEventListeners() {
|
||
if (tagEventListenersSetup) return; // Prevent duplicate setup
|
||
|
||
const addTagBtn = document.getElementById('add-tag-btn');
|
||
const tagInput = document.getElementById('new-tag-input');
|
||
|
||
if (addTagBtn) {
|
||
addTagBtn.addEventListener('click', addNewTag);
|
||
console.log('✅ Add tag button event listener added');
|
||
} else {
|
||
console.warn('❌ add-tag-btn element not found');
|
||
}
|
||
|
||
if (tagInput) {
|
||
tagInput.addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
addNewTag();
|
||
}
|
||
});
|
||
console.log('✅ Tag input keypress event listener added');
|
||
} else {
|
||
console.warn('❌ new-tag-input element not found');
|
||
}
|
||
|
||
tagEventListenersSetup = true;
|
||
console.log('✅ Tag management event listeners set up');
|
||
}
|
||
|
||
function showTaskTab(type) {
|
||
// Update tab buttons
|
||
document.querySelectorAll('.task-tab-btn').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
document.querySelector(`[data-type="${type}"]`).classList.add('active');
|
||
|
||
// Update task sections
|
||
document.querySelectorAll('.task-section').forEach(section => {
|
||
section.classList.remove('active');
|
||
});
|
||
|
||
if (type === 'tags') {
|
||
document.getElementById('tag-management-section').classList.add('active');
|
||
loadCustomTags(); // Load custom tags when tab is shown
|
||
} else {
|
||
document.getElementById(`${type}-tasks-section`).classList.add('active');
|
||
}
|
||
}
|
||
|
||
function addNewTask(type) {
|
||
const textId = `${type}-task-text`;
|
||
const tagsId = `${type}-task-tags`;
|
||
|
||
const taskText = document.getElementById(textId).value.trim();
|
||
const tagsDropdown = document.getElementById(tagsId);
|
||
|
||
// Get selected tags from dropdown
|
||
const selectedTags = Array.from(tagsDropdown.selectedOptions).map(option => option.value);
|
||
|
||
// Assign random duration based on task type
|
||
const minDuration = type === 'main' ? 180 : 120; // 3-8 minutes for main tasks, 2-5 minutes for consequence tasks
|
||
const maxDuration = type === 'main' ? 480 : 300;
|
||
const duration = Math.floor(Math.random() * (maxDuration - minDuration + 1)) + minDuration;
|
||
|
||
if (!taskText) {
|
||
alert('Please enter a task description');
|
||
return;
|
||
}
|
||
|
||
// Create new task object
|
||
const newTask = {
|
||
id: Date.now(), // Simple ID generation
|
||
text: taskText,
|
||
description: taskText,
|
||
tags: selectedTags,
|
||
duration: duration, // Duration in seconds - randomly assigned
|
||
isCustom: true,
|
||
type: type
|
||
};
|
||
|
||
// Add to Quick Play settings
|
||
if (!quickPlaySettings.customTasks) {
|
||
quickPlaySettings.customTasks = { main: [], consequence: [] };
|
||
}
|
||
if (!quickPlaySettings.customTasks[type]) {
|
||
quickPlaySettings.customTasks[type] = [];
|
||
}
|
||
|
||
quickPlaySettings.customTasks[type].push(newTask);
|
||
|
||
// Save settings
|
||
saveQuickPlaySettings();
|
||
|
||
// Clear form
|
||
document.getElementById(textId).value = '';
|
||
tagsDropdown.selectedIndex = -1; // Clear dropdown selection
|
||
|
||
// Reload task list
|
||
loadExistingTasks();
|
||
|
||
console.log(`✅ Added new ${type} task:`, newTask);
|
||
}
|
||
|
||
function loadExistingTasks() {
|
||
loadTaskList('main');
|
||
loadTaskList('consequence');
|
||
}
|
||
|
||
function loadTaskList(type) {
|
||
console.log(`📋 Loading ${type} task list`);
|
||
console.log('📋 Current disabledTasks state:', JSON.stringify(quickPlaySettings.disabledTasks));
|
||
|
||
const listElement = document.getElementById(`${type}-tasks-list`);
|
||
if (!listElement) return;
|
||
|
||
let tasks = [];
|
||
|
||
// Get preset tasks from consistent source
|
||
try {
|
||
const presetTasks = getTasksFromSource(type);
|
||
tasks = presetTasks.map(task => {
|
||
const isDisabled = quickPlaySettings.disabledTasks &&
|
||
quickPlaySettings.disabledTasks[type] &&
|
||
quickPlaySettings.disabledTasks[type].includes(task.id);
|
||
|
||
return {
|
||
...task,
|
||
isCustom: false,
|
||
type: type,
|
||
enabled: !isDisabled
|
||
};
|
||
});
|
||
} catch (e) {
|
||
console.log('Could not load preset tasks:', e);
|
||
}
|
||
|
||
// Add custom tasks
|
||
if (quickPlaySettings.customTasks && quickPlaySettings.customTasks[type]) {
|
||
const customTasks = quickPlaySettings.customTasks[type].map(task => {
|
||
const isDisabled = quickPlaySettings.disabledTasks &&
|
||
quickPlaySettings.disabledTasks[type] &&
|
||
quickPlaySettings.disabledTasks[type].includes(task.id);
|
||
|
||
return {
|
||
...task,
|
||
enabled: !isDisabled
|
||
};
|
||
});
|
||
tasks = tasks.concat(customTasks);
|
||
}
|
||
|
||
// Render tasks
|
||
listElement.innerHTML = tasks.map(task => `
|
||
<div class="task-item ${task.enabled === false ? 'disabled' : ''}" data-task-id="${task.id}">
|
||
<div class="task-content">
|
||
<div class="task-text">${task.text || task.description || 'Unnamed Task'}</div>
|
||
<div class="task-meta">
|
||
<div class="task-tags">
|
||
${(task.tags || []).map(tag => `<span class="task-tag">${tag}</span>`).join('')}
|
||
${!task.isCustom ? '<span class="task-tag preset-tag">preset</span>' : '<span class="task-tag custom-tag">custom</span>'}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="task-actions">
|
||
<label class="toggle-switch" title="Enable/Disable Task">
|
||
<input type="checkbox" ${task.enabled !== false ? 'checked' : ''}
|
||
onchange="toggleTaskEnabled('${type}', '${task.id}', this.checked)">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
${task.isCustom ? `<button class="btn btn-small btn-danger" onclick="deleteTask('${type}', ${task.id})" title="Delete Task">🗑️</button>` : ''}
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
console.log(`📋 Loaded ${tasks.length} ${type} tasks`);
|
||
}
|
||
|
||
function deleteTask(type, taskId) {
|
||
if (!quickPlaySettings.customTasks || !quickPlaySettings.customTasks[type]) return;
|
||
|
||
quickPlaySettings.customTasks[type] = quickPlaySettings.customTasks[type].filter(task => task.id !== taskId);
|
||
saveQuickPlaySettings();
|
||
loadExistingTasks();
|
||
console.log(`🗑️ Deleted ${type} task ${taskId}`);
|
||
}
|
||
|
||
function toggleTaskEnabled(type, taskId, enabled) {
|
||
console.log(`🔄 ${enabled ? 'Enabling' : 'Disabling'} ${type} task ${taskId}`);
|
||
console.log('🔍 Current disabledTasks before change:', JSON.stringify(quickPlaySettings.disabledTasks));
|
||
|
||
// Initialize disabled tasks tracking
|
||
if (!quickPlaySettings.disabledTasks) {
|
||
quickPlaySettings.disabledTasks = { main: [], consequence: [] };
|
||
console.log('🔧 Initialized disabledTasks structure');
|
||
}
|
||
if (!quickPlaySettings.disabledTasks[type]) {
|
||
quickPlaySettings.disabledTasks[type] = [];
|
||
console.log(`🔧 Initialized disabledTasks.${type} array`);
|
||
}
|
||
|
||
if (enabled) {
|
||
// Remove from disabled list
|
||
quickPlaySettings.disabledTasks[type] = quickPlaySettings.disabledTasks[type].filter(id => id !== taskId);
|
||
console.log(`✅ Removed ${taskId} from disabled list`);
|
||
} else {
|
||
// Add to disabled list
|
||
if (!quickPlaySettings.disabledTasks[type].includes(taskId)) {
|
||
quickPlaySettings.disabledTasks[type].push(taskId);
|
||
console.log(`❌ Added ${taskId} to disabled list`);
|
||
}
|
||
}
|
||
|
||
console.log('🔍 Current disabledTasks after change:', JSON.stringify(quickPlaySettings.disabledTasks));
|
||
saveQuickPlaySettings();
|
||
console.log('💾 Settings saved');
|
||
|
||
// Update visual state
|
||
const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
|
||
if (taskElement) {
|
||
if (enabled) {
|
||
taskElement.classList.remove('disabled');
|
||
} else {
|
||
taskElement.classList.add('disabled');
|
||
}
|
||
}
|
||
}
|
||
|
||
function saveQuickPlaySettings() {
|
||
try {
|
||
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
|
||
// console.log('💾 Quick Play settings saved');
|
||
} catch (e) {
|
||
console.error('Failed to save Quick Play settings:', e);
|
||
}
|
||
}
|
||
|
||
function updateTagSelectors() {
|
||
// Update include tags selector with custom tags
|
||
updateTagSelector('include-tags');
|
||
|
||
// Update exclude tags selector with custom tags
|
||
updateTagSelector('exclude-tags');
|
||
}
|
||
|
||
function updateTagSelector(selectorId, availableTags) {
|
||
const selector = document.getElementById(selectorId);
|
||
if (!selector) return;
|
||
|
||
// Get only custom tags (not preset tags)
|
||
const customTags = quickPlaySettings.customTags || [];
|
||
|
||
// Find the custom tags category or create it
|
||
let customCategory = selector.querySelector('.custom-tags-category');
|
||
if (!customCategory && customTags.length > 0) {
|
||
customCategory = document.createElement('div');
|
||
customCategory.className = 'tag-category custom-tags-category';
|
||
customCategory.innerHTML = `
|
||
<h4>Custom Tags</h4>
|
||
<div class="tag-options custom-tag-options"></div>
|
||
`;
|
||
selector.appendChild(customCategory);
|
||
}
|
||
|
||
// Update or remove the custom tags section
|
||
if (customTags.length > 0) {
|
||
const tagOptionsContainer = customCategory.querySelector('.custom-tag-options');
|
||
if (tagOptionsContainer) {
|
||
tagOptionsContainer.innerHTML = customTags.map(tag => `
|
||
<label class="tag-option">
|
||
<input type="checkbox" value="${tag}">
|
||
<span class="tag-name">${tag}</span>
|
||
</label>
|
||
`).join('');
|
||
}
|
||
} else if (customCategory) {
|
||
// Remove the custom tags section if no custom tags exist
|
||
customCategory.remove();
|
||
}
|
||
}
|
||
|
||
// Initialize tag selectors when the page loads
|
||
function initializeTagSelectors() {
|
||
// Load custom tags from settings
|
||
if (quickPlaySettings.customTags) {
|
||
updateTagSelectors();
|
||
}
|
||
|
||
// Initialize tag dropdowns
|
||
populateTagDropdowns();
|
||
}
|
||
|
||
function populateTagDropdowns() {
|
||
const presetTags = [
|
||
'anal', 'bbc', 'captions', 'cbt', 'chastity', 'cuckold',
|
||
'degrading', 'denial', 'edging', 'feminization', 'humiliation',
|
||
'interracial', 'joi', 'painful', 'punishment', 'sissy', 'verbal'
|
||
];
|
||
|
||
const customTags = quickPlaySettings.customTags || [];
|
||
const allTags = [...presetTags, ...customTags].sort();
|
||
|
||
// Update both dropdowns
|
||
updateTagDropdown('main-task-tags', allTags);
|
||
updateTagDropdown('consequence-task-tags', allTags);
|
||
}
|
||
|
||
function updateTagDropdown(dropdownId, tags) {
|
||
const dropdown = document.getElementById(dropdownId);
|
||
if (!dropdown) return;
|
||
|
||
dropdown.innerHTML = tags.map(tag =>
|
||
`<option value="${tag}">${tag}</option>`
|
||
).join('');
|
||
}
|
||
|
||
function addNewTag() {
|
||
const tagInput = document.getElementById('new-tag-input');
|
||
const tagName = tagInput.value.trim().toLowerCase();
|
||
|
||
if (!tagName) {
|
||
alert('Please enter a tag name');
|
||
return;
|
||
}
|
||
|
||
if (tagName.length > 20) {
|
||
alert('Tag name must be 20 characters or less');
|
||
return;
|
||
}
|
||
|
||
// Check if tag already exists
|
||
const existingTags = quickPlaySettings.customTags || [];
|
||
if (existingTags.includes(tagName)) {
|
||
alert('This tag already exists');
|
||
return;
|
||
}
|
||
|
||
// Add to custom tags
|
||
quickPlaySettings.customTags = [...existingTags, tagName];
|
||
saveQuickPlaySettings();
|
||
|
||
// Update UI
|
||
populateTagDropdowns();
|
||
loadCustomTags();
|
||
updateTagSelectors();
|
||
|
||
// Clear input
|
||
tagInput.value = '';
|
||
|
||
console.log('✅ Added new tag:', tagName);
|
||
}
|
||
|
||
function deleteTag(tagName) {
|
||
// Create custom styled confirmation dialog
|
||
showCustomConfirm(
|
||
`Are you sure you want to delete the tag "${tagName}"?`,
|
||
'Delete Tag',
|
||
() => {
|
||
// Confirmed - delete the tag
|
||
quickPlaySettings.customTags = (quickPlaySettings.customTags || []).filter(tag => tag !== tagName);
|
||
saveQuickPlaySettings();
|
||
|
||
// Update UI
|
||
populateTagDropdowns();
|
||
loadCustomTags();
|
||
updateTagSelectors();
|
||
|
||
// console.log('🗑️ Deleted tag:', tagName);
|
||
},
|
||
() => {
|
||
// Cancelled - do nothing
|
||
console.log('Tag deletion cancelled');
|
||
}
|
||
);
|
||
}
|
||
|
||
function loadCustomTags() {
|
||
const customTagsList = document.getElementById('custom-tags-list');
|
||
if (!customTagsList) return;
|
||
|
||
const customTags = quickPlaySettings.customTags || [];
|
||
|
||
if (customTags.length === 0) {
|
||
customTagsList.innerHTML = '<div class="empty-tags-message">No custom tags created yet. Add your first tag above!</div>';
|
||
return;
|
||
}
|
||
|
||
customTagsList.innerHTML = customTags.map(tag => `
|
||
<div class="tag-item">
|
||
<span class="tag-name">${tag}</span>
|
||
<button class="tag-delete-btn" onclick="deleteTag('${tag}')">✕</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function showCustomConfirm(message, title, onConfirm, onCancel) {
|
||
// Create modal overlay
|
||
const overlay = document.createElement('div');
|
||
overlay.className = 'custom-modal-overlay';
|
||
|
||
// Create modal dialog
|
||
const modal = document.createElement('div');
|
||
modal.className = 'custom-modal';
|
||
|
||
modal.innerHTML = `
|
||
<div class="custom-modal-header">
|
||
<h3>${title}</h3>
|
||
</div>
|
||
<div class="custom-modal-body">
|
||
<p>${message}</p>
|
||
</div>
|
||
<div class="custom-modal-footer">
|
||
<button class="btn btn-warning modal-cancel-btn">Cancel</button>
|
||
<button class="btn btn-danger modal-confirm-btn">OK</button>
|
||
</div>
|
||
`;
|
||
|
||
overlay.appendChild(modal);
|
||
document.body.appendChild(overlay);
|
||
|
||
// Add event listeners
|
||
const cancelBtn = modal.querySelector('.modal-cancel-btn');
|
||
const confirmBtn = modal.querySelector('.modal-confirm-btn');
|
||
|
||
const closeModal = () => {
|
||
document.body.removeChild(overlay);
|
||
};
|
||
|
||
cancelBtn.addEventListener('click', () => {
|
||
closeModal();
|
||
if (onCancel) onCancel();
|
||
});
|
||
|
||
confirmBtn.addEventListener('click', () => {
|
||
closeModal();
|
||
if (onConfirm) onConfirm();
|
||
});
|
||
|
||
// Close on overlay click
|
||
overlay.addEventListener('click', (e) => {
|
||
if (e.target === overlay) {
|
||
closeModal();
|
||
if (onCancel) onCancel();
|
||
}
|
||
});
|
||
|
||
// Focus the confirm button
|
||
setTimeout(() => confirmBtn.focus(), 100);
|
||
}
|
||
|
||
// ===== MESSAGE MANAGEMENT FUNCTIONS =====
|
||
|
||
function showMessageManagement() {
|
||
console.log('💬 Opening message management');
|
||
document.getElementById('quick-play-setup').style.display = 'none';
|
||
document.getElementById('quick-play-message-management').style.display = 'block';
|
||
initializeMessageManagement();
|
||
}
|
||
|
||
function backToQuickPlay() {
|
||
console.log('⬅️ Returning to Quick Play setup');
|
||
document.getElementById('quick-play-message-management').style.display = 'none';
|
||
document.getElementById('popup-image-management').style.display = 'none';
|
||
document.getElementById('quick-play-setup').style.display = 'block';
|
||
}
|
||
|
||
function showPopupImageManagement() {
|
||
console.log('🖼️ Opening popup image management');
|
||
document.getElementById('quick-play-setup').style.display = 'none';
|
||
document.getElementById('popup-image-management').style.display = 'block';
|
||
initializePopupImageManagement();
|
||
}
|
||
|
||
function initializeMessageManagement() {
|
||
// Set up tab switching
|
||
setupMessageTabs();
|
||
|
||
// Load flash message settings
|
||
loadFlashMessageSettings();
|
||
|
||
// Load appearance settings
|
||
loadMessageAppearanceSettings();
|
||
|
||
// Set up event listeners
|
||
setupMessageManagementEventListeners();
|
||
|
||
// Load existing messages
|
||
loadExistingMessages();
|
||
}
|
||
|
||
function initializePopupImageManagement() {
|
||
// Load popup image settings
|
||
loadPopupImageSettingsMain();
|
||
|
||
// Set up event listeners for popup image management
|
||
setupPopupImageManagementEventListeners();
|
||
|
||
// Update display values
|
||
updatePopupImageDisplays();
|
||
}
|
||
|
||
function setupMessageTabs() {
|
||
const tabButtons = document.querySelectorAll('.message-tab-btn');
|
||
const sections = document.querySelectorAll('.message-section');
|
||
|
||
tabButtons.forEach(button => {
|
||
button.addEventListener('click', () => {
|
||
// Remove active class from all tabs and sections
|
||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||
sections.forEach(section => section.classList.remove('active'));
|
||
|
||
// Add active class to clicked tab and corresponding section
|
||
button.classList.add('active');
|
||
let sectionId;
|
||
if (button.dataset.type === 'flash') {
|
||
sectionId = 'flash-messages-section';
|
||
} else if (button.dataset.type === 'appearance') {
|
||
sectionId = 'message-appearance-section';
|
||
} else if (button.dataset.type === 'import-export') {
|
||
sectionId = 'import-export-section';
|
||
}
|
||
|
||
const section = document.getElementById(sectionId);
|
||
if (section) {
|
||
section.classList.add('active');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function loadFlashMessageSettings() {
|
||
// Load flash message settings from localStorage or defaults
|
||
const flashSettings = JSON.parse(localStorage.getItem('flashMessageConfig') || '{}');
|
||
|
||
// Apply settings to UI elements
|
||
document.getElementById('flash-messages-enabled').checked = flashSettings.enabled !== false;
|
||
document.getElementById('display-duration').value = flashSettings.displayDuration || 3000;
|
||
document.getElementById('interval-delay').value = flashSettings.intervalDelay || 45000;
|
||
document.getElementById('time-variation').value = flashSettings.timeVariation || 5000;
|
||
document.getElementById('event-based-messages').checked = flashSettings.eventBased !== false;
|
||
document.getElementById('pause-on-hover').checked = flashSettings.pauseOnHover || false;
|
||
|
||
// Update display values
|
||
updateRangeDisplays();
|
||
}
|
||
|
||
function loadPopupImageSettings() {
|
||
// Load popup image settings from localStorage or defaults
|
||
const popupSettings = JSON.parse(localStorage.getItem('popupImageConfig') || '{}');
|
||
|
||
// Apply settings to UI elements
|
||
document.getElementById('popup-images-enabled').checked = popupSettings.enabled || false;
|
||
document.getElementById('popup-count-mode').value = popupSettings.countMode || 'fixed';
|
||
document.getElementById('popup-image-count').value = popupSettings.imageCount || 3;
|
||
document.getElementById('popup-duration-mode').value = popupSettings.durationMode || 'fixed';
|
||
document.getElementById('popup-display-duration').value = popupSettings.displayDuration || 8;
|
||
document.getElementById('popup-positioning-qp').value = popupSettings.positioning || 'random';
|
||
document.getElementById('popup-allow-overlap').checked = popupSettings.allowOverlap || false;
|
||
document.getElementById('popup-viewport-width').value = popupSettings.viewportWidth || 35;
|
||
document.getElementById('popup-viewport-height').value = popupSettings.viewportHeight || 40;
|
||
document.getElementById('popup-min-width').value = popupSettings.minWidth || 200;
|
||
document.getElementById('popup-max-width').value = popupSettings.maxWidth || 500;
|
||
document.getElementById('popup-min-height').value = popupSettings.minHeight || 150;
|
||
document.getElementById('popup-max-height').value = popupSettings.maxHeight || 400;
|
||
document.getElementById('popup-fade-animation').checked = popupSettings.fadeAnimation || false;
|
||
document.getElementById('popup-blur-background').checked = popupSettings.blurBackground || false;
|
||
document.getElementById('popup-show-timer').checked = popupSettings.showTimer || false;
|
||
document.getElementById('popup-prevent-close').checked = popupSettings.preventClose || false;
|
||
|
||
// Update display values
|
||
updatePopupDisplays();
|
||
}
|
||
|
||
function loadMessageAppearanceSettings() {
|
||
// Load appearance settings from localStorage or defaults
|
||
const appearanceSettings = JSON.parse(localStorage.getItem('flashMessageAppearance') || '{}');
|
||
console.log('🔍 Loading appearance settings:', appearanceSettings);
|
||
|
||
// Apply settings to UI elements
|
||
document.getElementById('message-position').value = appearanceSettings.position || 'center';
|
||
document.getElementById('animation-style').value = appearanceSettings.animation || 'fade';
|
||
document.getElementById('font-size').value = appearanceSettings.fontSize || 24;
|
||
document.getElementById('message-opacity').value = appearanceSettings.opacity || 90;
|
||
document.getElementById('text-color').value = appearanceSettings.textColor || '#ffffff';
|
||
document.getElementById('background-color').value = appearanceSettings.backgroundColor || '#007bff';
|
||
document.getElementById('remove-background').checked = appearanceSettings.removeBackground || false;
|
||
document.getElementById('text-stroke').checked = appearanceSettings.textStroke || false;
|
||
document.getElementById('stroke-color').value = appearanceSettings.strokeColor || '#000000';
|
||
document.getElementById('stroke-width').value = appearanceSettings.strokeWidth || 2;
|
||
|
||
console.log('✅ Applied appearance settings to UI controls');
|
||
|
||
// Update display values
|
||
updateAppearanceDisplays();
|
||
|
||
// Show/hide stroke options
|
||
toggleStrokeOptions();
|
||
}
|
||
|
||
function setupMessageManagementEventListeners() {
|
||
// Range slider listeners for real-time updates
|
||
document.getElementById('display-duration').addEventListener('input', updateRangeDisplays);
|
||
document.getElementById('interval-delay').addEventListener('input', updateRangeDisplays);
|
||
document.getElementById('time-variation').addEventListener('input', updateRangeDisplays);
|
||
document.getElementById('font-size').addEventListener('input', updateAppearanceDisplays);
|
||
document.getElementById('message-opacity').addEventListener('input', updateAppearanceDisplays);
|
||
document.getElementById('stroke-width').addEventListener('input', updateAppearanceDisplays);
|
||
|
||
// Appearance real-time update listeners
|
||
document.getElementById('message-position').addEventListener('change', updateFlashMessageAppearanceRealTime);
|
||
document.getElementById('animation-style').addEventListener('change', updateFlashMessageAppearanceRealTime);
|
||
document.getElementById('text-color').addEventListener('change', updateFlashMessageAppearanceRealTime);
|
||
document.getElementById('background-color').addEventListener('change', updateFlashMessageAppearanceRealTime);
|
||
document.getElementById('remove-background').addEventListener('change', updateFlashMessageAppearanceRealTime);
|
||
document.getElementById('text-stroke').addEventListener('change', function() {
|
||
toggleStrokeOptions();
|
||
updateFlashMessageAppearanceRealTime();
|
||
});
|
||
document.getElementById('stroke-color').addEventListener('change', updateFlashMessageAppearanceRealTime);
|
||
document.getElementById('stroke-width').addEventListener('input', updateFlashMessageAppearanceRealTime);
|
||
|
||
// Popup settings are now managed in separate popup image management screen
|
||
|
||
// Message text area listener
|
||
document.getElementById('new-message-text').addEventListener('input', updateCharCount);
|
||
|
||
// Button listeners
|
||
document.getElementById('add-message-btn').addEventListener('click', addNewMessage);
|
||
document.getElementById('preview-message-btn').addEventListener('click', previewMessage);
|
||
document.getElementById('test-flash-message').addEventListener('click', testFlashMessage);
|
||
document.getElementById('test-behavior-settings').addEventListener('click', testBehaviorSettings);
|
||
document.getElementById('save-message-settings-btn').addEventListener('click', saveAllMessageSettings);
|
||
document.getElementById('reset-appearance-btn').addEventListener('click', resetAppearanceSettings);
|
||
document.getElementById('preview-appearance-btn').addEventListener('click', previewAppearanceSettings);
|
||
|
||
// Import/Export listeners
|
||
document.getElementById('export-all-messages-btn').addEventListener('click', () => exportMessages('all'));
|
||
document.getElementById('export-enabled-messages-btn').addEventListener('click', () => exportMessages('enabled'));
|
||
document.getElementById('import-messages-btn').addEventListener('click', importMessages);
|
||
document.getElementById('reset-to-defaults-btn').addEventListener('click', resetToDefaults);
|
||
}
|
||
|
||
function updateRangeDisplays() {
|
||
document.getElementById('duration-display').textContent = (document.getElementById('display-duration').value / 1000).toFixed(1) + 's';
|
||
document.getElementById('interval-display').textContent = (document.getElementById('interval-delay').value / 1000).toFixed(0) + 's';
|
||
document.getElementById('variation-display').textContent = '±' + (document.getElementById('time-variation').value / 1000).toFixed(0) + 's';
|
||
}
|
||
|
||
function updatePopupDisplays() {
|
||
document.getElementById('popup-image-count-value').textContent = document.getElementById('popup-image-count').value;
|
||
document.getElementById('popup-display-duration-value').textContent = document.getElementById('popup-display-duration').value + 's';
|
||
document.getElementById('popup-viewport-width-value').textContent = document.getElementById('popup-viewport-width').value + '%';
|
||
document.getElementById('popup-viewport-height-value').textContent = document.getElementById('popup-viewport-height').value + '%';
|
||
}
|
||
|
||
function updateAppearanceDisplays() {
|
||
document.getElementById('font-size-display').textContent = document.getElementById('font-size').value + 'px';
|
||
document.getElementById('opacity-display').textContent = document.getElementById('message-opacity').value + '%';
|
||
document.getElementById('stroke-width-display').textContent = document.getElementById('stroke-width').value + 'px';
|
||
}
|
||
|
||
function updateFlashMessageAppearanceRealTime() {
|
||
// Update flash message system in real-time if it's running
|
||
if (typeof flashMessageSystem !== 'undefined' && flashMessageSystem) {
|
||
// Get current appearance settings from the form
|
||
const appearanceSettings = {
|
||
position: document.getElementById('message-position').value,
|
||
animation: document.getElementById('animation-style').value,
|
||
fontSize: parseInt(document.getElementById('font-size').value),
|
||
opacity: parseInt(document.getElementById('message-opacity').value),
|
||
textColor: document.getElementById('text-color').value,
|
||
backgroundColor: document.getElementById('background-color').value,
|
||
removeBackground: document.getElementById('remove-background').checked,
|
||
textStroke: document.getElementById('text-stroke').checked,
|
||
strokeColor: document.getElementById('stroke-color').value,
|
||
strokeWidth: parseInt(document.getElementById('stroke-width').value)
|
||
};
|
||
|
||
// Update the flash message system config
|
||
flashMessageSystem.config = {
|
||
...flashMessageSystem.config,
|
||
...appearanceSettings
|
||
};
|
||
|
||
// Recreate the message element with new styles
|
||
createFlashMessageElement();
|
||
|
||
console.log('⚡ Flash message appearance updated in real-time');
|
||
}
|
||
}
|
||
|
||
function toggleStrokeOptions() {
|
||
const strokeEnabled = document.getElementById('text-stroke').checked;
|
||
const strokeOptions = document.getElementById('stroke-options');
|
||
strokeOptions.style.display = strokeEnabled ? 'flex' : 'none';
|
||
}
|
||
|
||
function updatePopupCountMode() {
|
||
const mode = document.getElementById('popup-count-mode').value;
|
||
document.getElementById('popup-fixed-count').style.display = mode === 'fixed' ? 'block' : 'none';
|
||
document.getElementById('popup-range-count').style.display = mode === 'range' ? 'block' : 'none';
|
||
}
|
||
|
||
function updatePopupDurationMode() {
|
||
const mode = document.getElementById('popup-duration-mode').value;
|
||
document.getElementById('popup-fixed-duration').style.display = mode === 'fixed' ? 'block' : 'none';
|
||
document.getElementById('popup-range-duration').style.display = mode === 'range' ? 'block' : 'none';
|
||
}
|
||
|
||
function updateCharCount() {
|
||
const textarea = document.getElementById('new-message-text');
|
||
const counter = document.getElementById('message-char-count');
|
||
counter.textContent = textarea.value.length;
|
||
}
|
||
|
||
function loadExistingMessages() {
|
||
// Load messages from localStorage or use defaults
|
||
const messageList = document.getElementById('message-list');
|
||
const messageStats = document.getElementById('message-stats');
|
||
|
||
if (!messageList || !messageStats) return;
|
||
|
||
// Get custom messages or use defaults
|
||
let allMessages = JSON.parse(localStorage.getItem('customFlashMessages') || 'null');
|
||
|
||
// If no custom messages, use default messages from gameData
|
||
if (!allMessages) {
|
||
allMessages = [
|
||
// Motivational messages
|
||
{ id: 1, text: "Good goon! Keep stroking and watching!", category: "motivational", enabled: true },
|
||
{ id: 2, text: "Every edge makes you more submissive!", category: "motivational", enabled: true },
|
||
{ id: 3, text: "Porn is your new reality!", category: "motivational", enabled: true },
|
||
{ id: 4, text: "You're becoming such a good little gooner!", category: "motivational", enabled: true },
|
||
{ id: 5, text: "Real men fuck while you watch!", category: "motivational", enabled: true },
|
||
// Encouraging messages
|
||
{ id: 6, text: "Your dedication to gooning is impressive!", category: "encouraging", enabled: true },
|
||
{ id: 7, text: "Look how much porn you can handle now!", category: "encouraging", enabled: true },
|
||
{ id: 8, text: "You're building incredible porn tolerance!", category: "encouraging", enabled: true },
|
||
{ id: 9, text: "Accept your place as a beta watcher!", category: "encouraging", enabled: true },
|
||
{ id: 10, text: "Your future is endless gooning sessions!", category: "encouraging", enabled: true },
|
||
// Achievement messages
|
||
{ id: 11, text: "Excellent edging performance!", category: "achievement", enabled: true },
|
||
{ id: 12, text: "You're such a dedicated gooner!", category: "achievement", enabled: true },
|
||
{ id: 13, text: "Another victory for porn addiction!", category: "achievement", enabled: true },
|
||
{ id: 14, text: "Perfect submission!", category: "achievement", enabled: true },
|
||
{ id: 15, text: "You're mastering the art of denial!", category: "achievement", enabled: true },
|
||
// Persistence messages
|
||
{ id: 16, text: "Don't stop gooning now - embrace it!", category: "persistence", enabled: true },
|
||
{ id: 17, text: "Every stroke makes you weaker!", category: "persistence", enabled: true },
|
||
{ id: 18, text: "Push deeper into submission!", category: "persistence", enabled: true },
|
||
{ id: 19, text: "You belong on your knees watching!", category: "persistence", enabled: true },
|
||
{ id: 20, text: "True submission is forged through endless edging!", category: "persistence", enabled: true }
|
||
];
|
||
}
|
||
|
||
// Calculate statistics
|
||
const totalMessages = allMessages.length;
|
||
const enabledMessages = allMessages.filter(msg => msg.enabled !== false).length;
|
||
const disabledMessages = totalMessages - enabledMessages;
|
||
|
||
const categories = {};
|
||
allMessages.forEach(msg => {
|
||
const cat = msg.category || 'custom';
|
||
categories[cat] = (categories[cat] || 0) + 1;
|
||
});
|
||
|
||
// Update statistics display
|
||
messageStats.innerHTML = `
|
||
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
|
||
<span>Total Messages: <strong style="color: #00d4ff;">${totalMessages}</strong></span>
|
||
<span>Enabled: <strong style="color: #00ff64;">${enabledMessages}</strong></span>
|
||
<span>Disabled: <strong style="color: #ff6b35;">${disabledMessages}</strong></span>
|
||
</div>
|
||
<div style="font-size: 12px; color: #888888;">
|
||
Categories: ${Object.keys(categories).map(cat => `${cat} (${categories[cat]})`).join(', ')}
|
||
</div>
|
||
`;
|
||
|
||
// Generate message list HTML
|
||
messageList.innerHTML = allMessages.map(msg => `
|
||
<div class="message-item ${msg.enabled === false ? 'disabled' : ''}" data-id="${msg.id}">
|
||
<div class="message-content">
|
||
<div class="message-text">${msg.text}</div>
|
||
<div class="message-meta">
|
||
<span class="message-category ${msg.category}">${msg.category || 'custom'}</span>
|
||
<span class="message-id">ID: ${msg.id}</span>
|
||
</div>
|
||
</div>
|
||
<div class="message-actions">
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" ${msg.enabled !== false ? 'checked' : ''}
|
||
onchange="toggleMessageEnabled(${msg.id})">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
<button class="message-btn message-btn-edit" onclick="editMessage(${msg.id})" title="Edit Message">
|
||
✏️
|
||
</button>
|
||
<button class="message-btn message-btn-delete" onclick="deleteMessage(${msg.id})" title="Delete Message">
|
||
🗑️
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function addNewMessage() {
|
||
const messageText = document.getElementById('new-message-text').value.trim();
|
||
const category = document.getElementById('message-category').value;
|
||
const priority = document.getElementById('message-priority').value;
|
||
|
||
if (!messageText) {
|
||
alert('Please enter a message text');
|
||
return;
|
||
}
|
||
|
||
if (messageText.length > 200) {
|
||
alert('Message text must be 200 characters or less');
|
||
return;
|
||
}
|
||
|
||
// Get existing messages or defaults
|
||
let allMessages = JSON.parse(localStorage.getItem('customFlashMessages') || 'null');
|
||
if (!allMessages) {
|
||
allMessages = getDefaultMessages();
|
||
}
|
||
|
||
// Create new message
|
||
const newMessage = {
|
||
id: Date.now() + Math.floor(Math.random() * 1000),
|
||
text: messageText,
|
||
category: category || 'custom',
|
||
priority: priority || 'normal',
|
||
enabled: true,
|
||
isCustom: true,
|
||
createdAt: new Date().toISOString()
|
||
};
|
||
|
||
// Add to messages array
|
||
allMessages.push(newMessage);
|
||
|
||
// Save to localStorage
|
||
localStorage.setItem('customFlashMessages', JSON.stringify(allMessages));
|
||
|
||
console.log('Added new message:', newMessage);
|
||
|
||
// Clear form
|
||
document.getElementById('new-message-text').value = '';
|
||
updateCharCount();
|
||
|
||
// Reload message list
|
||
loadExistingMessages();
|
||
|
||
alert('Message added successfully!');
|
||
}
|
||
|
||
function previewMessage() {
|
||
const messageText = document.getElementById('new-message-text').value.trim();
|
||
if (!messageText) {
|
||
alert('Please enter a message text to preview');
|
||
return;
|
||
}
|
||
|
||
// Create a temporary preview overlay
|
||
showPreviewMessage(messageText);
|
||
}
|
||
|
||
function testFlashMessage() {
|
||
// Get a random enabled message for testing
|
||
let allMessages = JSON.parse(localStorage.getItem('customFlashMessages') || 'null');
|
||
if (!allMessages) {
|
||
allMessages = getDefaultMessages();
|
||
}
|
||
|
||
const enabledMessages = allMessages.filter(msg => msg.enabled !== false);
|
||
if (enabledMessages.length === 0) {
|
||
alert('No enabled messages to test!');
|
||
return;
|
||
}
|
||
|
||
const randomMessage = enabledMessages[Math.floor(Math.random() * enabledMessages.length)];
|
||
showPreviewMessage(randomMessage.text);
|
||
}
|
||
|
||
function testBehaviorSettings() {
|
||
// TODO: Test behavior settings
|
||
console.log('Testing behavior settings');
|
||
}
|
||
|
||
// Popup test functions moved to popup image management screen
|
||
|
||
function saveAllMessageSettings() {
|
||
// Save flash message settings
|
||
const flashSettings = {
|
||
enabled: document.getElementById('flash-messages-enabled').checked,
|
||
displayDuration: parseInt(document.getElementById('display-duration').value),
|
||
intervalDelay: parseInt(document.getElementById('interval-delay').value),
|
||
timeVariation: parseInt(document.getElementById('time-variation').value),
|
||
eventBased: document.getElementById('event-based-messages').checked,
|
||
pauseOnHover: document.getElementById('pause-on-hover').checked
|
||
};
|
||
|
||
// Popup image settings are now managed in separate popup image management screen
|
||
|
||
// Save appearance settings
|
||
const appearanceSettings = {
|
||
position: document.getElementById('message-position').value,
|
||
animation: document.getElementById('animation-style').value,
|
||
fontSize: parseInt(document.getElementById('font-size').value),
|
||
opacity: parseInt(document.getElementById('message-opacity').value),
|
||
textColor: document.getElementById('text-color').value,
|
||
backgroundColor: document.getElementById('background-color').value,
|
||
removeBackground: document.getElementById('remove-background').checked,
|
||
textStroke: document.getElementById('text-stroke').checked,
|
||
strokeColor: document.getElementById('stroke-color').value,
|
||
strokeWidth: parseInt(document.getElementById('stroke-width').value)
|
||
};
|
||
|
||
console.log('💾 Saving appearance settings:', appearanceSettings);
|
||
|
||
// Save to localStorage
|
||
localStorage.setItem('flashMessageConfig', JSON.stringify(flashSettings));
|
||
localStorage.setItem('flashMessageAppearance', JSON.stringify(appearanceSettings));
|
||
|
||
console.log('✅ Settings saved to localStorage');
|
||
|
||
// Update flash message system if it's running
|
||
if (typeof flashMessageSystem !== 'undefined' && flashMessageSystem) {
|
||
// Update the config with new appearance settings
|
||
flashMessageSystem.config = {
|
||
...flashMessageSystem.config,
|
||
...flashSettings,
|
||
...appearanceSettings
|
||
};
|
||
|
||
// Recreate the message element with new styles
|
||
createFlashMessageElement();
|
||
|
||
console.log('✨ Flash message system updated with new appearance settings');
|
||
}
|
||
|
||
console.log('💾 All message settings saved');
|
||
|
||
// Debug: Check what was actually saved
|
||
console.log('🔬 Debug - Checking localStorage after save:');
|
||
console.log('flashMessageAppearance:', localStorage.getItem('flashMessageAppearance'));
|
||
|
||
alert('Message settings saved successfully!');
|
||
}
|
||
|
||
// Debug function - can be called from browser console
|
||
window.debugAppearanceSettings = function() {
|
||
console.log('🔬 DEBUG: Current localStorage appearance settings:');
|
||
const stored = localStorage.getItem('flashMessageAppearance');
|
||
console.log('Raw:', stored);
|
||
if (stored) {
|
||
console.log('Parsed:', JSON.parse(stored));
|
||
}
|
||
|
||
console.log('🔬 DEBUG: Current form values:');
|
||
console.log('text-color:', document.getElementById('text-color').value);
|
||
console.log('background-color:', document.getElementById('background-color').value);
|
||
console.log('font-size:', document.getElementById('font-size').value);
|
||
console.log('opacity:', document.getElementById('message-opacity').value);
|
||
|
||
console.log('🔬 DEBUG: Current flashMessageSystem config:');
|
||
if (typeof flashMessageSystem !== 'undefined' && flashMessageSystem) {
|
||
console.log('flashMessageSystem.config:', flashMessageSystem.config);
|
||
} else {
|
||
console.log('flashMessageSystem not available');
|
||
}
|
||
};
|
||
|
||
function resetAppearanceSettings() {
|
||
document.getElementById('message-position').value = 'center';
|
||
document.getElementById('animation-style').value = 'fade';
|
||
document.getElementById('font-size').value = 24;
|
||
document.getElementById('message-opacity').value = 90;
|
||
document.getElementById('text-color').value = '#ffffff';
|
||
document.getElementById('background-color').value = '#007bff';
|
||
document.getElementById('remove-background').checked = false;
|
||
document.getElementById('text-stroke').checked = false;
|
||
document.getElementById('stroke-color').value = '#000000';
|
||
document.getElementById('stroke-width').value = 2;
|
||
updateAppearanceDisplays();
|
||
toggleStrokeOptions();
|
||
}
|
||
|
||
function previewAppearanceSettings() {
|
||
// TODO: Preview appearance settings
|
||
console.log('Previewing appearance settings');
|
||
}
|
||
|
||
// Popup Image Management Functions
|
||
function loadPopupImageSettingsMain() {
|
||
// Load popup image settings from localStorage or defaults
|
||
const popupSettings = JSON.parse(localStorage.getItem('popupImageConfig') || '{}');
|
||
|
||
// Apply settings to UI elements (using -main suffix for the new screen)
|
||
document.getElementById('popup-images-enabled-main').checked = popupSettings.enabled !== false;
|
||
document.getElementById('popup-frequency-main').value = popupSettings.frequency || 'medium';
|
||
document.getElementById('popup-min-interval-main').value = popupSettings.minInterval || 30;
|
||
document.getElementById('popup-max-interval-main').value = popupSettings.maxInterval || 120;
|
||
document.getElementById('popup-count-mode-main').value = popupSettings.countMode || 'fixed';
|
||
document.getElementById('popup-image-count-main').value = popupSettings.imageCount || 3;
|
||
document.getElementById('popup-min-count-main').value = popupSettings.minCount || 2;
|
||
document.getElementById('popup-max-count-main').value = popupSettings.maxCount || 5;
|
||
document.getElementById('popup-duration-mode-main').value = popupSettings.durationMode || 'fixed';
|
||
document.getElementById('popup-display-duration-main').value = popupSettings.displayDuration || 8;
|
||
document.getElementById('popup-min-duration-main').value = popupSettings.minDuration || 5;
|
||
document.getElementById('popup-max-duration-main').value = popupSettings.maxDuration || 15;
|
||
document.getElementById('popup-positioning-main').value = popupSettings.positioning || 'random';
|
||
document.getElementById('popup-allow-overlap-main').checked = popupSettings.allowOverlap || false;
|
||
document.getElementById('popup-viewport-width-main').value = popupSettings.viewportWidth || 35;
|
||
document.getElementById('popup-viewport-height-main').value = popupSettings.viewportHeight || 40;
|
||
document.getElementById('popup-min-width-main').value = popupSettings.minWidth || 200;
|
||
document.getElementById('popup-max-width-main').value = popupSettings.maxWidth || 500;
|
||
document.getElementById('popup-min-height-main').value = popupSettings.minHeight || 150;
|
||
document.getElementById('popup-max-height-main').value = popupSettings.maxHeight || 400;
|
||
document.getElementById('popup-fade-animation-main').checked = popupSettings.fadeAnimation || false;
|
||
document.getElementById('popup-blur-background-main').checked = popupSettings.blurBackground || false;
|
||
document.getElementById('popup-show-timer-main').checked = popupSettings.showTimer || false;
|
||
document.getElementById('popup-prevent-close-main').checked = popupSettings.preventClose || false;
|
||
|
||
// Update display values and visibility
|
||
updatePopupImageDisplays();
|
||
updatePopupCountModeMain();
|
||
updatePopupDurationModeMain();
|
||
}
|
||
|
||
function setupPopupImageManagementEventListeners() {
|
||
// Range slider listeners for real-time updates
|
||
document.getElementById('popup-image-count-main').addEventListener('input', updatePopupImageDisplays);
|
||
document.getElementById('popup-display-duration-main').addEventListener('input', updatePopupImageDisplays);
|
||
document.getElementById('popup-viewport-width-main').addEventListener('input', updatePopupImageDisplays);
|
||
document.getElementById('popup-viewport-height-main').addEventListener('input', updatePopupImageDisplays);
|
||
|
||
// Frequency listeners
|
||
document.getElementById('popup-frequency-main').addEventListener('change', updateFrequencySettings);
|
||
document.getElementById('popup-min-interval-main').addEventListener('input', updatePopupImageDisplays);
|
||
document.getElementById('popup-max-interval-main').addEventListener('input', updatePopupImageDisplays);
|
||
|
||
// Mode change listeners
|
||
document.getElementById('popup-count-mode-main').addEventListener('change', updatePopupCountModeMain);
|
||
document.getElementById('popup-duration-mode-main').addEventListener('change', updatePopupDurationModeMain);
|
||
|
||
// Test button listeners
|
||
document.getElementById('test-popup-single-main').addEventListener('click', () => testPopupImages(1));
|
||
document.getElementById('test-popup-multiple-main').addEventListener('click', testPopupImagesMultiple);
|
||
document.getElementById('clear-all-popups-main').addEventListener('click', clearAllPopups);
|
||
|
||
// Save button listener
|
||
document.getElementById('save-popup-settings-btn').addEventListener('click', savePopupImageSettings);
|
||
}
|
||
|
||
function updatePopupImageDisplays() {
|
||
// Update display values for ranges
|
||
const imageCount = document.getElementById('popup-image-count-main').value;
|
||
const displayDuration = document.getElementById('popup-display-duration-main').value;
|
||
const viewportWidth = document.getElementById('popup-viewport-width-main').value;
|
||
const viewportHeight = document.getElementById('popup-viewport-height-main').value;
|
||
|
||
document.getElementById('popup-image-count-value-main').textContent = imageCount;
|
||
document.getElementById('popup-display-duration-value-main').textContent = displayDuration + 's';
|
||
document.getElementById('popup-viewport-width-value-main').textContent = viewportWidth + '%';
|
||
document.getElementById('popup-viewport-height-value-main').textContent = viewportHeight + '%';
|
||
|
||
// Show warning for high counts
|
||
const warning = document.getElementById('popup-warning-main');
|
||
if (imageCount > 20) {
|
||
warning.style.display = 'block';
|
||
} else {
|
||
warning.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function updatePopupCountModeMain() {
|
||
const mode = document.getElementById('popup-count-mode-main').value;
|
||
const fixedCount = document.getElementById('popup-fixed-count-main');
|
||
const rangeCount = document.getElementById('popup-range-count-main');
|
||
|
||
if (mode === 'fixed') {
|
||
fixedCount.style.display = 'block';
|
||
rangeCount.style.display = 'none';
|
||
} else if (mode === 'range') {
|
||
fixedCount.style.display = 'none';
|
||
rangeCount.style.display = 'block';
|
||
} else {
|
||
fixedCount.style.display = 'none';
|
||
rangeCount.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function updatePopupDurationModeMain() {
|
||
const mode = document.getElementById('popup-duration-mode-main').value;
|
||
const fixedDuration = document.getElementById('popup-fixed-duration-main');
|
||
const rangeDuration = document.getElementById('popup-range-duration-main');
|
||
|
||
if (mode === 'fixed') {
|
||
fixedDuration.style.display = 'block';
|
||
rangeDuration.style.display = 'none';
|
||
} else if (mode === 'range') {
|
||
fixedDuration.style.display = 'none';
|
||
rangeDuration.style.display = 'block';
|
||
} else {
|
||
fixedDuration.style.display = 'none';
|
||
rangeDuration.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
function updateFrequencySettings() {
|
||
const frequency = document.getElementById('popup-frequency-main').value;
|
||
const minInterval = document.getElementById('popup-min-interval-main');
|
||
const maxInterval = document.getElementById('popup-max-interval-main');
|
||
|
||
// Update interval inputs based on frequency selection
|
||
switch (frequency) {
|
||
case 'low':
|
||
minInterval.value = 180; // 3 minutes
|
||
maxInterval.value = 300; // 5 minutes
|
||
break;
|
||
case 'medium':
|
||
minInterval.value = 120; // 2 minutes
|
||
maxInterval.value = 180; // 3 minutes
|
||
break;
|
||
case 'high':
|
||
minInterval.value = 60; // 1 minute
|
||
maxInterval.value = 120; // 2 minutes
|
||
break;
|
||
case 'constant':
|
||
minInterval.value = 30; // 30 seconds
|
||
maxInterval.value = 60; // 1 minute
|
||
break;
|
||
}
|
||
}
|
||
|
||
function testPopupImagesMultiple() {
|
||
const countMode = document.getElementById('popup-count-mode-main').value;
|
||
let count;
|
||
|
||
if (countMode === 'fixed') {
|
||
count = parseInt(document.getElementById('popup-image-count-main').value);
|
||
} else if (countMode === 'random') {
|
||
count = Math.floor(Math.random() * 10) + 1;
|
||
} else if (countMode === 'range') {
|
||
const min = parseInt(document.getElementById('popup-min-count-main').value);
|
||
const max = parseInt(document.getElementById('popup-max-count-main').value);
|
||
count = Math.floor(Math.random() * (max - min + 1)) + min;
|
||
}
|
||
|
||
testPopupImages(count);
|
||
}
|
||
|
||
function testPopupImages(count) {
|
||
if (gameInstance && gameInstance.popupImageManager) {
|
||
// Apply current settings to the popup manager
|
||
savePopupImageSettings();
|
||
|
||
// Test with the specified count using the periodic popup system
|
||
for (let i = 0; i < count; i++) {
|
||
setTimeout(() => {
|
||
gameInstance.popupImageManager.showPeriodicPopup();
|
||
}, i * 200); // Stagger the popups slightly
|
||
}
|
||
} else {
|
||
alert('Popup image system not available. Please start the game first.');
|
||
}
|
||
}
|
||
|
||
function clearAllPopups() {
|
||
if (gameInstance && gameInstance.popupImageManager) {
|
||
// Clear periodic popups by hiding any active ones
|
||
const periodicPopups = document.querySelectorAll('.periodic-popup-container');
|
||
periodicPopups.forEach(popup => {
|
||
gameInstance.popupImageManager.hidePeriodicPopup(popup);
|
||
});
|
||
}
|
||
}
|
||
|
||
function savePopupImageSettings() {
|
||
const settings = {
|
||
enabled: document.getElementById('popup-images-enabled-main').checked,
|
||
frequency: document.getElementById('popup-frequency-main').value,
|
||
minInterval: parseInt(document.getElementById('popup-min-interval-main').value),
|
||
maxInterval: parseInt(document.getElementById('popup-max-interval-main').value),
|
||
countMode: document.getElementById('popup-count-mode-main').value,
|
||
imageCount: parseInt(document.getElementById('popup-image-count-main').value),
|
||
minCount: parseInt(document.getElementById('popup-min-count-main').value),
|
||
maxCount: parseInt(document.getElementById('popup-max-count-main').value),
|
||
durationMode: document.getElementById('popup-duration-mode-main').value,
|
||
displayDuration: parseInt(document.getElementById('popup-display-duration-main').value),
|
||
minDuration: parseInt(document.getElementById('popup-min-duration-main').value),
|
||
maxDuration: parseInt(document.getElementById('popup-max-duration-main').value),
|
||
positioning: document.getElementById('popup-positioning-main').value,
|
||
allowOverlap: document.getElementById('popup-allow-overlap-main').checked,
|
||
viewportWidth: parseInt(document.getElementById('popup-viewport-width-main').value),
|
||
viewportHeight: parseInt(document.getElementById('popup-viewport-height-main').value),
|
||
minWidth: parseInt(document.getElementById('popup-min-width-main').value),
|
||
maxWidth: parseInt(document.getElementById('popup-max-width-main').value),
|
||
minHeight: parseInt(document.getElementById('popup-min-height-main').value),
|
||
maxHeight: parseInt(document.getElementById('popup-max-height-main').value),
|
||
fadeAnimation: document.getElementById('popup-fade-animation-main').checked,
|
||
blurBackground: document.getElementById('popup-blur-background-main').checked,
|
||
showTimer: document.getElementById('popup-show-timer-main').checked,
|
||
preventClose: document.getElementById('popup-prevent-close-main').checked
|
||
};
|
||
|
||
localStorage.setItem('popupImageConfig', JSON.stringify(settings));
|
||
|
||
// Update the status display
|
||
document.getElementById('popup-settings-updated-main').textContent = new Date().toLocaleString();
|
||
|
||
// Apply settings to game if running
|
||
if (gameInstance && gameInstance.popupImageManager) {
|
||
gameInstance.popupImageManager.updateConfig(settings);
|
||
gameInstance.popupImageManager.updatePeriodicSettings({
|
||
minInterval: settings.minInterval,
|
||
maxInterval: settings.maxInterval,
|
||
displayDuration: settings.displayDuration
|
||
});
|
||
}
|
||
|
||
// Update available images count
|
||
updateAvailableImagesCount();
|
||
|
||
console.log('🖼️ Popup image settings saved:', settings);
|
||
alert('Popup image settings saved successfully!');
|
||
}
|
||
|
||
async function updateAvailableImagesCount() {
|
||
if (gameInstance && gameInstance.popupImageManager) {
|
||
try {
|
||
const images = await gameInstance.popupImageManager.getLinkedImages();
|
||
document.getElementById('available-images-count-main').textContent = images.length;
|
||
} catch (error) {
|
||
console.error('Error getting available images count:', error);
|
||
document.getElementById('available-images-count-main').textContent = '0';
|
||
}
|
||
}
|
||
}
|
||
|
||
function exportMessages(type) {
|
||
// TODO: Export messages functionality
|
||
console.log('Exporting messages:', type);
|
||
}
|
||
|
||
function importMessages() {
|
||
document.getElementById('import-messages-file').click();
|
||
}
|
||
|
||
function resetToDefaults() {
|
||
if (confirm('Reset all messages to defaults? This cannot be undone.')) {
|
||
localStorage.removeItem('customFlashMessages');
|
||
loadExistingMessages();
|
||
console.log('Reset to default messages');
|
||
alert('Messages reset to defaults!');
|
||
}
|
||
}
|
||
|
||
function toggleMessageEnabled(messageId) {
|
||
let allMessages = JSON.parse(localStorage.getItem('customFlashMessages') || 'null');
|
||
|
||
// If no custom messages, get defaults first
|
||
if (!allMessages) {
|
||
allMessages = getDefaultMessages();
|
||
}
|
||
|
||
const messageIndex = allMessages.findIndex(msg => msg.id === messageId);
|
||
if (messageIndex !== -1) {
|
||
allMessages[messageIndex].enabled = !allMessages[messageIndex].enabled;
|
||
localStorage.setItem('customFlashMessages', JSON.stringify(allMessages));
|
||
loadExistingMessages();
|
||
}
|
||
}
|
||
|
||
function editMessage(messageId) {
|
||
let allMessages = JSON.parse(localStorage.getItem('customFlashMessages') || 'null');
|
||
|
||
if (!allMessages) {
|
||
allMessages = getDefaultMessages();
|
||
}
|
||
|
||
const message = allMessages.find(msg => msg.id === messageId);
|
||
if (message) {
|
||
const newText = prompt('Edit message text:', message.text);
|
||
if (newText !== null && newText.trim() !== '') {
|
||
message.text = newText.trim();
|
||
localStorage.setItem('customFlashMessages', JSON.stringify(allMessages));
|
||
loadExistingMessages();
|
||
}
|
||
}
|
||
}
|
||
|
||
function deleteMessage(messageId) {
|
||
if (confirm('Delete this message? This cannot be undone.')) {
|
||
let allMessages = JSON.parse(localStorage.getItem('customFlashMessages') || 'null');
|
||
|
||
if (!allMessages) {
|
||
allMessages = getDefaultMessages();
|
||
}
|
||
|
||
const filteredMessages = allMessages.filter(msg => msg.id !== messageId);
|
||
localStorage.setItem('customFlashMessages', JSON.stringify(filteredMessages));
|
||
loadExistingMessages();
|
||
}
|
||
}
|
||
|
||
function getDefaultMessages() {
|
||
return [
|
||
// Motivational messages
|
||
{ id: 1, text: "Good goon! Keep stroking and watching!", category: "motivational", enabled: true },
|
||
{ id: 2, text: "Every edge makes you more submissive!", category: "motivational", enabled: true },
|
||
{ id: 3, text: "Porn is your new reality!", category: "motivational", enabled: true },
|
||
{ id: 4, text: "You're becoming such a good little gooner!", category: "motivational", enabled: true },
|
||
{ id: 5, text: "Real men fuck while you watch!", category: "motivational", enabled: true },
|
||
// Encouraging messages
|
||
{ id: 6, text: "Your dedication to gooning is impressive!", category: "encouraging", enabled: true },
|
||
{ id: 7, text: "Look how much porn you can handle now!", category: "encouraging", enabled: true },
|
||
{ id: 8, text: "You're building incredible porn tolerance!", category: "encouraging", enabled: true },
|
||
{ id: 9, text: "Accept your place as a beta watcher!", category: "encouraging", enabled: true },
|
||
{ id: 10, text: "Your future is endless gooning sessions!", category: "encouraging", enabled: true },
|
||
// Achievement messages
|
||
{ id: 11, text: "Excellent edging performance!", category: "achievement", enabled: true },
|
||
{ id: 12, text: "You're such a dedicated gooner!", category: "achievement", enabled: true },
|
||
{ id: 13, text: "Another victory for porn addiction!", category: "achievement", enabled: true },
|
||
{ id: 14, text: "Perfect submission!", category: "achievement", enabled: true },
|
||
{ id: 15, text: "You're mastering the art of denial!", category: "achievement", enabled: true },
|
||
// Persistence messages
|
||
{ id: 16, text: "Don't stop gooning now - embrace it!", category: "persistence", enabled: true },
|
||
{ id: 17, text: "Every stroke makes you weaker!", category: "persistence", enabled: true },
|
||
{ id: 18, text: "Push deeper into submission!", category: "persistence", enabled: true },
|
||
{ id: 19, text: "You belong on your knees watching!", category: "persistence", enabled: true },
|
||
{ id: 20, text: "True submission is forged through endless edging!", category: "persistence", enabled: true }
|
||
];
|
||
}
|
||
|
||
function showPreviewMessage(messageText) {
|
||
// Create preview overlay
|
||
const overlay = document.createElement('div');
|
||
overlay.id = 'message-preview-overlay';
|
||
overlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 10001;
|
||
backdrop-filter: blur(5px);
|
||
`;
|
||
|
||
// Create message element with current settings
|
||
const messageElement = document.createElement('div');
|
||
const position = document.getElementById('message-position').value || 'center';
|
||
const animation = document.getElementById('animation-style').value || 'fade';
|
||
const fontSize = document.getElementById('font-size').value || 24;
|
||
const opacity = document.getElementById('message-opacity').value || 90;
|
||
const textColor = document.getElementById('text-color').value || '#ffffff';
|
||
const backgroundColor = document.getElementById('background-color').value || '#007bff';
|
||
const removeBackground = document.getElementById('remove-background').checked;
|
||
const textStroke = document.getElementById('text-stroke').checked;
|
||
const strokeColor = document.getElementById('stroke-color').value || '#000000';
|
||
const strokeWidth = document.getElementById('stroke-width').value || 2;
|
||
|
||
messageElement.textContent = messageText;
|
||
|
||
// Build CSS styles
|
||
let cssStyles = `
|
||
position: absolute;
|
||
color: ${textColor};
|
||
font-size: ${fontSize}px;
|
||
font-weight: bold;
|
||
padding: 20px 30px;
|
||
max-width: 400px;
|
||
text-align: center;
|
||
word-wrap: break-word;
|
||
opacity: ${opacity / 100};
|
||
`;
|
||
|
||
// Add background or remove it
|
||
if (removeBackground) {
|
||
cssStyles += `
|
||
background: transparent;
|
||
box-shadow: none;
|
||
border: none;
|
||
`;
|
||
} else {
|
||
cssStyles += `
|
||
background: ${backgroundColor};
|
||
border-radius: 15px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||
`;
|
||
}
|
||
|
||
// Add text stroke if enabled
|
||
if (textStroke) {
|
||
cssStyles += `
|
||
text-shadow:
|
||
-${strokeWidth}px -${strokeWidth}px 0 ${strokeColor},
|
||
${strokeWidth}px -${strokeWidth}px 0 ${strokeColor},
|
||
-${strokeWidth}px ${strokeWidth}px 0 ${strokeColor},
|
||
${strokeWidth}px ${strokeWidth}px 0 ${strokeColor};
|
||
`;
|
||
}
|
||
|
||
messageElement.style.cssText = cssStyles;
|
||
|
||
// Position the message
|
||
switch (position) {
|
||
case 'top-left':
|
||
messageElement.style.top = '50px';
|
||
messageElement.style.left = '50px';
|
||
break;
|
||
case 'top-center':
|
||
messageElement.style.top = '50px';
|
||
messageElement.style.left = '50%';
|
||
messageElement.style.transform = 'translateX(-50%)';
|
||
break;
|
||
case 'top-right':
|
||
messageElement.style.top = '50px';
|
||
messageElement.style.right = '50px';
|
||
break;
|
||
case 'center-left':
|
||
messageElement.style.top = '50%';
|
||
messageElement.style.left = '50px';
|
||
messageElement.style.transform = 'translateY(-50%)';
|
||
break;
|
||
case 'center':
|
||
default:
|
||
messageElement.style.top = '50%';
|
||
messageElement.style.left = '50%';
|
||
messageElement.style.transform = 'translate(-50%, -50%)';
|
||
break;
|
||
case 'center-right':
|
||
messageElement.style.top = '50%';
|
||
messageElement.style.right = '50px';
|
||
messageElement.style.transform = 'translateY(-50%)';
|
||
break;
|
||
case 'bottom-left':
|
||
messageElement.style.bottom = '50px';
|
||
messageElement.style.left = '50px';
|
||
break;
|
||
case 'bottom-center':
|
||
messageElement.style.bottom = '50px';
|
||
messageElement.style.left = '50%';
|
||
messageElement.style.transform = 'translateX(-50%)';
|
||
break;
|
||
case 'bottom-right':
|
||
messageElement.style.bottom = '50px';
|
||
messageElement.style.right = '50px';
|
||
break;
|
||
}
|
||
|
||
overlay.appendChild(messageElement);
|
||
document.body.appendChild(overlay);
|
||
|
||
// Apply animation
|
||
if (animation === 'slide') {
|
||
messageElement.style.opacity = '0';
|
||
messageElement.style.transform += ' translateY(50px)';
|
||
setTimeout(() => {
|
||
messageElement.style.transition = 'all 0.5s ease';
|
||
messageElement.style.opacity = opacity / 100;
|
||
messageElement.style.transform = messageElement.style.transform.replace('translateY(50px)', '');
|
||
}, 50);
|
||
} else if (animation === 'bounce') {
|
||
messageElement.style.animation = 'flashBounceIn 0.6s ease-out';
|
||
} else if (animation === 'pulse') {
|
||
messageElement.style.animation = 'flashPulseIn 0.8s ease-out';
|
||
}
|
||
|
||
// Close on click
|
||
overlay.addEventListener('click', () => {
|
||
document.body.removeChild(overlay);
|
||
});
|
||
|
||
// Auto-close after duration
|
||
const duration = document.getElementById('display-duration').value || 3000;
|
||
setTimeout(() => {
|
||
if (document.body.contains(overlay)) {
|
||
document.body.removeChild(overlay);
|
||
}
|
||
}, parseInt(duration));
|
||
}
|
||
|
||
// ===== FLASH MESSAGE SYSTEM FOR QUICK PLAY =====
|
||
|
||
let flashMessageSystem = null;
|
||
let flashMessageTimeout = null;
|
||
|
||
function initializeFlashMessages() {
|
||
// Check if flash messages are enabled
|
||
const flashSettings = JSON.parse(localStorage.getItem('flashMessageConfig') || '{}');
|
||
const isEnabled = flashSettings.enabled !== false;
|
||
|
||
if (!isEnabled) {
|
||
console.log('💬 Flash messages disabled');
|
||
return;
|
||
}
|
||
|
||
// Get messages
|
||
let allMessages = JSON.parse(localStorage.getItem('customFlashMessages') || 'null');
|
||
if (!allMessages) {
|
||
allMessages = getDefaultMessages();
|
||
}
|
||
|
||
const enabledMessages = allMessages.filter(msg => msg.enabled !== false);
|
||
if (enabledMessages.length === 0) {
|
||
console.log('💬 No enabled flash messages found');
|
||
return;
|
||
}
|
||
|
||
// Initialize system
|
||
// Load appearance settings
|
||
const appearanceSettings = JSON.parse(localStorage.getItem('flashMessageAppearance') || '{}');
|
||
|
||
flashMessageSystem = {
|
||
messages: enabledMessages,
|
||
config: {
|
||
enabled: true,
|
||
displayDuration: flashSettings.displayDuration || 3000,
|
||
intervalDelay: flashSettings.intervalDelay || 45000,
|
||
timeVariation: flashSettings.timeVariation || 5000,
|
||
eventBased: flashSettings.eventBased !== false,
|
||
pauseOnHover: flashSettings.pauseOnHover || false,
|
||
// Appearance settings from both sources
|
||
position: appearanceSettings.position || flashSettings.position || 'center',
|
||
animation: appearanceSettings.animation || flashSettings.animation || 'fade',
|
||
fontSize: appearanceSettings.fontSize || flashSettings.fontSize || 24,
|
||
opacity: appearanceSettings.opacity || flashSettings.opacity || 90,
|
||
textColor: appearanceSettings.textColor || flashSettings.textColor || '#ffffff',
|
||
backgroundColor: appearanceSettings.backgroundColor || flashSettings.backgroundColor || '#007bff',
|
||
// New appearance options
|
||
removeBackground: appearanceSettings.removeBackground || false,
|
||
textStroke: appearanceSettings.textStroke || false,
|
||
strokeColor: appearanceSettings.strokeColor || '#000000',
|
||
strokeWidth: appearanceSettings.strokeWidth || 2
|
||
},
|
||
isActive: false,
|
||
isPaused: false,
|
||
currentElement: null,
|
||
lastMessageIndex: -1
|
||
};
|
||
|
||
// Create message element
|
||
createFlashMessageElement();
|
||
|
||
// Start the system
|
||
startFlashMessageSystem();
|
||
|
||
console.log(`💬 Flash message system initialized with ${enabledMessages.length} messages`);
|
||
}
|
||
|
||
function createFlashMessageElement() {
|
||
// Remove existing element if any
|
||
const existing = document.getElementById('quick-play-flash-message');
|
||
if (existing) {
|
||
existing.remove();
|
||
}
|
||
|
||
// Create new element
|
||
const element = document.createElement('div');
|
||
element.id = 'quick-play-flash-message';
|
||
// Build CSS styles
|
||
let cssStyles = `
|
||
position: fixed;
|
||
display: none;
|
||
font-size: ${flashMessageSystem.config.fontSize}px;
|
||
font-weight: bold;
|
||
color: ${flashMessageSystem.config.textColor};
|
||
padding: 20px 30px;
|
||
max-width: 400px;
|
||
z-index: 9999;
|
||
text-align: center;
|
||
word-wrap: break-word;
|
||
transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
|
||
opacity: ${flashMessageSystem.config.opacity / 100};
|
||
pointer-events: none;
|
||
`;
|
||
|
||
// Add background or remove it
|
||
if (flashMessageSystem.config.removeBackground) {
|
||
cssStyles += `
|
||
background: transparent;
|
||
backdrop-filter: none;
|
||
box-shadow: none;
|
||
border: none;
|
||
`;
|
||
} else {
|
||
cssStyles += `
|
||
background: ${flashMessageSystem.config.backgroundColor};
|
||
border-radius: 15px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||
backdrop-filter: blur(5px);
|
||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||
`;
|
||
}
|
||
|
||
// Add text stroke if enabled
|
||
if (flashMessageSystem.config.textStroke) {
|
||
const strokeWidth = flashMessageSystem.config.strokeWidth;
|
||
const strokeColor = flashMessageSystem.config.strokeColor;
|
||
cssStyles += `
|
||
text-shadow:
|
||
-${strokeWidth}px -${strokeWidth}px 0 ${strokeColor},
|
||
${strokeWidth}px -${strokeWidth}px 0 ${strokeColor},
|
||
-${strokeWidth}px ${strokeWidth}px 0 ${strokeColor},
|
||
${strokeWidth}px ${strokeWidth}px 0 ${strokeColor};
|
||
`;
|
||
}
|
||
|
||
element.style.cssText = cssStyles;
|
||
|
||
// Position the element
|
||
positionFlashMessage(element);
|
||
|
||
// Add hover pause if enabled
|
||
if (flashMessageSystem.config.pauseOnHover) {
|
||
element.style.pointerEvents = 'auto';
|
||
element.addEventListener('mouseenter', () => {
|
||
if (flashMessageTimeout) {
|
||
clearTimeout(flashMessageTimeout);
|
||
}
|
||
});
|
||
element.addEventListener('mouseleave', () => {
|
||
scheduleHideMessage(flashMessageSystem.config.displayDuration);
|
||
});
|
||
}
|
||
|
||
document.body.appendChild(element);
|
||
flashMessageSystem.currentElement = element;
|
||
}
|
||
|
||
function positionFlashMessage(element) {
|
||
// Reset all positioning
|
||
element.style.top = '';
|
||
element.style.bottom = '';
|
||
element.style.left = '';
|
||
element.style.right = '';
|
||
element.style.transform = '';
|
||
|
||
const position = flashMessageSystem.config.position;
|
||
switch (position) {
|
||
case 'top-left':
|
||
element.style.top = '20px';
|
||
element.style.left = '20px';
|
||
break;
|
||
case 'top-center':
|
||
element.style.top = '20px';
|
||
element.style.left = '50%';
|
||
element.style.transform = 'translateX(-50%)';
|
||
break;
|
||
case 'top-right':
|
||
element.style.top = '20px';
|
||
element.style.right = '20px';
|
||
break;
|
||
case 'center-left':
|
||
element.style.top = '50%';
|
||
element.style.left = '20px';
|
||
element.style.transform = 'translateY(-50%)';
|
||
break;
|
||
case 'center':
|
||
default:
|
||
element.style.top = '50%';
|
||
element.style.left = '50%';
|
||
element.style.transform = 'translate(-50%, -50%)';
|
||
break;
|
||
case 'center-right':
|
||
element.style.top = '50%';
|
||
element.style.right = '20px';
|
||
element.style.transform = 'translateY(-50%)';
|
||
break;
|
||
case 'bottom-left':
|
||
element.style.bottom = '20px';
|
||
element.style.left = '20px';
|
||
break;
|
||
case 'bottom-center':
|
||
element.style.bottom = '20px';
|
||
element.style.left = '50%';
|
||
element.style.transform = 'translateX(-50%)';
|
||
break;
|
||
case 'bottom-right':
|
||
element.style.bottom = '20px';
|
||
element.style.right = '20px';
|
||
break;
|
||
}
|
||
}
|
||
|
||
function startFlashMessageSystem() {
|
||
if (!flashMessageSystem || flashMessageSystem.messages.length === 0) return;
|
||
|
||
flashMessageSystem.isActive = true;
|
||
flashMessageSystem.isPaused = false;
|
||
|
||
// Schedule first message
|
||
scheduleNextFlashMessage();
|
||
|
||
console.log('💬 Flash message system started');
|
||
}
|
||
|
||
function scheduleNextFlashMessage() {
|
||
if (!flashMessageSystem || !flashMessageSystem.isActive || flashMessageSystem.isPaused) return;
|
||
|
||
// Calculate delay with variation
|
||
const baseDelay = flashMessageSystem.config.intervalDelay;
|
||
const variation = flashMessageSystem.config.timeVariation;
|
||
const randomVariation = (Math.random() * 2 - 1) * variation; // -variation to +variation
|
||
const delay = Math.max(baseDelay + randomVariation, 5000); // Minimum 5 seconds
|
||
|
||
flashMessageTimeout = setTimeout(() => {
|
||
showRandomFlashMessage();
|
||
}, delay);
|
||
}
|
||
|
||
function showRandomFlashMessage() {
|
||
if (!flashMessageSystem || !flashMessageSystem.isActive || flashMessageSystem.isPaused) return;
|
||
if (!flashMessageSystem.currentElement || flashMessageSystem.messages.length === 0) return;
|
||
|
||
// Pick a random message (avoid repeating the last one if possible)
|
||
let messageIndex;
|
||
if (flashMessageSystem.messages.length > 1) {
|
||
do {
|
||
messageIndex = Math.floor(Math.random() * flashMessageSystem.messages.length);
|
||
} while (messageIndex === flashMessageSystem.lastMessageIndex);
|
||
} else {
|
||
messageIndex = 0;
|
||
}
|
||
|
||
const message = flashMessageSystem.messages[messageIndex];
|
||
flashMessageSystem.lastMessageIndex = messageIndex;
|
||
|
||
displayFlashMessage(message.text);
|
||
}
|
||
|
||
function displayFlashMessage(messageText) {
|
||
if (!flashMessageSystem || !flashMessageSystem.currentElement) return;
|
||
|
||
console.log('💬 Displaying flash message:', messageText);
|
||
|
||
const element = flashMessageSystem.currentElement;
|
||
element.textContent = messageText;
|
||
element.style.display = 'block';
|
||
element.style.opacity = '0';
|
||
|
||
// Apply animation
|
||
requestAnimationFrame(() => {
|
||
applyFlashAnimation(element, 'show');
|
||
});
|
||
|
||
// Schedule hide
|
||
scheduleHideMessage(flashMessageSystem.config.displayDuration);
|
||
}
|
||
|
||
function scheduleHideMessage(delay) {
|
||
flashMessageTimeout = setTimeout(() => {
|
||
hideFlashMessage();
|
||
}, delay);
|
||
}
|
||
|
||
function hideFlashMessage() {
|
||
if (!flashMessageSystem || !flashMessageSystem.currentElement) return;
|
||
|
||
const element = flashMessageSystem.currentElement;
|
||
applyFlashAnimation(element, 'hide');
|
||
|
||
setTimeout(() => {
|
||
element.style.display = 'none';
|
||
// Schedule next message
|
||
scheduleNextFlashMessage();
|
||
}, 500);
|
||
}
|
||
|
||
function applyFlashAnimation(element, type) {
|
||
const animation = flashMessageSystem.config.animation;
|
||
const opacity = flashMessageSystem.config.opacity / 100;
|
||
|
||
if (type === 'show') {
|
||
switch (animation) {
|
||
case 'slide':
|
||
element.style.transform += ' translateY(50px)';
|
||
element.style.opacity = '0';
|
||
setTimeout(() => {
|
||
element.style.transition = 'all 0.5s ease';
|
||
element.style.opacity = opacity;
|
||
element.style.transform = element.style.transform.replace('translateY(50px)', '');
|
||
}, 50);
|
||
break;
|
||
case 'bounce':
|
||
element.style.opacity = opacity;
|
||
element.style.animation = 'flashBounceIn 0.6s ease-out';
|
||
break;
|
||
case 'pulse':
|
||
element.style.opacity = opacity;
|
||
element.style.animation = 'flashPulseIn 0.8s ease-out';
|
||
break;
|
||
case 'fade':
|
||
default:
|
||
element.style.opacity = opacity;
|
||
break;
|
||
}
|
||
} else if (type === 'hide') {
|
||
switch (animation) {
|
||
case 'slide':
|
||
element.style.transform += ' translateY(-50px)';
|
||
element.style.opacity = '0';
|
||
break;
|
||
case 'bounce':
|
||
case 'pulse':
|
||
element.style.animation = 'flashFadeOut 0.5s ease-in';
|
||
break;
|
||
case 'fade':
|
||
default:
|
||
element.style.opacity = '0';
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
function stopFlashMessageSystem() {
|
||
if (!flashMessageSystem) return;
|
||
|
||
flashMessageSystem.isActive = false;
|
||
flashMessageSystem.isPaused = true;
|
||
|
||
if (flashMessageTimeout) {
|
||
clearTimeout(flashMessageTimeout);
|
||
flashMessageTimeout = null;
|
||
}
|
||
|
||
if (flashMessageSystem.currentElement) {
|
||
flashMessageSystem.currentElement.style.display = 'none';
|
||
}
|
||
|
||
console.log('💬 Flash message system stopped');
|
||
}
|
||
|
||
function pauseFlashMessageSystem() {
|
||
if (!flashMessageSystem) return;
|
||
|
||
flashMessageSystem.isPaused = true;
|
||
|
||
if (flashMessageTimeout) {
|
||
clearTimeout(flashMessageTimeout);
|
||
flashMessageTimeout = null;
|
||
}
|
||
|
||
console.log('💬 Flash message system paused');
|
||
}
|
||
|
||
function resumeFlashMessageSystem() {
|
||
if (!flashMessageSystem || !flashMessageSystem.isActive) return;
|
||
|
||
flashMessageSystem.isPaused = false;
|
||
scheduleNextFlashMessage();
|
||
|
||
console.log('💬 Flash message system resumed');
|
||
}
|
||
|
||
// Trigger event-based messages
|
||
function triggerEventFlashMessage(eventType) {
|
||
if (!flashMessageSystem || !flashMessageSystem.config.eventBased) return;
|
||
|
||
// Find messages for specific event types
|
||
let eventMessages = [];
|
||
switch (eventType) {
|
||
case 'taskComplete':
|
||
eventMessages = flashMessageSystem.messages.filter(msg => msg.category === 'achievement');
|
||
break;
|
||
case 'taskSkip':
|
||
eventMessages = flashMessageSystem.messages.filter(msg => msg.category === 'persistence');
|
||
break;
|
||
case 'gameStart':
|
||
eventMessages = flashMessageSystem.messages.filter(msg => msg.category === 'motivational');
|
||
break;
|
||
case 'streak':
|
||
eventMessages = flashMessageSystem.messages.filter(msg => msg.category === 'achievement');
|
||
break;
|
||
default:
|
||
eventMessages = flashMessageSystem.messages.filter(msg => msg.category === 'encouraging');
|
||
break;
|
||
}
|
||
|
||
if (eventMessages.length === 0) {
|
||
// Fallback to any available message
|
||
eventMessages = flashMessageSystem.messages;
|
||
}
|
||
|
||
if (eventMessages.length > 0) {
|
||
const randomMessage = eventMessages[Math.floor(Math.random() * eventMessages.length)];
|
||
|
||
// Clear current schedule temporarily
|
||
if (flashMessageTimeout) {
|
||
clearTimeout(flashMessageTimeout);
|
||
flashMessageTimeout = null;
|
||
}
|
||
|
||
displayFlashMessage(randomMessage.text);
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
/* Quick Play specific styles */
|
||
html, body {
|
||
margin: 0;
|
||
padding: 0;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.quick-play-mode {
|
||
background: linear-gradient(135deg, #0f0f23 0%, #1a1a3a 50%, #0f0f23 100%);
|
||
min-height: 100vh;
|
||
font-family: 'Electrolize', sans-serif;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
.recording-overlay-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.recording-overlay-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.6; }
|
||
}
|
||
|
||
.recording-timestamp {
|
||
color: #ffd700;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
.quick-play-header {
|
||
background: rgba(0, 0, 0, 0.9);
|
||
border-bottom: 1px solid #00d4ff;
|
||
padding: 8px 0;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
width: 100%;
|
||
z-index: 1000;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.quick-play-nav {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
padding: 0 15px;
|
||
}
|
||
|
||
.nav-left {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.nav-center {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 20px;
|
||
}
|
||
|
||
.nav-right {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.quick-play-nav h1 {
|
||
color: #00d4ff;
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 20px;
|
||
margin: 0;
|
||
text-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
|
||
}
|
||
|
||
.quick-play-controls {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* Compact button styling */
|
||
.quick-play-controls .btn {
|
||
padding: 6px 12px;
|
||
font-size: 13px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: rgba(100, 100, 100, 0.8);
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: rgba(120, 120, 120, 0.9);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: rgba(220, 53, 69, 0.8);
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: rgba(220, 53, 69, 0.9);
|
||
}
|
||
|
||
.game-status-bar {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 20px;
|
||
padding: 0; /* Remove padding for inline display */
|
||
background: transparent; /* Remove background for inline display */
|
||
border: none; /* Remove border for inline display */
|
||
font-size: 12px;
|
||
}
|
||
|
||
.status-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.status-label {
|
||
color: #aaa;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.status-value {
|
||
color: #00d4ff;
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.quick-play-main {
|
||
max-width: 1200px;
|
||
margin: 60px auto 0 auto;
|
||
padding: 15px;
|
||
min-height: calc(100vh - 80px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.setup-container {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 2px solid #00d4ff;
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.setup-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.setup-header h2 {
|
||
color: #00d4ff;
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 28px;
|
||
margin: 0 0 10px 0;
|
||
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
||
}
|
||
|
||
.setup-header p {
|
||
color: #aaa;
|
||
font-size: 16px;
|
||
margin: 0;
|
||
}
|
||
|
||
.setting-group {
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
background: rgba(0, 212, 255, 0.05);
|
||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.setting-group h3 {
|
||
color: #00d4ff;
|
||
font-size: 18px;
|
||
margin: 0 0 15px 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.setting-options {
|
||
display: flex;
|
||
gap: 15px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.time-preset, .difficulty-preset {
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
min-width: 80px;
|
||
text-align: center;
|
||
}
|
||
|
||
.time-preset:hover, .difficulty-preset:hover {
|
||
border-color: #00d4ff;
|
||
background: rgba(0, 212, 255, 0.1);
|
||
}
|
||
|
||
.time-preset.active, .difficulty-preset.active {
|
||
border-color: #00d4ff;
|
||
background: rgba(0, 212, 255, 0.2);
|
||
box-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
|
||
}
|
||
|
||
.preset-value, .difficulty-name {
|
||
color: #00d4ff;
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.preset-label, .difficulty-desc {
|
||
color: #aaa;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.difficulty-icon {
|
||
font-size: 24px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.audio-settings, .visual-settings {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.audio-option, .visual-option, .task-type-option {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.audio-option label, .visual-option label, .task-type-option label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
cursor: pointer;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.task-type-settings {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.task-type-option label {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 5px;
|
||
}
|
||
|
||
.task-type-option .option-text {
|
||
font-weight: bold;
|
||
}
|
||
|
||
.task-type-option .option-desc {
|
||
font-size: 12px;
|
||
color: #bbb;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.option-icon {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.setting-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.setting-row label {
|
||
color: #aaa;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.setting-row select {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
color: #00d4ff;
|
||
padding: 5px 10px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
/* Tag Selector Styles */
|
||
.tag-selector {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.tag-category {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.tag-category h4 {
|
||
color: #00d4ff;
|
||
font-size: 14px;
|
||
margin-bottom: 10px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.tag-options {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
.tag-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
padding: 8px 12px;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||
border-radius: 5px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.tag-option:hover {
|
||
background: rgba(0, 212, 255, 0.1);
|
||
border-color: rgba(0, 212, 255, 0.4);
|
||
}
|
||
|
||
.tag-option input[type="checkbox"] {
|
||
accent-color: #00d4ff;
|
||
margin: 0;
|
||
}
|
||
|
||
.tag-option .tag-name {
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tag-option input[type="checkbox"]:checked + .tag-name {
|
||
color: #00d4ff;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* Task Duration Display Styles */
|
||
.task-duration-container {
|
||
margin-top: 15px;
|
||
padding: 10px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 5px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.duration-label, .duration-remaining {
|
||
color: #aaa;
|
||
font-size: 12px;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.task-duration {
|
||
color: #00d4ff;
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.task-timer {
|
||
color: #00d4ff;
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
background: rgba(0, 212, 255, 0.1);
|
||
padding: 2px 8px;
|
||
border-radius: 3px;
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
min-width: 45px;
|
||
text-align: center;
|
||
}
|
||
|
||
.task-timer.warning {
|
||
color: #ffaa00;
|
||
border-color: rgba(255, 170, 0, 0.3);
|
||
background: rgba(255, 170, 0, 0.1);
|
||
}
|
||
|
||
.task-timer.complete {
|
||
color: #00ff00;
|
||
border-color: rgba(0, 255, 0, 0.3);
|
||
background: rgba(0, 255, 0, 0.1);
|
||
}
|
||
|
||
.slider-container {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex: 1;
|
||
}
|
||
|
||
.slider {
|
||
flex: 1;
|
||
height: 8px;
|
||
border-radius: 5px;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
outline: none;
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
}
|
||
|
||
.slider::-webkit-slider-thumb {
|
||
appearance: none;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: #00d4ff;
|
||
cursor: pointer;
|
||
box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
||
}
|
||
|
||
.slider::-moz-range-thumb {
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: #00d4ff;
|
||
cursor: pointer;
|
||
border: none;
|
||
box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
||
}
|
||
|
||
.slider-value {
|
||
min-width: 45px;
|
||
color: #00d4ff;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
}
|
||
|
||
.setting-description {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
margin-top: 5px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.setup-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 15px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.btn-large {
|
||
padding: 15px 30px;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.results-container {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 2px solid #00d4ff;
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.results-header h2 {
|
||
color: #00d4ff;
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 32px;
|
||
margin: 0 0 10px 0;
|
||
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
||
}
|
||
|
||
.results-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
margin: 30px 0;
|
||
}
|
||
|
||
.stat-item {
|
||
background: rgba(0, 212, 255, 0.1);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 24px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #aaa;
|
||
font-size: 14px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-value {
|
||
color: #00d4ff;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.results-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 15px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
/* Custom time input */
|
||
.time-preset.custom input {
|
||
background: transparent;
|
||
border: none;
|
||
color: #00d4ff;
|
||
text-align: center;
|
||
width: 60px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.time-preset.custom input:focus {
|
||
outline: none;
|
||
}
|
||
|
||
/* Game Container Fixes for Quick Play */
|
||
.quick-play-game {
|
||
flex: 0 0 auto; /* Auto-size based on content */
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: auto; /* Auto height based on content */
|
||
overflow: visible; /* Allow natural sizing */
|
||
}
|
||
|
||
#game-container-wrapper {
|
||
flex: 0 0 auto; /* Auto-size based on content */
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: none; /* Remove height restriction */
|
||
overflow: visible; /* Allow natural sizing */
|
||
}
|
||
|
||
.game-container {
|
||
flex: 0 0 auto; /* Auto-size based on content */
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: transparent !important; /* Override external CSS */
|
||
border: none !important; /* Override external CSS */
|
||
border-radius: 0 !important; /* Override external CSS */
|
||
padding: 0 !important; /* Override external CSS */
|
||
margin: 0 !important; /* Override external CSS */
|
||
max-height: none !important; /* Override external CSS */
|
||
overflow: visible !important; /* Override external CSS */
|
||
box-shadow: none !important; /* Override external CSS shadow */
|
||
width: auto !important; /* Override external CSS */
|
||
min-width: unset !important; /* Override external CSS */
|
||
min-height: unset !important; /* Override external CSS */
|
||
max-width: none !important; /* Override external CSS */
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.game-content {
|
||
flex: 0 0 auto; /* Auto-size based on content */
|
||
display: flex;
|
||
gap: 20px;
|
||
max-height: none; /* Remove height restriction */
|
||
overflow: visible; /* Allow natural sizing */
|
||
align-items: flex-start; /* Align to top */
|
||
background: transparent !important; /* Override external CSS dark background */
|
||
padding: 0 !important; /* Override external CSS padding */
|
||
min-height: auto !important; /* Override external CSS min-height */
|
||
}
|
||
|
||
.main-content-area {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0; /* Allow flex to shrink */
|
||
overflow: visible; /* Allow natural sizing */
|
||
height: auto; /* Auto height based on content */
|
||
}
|
||
|
||
.control-sidebar {
|
||
flex-shrink: 0;
|
||
width: 200px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 10px;
|
||
padding: 15px;
|
||
gap: 10px;
|
||
height: fit-content; /* Size to content */
|
||
}
|
||
|
||
/* Video Control Panel Styles */
|
||
.video-control-panel {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 1px solid rgba(139, 92, 246, 0.4);
|
||
border-radius: 8px;
|
||
margin-bottom: 15px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.video-control-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px 12px;
|
||
background: rgba(139, 92, 246, 0.2);
|
||
cursor: pointer;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.video-control-header:hover {
|
||
background: rgba(139, 92, 246, 0.3);
|
||
}
|
||
|
||
.video-control-icon {
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.video-control-title {
|
||
font-weight: 600;
|
||
color: #E5E7EB;
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.video-control-toggle {
|
||
font-size: 0.9rem;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.video-control-toggle.collapsed {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
.video-control-content {
|
||
padding: 12px;
|
||
display: block;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.video-control-content.collapsed {
|
||
display: none;
|
||
}
|
||
|
||
.control-group {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.control-group:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.control-label {
|
||
display: block;
|
||
font-size: 0.8rem;
|
||
color: #9CA3AF;
|
||
margin-bottom: 5px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.control-row {
|
||
display: flex;
|
||
gap: 5px;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.control-btn {
|
||
flex: 1;
|
||
padding: 8px 4px;
|
||
background: rgba(59, 130, 246, 0.2);
|
||
border: 1px solid rgba(59, 130, 246, 0.4);
|
||
border-radius: 4px;
|
||
color: white;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.control-btn:hover {
|
||
background: rgba(59, 130, 246, 0.4);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.control-btn:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.volume-control {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.volume-slider {
|
||
flex: 1;
|
||
height: 4px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 2px;
|
||
outline: none;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
}
|
||
|
||
.volume-slider::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 16px;
|
||
height: 16px;
|
||
background: #8B5CF6;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.volume-slider::-moz-range-thumb {
|
||
width: 16px;
|
||
height: 16px;
|
||
background: #8B5CF6;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
.volume-display {
|
||
font-size: 0.75rem;
|
||
color: #9CA3AF;
|
||
min-width: 30px;
|
||
text-align: right;
|
||
}
|
||
|
||
.playlist-select {
|
||
width: 100%;
|
||
padding: 6px 8px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 4px;
|
||
color: white;
|
||
font-size: 0.8rem;
|
||
outline: none;
|
||
}
|
||
|
||
.playlist-select:focus {
|
||
border-color: #8B5CF6;
|
||
}
|
||
|
||
.video-info {
|
||
background: rgba(0, 0, 0, 0.4);
|
||
border-radius: 4px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.current-video-name {
|
||
font-size: 0.75rem;
|
||
color: #E5E7EB;
|
||
margin-bottom: 5px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.video-progress {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
height: 3px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: #8B5CF6;
|
||
border-radius: 2px;
|
||
width: 0%;
|
||
transition: width 0.1s ease;
|
||
}
|
||
|
||
.video-time {
|
||
font-size: 0.7rem;
|
||
color: #9CA3AF;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Task Display Styles */
|
||
.task-display-container {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
border-radius: 15px;
|
||
padding: 15px;
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
min-height: 0; /* Allow flex to shrink */
|
||
overflow: visible; /* Allow natural sizing */
|
||
height: auto; /* Auto height based on content */
|
||
}
|
||
|
||
.task-text-container {
|
||
flex-shrink: 0; /* Don't shrink the text area */
|
||
margin-bottom: 10px;
|
||
min-height: auto; /* Allow to be as small as needed */
|
||
}
|
||
|
||
.task-text-container h3 {
|
||
color: #00d4ff;
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 22px;
|
||
margin: 0 0 8px 0;
|
||
text-shadow: 0 0 15px rgba(0, 212, 255, 0.5);
|
||
text-align: center;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.task-text-container p {
|
||
color: #fff;
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
margin: 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.task-image-container {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 0; /* Allow flex to work properly */
|
||
overflow: hidden;
|
||
padding: 10px;
|
||
max-height: 100%; /* Ensure container doesn't exceed parent */
|
||
height: 100%; /* Take full available height */
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.task-image {
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
width: auto;
|
||
height: auto;
|
||
object-fit: contain; /* Maintain aspect ratio and fit completely */
|
||
object-position: center;
|
||
border-radius: 10px;
|
||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||
box-shadow: 0 0 20px rgba(0, 212, 255, 0.2);
|
||
display: block;
|
||
}
|
||
|
||
/* Ensure images scale properly based on orientation */
|
||
.task-image[style*="aspect-ratio"] {
|
||
object-fit: contain;
|
||
}
|
||
|
||
/* Additional constraint for very large images */
|
||
.task-image {
|
||
max-width: min(calc(100vw - 300px), 100%); /* Account for sidebar */
|
||
max-height: min(calc(100vh - 300px), 100%); /* Account for header and text */
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
width: 100%;
|
||
}
|
||
|
||
.action-buttons .btn {
|
||
padding: 12px 16px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-transform: uppercase;
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.action-buttons .btn-success {
|
||
background: linear-gradient(135deg, #28a745, #20c997);
|
||
color: white;
|
||
}
|
||
|
||
.action-buttons .btn-success:hover {
|
||
background: linear-gradient(135deg, #218838, #1ba085);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.action-buttons .btn-warning {
|
||
background: linear-gradient(135deg, #ffc107, #fd7e14);
|
||
color: white;
|
||
}
|
||
|
||
.action-buttons .btn-warning:hover {
|
||
background: linear-gradient(135deg, #e0a800, #e8690b);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.action-buttons .btn-danger {
|
||
background: linear-gradient(135deg, #dc3545, #c82333);
|
||
color: white;
|
||
}
|
||
|
||
.action-buttons .btn-danger:hover {
|
||
background: linear-gradient(135deg, #c82333, #a71e2a);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/* Responsive design */
|
||
@media (max-width: 768px) {
|
||
.quick-play-nav {
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.quick-play-controls {
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.game-status-bar {
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
}
|
||
|
||
.setting-options {
|
||
justify-content: center;
|
||
}
|
||
|
||
.results-stats {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.setup-actions, .results-actions {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
/* Game screen mobile adjustments - stack vertically */
|
||
.quick-play-game {
|
||
height: auto; /* Auto height based on content */
|
||
}
|
||
|
||
.game-content {
|
||
flex: 0 0 auto; /* Auto-size based on content */
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
max-height: none; /* Remove height restriction */
|
||
}
|
||
|
||
.control-sidebar {
|
||
width: 100%;
|
||
flex-shrink: 1;
|
||
order: 2; /* Put buttons at bottom on mobile */
|
||
}
|
||
|
||
.main-content-area {
|
||
order: 1; /* Content area at top on mobile */
|
||
}
|
||
|
||
.action-buttons {
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.action-buttons .btn {
|
||
flex: 1;
|
||
max-width: 120px;
|
||
font-size: 12px;
|
||
padding: 10px 8px;
|
||
}
|
||
|
||
.task-text-container h3 {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.task-text-container p {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
/* Medium screens - adjust sidebar width */
|
||
@media (max-width: 1024px) and (min-width: 769px) {
|
||
.control-sidebar {
|
||
width: 180px;
|
||
}
|
||
|
||
.action-buttons .btn {
|
||
font-size: 13px;
|
||
padding: 10px 12px;
|
||
}
|
||
}
|
||
|
||
/* Extra small screens */
|
||
@media (max-width: 480px) {
|
||
.quick-play-main {
|
||
padding: 10px;
|
||
margin-top: 60px;
|
||
}
|
||
|
||
.quick-play-game {
|
||
height: auto; /* Auto height based on content */
|
||
}
|
||
|
||
.game-container {
|
||
padding: 15px;
|
||
}
|
||
|
||
.control-sidebar {
|
||
padding: 15px;
|
||
}
|
||
|
||
/* Video control panel mobile adjustments */
|
||
.video-control-panel {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.video-control-header {
|
||
padding: 8px 10px;
|
||
}
|
||
|
||
.video-control-title {
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.control-btn {
|
||
padding: 6px 3px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.control-label {
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.current-video-name {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.action-buttons .btn {
|
||
font-size: 11px;
|
||
padding: 8px 6px;
|
||
}
|
||
|
||
.task-text-container h3 {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.task-text-container p {
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
|
||
/* Large screens - optimize for better image display */
|
||
@media (min-width: 1200px) {
|
||
.quick-play-game {
|
||
height: auto; /* Auto height based on content */
|
||
}
|
||
|
||
.control-sidebar {
|
||
width: 220px;
|
||
}
|
||
|
||
.task-image-container {
|
||
padding: 20px 0;
|
||
}
|
||
}
|
||
|
||
/* Background Video Styles */
|
||
.background-video-container {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: -1;
|
||
background: #000;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.background-video-container video {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
object-position: center;
|
||
transition: opacity 0.3s ease;
|
||
opacity: 0.4; /* Increased from very low to visible but not overwhelming */
|
||
}
|
||
|
||
.background-video-controls {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 60px;
|
||
background: none;
|
||
border-radius: 0;
|
||
padding: 0;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
z-index: 10;
|
||
min-width: auto;
|
||
width: auto;
|
||
}
|
||
|
||
.background-video-container:hover .background-video-controls {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Video toggle and mute buttons removed for streamlined interface */
|
||
|
||
/* Video visibility states */
|
||
.background-video.video-hidden {
|
||
opacity: 0 !important;
|
||
}
|
||
|
||
.background-video.video-dim {
|
||
opacity: 0.2 !important;
|
||
}
|
||
|
||
.background-video.video-normal {
|
||
opacity: 0.4 !important;
|
||
}
|
||
|
||
.background-video.video-bright {
|
||
opacity: 0.7 !important;
|
||
}
|
||
|
||
.background-video-controls .controls-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
}
|
||
|
||
.background-video-controls .control-btn {
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: none;
|
||
color: white;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
padding: 3px;
|
||
border-radius: 50%;
|
||
transition: all 0.2s ease;
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
backdrop-filter: blur(3px);
|
||
}
|
||
|
||
.background-video-controls .control-btn:hover {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.background-video-controls .volume-control {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 12px;
|
||
padding: 2px;
|
||
backdrop-filter: blur(3px);
|
||
}
|
||
|
||
.background-video-controls .volume-slider {
|
||
width: 40px;
|
||
height: 2px;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
border-radius: 1px;
|
||
outline: none;
|
||
cursor: pointer;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
}
|
||
|
||
.background-video-controls .volume-slider::-webkit-slider-thumb {
|
||
appearance: none;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: #00d4ff;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
.background-video-controls .volume-slider::-moz-range-thumb {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: #00d4ff;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
/* Ensure game content appears above background video */
|
||
.game-content {
|
||
position: relative;
|
||
z-index: 2;
|
||
background: rgba(0, 0, 0, 0.1); /* Slight overlay for better text readability */
|
||
border-radius: 15px;
|
||
}
|
||
|
||
/* Enhance task display container for better visibility over video */
|
||
.task-display-container {
|
||
background: rgba(0, 0, 0, 0.7) !important;
|
||
backdrop-filter: blur(5px);
|
||
border: 2px solid rgba(0, 212, 255, 0.5) !important;
|
||
}
|
||
|
||
.control-sidebar {
|
||
background: rgba(0, 0, 0, 0.8) !important;
|
||
backdrop-filter: blur(5px);
|
||
border: 1px solid rgba(0, 212, 255, 0.5) !important;
|
||
}
|
||
|
||
/* Video mode selection styles */
|
||
.video-settings {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.video-options {
|
||
margin-left: 20px;
|
||
padding: 15px;
|
||
background: rgba(0, 212, 255, 0.05);
|
||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.video-option {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.video-option label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
cursor: pointer;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* Mobile adjustments for background video */
|
||
@media (max-width: 768px) {
|
||
.background-video-controls {
|
||
bottom: 5px;
|
||
padding: 6px 12px;
|
||
}
|
||
|
||
.background-video-controls .control-btn {
|
||
font-size: 12px;
|
||
padding: 3px 6px;
|
||
}
|
||
|
||
.background-video-controls .volume-slider {
|
||
width: 40px;
|
||
}
|
||
|
||
.game-content {
|
||
background: rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.task-display-container {
|
||
background: rgba(0, 0, 0, 0.8) !important;
|
||
}
|
||
|
||
.control-sidebar {
|
||
background: rgba(0, 0, 0, 0.9) !important;
|
||
}
|
||
}
|
||
|
||
/* Task Management Screen Styles */
|
||
.quick-play-task-management {
|
||
max-width: 1200px;
|
||
margin: 60px auto 0 auto;
|
||
padding: 20px;
|
||
min-height: calc(100vh - 80px);
|
||
}
|
||
|
||
.task-management-container {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 2px solid #00d4ff;
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
}
|
||
|
||
.task-management-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.task-management-header h2 {
|
||
color: #00d4ff;
|
||
font-family: 'Audiowide', sans-serif;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.task-type-tabs {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-bottom: 30px;
|
||
border-bottom: 2px solid rgba(0, 212, 255, 0.3);
|
||
padding-bottom: 15px;
|
||
}
|
||
|
||
.task-tab-btn {
|
||
padding: 12px 20px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 8px;
|
||
color: #aaa;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.task-tab-btn.active {
|
||
background: rgba(0, 212, 255, 0.2);
|
||
border-color: #00d4ff;
|
||
color: #00d4ff;
|
||
}
|
||
|
||
.task-tab-btn:hover {
|
||
background: rgba(0, 212, 255, 0.1);
|
||
border-color: #00d4ff;
|
||
}
|
||
|
||
.task-section {
|
||
display: none;
|
||
}
|
||
|
||
.task-section.active {
|
||
display: block;
|
||
}
|
||
|
||
.add-task-section {
|
||
background: rgba(0, 0, 0, 0.4);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.add-task-section h3 {
|
||
color: #00d4ff;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.task-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.form-row label {
|
||
color: #fff;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.form-row textarea {
|
||
padding: 10px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 5px;
|
||
color: #fff;
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.form-row textarea::placeholder {
|
||
color: #666;
|
||
}
|
||
|
||
.tag-input-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.tag-input-section input {
|
||
padding: 10px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 5px;
|
||
color: #fff;
|
||
}
|
||
|
||
.tag-input-section input::placeholder {
|
||
color: #666;
|
||
}
|
||
|
||
.popular-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.tag-label {
|
||
color: #aaa;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.tag-suggestion {
|
||
padding: 4px 8px;
|
||
background: rgba(0, 212, 255, 0.2);
|
||
border: 1px solid rgba(0, 212, 255, 0.4);
|
||
border-radius: 12px;
|
||
color: #00d4ff;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.tag-suggestion:hover {
|
||
background: rgba(0, 212, 255, 0.3);
|
||
border-color: #00d4ff;
|
||
}
|
||
|
||
.form-actions {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.existing-tasks-section {
|
||
background: rgba(0, 0, 0, 0.2);
|
||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.existing-tasks-section h3 {
|
||
color: #00d4ff;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.task-filter {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.search-input, .filter-select {
|
||
padding: 8px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 5px;
|
||
color: #fff;
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
}
|
||
|
||
.filter-select {
|
||
min-width: 150px;
|
||
}
|
||
|
||
.task-display-list {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.task-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 15px;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||
border-radius: 8px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.task-item:hover {
|
||
background: rgba(0, 212, 255, 0.1);
|
||
border-color: #00d4ff;
|
||
}
|
||
|
||
.task-content {
|
||
flex: 1;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.task-text {
|
||
color: #fff;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.task-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 5px;
|
||
}
|
||
|
||
.task-tag {
|
||
padding: 2px 6px;
|
||
background: rgba(0, 212, 255, 0.3);
|
||
border-radius: 10px;
|
||
color: #00d4ff;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.preset-tag {
|
||
background: rgba(0, 255, 100, 0.3);
|
||
color: #00ff64;
|
||
}
|
||
|
||
.custom-tag {
|
||
background: rgba(255, 165, 0, 0.3);
|
||
color: #ffa500;
|
||
}
|
||
|
||
.task-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
/* Toggle Switch Styles */
|
||
.toggle-switch {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 40px;
|
||
height: 20px;
|
||
}
|
||
|
||
.toggle-switch input {
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
|
||
.toggle-slider {
|
||
position: absolute;
|
||
cursor: pointer;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: #ccc;
|
||
transition: .4s;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
.toggle-slider:before {
|
||
position: absolute;
|
||
content: "";
|
||
height: 16px;
|
||
width: 16px;
|
||
left: 2px;
|
||
bottom: 2px;
|
||
background-color: white;
|
||
transition: .4s;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
input:checked + .toggle-slider {
|
||
background-color: #00d4ff;
|
||
}
|
||
|
||
input:checked + .toggle-slider:before {
|
||
transform: translateX(20px);
|
||
}
|
||
|
||
.task-item.disabled {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.task-item.disabled .task-text {
|
||
text-decoration: line-through;
|
||
color: #666;
|
||
}
|
||
|
||
.task-management-actions {
|
||
display: flex;
|
||
gap: 15px;
|
||
justify-content: center;
|
||
margin-top: 30px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid rgba(0, 212, 255, 0.3);
|
||
}
|
||
|
||
.btn-small {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* Multi-select and Tag Management Styles */
|
||
.multi-select-container {
|
||
width: 100%;
|
||
}
|
||
|
||
.tag-dropdown {
|
||
width: 100%;
|
||
background: rgba(0, 20, 40, 0.8);
|
||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 8px;
|
||
color: #00d4ff;
|
||
padding: 8px;
|
||
font-family: 'Orbitron', monospace;
|
||
font-size: 14px;
|
||
min-height: 120px;
|
||
}
|
||
|
||
.tag-dropdown option {
|
||
padding: 4px 8px;
|
||
background: rgba(0, 20, 40, 0.9);
|
||
color: #00d4ff;
|
||
border-bottom: 1px solid rgba(0, 212, 255, 0.1);
|
||
}
|
||
|
||
.tag-dropdown option:checked {
|
||
background: rgba(0, 212, 255, 0.3);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.form-help {
|
||
color: rgba(0, 212, 255, 0.7);
|
||
font-size: 12px;
|
||
margin-top: 4px;
|
||
display: block;
|
||
}
|
||
|
||
.add-tag-section {
|
||
background: rgba(0, 20, 40, 0.6);
|
||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.tag-form {
|
||
display: flex;
|
||
gap: 15px;
|
||
align-items: end;
|
||
}
|
||
|
||
.tag-form .form-group {
|
||
flex: 1;
|
||
}
|
||
|
||
#new-tag-input {
|
||
width: 100%;
|
||
background: rgba(0, 20, 40, 0.8);
|
||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 8px;
|
||
color: #00d4ff;
|
||
padding: 12px;
|
||
font-family: 'Orbitron', monospace;
|
||
font-size: 14px;
|
||
outline: none;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
#new-tag-input:focus {
|
||
border-color: rgba(0, 212, 255, 0.8);
|
||
box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
|
||
}
|
||
|
||
#new-tag-input::placeholder {
|
||
color: rgba(0, 212, 255, 0.5);
|
||
}
|
||
|
||
.tags-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 15px;
|
||
min-height: 50px;
|
||
padding: 15px;
|
||
background: rgba(0, 20, 40, 0.4);
|
||
border: 2px solid rgba(0, 212, 255, 0.2);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.tag-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: rgba(0, 212, 255, 0.2);
|
||
color: #00d4ff;
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
border: 1px solid rgba(0, 212, 255, 0.3);
|
||
}
|
||
|
||
.tag-delete-btn {
|
||
background: rgba(255, 50, 50, 0.7);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 3px;
|
||
padding: 2px 6px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.tag-delete-btn:hover {
|
||
background: rgba(255, 50, 50, 1);
|
||
}
|
||
|
||
.tag-info {
|
||
margin-top: 15px;
|
||
color: rgba(0, 212, 255, 0.6);
|
||
font-size: 14px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.empty-tags-message {
|
||
text-align: center;
|
||
color: rgba(0, 212, 255, 0.5);
|
||
font-style: italic;
|
||
padding: 20px;
|
||
}
|
||
|
||
/* Custom Modal Styles */
|
||
.custom-modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 10000;
|
||
backdrop-filter: blur(5px);
|
||
}
|
||
|
||
.custom-modal {
|
||
background: linear-gradient(135deg, rgba(0, 20, 40, 0.95), rgba(0, 40, 80, 0.95));
|
||
border: 2px solid rgba(0, 212, 255, 0.6);
|
||
border-radius: 12px;
|
||
min-width: 400px;
|
||
max-width: 500px;
|
||
box-shadow: 0 0 30px rgba(0, 212, 255, 0.4);
|
||
font-family: 'Orbitron', monospace;
|
||
animation: modalSlideIn 0.3s ease-out;
|
||
}
|
||
|
||
@keyframes modalSlideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: scale(0.8) translateY(-50px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: scale(1) translateY(0);
|
||
}
|
||
}
|
||
|
||
.custom-modal-header {
|
||
background: rgba(0, 212, 255, 0.1);
|
||
padding: 15px 20px;
|
||
border-bottom: 1px solid rgba(0, 212, 255, 0.3);
|
||
border-radius: 10px 10px 0 0;
|
||
}
|
||
|
||
.custom-modal-header h3 {
|
||
margin: 0;
|
||
color: #00d4ff;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
||
}
|
||
|
||
.custom-modal-body {
|
||
padding: 20px;
|
||
color: #ffffff;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.custom-modal-body p {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.custom-modal-footer {
|
||
padding: 15px 20px;
|
||
border-top: 1px solid rgba(0, 212, 255, 0.3);
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: flex-end;
|
||
border-radius: 0 0 10px 10px;
|
||
}
|
||
|
||
.modal-cancel-btn {
|
||
background: rgba(150, 150, 150, 0.3);
|
||
border: 1px solid rgba(150, 150, 150, 0.5);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.modal-cancel-btn:hover {
|
||
background: rgba(150, 150, 150, 0.5);
|
||
border-color: rgba(150, 150, 150, 0.8);
|
||
}
|
||
|
||
.modal-confirm-btn {
|
||
background: rgba(255, 50, 50, 0.7);
|
||
border: 1px solid rgba(255, 50, 50, 0.8);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.modal-confirm-btn:hover {
|
||
background: rgba(255, 50, 50, 0.9);
|
||
border-color: rgba(255, 50, 50, 1);
|
||
box-shadow: 0 0 15px rgba(255, 50, 50, 0.5);
|
||
}
|
||
|
||
/* Duration Input Styling */
|
||
.duration-input-container {
|
||
margin-top: 10px;
|
||
padding: 10px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 5px;
|
||
border: 1px solid rgba(255, 107, 53, 0.3);
|
||
}
|
||
|
||
.duration-input-container label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
color: #ff6b35;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.duration-input-container input {
|
||
width: 100%;
|
||
padding: 8px;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
border: 1px solid rgba(255, 107, 53, 0.5);
|
||
border-radius: 3px;
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.duration-input-container input:focus {
|
||
outline: none;
|
||
border-color: #ff6b35;
|
||
box-shadow: 0 0 5px rgba(255, 107, 53, 0.3);
|
||
}
|
||
|
||
.task-meta {
|
||
margin-top: 8px;
|
||
padding: 5px 0;
|
||
font-size: 12px;
|
||
color: #cccccc;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.task-duration {
|
||
display: inline-block;
|
||
background: rgba(255, 107, 53, 0.2);
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
margin-right: 8px;
|
||
color: #ff6b35;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* Message Management Styles */
|
||
.quick-play-message-management {
|
||
display: none;
|
||
background: linear-gradient(135deg, #0f0f23 0%, #1a1a3a 50%, #0f0f23 100%);
|
||
min-height: 100vh;
|
||
padding: 80px 20px 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.message-management-container {
|
||
max-width: 1000px;
|
||
margin: 0 auto;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 12px;
|
||
padding: 30px;
|
||
border: 1px solid #00d4ff;
|
||
box-shadow: 0 8px 32px rgba(0, 212, 255, 0.2);
|
||
}
|
||
|
||
.message-management-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
position: relative;
|
||
}
|
||
|
||
.back-button {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
background: rgba(255, 107, 53, 0.2);
|
||
color: #ff6b35;
|
||
border: 1px solid #ff6b35;
|
||
padding: 8px 16px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.back-button:hover {
|
||
background: rgba(255, 107, 53, 0.4);
|
||
transform: translateX(-2px);
|
||
}
|
||
|
||
.message-type-tabs {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 30px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.message-tab-btn {
|
||
background: rgba(0, 0, 0, 0.4);
|
||
color: #cccccc;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
padding: 12px 24px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.message-tab-btn.active,
|
||
.message-tab-btn:hover {
|
||
background: rgba(0, 212, 255, 0.3);
|
||
color: #00d4ff;
|
||
border-color: #00d4ff;
|
||
}
|
||
|
||
.message-section {
|
||
display: none;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.message-section.active {
|
||
display: block;
|
||
}
|
||
|
||
.message-section h3 {
|
||
color: #00d4ff;
|
||
margin-bottom: 20px;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.message-setting-group {
|
||
background: rgba(0, 0, 0, 0.2);
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
margin-bottom: 15px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.message-setting-group h4 {
|
||
color: #ffffff;
|
||
margin-bottom: 15px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.message-control-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
margin-bottom: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.message-control-row label {
|
||
color: #cccccc;
|
||
min-width: 120px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.message-control-row input[type="range"] {
|
||
flex: 1;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.message-control-row input[type="number"],
|
||
.message-control-row select {
|
||
background: rgba(0, 0, 0, 0.5);
|
||
color: #ffffff;
|
||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||
border-radius: 4px;
|
||
padding: 6px 10px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.message-control-row input[type="color"] {
|
||
width: 50px;
|
||
height: 35px;
|
||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||
border-radius: 4px;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.message-control-row .range-display {
|
||
color: #00d4ff;
|
||
font-weight: bold;
|
||
min-width: 60px;
|
||
text-align: center;
|
||
}
|
||
|
||
.message-textarea {
|
||
width: 100%;
|
||
min-height: 100px;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
color: #ffffff;
|
||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||
border-radius: 6px;
|
||
padding: 12px;
|
||
font-family: inherit;
|
||
font-size: 14px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.message-char-counter {
|
||
text-align: right;
|
||
color: #888888;
|
||
font-size: 12px;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.message-button-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.message-btn {
|
||
background: rgba(0, 212, 255, 0.2);
|
||
color: #00d4ff;
|
||
border: 1px solid #00d4ff;
|
||
padding: 8px 16px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.message-btn:hover {
|
||
background: rgba(0, 212, 255, 0.4);
|
||
}
|
||
|
||
.message-btn.primary {
|
||
background: rgba(255, 107, 53, 0.2);
|
||
color: #ff6b35;
|
||
border-color: #ff6b35;
|
||
}
|
||
|
||
.message-btn.primary:hover {
|
||
background: rgba(255, 107, 53, 0.4);
|
||
}
|
||
|
||
.message-list {
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 6px;
|
||
padding: 10px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.loading-messages {
|
||
text-align: center;
|
||
color: #888888;
|
||
padding: 20px;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Message List Item Styles */
|
||
.message-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px;
|
||
margin-bottom: 8px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 6px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.message-item:hover {
|
||
background: rgba(0, 212, 255, 0.1);
|
||
border-color: rgba(0, 212, 255, 0.3);
|
||
}
|
||
|
||
.message-item.disabled {
|
||
opacity: 0.5;
|
||
background: rgba(100, 100, 100, 0.2);
|
||
}
|
||
|
||
.message-content {
|
||
flex: 1;
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.message-text {
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
margin-bottom: 5px;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.message-item.disabled .message-text {
|
||
text-decoration: line-through;
|
||
color: #888888;
|
||
}
|
||
|
||
.message-meta {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.message-category {
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
font-size: 11px;
|
||
font-weight: bold;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.message-category.motivational {
|
||
background: rgba(255, 107, 53, 0.3);
|
||
color: #ff6b35;
|
||
}
|
||
|
||
.message-category.encouraging {
|
||
background: rgba(0, 212, 255, 0.3);
|
||
color: #00d4ff;
|
||
}
|
||
|
||
.message-category.achievement {
|
||
background: rgba(0, 255, 100, 0.3);
|
||
color: #00ff64;
|
||
}
|
||
|
||
.message-category.persistence {
|
||
background: rgba(255, 165, 0, 0.3);
|
||
color: #ffa500;
|
||
}
|
||
|
||
.message-category.custom {
|
||
background: rgba(128, 0, 255, 0.3);
|
||
color: #8000ff;
|
||
}
|
||
|
||
.message-id {
|
||
color: #666666;
|
||
font-size: 10px;
|
||
}
|
||
|
||
.message-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.message-btn-edit,
|
||
.message-btn-delete {
|
||
background: rgba(0, 0, 0, 0.5);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
color: #ffffff;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.message-btn-edit:hover {
|
||
background: rgba(0, 212, 255, 0.3);
|
||
border-color: #00d4ff;
|
||
}
|
||
|
||
.message-btn-delete:hover {
|
||
background: rgba(255, 107, 53, 0.3);
|
||
border-color: #ff6b35;
|
||
}
|
||
|
||
/* Flash Message Animations */
|
||
@keyframes flashBounceIn {
|
||
0% {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) scale(0.3);
|
||
}
|
||
50% {
|
||
opacity: 1;
|
||
transform: translate(-50%, -50%) scale(1.1);
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
transform: translate(-50%, -50%) scale(1);
|
||
}
|
||
}
|
||
|
||
@keyframes flashPulseIn {
|
||
0% {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) scale(0.8);
|
||
}
|
||
25% {
|
||
opacity: 0.5;
|
||
transform: translate(-50%, -50%) scale(1.1);
|
||
}
|
||
50% {
|
||
opacity: 0.8;
|
||
transform: translate(-50%, -50%) scale(0.9);
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
transform: translate(-50%, -50%) scale(1);
|
||
}
|
||
}
|
||
|
||
@keyframes flashFadeOut {
|
||
0% {
|
||
opacity: 1;
|
||
transform: translate(-50%, -50%) scale(1);
|
||
}
|
||
100% {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) scale(0.8);
|
||
}
|
||
}
|
||
|
||
/* Import/Export Section Styles */
|
||
.section-description {
|
||
color: #aaa;
|
||
font-size: 0.9em;
|
||
margin-bottom: 20px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.import-export-controls {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 30px;
|
||
}
|
||
|
||
.import-export-controls h5 {
|
||
color: #fff;
|
||
margin: 0 0 15px 0;
|
||
font-size: 1.1em;
|
||
border-bottom: 2px solid #444;
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.export-options, .import-options, .reset-options {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.import-mode {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.import-mode label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
color: #ddd;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.radio-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.radio-group label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
padding: 5px;
|
||
border-radius: 5px;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.radio-group label:hover {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
.radio-group input[type="radio"] {
|
||
margin: 0;
|
||
}
|
||
|
||
.reset-options {
|
||
border-color: rgba(255, 193, 7, 0.3);
|
||
background: rgba(255, 193, 7, 0.05);
|
||
}
|
||
|
||
/* Session Videos Gallery Styles */
|
||
.session-videos-gallery {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100vh;
|
||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||
overflow-y: auto;
|
||
z-index: 100;
|
||
}
|
||
|
||
.videos-container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.videos-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.videos-header h2 {
|
||
color: #ffffff;
|
||
font-size: 2rem;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.videos-header p {
|
||
color: #b8b8b8;
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.videos-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.video-item {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 15px;
|
||
padding: 15px;
|
||
backdrop-filter: blur(10px);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.video-item:hover {
|
||
transform: translateY(-5px);
|
||
border-color: rgba(0, 255, 255, 0.5);
|
||
box-shadow: 0 10px 30px rgba(0, 255, 255, 0.2);
|
||
}
|
||
|
||
.video-preview {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 200px;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
margin-bottom: 15px;
|
||
background: #000;
|
||
}
|
||
|
||
.video-preview video {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.video-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.video-preview:hover .video-overlay {
|
||
opacity: 1;
|
||
}
|
||
|
||
.play-btn {
|
||
background: rgba(0, 255, 255, 0.9);
|
||
border: none;
|
||
border-radius: 50%;
|
||
width: 60px;
|
||
height: 60px;
|
||
font-size: 1.2rem;
|
||
color: #000;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.play-btn:hover {
|
||
background: rgba(0, 255, 255, 1);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.video-info {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.video-title {
|
||
color: #ffffff;
|
||
font-size: 1.1rem;
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.video-meta {
|
||
color: #b8b8b8;
|
||
font-size: 0.9rem;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.video-meta span {
|
||
margin-right: 15px;
|
||
}
|
||
|
||
.video-settings {
|
||
color: #888;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.video-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.video-actions .btn {
|
||
flex: 1;
|
||
padding: 8px 12px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.videos-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 20px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.no-videos-message {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
}
|
||
|
||
.empty-state {
|
||
max-width: 400px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 4rem;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.empty-state h3 {
|
||
color: #ffffff;
|
||
font-size: 1.5rem;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.empty-state p {
|
||
color: #b8b8b8;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* Video Player Overlay */
|
||
.video-player-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100vh;
|
||
background: rgba(0, 0, 0, 0.95);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 2000;
|
||
}
|
||
|
||
.video-player-container {
|
||
position: relative;
|
||
max-width: 90vw;
|
||
max-height: 90vh;
|
||
}
|
||
|
||
.video-player-container video {
|
||
width: 100%;
|
||
height: auto;
|
||
max-height: 90vh;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.close-player {
|
||
position: absolute;
|
||
top: -40px;
|
||
right: 0;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border: none;
|
||
border-radius: 50%;
|
||
width: 35px;
|
||
height: 35px;
|
||
color: #fff;
|
||
font-size: 1.2rem;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.close-player:hover {
|
||
background: rgba(255, 255, 255, 0.4);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
/* Webcam Viewer Styles */
|
||
.webcam-viewer {
|
||
position: fixed;
|
||
z-index: 1000;
|
||
border: 2px solid rgba(0, 255, 255, 0.6);
|
||
border-radius: 10px;
|
||
background: rgba(0, 20, 40, 0.9);
|
||
backdrop-filter: blur(5px);
|
||
box-shadow: 0 4px 20px rgba(0, 255, 255, 0.3);
|
||
transition: all 0.3s ease;
|
||
cursor: move;
|
||
display: none;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.webcam-viewer.active {
|
||
display: block;
|
||
}
|
||
|
||
.webcam-viewer video {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.webcam-viewer .viewer-controls {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 5px;
|
||
display: flex;
|
||
gap: 5px;
|
||
opacity: 0.7;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
.webcam-viewer:hover .viewer-controls {
|
||
opacity: 1;
|
||
}
|
||
|
||
.webcam-viewer .control-btn {
|
||
background: rgba(0, 0, 0, 0.7);
|
||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||
color: white;
|
||
border-radius: 3px;
|
||
padding: 2px 5px;
|
||
font-size: 10px;
|
||
cursor: pointer;
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
.webcam-viewer .control-btn:hover {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.webcam-viewer .recording-indicator {
|
||
position: absolute;
|
||
top: 5px;
|
||
left: 5px;
|
||
background: #ff4444;
|
||
color: white;
|
||
padding: 2px 6px;
|
||
border-radius: 10px;
|
||
font-size: 10px;
|
||
font-weight: bold;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
|
||
/* Size variants */
|
||
.webcam-viewer.small {
|
||
width: 150px;
|
||
height: 84px; /* 16:9 aspect ratio */
|
||
}
|
||
|
||
.webcam-viewer.medium {
|
||
width: 200px;
|
||
height: 113px; /* 16:9 aspect ratio */
|
||
}
|
||
|
||
.webcam-viewer.large {
|
||
width: 250px;
|
||
height: 141px; /* 16:9 aspect ratio */
|
||
}
|
||
|
||
/* Position variants */
|
||
.webcam-viewer.bottom-right {
|
||
bottom: 20px;
|
||
right: 20px;
|
||
}
|
||
|
||
.webcam-viewer.bottom-left {
|
||
bottom: 20px;
|
||
left: 20px;
|
||
}
|
||
|
||
.webcam-viewer.top-right {
|
||
top: 20px;
|
||
right: 20px;
|
||
}
|
||
|
||
.webcam-viewer.top-left {
|
||
top: 20px;
|
||
left: 20px;
|
||
}
|
||
|
||
/* Webcam Task Overlay Styles */
|
||
.webcam-task-overlay {
|
||
position: absolute;
|
||
bottom: 8px;
|
||
left: 8px;
|
||
right: 8px;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 1px solid #00ff00;
|
||
border-radius: 6px;
|
||
padding: 8px 10px;
|
||
font-family: 'Arial', sans-serif;
|
||
font-size: 11px;
|
||
line-height: 1.3;
|
||
color: #ffffff;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
|
||
z-index: 10;
|
||
}
|
||
|
||
.webcam-task-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 4px;
|
||
font-size: 10px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.webcam-task-overlay #webcam-task-type {
|
||
color: #00ff00;
|
||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
|
||
}
|
||
|
||
.webcam-task-overlay #webcam-task-timer {
|
||
color: #ffa500;
|
||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
|
||
}
|
||
|
||
.webcam-task-overlay #webcam-task-text {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
|
||
word-wrap: break-word;
|
||
max-height: 3em;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Hide webcam task overlay when not recording */
|
||
.webcam-viewer:not(.recording) .webcam-task-overlay {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Show webcam task overlay when recording */
|
||
.webcam-viewer.recording .webcam-task-overlay {
|
||
display: block !important;
|
||
}
|
||
</style>
|
||
|
||
<!-- Floating Webcam Viewer -->
|
||
<div id="webcam-viewer" class="webcam-viewer">
|
||
<div class="recording-indicator">● REC</div>
|
||
<video id="webcam-preview" autoplay muted></video>
|
||
|
||
<!-- Task Overlay on Webcam Recording -->
|
||
<div id="webcam-task-overlay" class="webcam-task-overlay">
|
||
<div class="webcam-task-header">
|
||
<span id="webcam-task-type">SESSION</span>
|
||
<span id="webcam-task-timer">00:00</span>
|
||
</div>
|
||
<div id="webcam-task-text">Training session active...</div>
|
||
</div>
|
||
|
||
<div class="viewer-controls">
|
||
<button class="control-btn" id="toggle-webcam-viewer" title="Hide Webcam">👁️</button>
|
||
<button class="control-btn" id="stop-recording" title="Stop Recording">⏹️</button>
|
||
</div>
|
||
</div>
|
||
|
||
</body>
|
||
</html> |