training-academy/quick-play.html

11163 lines
477 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: file: blob: http://localhost:* https:; connect-src 'self' http://localhost:* https: ws://localhost:*; img-src 'self' data: file: blob:; media-src 'self' data: file: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;">
<title>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>