11163 lines
477 KiB
HTML
11163 lines
477 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/color-variables.css">
|
||
<link rel="stylesheet" href="src/styles/styles.css">
|
||
<link rel="stylesheet" href="src/styles/base-video-player.css">
|
||
<link rel="stylesheet" href="src/styles/overlay-video-player.css">
|
||
<link href="https://fonts.googleapis.com/css2?family=Audiowide&family=Michroma&family=Electrolize:wght@400&display=swap" rel="stylesheet">
|
||
<script src="https://unpkg.com/jszip@3.10.1/dist/jszip.min.js" crossorigin="anonymous"></script>
|
||
<script src="src/utils/themeManager.js"></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">
|
||
<!-- Theme Switcher -->
|
||
<div id="theme-switcher-container"></div>
|
||
|
||
<button id="back-to-home" class="btn btn-secondary" title="Return to main menu and exit Quick Play">🏠 Home</button>
|
||
<button id="open-hypno-gallery" class="btn btn-gallery" title="Open Hypno Gallery in separate window">
|
||
<span class="btn-icon">🎭</span>
|
||
<span class="btn-text">Gallery</span>
|
||
</button>
|
||
<button id="open-porn-cinema" class="btn btn-cinema" title="Open Porn Cinema in separate window">
|
||
<span class="btn-icon">🎬</span>
|
||
<span class="btn-text">Cinema</span>
|
||
</button>
|
||
<button id="quick-play-webcam-btn" class="btn btn-photo" title="Take a photo with webcam">
|
||
<span class="btn-icon">📸</span>
|
||
<span class="btn-text">Photo</span>
|
||
</button>
|
||
<button id="pause-game-btn" class="btn btn-secondary" style="display: none;" title="Pause the current session - timer and tasks will stop">⏸️ 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 class="setup-instructions">
|
||
<div class="instruction-box">
|
||
<p style="margin: 0; color: var(--text-muted); line-height: 1.6;">
|
||
Quick Play offers a streamlined training experience with customizable tasks, multimedia enhancements, and progress tracking. Configure your session duration, choose which tasks to include, enable optional features like videos and messages, and start your session instantly. All settings can be saved as presets for future use.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Floating Start Button -->
|
||
<button id="floating-start-btn" class="floating-start-button" title="Begin your Quick Play session with current settings">
|
||
<span class="btn-icon">⚡</span>
|
||
<span class="btn-text">Start Session</span>
|
||
</button>
|
||
|
||
<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>
|
||
|
||
<!-- Task Management Settings -->
|
||
<div class="setting-group">
|
||
<h3>📝 Task Management</h3>
|
||
<div class="task-management-settings">
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Complete challenges that appear during your session. Enable/disable specific tasks to customize your experience. Use the tags below to filter which types of tasks will appear.</strong>
|
||
</div>
|
||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-bottom: 15px;">
|
||
<div class="setting-description" style="flex: 1;">
|
||
View, edit, and organize your tasks and consequences. Configure which tasks appear in your session.
|
||
</div>
|
||
<button id="manage-tasks-btn" class="btn btn-secondary" title="View, edit, and organize your main tasks and consequences">
|
||
⚙️ Manage Tasks
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Include Tags Collapsible -->
|
||
<div class="collapsible-section">
|
||
<button class="collapsible-header" data-target="include-tags-content" title="Expand to select specific tags to include in your session">
|
||
<span class="collapse-icon">▶</span>
|
||
<span>🏷️ Include Tags</span>
|
||
</button>
|
||
<div class="collapsible-content" id="include-tags-content" style="display: none;">
|
||
<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>
|
||
</div>
|
||
|
||
<!-- Exclude Tags Collapsible -->
|
||
<div class="collapsible-section">
|
||
<button class="collapsible-header" data-target="exclude-tags-content" title="Expand to select specific tags to exclude from your session">
|
||
<span class="collapse-icon">▶</span>
|
||
<span>🚫 Exclude Tags</span>
|
||
</button>
|
||
<div class="collapsible-content" id="exclude-tags-content" style="display: none;">
|
||
<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>
|
||
</div>
|
||
|
||
<!-- Video Settings -->
|
||
<div class="setting-group">
|
||
<h3>🎬 Video Experience</h3>
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Choose background videos, popup rewards, or focus mode. Videos enhance immersion and provide motivation during your training session.</strong>
|
||
</div>
|
||
<div class="video-settings">
|
||
<div class="video-option">
|
||
<input type="checkbox" id="enable-background-video">
|
||
<label for="enable-background-video">
|
||
<span class="option-icon">🎬</span>
|
||
<span class="option-text">Enable Background Video</span>
|
||
</label>
|
||
</div>
|
||
<div class="video-options" id="video-options" style="display: none;">
|
||
<div class="setting-row">
|
||
<label for="video-opacity">Video Opacity: <span id="video-opacity-value">70%</span></label>
|
||
<div class="slider-container">
|
||
<input type="range" id="video-opacity" min="0.1" max="1.0" step="0.05" value="0.7" class="slider">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Webcam Recording Settings -->
|
||
<div class="setting-group">
|
||
<h3>📹 Webcam Recording</h3>
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Enable session recording to save verification photos and earn bonus XP for your dedication. All recordings are stored locally on your device for privacy and security.</strong>
|
||
</div>
|
||
<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="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" title="Choose where to save session recordings and webcam photos">📁 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>
|
||
|
||
<!-- Flash Messages Settings -->
|
||
<div class="setting-group">
|
||
<h3>💬 Flash Messages</h3>
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Flash messages appear periodically with encouragement, instructions, and motivation during your session. Customize timing, content, and appearance to match your preferences.</strong>
|
||
</div>
|
||
<div class="message-settings">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 15px;">
|
||
<div class="video-option" style="flex: 1;">
|
||
<input type="checkbox" id="enable-flash-messages" checked>
|
||
<label for="enable-flash-messages">
|
||
<span class="option-icon">💬</span>
|
||
<span class="option-text">Enable Flash Messages</span>
|
||
</label>
|
||
</div>
|
||
<button id="manage-messages-btn" class="btn btn-secondary" title="Customize flash messages, timing, and appearance settings">
|
||
⚙️ Configure Messages
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Popup Images Settings -->
|
||
<div class="setting-group">
|
||
<h3>🖼️ Popup Images</h3>
|
||
<div class="setting-description" style="margin-bottom: 15px; padding: 12px; background: var(--bg-primary-overlay-10); border-left: 3px solid var(--color-primary); border-radius: 5px;">
|
||
<strong style="color: var(--color-primary); font-size: 15px;">Images from your library appear as rewards and distractions. Configure timing and positioning for your preference to create the perfect level of visual stimulation.</strong>
|
||
</div>
|
||
<div class="popup-settings">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 15px;">
|
||
<div class="video-option" style="flex: 1;">
|
||
<input type="checkbox" id="enable-popup-images">
|
||
<label for="enable-popup-images">
|
||
<span class="option-icon">🖼️</span>
|
||
<span class="option-text">Enable Popup Images</span>
|
||
</label>
|
||
</div>
|
||
<button id="manage-popup-images-btn" class="btn btn-secondary" title="Configure popup image settings, timing, and positioning">
|
||
⚙️ Configure Popup Images
|
||
</button>
|
||
</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>
|
||
|
||
<!-- 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>
|
||
|
||
|
||
|
||
|
||
</div>
|
||
|
||
<div class="setup-actions">
|
||
<button id="load-preset" class="btn btn-secondary" title="Load a previously saved configuration preset to restore all settings">
|
||
📁 Load Saved Preset
|
||
</button>
|
||
<button id="save-preset" class="btn btn-secondary" title="Save current settings as a preset for future sessions">
|
||
💾 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" title="View and edit your main session tasks">
|
||
📋 Main Tasks
|
||
</button>
|
||
<button id="consequence-tasks-tab" class="task-tab-btn" data-type="consequence" title="Manage tasks that trigger when you fail main tasks">
|
||
🚨 Consequence Tasks
|
||
</button>
|
||
<button id="tag-management-tab" class="task-tab-btn" data-type="tags" title="Create and organize tags for better task filtering">
|
||
🏷️ 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" title="Create a new main task with the description and tags above">+ 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" title="Create a new consequence task for failed main tasks">+ 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" title="Create a new custom tag for organizing tasks">➕ 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" title="Export all tasks and tags to a JSON file for backup or sharing">📤 Export Tasks</button>
|
||
<button id="import-tasks-btn" class="btn btn-secondary" title="Import tasks and tags from a previously exported JSON file">📥 Import Tasks</button>
|
||
<button id="reset-tasks-btn" class="btn btn-warning" title="Delete all custom tasks and reset to default preset tasks (cannot be undone)">🔄 Reset to Defaults</button>
|
||
<button id="back-to-setup-btn" class="btn btn-secondary" title="Return to the main Quick Play setup screen">⬅️ 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" title="Configure flash message content, timing, and behavior">
|
||
💫 Flash Messages
|
||
</button>
|
||
<button id="message-appearance-tab" class="message-tab-btn" data-type="appearance" title="Customize message styling, colors, fonts, and visual effects">
|
||
🎨 Appearance
|
||
</button>
|
||
<button id="import-export-tab" class="message-tab-btn" data-type="import-export" title="Import/export message configurations and manage message presets">
|
||
📁 Import/Export
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Flash Messages Section -->
|
||
<div id="flash-messages-section" class="message-section active">
|
||
<div class="message-section-content">
|
||
<!-- 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">
|
||
<!-- 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,
|
||
enablePopupImages: false,
|
||
duration: 'medium', // Task duration setting
|
||
includeTags: [], // Tags to include
|
||
excludeTags: [], // Tags to exclude
|
||
includeStandardTasks: true,
|
||
consequenceChance: 100, // 100% chance by default
|
||
// Video settings
|
||
videoMode: 'none',
|
||
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() {
|
||
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) {
|
||
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;
|
||
}
|
||
}
|
||
|
||
// ===== ANIMATION EVENT LISTENERS =====
|
||
|
||
// Listen for level up events
|
||
window.addEventListener('levelUp', (event) => {
|
||
console.log('🎉 Quick Play: Level up event received:', event.detail);
|
||
});
|
||
|
||
// Listen for achievement events
|
||
window.addEventListener('achievementUnlocked', (event) => {
|
||
console.log('🏆 Quick Play: Achievement unlocked event received:', event.detail);
|
||
});
|
||
|
||
// Initialize Quick Play when page loads
|
||
document.addEventListener('DOMContentLoaded', async function() {
|
||
// Initialize theme switcher UI
|
||
if (window.themeManager) {
|
||
const themeSwitcher = window.themeManager.createThemeToggle();
|
||
const container = document.getElementById('theme-switcher-container');
|
||
if (container) {
|
||
container.appendChild(themeSwitcher);
|
||
}
|
||
}
|
||
|
||
try {
|
||
// Check MP4 support first
|
||
checkMP4Support();
|
||
|
||
// Load saved settings
|
||
await loadSavedSettings();
|
||
|
||
// Setup event listeners
|
||
setupEventListeners();
|
||
|
||
// Initialize player stats
|
||
if (typeof PlayerStats !== 'undefined') {
|
||
window.playerStats = new PlayerStats();
|
||
}
|
||
|
||
// Initialize desktop file manager if in Electron environment
|
||
await initializeFileManager();
|
||
|
||
// 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) {
|
||
// Store in IndexedDB for more reliable persistence
|
||
try {
|
||
const request = indexedDB.open('webcamStorage', 1);
|
||
request.onupgradeneeded = (event) => {
|
||
const db = event.target.result;
|
||
if (!db.objectStoreNames.contains('directoryHandles')) {
|
||
db.createObjectStore('directoryHandles', { keyPath: 'id' });
|
||
}
|
||
};
|
||
|
||
return new Promise((resolve, reject) => {
|
||
request.onsuccess = async (event) => {
|
||
try {
|
||
const db = event.target.result;
|
||
const transaction = db.transaction(['directoryHandles'], 'readwrite');
|
||
const store = transaction.objectStore('directoryHandles');
|
||
await store.put({
|
||
id: 'webcam-directory',
|
||
handle: directoryHandle,
|
||
name: directoryHandle.name,
|
||
timestamp: Date.now()
|
||
});
|
||
resolve('indexeddb-stored');
|
||
} catch (error) {
|
||
reject(error);
|
||
}
|
||
};
|
||
request.onerror = () => reject(request.error);
|
||
});
|
||
} catch (error) {
|
||
console.log('IndexedDB storage failed:', error);
|
||
}
|
||
|
||
// Fallback - just return a reference ID
|
||
return `handle-${Date.now()}`;
|
||
}
|
||
|
||
async function retrieveDirectoryHandle(handleId) {
|
||
// Retrieve from IndexedDB
|
||
try {
|
||
const request = indexedDB.open('webcamStorage', 1);
|
||
return new Promise((resolve, reject) => {
|
||
request.onsuccess = async (event) => {
|
||
try {
|
||
const db = event.target.result;
|
||
const transaction = db.transaction(['directoryHandles'], 'readonly');
|
||
const store = transaction.objectStore('directoryHandles');
|
||
const getRequest = store.get('webcam-directory');
|
||
|
||
getRequest.onsuccess = () => {
|
||
const result = getRequest.result;
|
||
if (result && result.handle) {
|
||
resolve(result.handle);
|
||
} else {
|
||
resolve(null);
|
||
}
|
||
};
|
||
getRequest.onerror = () => resolve(null);
|
||
} catch (error) {
|
||
resolve(null);
|
||
}
|
||
};
|
||
request.onerror = () => resolve(null);
|
||
});
|
||
} catch (error) {
|
||
console.log('IndexedDB retrieval failed:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
// Try to restore directory handle if available
|
||
try {
|
||
const handleId = localStorage.getItem('webcamDirectoryHandleId');
|
||
if (handleId && typeof retrieveDirectoryHandle !== 'undefined') {
|
||
const directoryHandle = await retrieveDirectoryHandle(handleId);
|
||
if (directoryHandle) {
|
||
quickPlaySettings.webcamDirectoryHandle = directoryHandle;
|
||
console.log('📁 Restored directory handle for:', savedDirectory);
|
||
}
|
||
}
|
||
} catch (handleError) {
|
||
console.log('📁 Could not restore directory handle, will use fallback download');
|
||
}
|
||
|
||
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 video settings
|
||
document.getElementById('enable-background-video').checked = quickPlaySettings.videoMode === 'background';
|
||
const opacityValue = quickPlaySettings.videoOpacity || 0.7;
|
||
document.getElementById('video-opacity').value = opacityValue;
|
||
document.getElementById('video-opacity-value').textContent = Math.round(opacityValue * 100) + '%';
|
||
|
||
// Show/hide video options based on background video setting
|
||
const videoOptions = document.getElementById('video-options');
|
||
if (quickPlaySettings.videoMode === 'background') {
|
||
videoOptions.style.display = 'block';
|
||
} else {
|
||
videoOptions.style.display = 'none';
|
||
}
|
||
|
||
// Apply flash messages settings
|
||
document.getElementById('enable-flash-messages').checked = quickPlaySettings.enableFlashMessages;
|
||
|
||
// Apply popup images settings
|
||
document.getElementById('enable-popup-images').checked = quickPlaySettings.enablePopupImages;
|
||
|
||
// Task type settings (standard tasks always included in Quick Play)
|
||
// includeStandardTasks checkbox removed - Quick Play always uses standard tasks
|
||
|
||
// 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);
|
||
});
|
||
|
||
// Apply webcam recording settings
|
||
document.getElementById('enable-session-recording').checked = quickPlaySettings.enableSessionRecording;
|
||
updateWebcamOptionsVisibility();
|
||
|
||
// Video options visibility is now handled in the loadSettings function above
|
||
}
|
||
|
||
|
||
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 = 0.3;
|
||
|
||
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);
|
||
console.log(`🔊 Using volume from slider: ${volumeSlider.value}% -> ${targetVolume}`);
|
||
}
|
||
|
||
// Set volume and mute state based on settings
|
||
video.volume = targetVolume;
|
||
video.muted = 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 enableBackground = document.getElementById('enable-background-video');
|
||
|
||
if (enableBackground && enableBackground.checked) {
|
||
videoOptions.style.display = 'block';
|
||
} else {
|
||
videoOptions.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
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() {
|
||
// Don't update if game is ending or not running
|
||
if (!isGameRunning || window.isEndingGame) return;
|
||
|
||
if (!recordingOverlayEnabled) return;
|
||
|
||
// 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 = 'var(--color-primary)';
|
||
console.log('🎬 Set webcam overlay type to TASK');
|
||
} else if (taskTitleText) {
|
||
overlayTaskType.textContent = 'SESSION';
|
||
overlayTaskType.style.color = 'var(--color-accent-gold)';
|
||
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() {
|
||
// Don't run if game is ending or not running
|
||
if (!isGameRunning || window.isEndingGame) return;
|
||
|
||
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 = 'var(--color-primary)';
|
||
} else if (title) {
|
||
webcamTaskType.textContent = 'SESSION';
|
||
webcamTaskType.style.color = 'var(--color-accent-gold)';
|
||
} 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');
|
||
}
|
||
|
||
// Hypno Gallery button
|
||
const openHypnoGalleryBtn = document.getElementById('open-hypno-gallery');
|
||
if (openHypnoGalleryBtn) {
|
||
openHypnoGalleryBtn.addEventListener('click', async (e) => {
|
||
e.preventDefault();
|
||
console.log('Opening Hypno Gallery in separate window');
|
||
|
||
try {
|
||
// Try Electron child window first (desktop app)
|
||
if (window.electronAPI && window.electronAPI.openChildWindow) {
|
||
console.log('🖥️ Using Electron child window for Hypno Gallery');
|
||
const result = await window.electronAPI.openChildWindow({
|
||
url: 'hypno-gallery.html',
|
||
windowName: 'Hypno Gallery',
|
||
width: 1200,
|
||
height: 800
|
||
});
|
||
|
||
if (result.success) {
|
||
// Add visual feedback with animation
|
||
const originalHTML = openHypnoGalleryBtn.innerHTML;
|
||
const iconSpan = '<span class="btn-icon">👁️</span>';
|
||
const textSpan = result.action === 'focused' ? '<span class="btn-text">Focused</span>' : '<span class="btn-text">Opened</span>';
|
||
|
||
openHypnoGalleryBtn.innerHTML = iconSpan + textSpan;
|
||
openHypnoGalleryBtn.disabled = true;
|
||
openHypnoGalleryBtn.classList.add('success');
|
||
|
||
// Reset button after animation
|
||
const resetTime = result.action === 'focused' ? 2000 : 3000;
|
||
setTimeout(() => {
|
||
openHypnoGalleryBtn.innerHTML = originalHTML;
|
||
openHypnoGalleryBtn.disabled = false;
|
||
openHypnoGalleryBtn.classList.remove('success');
|
||
}, resetTime);
|
||
|
||
console.log(`✅ Hypno Gallery ${result.action === 'focused' ? 'focused' : 'opened'} successfully`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Fallback to browser popup (web version)
|
||
console.log('🌐 Falling back to browser popup for Hypno Gallery');
|
||
|
||
// Transfer media library data to localStorage for the popup window
|
||
if (window.desktopFileManager && window.desktopFileManager.unifiedImageLibrary) {
|
||
const imageLibrary = window.desktopFileManager.unifiedImageLibrary;
|
||
localStorage.setItem('popupImageLibrary', JSON.stringify(imageLibrary));
|
||
console.log(`📁 Transferred ${imageLibrary.length} images to localStorage for popup`);
|
||
}
|
||
|
||
// Open hypno-gallery.html in a new window
|
||
const galleryWindow = window.open(
|
||
'hypno-gallery.html',
|
||
'hypno-gallery',
|
||
'width=1200,height=800,scrollbars=yes,resizable=yes,menubar=no,toolbar=no,location=no,status=no'
|
||
);
|
||
|
||
if (galleryWindow) {
|
||
// Focus the new window
|
||
galleryWindow.focus();
|
||
|
||
// Add visual feedback
|
||
const originalText = openHypnoGalleryBtn.innerHTML;
|
||
openHypnoGalleryBtn.innerHTML = '✅ Gallery Opened';
|
||
openHypnoGalleryBtn.disabled = true;
|
||
|
||
// Reset button after 3 seconds
|
||
setTimeout(() => {
|
||
openHypnoGalleryBtn.innerHTML = originalText;
|
||
openHypnoGalleryBtn.disabled = false;
|
||
}, 3000);
|
||
|
||
console.log('Hypno Gallery opened successfully');
|
||
} else {
|
||
console.error('Failed to open Hypno Gallery - popup blocked?');
|
||
alert('Could not open Hypno Gallery. Please check if popups are blocked.');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Error opening Hypno Gallery:', error);
|
||
alert('Failed to open Hypno Gallery. Please try again.');
|
||
}
|
||
});
|
||
} else {
|
||
console.warn('open-hypno-gallery button not found');
|
||
}
|
||
|
||
// Porn Cinema button
|
||
const openPornCinemaBtn = document.getElementById('open-porn-cinema');
|
||
if (openPornCinemaBtn) {
|
||
openPornCinemaBtn.addEventListener('click', async (e) => {
|
||
e.preventDefault();
|
||
console.log('Opening Porn Cinema in separate window');
|
||
|
||
try {
|
||
// Try Electron child window first (desktop app)
|
||
if (window.electronAPI && window.electronAPI.openChildWindow) {
|
||
console.log('🖥️ Using Electron child window for Porn Cinema');
|
||
const result = await window.electronAPI.openChildWindow({
|
||
url: 'porn-cinema.html',
|
||
windowName: 'Porn Cinema',
|
||
width: 1400,
|
||
height: 900
|
||
});
|
||
|
||
if (result.success) {
|
||
// Add visual feedback with animation
|
||
const originalHTML = openPornCinemaBtn.innerHTML;
|
||
const iconSpan = '<span class="btn-icon">👁️</span>';
|
||
const textSpan = result.action === 'focused' ? '<span class="btn-text">Focused</span>' : '<span class="btn-text">Opened</span>';
|
||
|
||
openPornCinemaBtn.innerHTML = iconSpan + textSpan;
|
||
openPornCinemaBtn.disabled = true;
|
||
openPornCinemaBtn.classList.add('success');
|
||
|
||
// Reset button after animation
|
||
const resetTime = result.action === 'focused' ? 2000 : 3000;
|
||
setTimeout(() => {
|
||
openPornCinemaBtn.innerHTML = originalHTML;
|
||
openPornCinemaBtn.disabled = false;
|
||
openPornCinemaBtn.classList.remove('success');
|
||
}, resetTime);
|
||
|
||
console.log(`✅ Porn Cinema ${result.action === 'focused' ? 'focused' : 'opened'} successfully`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Fallback to browser popup (web version)
|
||
console.log('🌐 Falling back to browser popup for Porn Cinema');
|
||
|
||
// Transfer video library data to localStorage for the popup window
|
||
if (window.desktopFileManager && typeof window.desktopFileManager.getAllVideos === 'function') {
|
||
const videoLibrary = window.desktopFileManager.getAllVideos();
|
||
localStorage.setItem('popupVideoLibrary', JSON.stringify(videoLibrary));
|
||
console.log(`🎬 Transferred ${videoLibrary.length} videos to localStorage for popup`);
|
||
}
|
||
|
||
// Open porn-cinema.html in a new window
|
||
const cinemaWindow = window.open(
|
||
'porn-cinema.html',
|
||
'porn-cinema',
|
||
'width=1400,height=900,scrollbars=yes,resizable=yes,menubar=no,toolbar=no,location=no,status=no'
|
||
);
|
||
|
||
if (cinemaWindow) {
|
||
// Focus the new window
|
||
cinemaWindow.focus();
|
||
|
||
// Add visual feedback
|
||
const originalText = openPornCinemaBtn.innerHTML;
|
||
openPornCinemaBtn.innerHTML = '✅ Cinema Opened';
|
||
openPornCinemaBtn.disabled = true;
|
||
|
||
// Reset button after 3 seconds
|
||
setTimeout(() => {
|
||
openPornCinemaBtn.innerHTML = originalText;
|
||
openPornCinemaBtn.disabled = false;
|
||
}, 3000);
|
||
|
||
console.log('Porn Cinema opened successfully');
|
||
} else {
|
||
console.error('Failed to open Porn Cinema - popup blocked?');
|
||
alert('Could not open Porn Cinema. Please check if popups are blocked.');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Error opening Porn Cinema:', error);
|
||
alert('Failed to open Porn Cinema. Please try again.');
|
||
}
|
||
});
|
||
} else {
|
||
console.warn('open-porn-cinema 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();
|
||
});
|
||
}
|
||
|
||
|
||
|
||
// Webcam photo button
|
||
const webcamBtn = document.getElementById('quick-play-webcam-btn');
|
||
if (webcamBtn) {
|
||
webcamBtn.addEventListener('click', async (e) => {
|
||
e.preventDefault();
|
||
console.log('Webcam photo button clicked');
|
||
|
||
// Add visual feedback with animation
|
||
const originalHTML = webcamBtn.innerHTML;
|
||
const iconSpan = '<span class="btn-icon">📷</span>';
|
||
const textSpan = '<span class="btn-text">Opening</span>';
|
||
|
||
webcamBtn.innerHTML = iconSpan + textSpan;
|
||
webcamBtn.disabled = true;
|
||
webcamBtn.classList.add('success');
|
||
|
||
try {
|
||
await openQuickPlayWebcam();
|
||
|
||
// Show success state briefly
|
||
webcamBtn.innerHTML = '<span class="btn-icon">✅</span><span class="btn-text">Opened</span>';
|
||
|
||
setTimeout(() => {
|
||
webcamBtn.innerHTML = originalHTML;
|
||
webcamBtn.disabled = false;
|
||
webcamBtn.classList.remove('success');
|
||
}, 2000);
|
||
|
||
} catch (error) {
|
||
console.error('Webcam error:', error);
|
||
webcamBtn.innerHTML = '<span class="btn-icon">❌</span><span class="btn-text">Error</span>';
|
||
|
||
setTimeout(() => {
|
||
webcamBtn.innerHTML = originalHTML;
|
||
webcamBtn.disabled = false;
|
||
webcamBtn.classList.remove('success');
|
||
}, 2000);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 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 controls removed
|
||
|
||
// Video settings
|
||
document.getElementById('enable-background-video').addEventListener('change', (e) => {
|
||
quickPlaySettings.videoMode = e.target.checked ? 'background' : 'none';
|
||
const videoOptions = document.getElementById('video-options');
|
||
if (e.target.checked) {
|
||
videoOptions.style.display = 'block';
|
||
} else {
|
||
videoOptions.style.display = 'none';
|
||
}
|
||
});
|
||
document.getElementById('video-opacity').addEventListener('input', (e) => {
|
||
const value = parseFloat(e.target.value);
|
||
quickPlaySettings.videoOpacity = value;
|
||
document.getElementById('video-opacity-value').textContent = Math.round(value * 100) + '%';
|
||
// Save settings immediately when opacity changes
|
||
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
|
||
console.log('💾 Video opacity saved:', value);
|
||
});
|
||
|
||
// Flash Messages settings
|
||
document.getElementById('enable-flash-messages').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableFlashMessages = e.target.checked;
|
||
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
|
||
|
||
// Also update the actual config that the flash message system uses
|
||
const flashConfig = JSON.parse(localStorage.getItem('flashMessageConfig') || '{}');
|
||
flashConfig.enabled = e.target.checked;
|
||
localStorage.setItem('flashMessageConfig', JSON.stringify(flashConfig));
|
||
|
||
console.log('💾 Flash messages enabled:', e.target.checked);
|
||
});
|
||
|
||
// Popup Images settings
|
||
document.getElementById('enable-popup-images').addEventListener('change', (e) => {
|
||
quickPlaySettings.enablePopupImages = e.target.checked;
|
||
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
|
||
|
||
// Also update the actual config that the popup system uses
|
||
const popupConfig = JSON.parse(localStorage.getItem('popupImageConfig') || '{}');
|
||
popupConfig.enabled = e.target.checked;
|
||
localStorage.setItem('popupImageConfig', JSON.stringify(popupConfig));
|
||
|
||
console.log('💾 Popup images enabled:', e.target.checked);
|
||
});
|
||
|
||
// Webcam recording settings
|
||
document.getElementById('enable-session-recording').addEventListener('change', (e) => {
|
||
quickPlaySettings.enableSessionRecording = e.target.checked;
|
||
updateWebcamOptionsVisibility();
|
||
|
||
// Save settings immediately when recording preference changes
|
||
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
|
||
console.log('💾 Recording preference saved:', e.target.checked);
|
||
});
|
||
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);
|
||
|
||
// Save the complete settings to ensure directory persists
|
||
localStorage.setItem('quickPlaySettings', JSON.stringify(quickPlaySettings));
|
||
console.log('💾 Settings saved with new directory');
|
||
|
||
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;
|
||
});
|
||
|
||
// 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
|
||
// start-quick-play button removed - now using floating-start-btn only
|
||
document.getElementById('floating-start-btn').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);
|
||
|
||
// Collapsible sections
|
||
document.querySelectorAll('.collapsible-header').forEach(header => {
|
||
header.addEventListener('click', function() {
|
||
const targetId = this.getAttribute('data-target');
|
||
const content = document.getElementById(targetId);
|
||
|
||
if (content) {
|
||
const isVisible = content.style.display !== 'none';
|
||
content.style.display = isVisible ? 'none' : 'block';
|
||
this.classList.toggle('active', !isVisible);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Message management buttons
|
||
document.getElementById('manage-messages-btn').addEventListener('click', showMessageManagement);
|
||
// back-to-setup-from-messages-btn uses inline onclick handler
|
||
|
||
// Popup image management buttons
|
||
document.getElementById('manage-popup-images-btn').addEventListener('click', showPopupImageManagement);
|
||
// back-to-setup-from-popup-images-btn uses inline onclick handler
|
||
|
||
// 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');
|
||
|
||
// Track saved photo IDs to prevent duplicates
|
||
const savedPhotoIds = new Set();
|
||
|
||
// Intercept localStorage.setItem to save photos to file system
|
||
const originalSetItem = localStorage.setItem.bind(localStorage);
|
||
localStorage.setItem = function(key, value) {
|
||
// Call original first
|
||
originalSetItem(key, value);
|
||
|
||
// If it's a photo being saved, also save to file system
|
||
if ((key === 'capturedPhotos' || key === 'verificationPhotos') && window.desktopFileManager?.isElectron) {
|
||
try {
|
||
const photos = JSON.parse(value);
|
||
if (Array.isArray(photos) && photos.length > 0) {
|
||
const lastPhoto = photos[photos.length - 1];
|
||
if (lastPhoto.data) {
|
||
// Create unique ID based on timestamp and first 20 chars of data
|
||
const photoId = `${lastPhoto.timestamp}_${lastPhoto.data.substring(0, 20)}`;
|
||
|
||
// Only save if we haven't saved this photo yet
|
||
if (!savedPhotoIds.has(photoId)) {
|
||
savedPhotoIds.add(photoId);
|
||
|
||
// Save to file system asynchronously
|
||
window.desktopFileManager.savePhoto(lastPhoto.data, 'quick-play')
|
||
.then(photoData => {
|
||
if (photoData) {
|
||
console.log('📸 Intercepted and saved photo to file system:', photoData.filename);
|
||
}
|
||
})
|
||
.catch(err => console.error('📸 Failed to intercept save:', err));
|
||
}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
// Ignore parsing errors
|
||
}
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
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
|
||
};
|
||
|
||
// 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: 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 current video by 10 seconds">⏪</button>
|
||
<button id="video-play-pause" class="control-btn" title="Toggle play/pause for current video">⏸️</button>
|
||
<button id="video-forward" class="control-btn" title="Fast forward current video by 10 seconds">⏩</button>
|
||
<button id="video-skip" class="control-btn" title="Skip to next random video in playlist">⏭️</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;" title="Mark current task as completed and earn XP">Complete Task</button>
|
||
<button id="skip-task" class="btn btn-warning" style="display: none;" title="Skip current task - may trigger consequence task">Skip Task</button>
|
||
<button id="end-game" class="btn btn-danger" style="display: none;" title="End the Quick Play session and return to setup">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,
|
||
// 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();
|
||
|
||
// Start timer
|
||
startGameTimer(config.timeLimit);
|
||
|
||
// Load first task
|
||
loadNextTask();
|
||
}
|
||
|
||
function startGameTimer(timeLimit) {
|
||
// Clear any existing timer first
|
||
if (window.gameTimerInterval) {
|
||
clearInterval(window.gameTimerInterval);
|
||
window.gameTimerInterval = null;
|
||
}
|
||
|
||
// Handle endless mode
|
||
if (timeLimit === -1) {
|
||
console.log('🔄 Starting endless session - no time limit');
|
||
let timeElapsed = 0;
|
||
window.gameTimerInterval = setInterval(() => {
|
||
if (!isGameRunning || (gameInstance && gameInstance.gameState && gameInstance.gameState.isPaused)) {
|
||
return;
|
||
}
|
||
|
||
timeElapsed++;
|
||
updateGameStatus({ timer: formatTime(timeElapsed) + ' ∞' });
|
||
}, 1000);
|
||
return;
|
||
}
|
||
|
||
// Handle timed mode
|
||
let timeLeft = timeLimit;
|
||
window.gameTimerInterval = setInterval(() => {
|
||
if (!isGameRunning || (gameInstance && gameInstance.gameState && gameInstance.gameState.isPaused)) {
|
||
return;
|
||
}
|
||
|
||
timeLeft--;
|
||
updateGameStatus({ timer: formatTime(timeLeft) });
|
||
|
||
if (timeLeft <= 0) {
|
||
clearInterval(window.gameTimerInterval);
|
||
window.gameTimerInterval = null;
|
||
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)
|
||
);
|
||
});
|
||
}
|
||
|
||
// 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)
|
||
);
|
||
});
|
||
}
|
||
|
||
if (filteredTasks.length > 0) {
|
||
const randomTask = filteredTasks[Math.floor(Math.random() * filteredTasks.length)];
|
||
|
||
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);
|
||
|
||
// Clear inactivity timeout when completing task
|
||
if (inactivityTimeout) {
|
||
clearTimeout(inactivityTimeout);
|
||
inactivityTimeout = null;
|
||
}
|
||
|
||
// Reset pause state
|
||
isTimerPaused = false;
|
||
|
||
// 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
|
||
window.taskTimer = null;
|
||
let taskTimeRemaining = 0;
|
||
let taskCompleteAllowed = false;
|
||
let inactivityTimeout = null;
|
||
let isTimerPaused = false;
|
||
let pausedTimeRemaining = 0;
|
||
|
||
const timeoutMessages = [
|
||
"Hello? Did you fall asleep, slut? 🥱",
|
||
"Still there, or did you chicken out? 🐔",
|
||
"Getting bored already? Typical... 💅",
|
||
"Did you get distracted, or are you just slow? 🤔",
|
||
"Knock knock... anyone home? 🚪",
|
||
"Are you still playing or did you give up? 😏",
|
||
"Time to wake up, sleeping beauty! ⏰",
|
||
"Lost interest already? How disappointing... 😒",
|
||
"Did you forget what you were doing? 🤦",
|
||
"Still with us, or did reality call? 📱",
|
||
"Zoned out? That's not very obedient... 😈",
|
||
"Are we having fun yet? ...Anyone? 🙄",
|
||
"Did you wander off, or are you just thinking really hard? 🧠",
|
||
"Helloooo? Earth to slut! 🌍",
|
||
"Taking a break without permission? Naughty... 😤"
|
||
];
|
||
|
||
function startInactivityTimeout() {
|
||
// Clear any existing timeout
|
||
if (inactivityTimeout) {
|
||
clearTimeout(inactivityTimeout);
|
||
}
|
||
|
||
// Start 30-second timeout
|
||
inactivityTimeout = setTimeout(() => {
|
||
if (taskTimeRemaining <= 0 && !isTimerPaused) {
|
||
// Timer has hit 0 but task wasn't completed
|
||
pauseTimer();
|
||
showTimeoutDialog();
|
||
}
|
||
}, 30000); // 30 seconds
|
||
}
|
||
|
||
function pauseTimer() {
|
||
if (window.taskTimer && !isTimerPaused) {
|
||
clearInterval(window.taskTimer);
|
||
isTimerPaused = true;
|
||
pausedTimeRemaining = taskTimeRemaining;
|
||
console.log('⏸️ Timer paused due to inactivity');
|
||
}
|
||
}
|
||
|
||
function resumeTimer(timerElement, completeButton) {
|
||
if (isTimerPaused) {
|
||
isTimerPaused = false;
|
||
taskTimeRemaining = pausedTimeRemaining;
|
||
|
||
// Restart the timer interval
|
||
window.taskTimer = setInterval(() => {
|
||
try {
|
||
taskTimeRemaining--;
|
||
updateTimerDisplay(timerElement);
|
||
|
||
if (taskTimeRemaining <= 0) {
|
||
clearInterval(window.taskTimer);
|
||
window.taskTimer = null;
|
||
taskCompleteAllowed = true;
|
||
|
||
const currentCompleteBtn = document.getElementById('complete-task');
|
||
if (currentCompleteBtn) {
|
||
currentCompleteBtn.disabled = false;
|
||
currentCompleteBtn.textContent = 'Complete Task';
|
||
currentCompleteBtn.style.opacity = '1';
|
||
}
|
||
|
||
if (timerElement) {
|
||
timerElement.style.color = '#00ff00';
|
||
timerElement.textContent = 'COMPLETE!';
|
||
setTimeout(() => {
|
||
timerElement.style.color = '';
|
||
timerElement.textContent = '0:00';
|
||
}, 2000);
|
||
}
|
||
|
||
// Start inactivity timeout after timer completes
|
||
startInactivityTimeout();
|
||
}
|
||
} catch (error) {
|
||
console.error('🔧 Error in timer callback:', error);
|
||
}
|
||
}, 1000);
|
||
|
||
console.log('▶️ Timer resumed');
|
||
}
|
||
}
|
||
|
||
function showTimeoutDialog() {
|
||
const message = timeoutMessages[Math.floor(Math.random() * timeoutMessages.length)];
|
||
|
||
const dialog = document.createElement('div');
|
||
dialog.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.95);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 100000;
|
||
animation: fadeIn 0.3s ease;
|
||
`;
|
||
|
||
dialog.innerHTML = `
|
||
<style>
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
}
|
||
</style>
|
||
<div style="
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
padding: 40px 60px;
|
||
border-radius: 20px;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||
text-align: center;
|
||
max-width: 500px;
|
||
animation: pulse 2s ease-in-out infinite;
|
||
">
|
||
<div style="
|
||
font-size: 64px;
|
||
margin-bottom: 20px;
|
||
">⏰</div>
|
||
<h2 style="
|
||
color: white;
|
||
font-size: 28px;
|
||
margin-bottom: 20px;
|
||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||
">${message}</h2>
|
||
<p style="
|
||
color: rgba(255, 255, 255, 0.9);
|
||
font-size: 18px;
|
||
margin-bottom: 30px;
|
||
">You've been idle for 30 seconds.<br>Click below to continue your session.</p>
|
||
<button id="continue-session-btn" style="
|
||
background: white;
|
||
color: #667eea;
|
||
border: none;
|
||
padding: 15px 40px;
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||
transition: transform 0.2s ease;
|
||
" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
|
||
Yes, I'm Still Here! 👋
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(dialog);
|
||
|
||
// Add click handler
|
||
const continueBtn = dialog.querySelector('#continue-session-btn');
|
||
continueBtn.addEventListener('click', () => {
|
||
dialog.remove();
|
||
const timerElement = document.getElementById('task-timer');
|
||
const completeButton = document.getElementById('complete-task');
|
||
resumeTimer(timerElement, completeButton);
|
||
|
||
// Restart inactivity timeout
|
||
if (taskTimeRemaining <= 0) {
|
||
startInactivityTimeout();
|
||
}
|
||
});
|
||
}
|
||
|
||
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 (window.taskTimer) {
|
||
clearInterval(window.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 {
|
||
window.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(window.taskTimer);
|
||
window.taskTimer = null;
|
||
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');
|
||
|
||
// Start inactivity timeout - will trigger after 30 seconds if user doesn't complete task
|
||
startInactivityTimeout();
|
||
}
|
||
} catch (error) {
|
||
console.error('🔧 Error in timer callback:', error);
|
||
}
|
||
}, 1000);
|
||
|
||
console.log('🔧 Timer interval created with ID:', window.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) {
|
||
// Don't update if game is ending or not running
|
||
if (!isGameRunning || window.isEndingGame) return;
|
||
|
||
// 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 = 'var(--color-primary)'; // Themed color normally
|
||
}
|
||
|
||
// Update recording overlay when timer changes
|
||
syncTaskWithOverlay();
|
||
}
|
||
|
||
function stopTaskTimer() {
|
||
if (window.taskTimer) {
|
||
clearInterval(window.taskTimer);
|
||
window.taskTimer = null;
|
||
console.log('✅ Task timer stopped and cleared');
|
||
}
|
||
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) {
|
||
// Clear inactivity timeout when skipping
|
||
if (inactivityTimeout) {
|
||
clearTimeout(inactivityTimeout);
|
||
inactivityTimeout = null;
|
||
}
|
||
|
||
// Reset pause state
|
||
isTimerPaused = false;
|
||
|
||
// Load consequence or next task
|
||
loadNextTask();
|
||
}
|
||
|
||
function endGame() {
|
||
console.log('🛑 ========== END GAME CALLED ==========');
|
||
|
||
try {
|
||
// 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;
|
||
|
||
// Clear ALL timers and intervals immediately - MOST IMPORTANT
|
||
console.log('🛑 Clearing all timers and intervals...');
|
||
|
||
// Clear game timer interval
|
||
if (window.gameTimerInterval) {
|
||
console.log('🛑 Clearing gameTimerInterval ID:', window.gameTimerInterval);
|
||
clearInterval(window.gameTimerInterval);
|
||
window.gameTimerInterval = null;
|
||
}
|
||
|
||
// Clear XP display interval
|
||
if (window.xpDisplayInterval) {
|
||
console.log('🛑 Clearing xpDisplayInterval ID:', window.xpDisplayInterval);
|
||
clearInterval(window.xpDisplayInterval);
|
||
window.xpDisplayInterval = null;
|
||
}
|
||
|
||
// Clear recording timer interval
|
||
if (window.recordingTimerInterval) {
|
||
console.log('🛑 Clearing recordingTimerInterval ID:', window.recordingTimerInterval);
|
||
clearInterval(window.recordingTimerInterval);
|
||
window.recordingTimerInterval = null;
|
||
}
|
||
|
||
// Clear task timer - THIS IS CRITICAL
|
||
if (window.taskTimer) {
|
||
console.log('🛑 Clearing taskTimer ID:', window.taskTimer);
|
||
clearInterval(window.taskTimer);
|
||
window.taskTimer = null;
|
||
}
|
||
|
||
// Clear inactivity timeout
|
||
if (inactivityTimeout) {
|
||
console.log('🛑 Clearing inactivityTimeout');
|
||
clearTimeout(inactivityTimeout);
|
||
inactivityTimeout = null;
|
||
}
|
||
|
||
// Reset pause state
|
||
isTimerPaused = false;
|
||
taskTimeRemaining = 0;
|
||
|
||
// Clear any task-related timers from game instance
|
||
if (gameInstance) {
|
||
if (gameInstance.taskTimer) {
|
||
console.log('🛑 Clearing gameInstance.taskTimer');
|
||
clearInterval(gameInstance.taskTimer);
|
||
gameInstance.taskTimer = null;
|
||
}
|
||
if (gameInstance.gameTimer) {
|
||
console.log('🛑 Clearing gameInstance.gameTimer');
|
||
clearInterval(gameInstance.gameTimer);
|
||
gameInstance.gameTimer = null;
|
||
}
|
||
}
|
||
|
||
// IMMEDIATELY STOP ALL VIDEOS - MOST IMPORTANT FOR USER EXPERIENCE
|
||
console.log('🛑 Stopping all videos...');
|
||
const allVideos = document.querySelectorAll('video');
|
||
console.log(`🛑 Found ${allVideos.length} video elements`);
|
||
allVideos.forEach((video, index) => {
|
||
console.log(`🛑 Stopping video ${index + 1}:`, video.id || 'no-id', video.src?.substring(0, 50));
|
||
video.pause();
|
||
video.muted = true;
|
||
video.currentTime = 0;
|
||
video.src = '';
|
||
// Remove source elements too
|
||
const sources = video.querySelectorAll('source');
|
||
sources.forEach(source => source.src = '');
|
||
});
|
||
|
||
// Stop flash message system
|
||
stopFlashMessageSystem();
|
||
|
||
// Stop all task loading and selection
|
||
currentTask = null;
|
||
|
||
// Stop the task timer (redundant but safe)
|
||
stopTaskTimer();
|
||
|
||
// Clear any pending task loads
|
||
if (window.nextTaskTimeout) {
|
||
clearTimeout(window.nextTaskTimeout);
|
||
window.nextTaskTimeout = null;
|
||
}
|
||
|
||
// Clean up game instance
|
||
if (gameInstance) {
|
||
try {
|
||
// Stop any task presentation
|
||
if (gameInstance.currentTask) {
|
||
gameInstance.currentTask = null;
|
||
}
|
||
|
||
if (gameInstance.pauseGame) {
|
||
gameInstance.pauseGame();
|
||
}
|
||
if (gameInstance.cleanup) {
|
||
gameInstance.cleanup();
|
||
}
|
||
if (gameInstance.audioManager) {
|
||
gameInstance.audioManager.stopAll();
|
||
}
|
||
} catch (cleanupError) {
|
||
console.error('❌ Error during game cleanup:', cleanupError);
|
||
}
|
||
}
|
||
|
||
// Clean up background video (check if it exists first)
|
||
if (typeof backgroundVideoPlayer !== 'undefined' && backgroundVideoPlayer) {
|
||
try {
|
||
backgroundVideoPlayer.pause();
|
||
if (backgroundVideoPlayer.destroy) {
|
||
backgroundVideoPlayer.destroy();
|
||
}
|
||
backgroundVideoPlayer = null;
|
||
} catch (videoError) {
|
||
console.error('❌ Error stopping background video via backgroundVideoPlayer:', videoError);
|
||
}
|
||
}
|
||
|
||
// Quick Play specific background video cleanup (already done above but being thorough)
|
||
const backgroundVideo = document.getElementById('background-video');
|
||
if (backgroundVideo) {
|
||
try {
|
||
backgroundVideo.pause();
|
||
backgroundVideo.currentTime = 0;
|
||
backgroundVideo.src = '';
|
||
backgroundVideo.load(); // Force reload to clear
|
||
const source = backgroundVideo.querySelector('source');
|
||
if (source) {
|
||
source.src = '';
|
||
}
|
||
} catch (bgVideoError) {
|
||
console.error('❌ Error stopping Quick Play background video:', bgVideoError);
|
||
}
|
||
}
|
||
|
||
// Clean up video player manager
|
||
if (window.videoPlayerManager) {
|
||
try {
|
||
window.videoPlayerManager.stopAllVideos();
|
||
} catch (videoManagerError) {
|
||
console.error('❌ Error stopping videos via VideoPlayerManager:', videoManagerError);
|
||
}
|
||
}
|
||
|
||
console.log('✅ All timers cleared and videos stopped');
|
||
|
||
// 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 file system first (Electron)
|
||
if (window.desktopFileManager && window.desktopFileManager.isElectron) {
|
||
try {
|
||
const videoData = await window.desktopFileManager.saveVideo(blob, 'quick-play', extension);
|
||
if (videoData) {
|
||
console.log('✅ Session recording saved to file system:', videoData.filename);
|
||
|
||
if (window.flashMessageManager) {
|
||
window.flashMessageManager.show(`📹 Recording saved: ${videoData.filename}`, 'positive');
|
||
}
|
||
|
||
recordedChunks = [];
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ File system save failed, trying directory handle:', error);
|
||
}
|
||
}
|
||
|
||
// Try to save to user-selected directory (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
|
||
|
||
// Ctrl+Shift+F to focus main window
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'F') {
|
||
e.preventDefault();
|
||
if (window.electronAPI && window.electronAPI.focusMainWindow) {
|
||
window.electronAPI.focusMainWindow().then(result => {
|
||
if (result.success) {
|
||
console.log('🎯 Main window focused via keyboard shortcut');
|
||
}
|
||
});
|
||
}
|
||
return;
|
||
}
|
||
|
||
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: var(--color-warning); 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: var(--color-primary); 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: var(--color-warning); 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: var(--color-primary); 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
|
||
// Note: enabled state is controlled by the main Quick Play screen checkbox
|
||
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: var(--color-primary);">${totalMessages}</strong></span>
|
||
<span>Enabled: <strong style="color: var(--color-success);">${enabledMessages}</strong></span>
|
||
<span>Disabled: <strong style="color: var(--color-warning);">${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
|
||
// Note: enabled state is controlled by the main Quick Play screen checkbox
|
||
const flashSettings = {
|
||
enabled: quickPlaySettings.enableFlashMessages, // Use main screen setting
|
||
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)
|
||
// Note: enabled state is controlled by the main Quick Play screen checkbox
|
||
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() {
|
||
// Note: enabled state is controlled by the main Quick Play screen checkbox
|
||
const settings = {
|
||
enabled: quickPlaySettings.enablePopupImages, // Use main screen setting
|
||
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: var(--color-background-gradient);
|
||
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: var(--color-accent-gold);
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
.quick-play-header {
|
||
background: var(--header-bg);
|
||
border-bottom: 1px solid var(--header-border);
|
||
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: var(--header-title-color);
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 20px;
|
||
margin: 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.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: var(--btn-secondary-bg);
|
||
color: var(--btn-secondary-text);
|
||
border: 1px solid var(--btn-secondary-border);
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: var(--btn-secondary-hover-bg);
|
||
border-color: var(--btn-secondary-hover-border);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: var(--btn-danger-bg);
|
||
color: var(--btn-danger-text);
|
||
border: 1px solid var(--btn-danger-border);
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: var(--btn-danger-hover-bg);
|
||
border-color: var(--btn-danger-hover-border);
|
||
}
|
||
|
||
.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: var(--color-primary);
|
||
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: var(--bg-overlay-medium);
|
||
border: 2px solid var(--color-primary);
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.setup-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.setup-header h2 {
|
||
color: var(--color-primary);
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 28px;
|
||
margin: 0 0 10px 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.setup-header p {
|
||
color: #aaa;
|
||
font-size: 16px;
|
||
margin: 0;
|
||
}
|
||
|
||
/* Setup Instructions Styles */
|
||
.setup-instructions {
|
||
margin-top: 25px;
|
||
}
|
||
|
||
.instruction-box {
|
||
background: linear-gradient(135deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4)), var(--bg-primary-overlay-20);
|
||
border: 1px solid var(--border-primary);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
text-align: left;
|
||
}
|
||
|
||
.instruction-box h4 {
|
||
color: var(--color-primary);
|
||
font-family: 'Electrolize', sans-serif;
|
||
font-size: 16px;
|
||
margin: 0 0 15px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.instruction-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
.instruction-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border-primary);
|
||
}
|
||
|
||
.instruction-icon {
|
||
font-size: 20px;
|
||
min-width: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.instruction-text {
|
||
color: #ccc;
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.instruction-text strong {
|
||
color: var(--color-primary);
|
||
font-weight: bold;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.instruction-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.instruction-item {
|
||
padding: 10px;
|
||
}
|
||
|
||
.instruction-text {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
/* Floating Start Button */
|
||
.floating-start-button {
|
||
position: fixed;
|
||
bottom: 30px;
|
||
right: 30px;
|
||
z-index: 1000;
|
||
padding: 18px 35px;
|
||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||
border: none;
|
||
border-radius: 50px;
|
||
color: #fff;
|
||
font-family: 'Electrolize', sans-serif;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
transition: all 0.3s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
animation: pulse-glow 2s ease-in-out infinite;
|
||
}
|
||
|
||
.floating-start-button:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 12px 35px var(--bg-primary-overlay-20);
|
||
background: linear-gradient(135deg, var(--color-primary-light) 0%, var(--color-primary) 100%);
|
||
}
|
||
|
||
.floating-start-button:active {
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.floating-start-button .btn-icon {
|
||
font-size: 24px;
|
||
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
|
||
}
|
||
|
||
.floating-start-button .btn-text {
|
||
font-size: 18px;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
@keyframes pulse-glow {
|
||
0%, 100% {
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
50% {
|
||
box-shadow: 0 8px 35px var(--bg-primary-overlay-20);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.floating-start-button {
|
||
bottom: 20px;
|
||
right: 20px;
|
||
padding: 15px 28px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.floating-start-button .btn-icon {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.floating-start-button .btn-text {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
/* Collapsible Section Styles */
|
||
.collapsible-section {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.collapsible-header {
|
||
width: 100%;
|
||
background: var(--bg-primary-overlay-10);
|
||
border: 1px solid var(--border-primary);
|
||
border-radius: 8px;
|
||
padding: 12px 15px;
|
||
color: var(--color-primary);
|
||
font-family: 'Electrolize', sans-serif;
|
||
font-size: 14px;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.collapsible-header:hover {
|
||
background: var(--color-primary-transparent);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.collapse-icon {
|
||
font-size: 12px;
|
||
transition: transform 0.3s ease;
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
.collapsible-header.active .collapse-icon {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.collapsible-content {
|
||
margin-top: 15px;
|
||
padding: 15px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border: 1px solid var(--color-primary-border);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.setting-group {
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
background: var(--color-primary-transparent);
|
||
border: 1px solid var(--color-primary-border);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.setting-group h3 {
|
||
color: var(--color-primary);
|
||
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 var(--color-primary-border);
|
||
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: var(--color-primary);
|
||
background: var(--color-primary-transparent);
|
||
}
|
||
|
||
.time-preset.active, .difficulty-preset.active {
|
||
border-color: var(--color-primary);
|
||
background: var(--bg-primary-overlay-20);
|
||
box-shadow: var(--shadow-glow-primary);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.preset-value, .difficulty-name {
|
||
color: var(--color-primary);
|
||
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 var(--color-primary-border);
|
||
color: var(--color-primary);
|
||
padding: 5px 10px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
/* Tag Selector Styles */
|
||
.tag-selector {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.tag-category {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.tag-category h4 {
|
||
color: var(--color-primary);
|
||
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 var(--color-primary-border);
|
||
border-radius: 5px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.tag-option:hover {
|
||
background: var(--color-primary-transparent);
|
||
border-color: var(--color-primary-hover);
|
||
}
|
||
|
||
.tag-option input[type="checkbox"] {
|
||
accent-color: var(--color-primary);
|
||
margin: 0;
|
||
}
|
||
|
||
.tag-option .tag-name {
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tag-option input[type="checkbox"]:checked + .tag-name {
|
||
color: var(--color-primary);
|
||
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 var(--color-primary-border);
|
||
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: var(--color-primary);
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.task-timer {
|
||
color: var(--color-primary);
|
||
font-weight: bold;
|
||
font-size: 16px;
|
||
background: var(--color-primary-transparent);
|
||
padding: 2px 8px;
|
||
border-radius: 3px;
|
||
border: 1px solid var(--color-primary-border);
|
||
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 var(--color-primary-border);
|
||
}
|
||
|
||
.slider::-webkit-slider-thumb {
|
||
appearance: none;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: var(--color-primary);
|
||
cursor: pointer;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.slider::-moz-range-thumb {
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: var(--color-primary);
|
||
cursor: pointer;
|
||
border: none;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.slider-value {
|
||
min-width: 45px;
|
||
color: var(--color-primary);
|
||
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 var(--color-primary);
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.results-header h2 {
|
||
color: var(--color-primary);
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 32px;
|
||
margin: 0 0 10px 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.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: var(--color-primary);
|
||
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: var(--color-primary);
|
||
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: var(--color-secondary);
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.volume-slider::-moz-range-thumb {
|
||
width: 16px;
|
||
height: 16px;
|
||
background: var(--color-secondary);
|
||
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: var(--color-secondary);
|
||
}
|
||
|
||
.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: var(--color-secondary);
|
||
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 var(--color-primary-border);
|
||
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: var(--color-primary);
|
||
font-family: 'Audiowide', sans-serif;
|
||
font-size: 22px;
|
||
margin: 0 0 8px 0;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
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: var(--color-primary);
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
.background-video-controls .volume-slider::-moz-range-thumb {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--color-primary);
|
||
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 var(--color-primary-border) !important;
|
||
}
|
||
|
||
.control-sidebar {
|
||
background: rgba(0, 0, 0, 0.8) !important;
|
||
backdrop-filter: blur(5px);
|
||
border: 1px solid var(--color-primary-border) !important;
|
||
}
|
||
|
||
/* Video mode selection styles */
|
||
.video-settings {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.video-options {
|
||
margin-left: 20px;
|
||
padding: 15px;
|
||
background: var(--color-primary-transparent);
|
||
border: 1px solid var(--color-primary-border);
|
||
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 var(--color-primary);
|
||
border-radius: 15px;
|
||
padding: 30px;
|
||
}
|
||
|
||
.task-management-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.task-management-header h2 {
|
||
color: var(--color-primary);
|
||
font-family: 'Audiowide', sans-serif;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.task-type-tabs {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-bottom: 30px;
|
||
border-bottom: 2px solid var(--color-primary-border);
|
||
padding-bottom: 15px;
|
||
}
|
||
|
||
.task-tab-btn {
|
||
padding: 12px 20px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 2px solid var(--color-primary-border);
|
||
border-radius: 8px;
|
||
color: #aaa;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.task-tab-btn.active {
|
||
background: var(--color-primary-transparent);
|
||
border-color: var(--color-primary);
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
.task-tab-btn:hover {
|
||
background: var(--color-primary-transparent);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.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: var(--color-primary);
|
||
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: var(--color-primary-transparent);
|
||
border: 1px solid var(--color-primary-hover);
|
||
border-radius: 12px;
|
||
color: var(--color-primary);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.tag-suggestion:hover {
|
||
background: var(--color-primary-transparent);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.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: var(--color-primary);
|
||
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: var(--color-primary-transparent);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.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: var(--color-primary-transparent);
|
||
border-radius: 10px;
|
||
color: var(--color-primary);
|
||
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: var(--color-primary);
|
||
}
|
||
|
||
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 var(--color-primary-border);
|
||
border-radius: 8px;
|
||
color: var(--color-primary);
|
||
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: var(--color-primary);
|
||
border-bottom: 1px solid var(--color-primary-transparent);
|
||
}
|
||
|
||
.tag-dropdown option:checked {
|
||
background: var(--color-primary-transparent);
|
||
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 var(--color-primary-border);
|
||
border-radius: 8px;
|
||
color: var(--color-primary);
|
||
padding: 12px;
|
||
font-family: 'Orbitron', monospace;
|
||
font-size: 14px;
|
||
outline: none;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
#new-tag-input:focus {
|
||
border-color: var(--color-primary-hover);
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
#new-tag-input::placeholder {
|
||
color: var(--color-primary-transparent);
|
||
}
|
||
|
||
.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: var(--color-primary-transparent);
|
||
color: var(--color-primary);
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
border: 1px solid var(--color-primary-border);
|
||
}
|
||
|
||
.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 var(--color-primary-hover);
|
||
border-radius: 12px;
|
||
min-width: 400px;
|
||
max-width: 500px;
|
||
box-shadow: var(--shadow-glow-primary);
|
||
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: var(--color-primary);
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
text-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.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: var(--color-background-gradient);
|
||
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 var(--color-primary);
|
||
box-shadow: var(--shadow-glow-primary);
|
||
}
|
||
|
||
.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: var(--color-primary-transparent);
|
||
color: var(--color-primary);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.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: var(--color-primary);
|
||
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: var(--color-primary);
|
||
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: var(--color-primary-transparent);
|
||
color: var(--color-primary);
|
||
border: 1px solid var(--color-primary);
|
||
padding: 8px 16px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.message-btn:hover {
|
||
background: var(--color-primary-transparent);
|
||
}
|
||
|
||
.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: var(--color-primary-transparent);
|
||
border-color: var(--color-primary-border);
|
||
}
|
||
|
||
.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: var(--color-primary-transparent);
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
.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: var(--color-primary-transparent);
|
||
border-color: var(--color-primary);
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* Hide webcam task overlay from preview (it will still be rendered on recording canvas) */
|
||
.webcam-viewer.recording .webcam-task-overlay {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Enhanced Gallery, Cinema, and Photo Button Styles */
|
||
.btn-gallery, .btn-cinema, .btn-photo {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-decoration: none;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.btn-gallery {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-cinema {
|
||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-photo {
|
||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-gallery:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
|
||
}
|
||
|
||
.btn-cinema:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.4);
|
||
background: linear-gradient(135deg, #e081e9 0%, #e3455a 100%);
|
||
}
|
||
|
||
.btn-photo:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(52, 73, 94, 0.4);
|
||
background: linear-gradient(135deg, #1a252f 0%, #2c3e50 100%);
|
||
}
|
||
|
||
.btn-gallery:active, .btn-cinema:active, .btn-photo:active {
|
||
transform: translateY(0);
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.btn-gallery:disabled, .btn-cinema:disabled, .btn-photo:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
.btn-icon {
|
||
font-size: 16px;
|
||
margin-right: 6px;
|
||
display: inline-block;
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
/* Animation for button feedback */
|
||
.btn-gallery.success, .btn-cinema.success, .btn-photo.success {
|
||
animation: successPulse 0.6s ease-out;
|
||
}
|
||
|
||
@keyframes successPulse {
|
||
0% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
100% { transform: scale(1); }
|
||
}
|
||
|
||
/* Responsive adjustments */
|
||
@media (max-width: 768px) {
|
||
.btn-gallery, .btn-cinema, .btn-photo {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.btn-icon {
|
||
font-size: 14px;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
|
||
</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> |