REMOVED: Complete AI task generation system program-wide

DELETED FILES:
- src/features/tasks/aiTaskManager.js (entire AI task generation system)

 UPDATED FILES:
- index.html: Removed AI Tasks tab from annoyance management, simplified to Import/Export only
- src/core/game.js: Removed aiTaskManager initialization, event handlers, and all AI task methods
- training-academy.html: Removed aiTaskManager.js script import
- src/README.md: Removed aiTaskManager.js documentation
- docs/ANNOYANCE_SETTINGS_DOCUMENTATION.md: Removed AI Tasks section, updated description
- Start-webgame.bat: Removed Ollama service startup commands
- src/styles/styles.css: Removed all AI Tasks tab styling (150+ lines)

 VERIFICATION:
- No remaining references to aiTaskManager, AITaskManager, ollama, or Ollama in JavaScript files
- Annoyance management now only contains Import/Export functionality
- All AI task event handlers and UI elements removed
- Documentation updated to reflect removal
- Startup script no longer attempts to start Ollama service

 RESULT: Clean codebase with AI task generation completely removed as requested
This commit is contained in:
dilgenfritz 2025-11-08 18:13:32 -06:00
parent c9c33df6fc
commit 1c129de6f6
14 changed files with 9635 additions and 1505 deletions

View File

@ -1,11 +1,4 @@
@echo off
REM --- Start Ollama Service ---
echo Starting Ollama AI service...
start /min ollama serve
REM --- Wait a few seconds for Ollama to initialize ---
timeout /t 3 /nobreak >nul
REM --- Start the Gooner Training Academy webGame ---
echo Launching Gooner Training Academy...
REM If you use npm:

View File

@ -0,0 +1,183 @@
# Annoyance Management System - Complete Settings Documentation
## Overview
The "Annoyance Management" system (currently named, planned to be renamed to "Settings") is a configuration interface for importing and exporting message data.
## Tab Structure
### 1. 💬 Messages Tab
**Purpose**: Manage motivational flash messages that appear during gameplay
#### Message Management
- **Enable Flash Messages** (checkbox) - Master toggle for all flash messages
- **Add Message Button** - Create new custom messages
#### Message Editor
- **Message Text** (textarea, 200 char limit) - The actual message content
- **Category** (dropdown):
- 💪 Motivational
- 🌟 Encouraging
- 🏆 Achievement
- 🔥 Persistence
- ✨ Custom
- **Priority** (dropdown):
- Normal
- High
- Low
#### Message List Controls
- **Category Filter** (dropdown) - Filter messages by category (All, Motivational, Encouraging, Achievement, Persistence, Custom)
- **Show Disabled Messages** (checkbox) - Include disabled messages in list
- **Message Stats Display** - Shows count of total/enabled/disabled messages
---
### 2. 🎨 Appearance Tab
**Purpose**: Customize visual appearance of flash messages
#### Position & Animation
- **Position** (dropdown):
- Center
- Top Center
- Bottom Center
- Top Left
- Top Right
- Bottom Left
- Bottom Right
- Center Left
- Center Right
- **Animation** (dropdown):
- Fade
- Slide
- Bounce
- Pulse
#### Visual Styling
- **Font Size** (slider: 16px-48px, default: 24px)
- **Opacity** (slider: 50%-100%, default: 90%)
- **Text Color** (color picker, default: #ffffff)
- **Background Color** (color picker, default: #007bff)
#### Controls
- **Reset to Defaults Button** - Restore default appearance settings
- **Preview Style Button** - Test current appearance settings
---
### 3. ⚡ Behavior Tab
**Purpose**: Configure timing and behavior of flash messages
#### Core Behavior Settings
- **Focus Interruption Chance** (slider: 0%-50%, default: 0%)
- Chance for focus-hold interruptions during scenario adventures
- 0% = disabled, max 50%
- **Display Duration** (slider: 1.0s-10.0s, default: 3.0s)
- **Interval Between Messages** (slider: 10s-300s, default: 45s)
- **Random Variation** (slider: 0s-30s, default: ±5s)
- Adds random time variation to prevent predictability
#### Advanced Options
- **Enable Event-Based Messages** (checkbox, default: checked)
- Show special messages for task completion, streaks, etc.
- **Pause Timer on Message Hover** (checkbox, default: unchecked)
- Pause message fade when hovering (useful for reading)
#### Testing
- **Test Current Settings Button** - Preview behavior with current settings
---
### 4. 🖼️ Popup Images Tab
**Purpose**: Configure punishment popups that appear when tasks are skipped
#### Master Control
- **Enable Punishment Popups** (toggle switch)
#### Image Count Settings
- **Count Mode** (dropdown):
- Fixed Amount
- Random (1-10)
- Custom Range
- **Number of Images** (slider: 1-40, default: 3) - for Fixed mode
- **Custom Range** (number inputs) - Min: 1-20, Max: 2-40
#### Display Duration Settings
- **Duration Mode** (dropdown):
- Fixed Duration
- Random (5-15s)
- Custom Range
- **Duration** (slider: 3s-30s, default: 8s) - for Fixed mode
- **Custom Range** (number inputs) - Min: 2-20s, Max: 5-60s
#### Positioning & Layout
- **Layout Style** (dropdown):
- Random Positions
- Cascading
- Grid Layout
- Center (stacked)
- **Allow Overlapping** (toggle switch)
#### Size Settings
- **Max Viewport Width** (slider: 20%-60%, default: 35%)
- **Max Viewport Height** (slider: 20%-60%, default: 40%)
- **Min Width** (number input: 150-400px, default: 200px)
- **Max Width** (number input: 300-800px, default: 500px)
- **Min Height** (number input: 100-300px, default: 150px)
- **Max Height** (number input: 200-600px, default: 400px)
#### Visual Effects
- **Fade In/Out Animation** (toggle switch)
- **Blur Background** (toggle switch)
- **Show Countdown Timer** (toggle switch)
- **Prevent Manual Close** (toggle switch)
#### Testing & Status
- **Test 1 Popup Button** - Test single popup
- **Test Multiple Button** - Test multiple popups
- **Clear All Button** - Remove all active popups
- **Available Images Count** - Display count of available images
- **Active Popups Count** - Display count of currently active popups
---
### 5. 📁 Import/Export
**Purpose**: Backup, share, and manage message configurations
#### Export Options
- **Export All Messages Button** - Export complete message set
- **Export Enabled Only Button** - Export only active messages
- **Export Custom Only Button** - Export only user-created messages
#### Import Options
- **Import Messages Button** - Load messages from file
- **File Input** - JSON file selection
- **Import Mode** (radio buttons):
- Merge with existing (default)
- Replace all messages
#### Reset Options
- **Reset to Default Messages Button** - Restore original message set
- **Clear All Messages Button** - Remove all messages (⚠️ Warning: Cannot be undone)
---
## Global Controls
- **Back to Start Button** - Return to main menu
- **Save All Settings Button** - Apply and save all configuration changes
---
## Technical Notes
- All settings are persisted to localStorage
- Flash messages use the FlashMessageManager class
- Popup images use the PopupImageManager class
- Import/Export uses JSON format for data portability
- Settings are organized in a tabbed interface for better UX
## Recommended Improvements
1. **Rename "Annoyance" to "Settings"** - More user-friendly terminology
2. **Add tooltips** - Better explain complex settings
3. **Preview functionality** - Real-time preview for all visual settings
4. **Preset configurations** - Quick setup options for different use cases
5. **Advanced scheduling** - Time-based message scheduling

View File

@ -802,561 +802,12 @@
<!-- Annoyance Management Screen -->
<div id="annoyance-management-screen" class="screen">
<h2>😈 Annoyance Management</h2>
<p>Configure flash messages and motivational features to enhance your experience!</p>
<!-- Tab Navigation -->
<div class="annoyance-tabs">
<button id="messages-tab" class="annoyance-tab active">💬 Messages</button>
<button id="appearance-tab" class="annoyance-tab">🎨 Appearance</button>
<button id="behavior-tab" class="annoyance-tab">⚡ Behavior</button>
<button id="popup-images-tab" class="annoyance-tab">🖼️ Popup Images</button>
<button id="ai-tasks-tab" class="annoyance-tab">🤖 AI Tasks</button>
<button id="import-export-tab" class="annoyance-tab">📁 Import/Export</button>
</div>
<p>Manage message import/export and data settings.</p>
<p><strong>Note:</strong> Flash messages and popup images have been moved to Quick Play settings.</p>
<!-- Messages Tab -->
<div id="messages-tab-content" class="annoyance-tab-content active">
<div class="annoyance-section">
<div class="section-header">
<h3>💬 Message Management</h3>
<div class="header-controls">
<label>
<input type="checkbox" id="flash-messages-enabled" checked>
Enable Flash Messages
</label>
<button id="add-new-message-btn" class="btn btn-success btn-small">+ Add Message</button>
</div>
</div>
<!-- Message Editor -->
<div id="message-editor" class="message-editor" style="display: none;">
<div class="editor-header">
<h4 id="editor-title">Add New Message</h4>
<button id="close-editor-btn" class="btn btn-outline btn-small">✕ Close</button>
</div>
<div class="editor-form">
<div class="form-group">
<label>Message Text:</label>
<textarea id="message-text" placeholder="Enter your motivational message..." maxlength="200" rows="3"></textarea>
<div class="char-counter">
<span id="char-count">0</span>/200 characters
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Category:</label>
<select id="message-category">
<option value="motivational">💪 Motivational</option>
<option value="encouraging">🌟 Encouraging</option>
<option value="achievement">🏆 Achievement</option>
<option value="persistence">🔥 Persistence</option>
<option value="custom">✨ Custom</option>
</select>
</div>
<div class="form-group">
<label>Priority:</label>
<select id="message-priority">
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="low">Low</option>
</select>
</div>
</div>
<div class="editor-actions">
<button id="save-message-btn" class="btn btn-primary">Save Message</button>
<button id="preview-current-message-btn" class="btn btn-info">Preview</button>
<button id="cancel-edit-btn" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
<!-- Message List -->
<div class="message-list-section">
<div class="list-header">
<div class="list-filters">
<label>Filter by Category:
<select id="category-filter">
<option value="all">All Categories</option>
<option value="motivational">💪 Motivational</option>
<option value="encouraging">🌟 Encouraging</option>
<option value="achievement">🏆 Achievement</option>
<option value="persistence">🔥 Persistence</option>
<option value="custom">✨ Custom</option>
</select>
</label>
<label>
<input type="checkbox" id="show-disabled-messages"> Show Disabled
</label>
</div>
<div class="list-stats">
<span id="message-stats">20 messages (18 enabled, 2 disabled)</span>
</div>
</div>
<div id="message-list" class="message-list">
<!-- Messages will be populated here -->
</div>
</div>
</div>
</div>
<!-- Appearance Tab -->
<div id="appearance-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>🎨 Visual Appearance</h3>
<div class="appearance-controls">
<div class="control-row">
<div class="control-group">
<label>Position:</label>
<select id="message-position">
<option value="center">Center</option>
<option value="top-center">Top Center</option>
<option value="bottom-center">Bottom Center</option>
<option value="top-left">Top Left</option>
<option value="top-right">Top Right</option>
<option value="bottom-left">Bottom Left</option>
<option value="bottom-right">Bottom Right</option>
<option value="center-left">Center Left</option>
<option value="center-right">Center Right</option>
</select>
</div>
<div class="control-group">
<label>Animation:</label>
<select id="animation-style">
<option value="fade">Fade</option>
<option value="slide">Slide</option>
<option value="bounce">Bounce</option>
<option value="pulse">Pulse</option>
</select>
</div>
</div>
<div class="control-row">
<div class="control-group">
<label>Font Size: <span id="font-size-display">24px</span></label>
<input type="range" id="font-size" min="16" max="48" value="24" step="2">
</div>
<div class="control-group">
<label>Opacity: <span id="opacity-display">90%</span></label>
<input type="range" id="message-opacity" min="50" max="100" value="90" step="5">
</div>
</div>
<div class="control-row">
<div class="control-group">
<label>Text Color:</label>
<input type="color" id="text-color" value="#ffffff">
</div>
<div class="control-group">
<label>Background Color:</label>
<input type="color" id="background-color" value="#007bff">
</div>
</div>
<div class="control-group">
<button id="reset-appearance-btn" class="btn btn-outline">Reset to Defaults</button>
<button id="preview-appearance-btn" class="btn btn-info">Preview Style</button>
</div>
</div>
</div>
</div>
<!-- Behavior Tab -->
<div id="behavior-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>⚡ Behavior Settings</h3>
<div class="behavior-controls">
<div class="control-group">
<label>🧘 Focus Interruption Chance: <span id="focus-interruption-display">0%</span></label>
<input type="range" id="focus-interruption-chance" min="0" max="50" value="0" step="5">
<small class="help-text">Chance for focus-hold interruptions during scenario adventures (0% = disabled, max 50%)</small>
</div>
<div class="control-group">
<label>Display Duration: <span id="duration-display">3.0s</span></label>
<input type="range" id="display-duration" min="1000" max="10000" value="3000" step="500">
</div>
<div class="control-group">
<label>Interval Between Messages: <span id="interval-display">45s</span></label>
<input type="range" id="interval-delay" min="10000" max="300000" value="45000" step="5000">
</div>
<div class="control-group">
<label>Random Variation: <span id="variation-display">±5s</span></label>
<input type="range" id="time-variation" min="0" max="30000" value="5000" step="1000">
<small class="help-text">Adds random time variation to prevent predictability</small>
</div>
<div class="control-row">
<div class="control-group">
<label>
<input type="checkbox" id="event-based-messages" checked>
Enable Event-Based Messages
</label>
<small class="help-text">Show special messages for task completion, streaks, etc.</small>
</div>
<div class="control-group">
<label>
<input type="checkbox" id="pause-on-hover">
Pause Timer on Message Hover
</label>
<small class="help-text">Pause message fade when hovering (useful for reading)</small>
</div>
</div>
<div class="control-group">
<button id="test-behavior-btn" class="btn btn-success">Test Current Settings</button>
</div>
</div>
</div>
</div>
<!-- Popup Images Tab -->
<div id="popup-images-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>🖼️ Punishment Popups</h3>
<p class="help-text">Configure consequence images that appear when tasks are skipped</p>
<!-- Enable/Disable -->
<div class="control-section">
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-images-enabled" />
<span class="switch"></span>
Enable Punishment Popups
</label>
</div>
</div>
<!-- Image Count Settings -->
<div class="control-section">
<h4>📊 Number of Images</h4>
<div class="control-group">
<label for="popup-count-mode">Count Mode:</label>
<select id="popup-count-mode">
<option value="fixed">Fixed Amount</option>
<option value="random">Random (1-10)</option>
<option value="range">Custom Range</option>
</select>
</div>
<div id="popup-fixed-count" class="control-group">
<label for="popup-image-count">Number of Images:</label>
<input type="range" id="popup-image-count" min="1" max="40" value="3" />
<span id="popup-image-count-value">3</span>
</div>
<div id="popup-range-count" class="control-group" style="display: none;">
<div class="range-inputs">
<div>
<label for="popup-min-count">Minimum:</label>
<input type="number" id="popup-min-count" min="1" max="20" value="2" />
</div>
<div>
<label for="popup-max-count">Maximum:</label>
<input type="number" id="popup-max-count" min="2" max="40" value="5" />
</div>
</div>
</div>
</div>
<!-- Display Duration Settings -->
<div class="control-section">
<h4>⏱️ Display Duration</h4>
<div class="control-group">
<label for="popup-duration-mode">Duration Mode:</label>
<select id="popup-duration-mode">
<option value="fixed">Fixed Duration</option>
<option value="random">Random (5-15s)</option>
<option value="range">Custom Range</option>
</select>
</div>
<div id="popup-fixed-duration" class="control-group">
<label for="popup-display-duration">Duration (seconds):</label>
<input type="range" id="popup-display-duration" min="3" max="30" value="8" />
<span id="popup-display-duration-value">8s</span>
</div>
<div id="popup-range-duration" class="control-group" style="display: none;">
<div class="range-inputs">
<div>
<label for="popup-min-duration">Min (seconds):</label>
<input type="number" id="popup-min-duration" min="2" max="20" value="5" />
</div>
<div>
<label for="popup-max-duration">Max (seconds):</label>
<input type="number" id="popup-max-duration" min="5" max="60" value="15" />
</div>
</div>
</div>
</div>
<!-- Positioning & Appearance -->
<div class="control-section">
<h4>🎯 Positioning</h4>
<div class="control-group">
<label for="popup-positioning">Layout Style:</label>
<select id="popup-positioning">
<option value="random">Random Positions</option>
<option value="cascade">Cascading</option>
<option value="grid">Grid Layout</option>
<option value="center">Centered (stacked)</option>
</select>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-allow-overlap" />
<span class="switch"></span>
Allow Overlapping
</label>
</div>
</div>
<!-- Size Settings -->
<div class="control-section">
<h4>📏 Size Settings</h4>
<p class="help-text">Popups automatically size to match image proportions within these limits</p>
<div class="control-group">
<label for="popup-viewport-width">Max Viewport Width:</label>
<input type="range" id="popup-viewport-width" min="20" max="60" value="35" />
<span id="popup-viewport-width-value">35%</span>
</div>
<div class="control-group">
<label for="popup-viewport-height">Max Viewport Height:</label>
<input type="range" id="popup-viewport-height" min="20" max="60" value="40" />
<span id="popup-viewport-height-value">40%</span>
</div>
<div class="control-group">
<div class="range-inputs">
<div>
<label for="popup-min-width">Min Width (px):</label>
<input type="number" id="popup-min-width" min="150" max="400" value="200" />
</div>
<div>
<label for="popup-max-width">Max Width (px):</label>
<input type="number" id="popup-max-width" min="300" max="800" value="500" />
</div>
</div>
</div>
<div class="control-group">
<div class="range-inputs">
<div>
<label for="popup-min-height">Min Height (px):</label>
<input type="number" id="popup-min-height" min="100" max="300" value="150" />
</div>
<div>
<label for="popup-max-height">Max Height (px):</label>
<input type="number" id="popup-max-height" min="200" max="600" value="400" />
</div>
</div>
</div>
</div>
<!-- Visual Effects -->
<div class="control-section">
<h4>✨ Visual Effects</h4>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-fade-animation" />
<span class="switch"></span>
Fade In/Out Animation
</label>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-blur-background" />
<span class="switch"></span>
Blur Background
</label>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-show-timer" />
<span class="switch"></span>
Show Countdown Timer
</label>
</div>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="popup-prevent-close" />
<span class="switch"></span>
Prevent Manual Close
</label>
</div>
</div>
<!-- Test & Preview -->
<div class="control-section">
<h4>🧪 Testing</h4>
<div class="test-buttons">
<button id="test-popup-single" class="btn btn-info">Test 1 Popup</button>
<button id="test-popup-multiple" class="btn btn-primary">Test Multiple</button>
<button id="clear-all-popups" class="btn btn-danger">Clear All</button>
</div>
<p class="help-text">Test your popup settings to see how they look</p>
<div id="popup-warning" class="warning-text" style="display: none;">
⚠️ High popup counts (>20) may impact performance and visibility
</div>
</div>
<!-- Status & Info -->
<div class="control-section">
<div class="info-display">
<div class="info-item">
<span class="info-label">Available Images:</span>
<span id="available-images-count">0</span>
</div>
<div class="info-item">
<span class="info-label">Active Popups:</span>
<span id="active-popups-count">0</span>
</div>
</div>
</div>
</div>
</div>
<!-- AI Tasks Tab -->
<div id="ai-tasks-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3>🤖 AI Task Generation</h3>
<p class="help-text">Let AI create personalized edging tasks using your local Ollama installation</p>
<!-- Connection Status -->
<div class="control-section">
<h4>📡 Connection Status</h4>
<div class="ai-status-display">
<div class="status-item">
<span class="status-label">Ollama Service:</span>
<span id="ollama-status" class="status-value">Checking...</span>
</div>
<div class="status-item">
<span class="status-label">Available Models:</span>
<span id="models-count" class="status-value">0</span>
</div>
<div class="status-item">
<span class="status-label">Current Model:</span>
<span id="current-model" class="status-value">None</span>
</div>
</div>
<button id="test-ai-connection" class="btn btn-info">Test Connection</button>
</div>
<!-- AI Configuration -->
<div class="control-section">
<h4>⚙️ AI Configuration</h4>
<div class="control-group">
<label class="switch-label">
<input type="checkbox" id="ai-tasks-enabled" />
<span class="switch"></span>
Enable AI Task Generation
</label>
</div>
<div class="control-group">
<label for="ai-model-select">Model Selection:</label>
<select id="ai-model-select">
<option value="">Select a model...</option>
</select>
</div>
<div class="control-group">
<label for="ai-temperature">Creativity (Temperature):</label>
<input type="range" id="ai-temperature" min="0.1" max="2.0" step="0.1" value="0.8" />
<span id="ai-temperature-value">0.8</span>
</div>
<div class="control-group">
<label for="ai-max-tokens">Max Response Length:</label>
<input type="range" id="ai-max-tokens" min="100" max="500" step="50" value="300" />
<span id="ai-max-tokens-value">300</span>
</div>
</div>
<!-- User Preferences -->
<div class="control-section">
<h4>👤 Your Preferences</h4>
<div class="control-group">
<label for="ai-experience">Experience Level:</label>
<select id="ai-experience">
<option value="beginner">Beginner</option>
<option value="intermediate" selected>Intermediate</option>
<option value="advanced">Advanced</option>
<option value="expert">Expert</option>
</select>
</div>
<div class="control-group">
<label for="ai-intensity">Default Intensity:</label>
<select id="ai-intensity">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="extreme">Extreme</option>
</select>
</div>
<div class="control-group">
<label for="ai-duration">Preferred Duration (minutes):</label>
<input type="range" id="ai-duration" min="3" max="30" value="5" />
<span id="ai-duration-value">5</span>
</div>
<div class="control-group">
<label for="ai-style">Instruction Style:</label>
<select id="ai-style">
<option value="instructional" selected>Instructional</option>
<option value="descriptive">Descriptive</option>
<option value="commanding">Commanding</option>
<option value="encouraging">Encouraging</option>
</select>
</div>
</div>
<!-- Testing & Preview -->
<div class="control-section">
<h4>🧪 Testing</h4>
<div class="ai-test-buttons">
<button id="generate-test-task" class="btn btn-primary">Generate Test Task</button>
<button id="generate-consequence-task" class="btn btn-danger">Generate Consequence</button>
<button id="clear-test-output" class="btn btn-secondary">Clear Output</button>
</div>
<div class="control-group">
<label for="test-task-output">Generated Task Preview:</label>
<div id="test-task-output" class="task-preview">
Click "Generate Test Task" to see AI-generated content...
</div>
</div>
</div>
<!-- Installation Help -->
<div class="control-section">
<h4>📚 Setup Help</h4>
<div class="help-content">
<p><strong>Need to install Ollama?</strong></p>
<ol>
<li>Download from <a href="https://ollama.ai" target="_blank">ollama.ai</a></li>
<li>Install recommended NSFW models:</li>
<ul>
<li><code>ollama pull dolphin-mistral:7b</code></li>
<li><code>ollama pull wizardlm-uncensored:7b</code></li>
</ul>
<li>Ensure Ollama service is running</li>
<li>Click "Test Connection" above</li>
</ol>
<p class="help-text">AI tasks are generated locally for complete privacy!</p>
</div>
</div>
</div>
</div>
<!-- Import/Export Tab -->
<div id="import-export-tab-content" class="annoyance-tab-content">
<div class="annoyance-section">
<h3><EFBFBD> Import & Export</h3>
<!-- Import/Export Content -->
<div class="annoyance-section">
<h3>📁 Import & Export</h3>
<div class="import-export-controls">
<div class="control-section">
<h4>💾 Export Messages</h4>
@ -1859,7 +1310,7 @@
<!-- Script Loading Order -->
<script src="src/features/ui/flashMessageManager.js"></script>
<script src="src/features/images/popupImageManager.js"></script>
<script src="src/features/tasks/aiTaskManager.js"></script>
<script src="src/features/audio/audioManager.js"></script>
<script src="src/features/stats/playerStats.js"></script>
<script src="src/core/gameModeManager.js"></script>
@ -4960,7 +4411,9 @@
localStorage.setItem('capturedPhotos', JSON.stringify(capturedPhotos));
// Show success message
showFlashMessage(`📸 Photo deleted successfully!`, 'success');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📸 Photo deleted successfully!`, 'info');
}
// Refresh the photo galleries
setupLibraryGalleryTab();
@ -5024,7 +4477,9 @@
link.click();
document.body.removeChild(link);
showFlashMessage(`📥 Photo downloaded: ${filename}`, 'success');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Photo downloaded: ${filename}`, 'info');
}
console.log(`📥 Downloaded photo: ${filename}`);
}
@ -5034,7 +4489,9 @@
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
if (selectedCheckboxes.length === 0) {
showFlashMessage('⚠️ No photos selected for download', 'warning');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('⚠️ No photos selected for download', 'error');
}
return;
}
@ -5046,7 +4503,9 @@
}
// Multiple photos - create zip
showFlashMessage('📦 Creating zip file...', 'info');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('📦 Creating zip file...', 'info');
}
try {
// Create zip file (using JSZip if available, otherwise download individually)
@ -5073,7 +4532,9 @@
link.click();
document.body.removeChild(link);
showFlashMessage(`📥 Downloaded ${selectedCheckboxes.length} photos as zip file`, 'success');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Downloaded ${selectedCheckboxes.length} photos as zip file`, 'info');
}
} else {
// Fallback: download individually
selectedCheckboxes.forEach((checkbox, downloadIndex) => {
@ -5083,11 +4544,15 @@
}, downloadIndex * 100); // Stagger downloads
});
showFlashMessage(`📥 Downloading ${selectedCheckboxes.length} photos individually`, 'info');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`📥 Downloading ${selectedCheckboxes.length} photos individually`, 'info');
}
}
} catch (error) {
console.error('Download error:', error);
showFlashMessage('❌ Error creating download', 'error');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('❌ Error creating download', 'error');
}
}
}
@ -5096,7 +4561,9 @@
const selectedCheckboxes = document.querySelectorAll('.photo-select:checked');
if (selectedCheckboxes.length === 0) {
showFlashMessage('⚠️ No photos selected for deletion', 'warning');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show('⚠️ No photos selected for deletion', 'error');
}
return;
}
@ -5117,7 +4584,9 @@
localStorage.setItem('capturedPhotos', JSON.stringify(capturedPhotos));
// Show success message
showFlashMessage(`🗑️ Successfully deleted ${indicesToDelete.length} photos!`, 'success');
if (window.game && window.game.flashMessageManager) {
window.game.flashMessageManager.show(`🗑️ Successfully deleted ${indicesToDelete.length} photos!`, 'info');
}
// Refresh the photo galleries
setupLibraryGalleryTab();

6264
index_temp.html Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,13 +33,14 @@
<div class="shortcut-item"><kbd></kbd><kbd></kbd> Seek ±10s</div>
<div class="shortcut-item"><kbd></kbd><kbd></kbd> Volume ±10%</div>
<div class="shortcut-item"><kbd>F</kbd> Fullscreen</div>
<div class="shortcut-item"><kbd>T</kbd> Theater Mode</div>
<div class="shortcut-item"><kbd>M</kbd> Mute/Unmute</div>
<div class="shortcut-item"><kbd>1-4</kbd> Quality</div>
<div class="shortcut-item"><kbd>Enter</kbd> Add to Playlist</div>
<div class="shortcut-item"><kbd>N</kbd> Next Video</div>
<div class="shortcut-item"><kbd>P</kbd> Previous Video</div>
<div class="shortcut-item"><kbd>S</kbd> Shuffle Playlist</div>
<div class="shortcut-item"><kbd>Escape</kbd> Exit Fullscreen</div>
<div class="shortcut-item"><kbd>Escape</kbd> Exit Theater/Fullscreen</div>
<div class="shortcut-item"><kbd>?</kbd> Toggle This Help</div>
</div>
</div>
@ -274,7 +275,7 @@
break;
}
console.log(`⏳ Waiting for desktop file manager to initialize... (${retries + 1}/${maxRetries})`);
// Waiting for desktop file manager to initialize...
await new Promise(resolve => setTimeout(resolve, 100));
retries++;
}
@ -394,12 +395,151 @@
}, 100);
}
// Theater mode functionality
let theaterModeActive = false;
let headerHideTimeout = null;
let escapeHintTimeout = null;
function createTheaterModeElements() {
// Create header hover zone for theater mode
const hoverZone = document.createElement('div');
hoverZone.className = 'header-hover-zone';
hoverZone.id = 'header-hover-zone';
document.body.appendChild(hoverZone);
// Create escape hint
const escapeHint = document.createElement('div');
escapeHint.className = 'theater-escape-hint';
escapeHint.id = 'theater-escape-hint';
escapeHint.innerHTML = 'Press <kbd>T</kbd> or <kbd>Esc</kbd> to exit theater mode';
document.body.appendChild(escapeHint);
}
function setupTheaterMouseTracking() {
const header = document.querySelector('.cinema-header');
const hoverZone = document.getElementById('header-hover-zone');
const escapeHint = document.getElementById('theater-escape-hint');
if (!hoverZone || !header) return;
// Show header when mouse enters hover zone
hoverZone.addEventListener('mouseenter', () => {
if (theaterModeActive) {
header.classList.remove('auto-hide');
header.classList.add('show-on-hover');
clearTimeout(headerHideTimeout);
}
});
// Hide header when mouse leaves hover zone
hoverZone.addEventListener('mouseleave', () => {
if (theaterModeActive) {
headerHideTimeout = setTimeout(() => {
header.classList.remove('show-on-hover');
header.classList.add('auto-hide');
}, 2000);
}
});
// Auto-hide escape hint after 5 seconds
if (escapeHint) {
escapeHintTimeout = setTimeout(() => {
escapeHint.classList.add('fade-out');
}, 5000);
}
}
function toggleTheaterMode() {
theaterModeActive = !theaterModeActive;
const body = document.body;
const sidebar = document.querySelector('.cinema-sidebar');
const librarySection = document.querySelector('.video-library-section');
const mainContentArea = document.querySelector('.main-content-area');
const cinemaMain = document.querySelector('.cinema-main');
const theaterBtn = document.getElementById('theater-mode');
const theaterBtnControl = document.getElementById('theater-mode-btn');
const header = document.querySelector('.cinema-header');
const escapeHint = document.getElementById('theater-escape-hint');
if (theaterModeActive) {
// Enter theater mode
body.classList.add('theater-mode-active');
sidebar.style.display = 'none';
librarySection.style.display = 'none';
cinemaMain.style.gridTemplateColumns = '1fr';
mainContentArea.style.padding = '0';
// Create theater mode elements if they don't exist
if (!document.getElementById('header-hover-zone')) {
createTheaterModeElements();
}
// Setup mouse tracking for header auto-hide
setupTheaterMouseTracking();
// Initially hide header after 3 seconds
headerHideTimeout = setTimeout(() => {
if (header) {
header.classList.add('auto-hide');
}
}, 3000);
// Update button text
if (theaterBtn) theaterBtn.innerHTML = '🎭 Exit Theater';
if (theaterBtnControl) theaterBtnControl.innerHTML = '🎭';
console.log('🎭 Theater mode activated - Video player fills entire window');
} else {
// Exit theater mode
body.classList.remove('theater-mode-active');
sidebar.style.display = 'flex';
librarySection.style.display = 'block';
cinemaMain.style.gridTemplateColumns = '1fr 320px';
mainContentArea.style.padding = '20px';
// Clean up theater mode elements
clearTimeout(headerHideTimeout);
clearTimeout(escapeHintTimeout);
if (header) {
header.classList.remove('auto-hide', 'show-on-hover');
}
if (escapeHint) {
escapeHint.classList.remove('fade-out');
}
// Update button text
if (theaterBtn) theaterBtn.innerHTML = '🎭 Theater';
if (theaterBtnControl) theaterBtnControl.innerHTML = '🎭';
console.log('🎭 Theater mode deactivated - Normal layout restored');
}
}
// Theater mode button event listeners
document.getElementById('theater-mode').addEventListener('click', toggleTheaterMode);
document.getElementById('theater-mode-btn').addEventListener('click', toggleTheaterMode);
// Keyboard shortcut to toggle help
document.addEventListener('keydown', (e) => {
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
const help = document.getElementById('shortcuts-help');
help.style.display = help.style.display === 'none' ? 'block' : 'none';
}
// Theater mode keyboard shortcuts
if (e.key.toLowerCase() === 't' && !e.ctrlKey && !e.altKey && !e.shiftKey) {
// Only toggle if not typing in an input field
if (!['INPUT', 'TEXTAREA'].includes(e.target.tagName)) {
toggleTheaterMode();
e.preventDefault();
}
}
// Escape key to exit theater mode
if (e.key === 'Escape' && theaterModeActive) {
toggleTheaterMode();
e.preventDefault();
}
});
// Sidebar tab functionality

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ Organized by functional area, each feature is self-contained:
#### `/features/tasks` - Task Management
- **`interactiveTaskManager.js`** - Task execution, photography integration, and scenario handling
- **`aiTaskManager.js`** - AI-powered task generation using local Ollama models
#### `/features/audio` - Audio System
- **`audioManager.js`** - Background music, sound effects, and audio controls

View File

@ -113,13 +113,9 @@ class TaskChallengeGame {
window.popupImageManager = this.popupImageManager; // Make available globally for HTML onclick handlers
this.updateLoadingProgress(60, 'Popup system loaded...');
// Initialize AI Task Generation System
this.aiTaskManager = new AITaskManager(this.dataManager);
this.updateLoadingProgress(65, 'AI task manager loaded...');
// Initialize Audio Management System
this.audioManager = new AudioManager(this.dataManager, this);
this.updateLoadingProgress(70, 'Audio manager loaded...');
this.updateLoadingProgress(65, 'Audio manager loaded...');
// Initialize Game Mode Manager
this.gameModeManager = new GameModeManager();
@ -7386,18 +7382,7 @@ TaskChallengeGame.prototype.setupAnnoyanceManagementEventListeners = function()
// Tab navigation
document.getElementById('messages-tab').onclick = () => this.showAnnoyanceTab('messages');
document.getElementById('appearance-tab').onclick = () => this.showAnnoyanceTab('appearance');
document.getElementById('behavior-tab').onclick = () => this.showAnnoyanceTab('behavior');
document.getElementById('popup-images-tab').onclick = () => this.showAnnoyanceTab('popup-images');
document.getElementById('import-export-tab').onclick = () => this.showAnnoyanceTab('import-export');
document.getElementById('ai-tasks-tab').onclick = () => this.showAnnoyanceTab('ai-tasks');
this.setupMessagesTabListeners();
this.setupAppearanceTabListeners();
this.setupBehaviorTabListeners();
this.setupPopupImagesTabListeners();
this.setupImportExportTabListeners();
this.setupAITasksTabListeners();
};
TaskChallengeGame.prototype.showAnnoyanceTab = function(tabName) {
@ -7411,24 +7396,9 @@ TaskChallengeGame.prototype.showAnnoyanceTab = function(tabName) {
// Load tab-specific content
switch(tabName) {
case 'messages':
this.loadMessagesTab();
break;
case 'appearance':
this.loadAppearanceTab();
break;
case 'behavior':
this.loadBehaviorTab();
break;
case 'popup-images':
this.loadPopupImagesSettings();
break;
case 'import-export':
this.loadImportExportTab();
break;
case 'ai-tasks':
this.loadAITasksTab();
break;
}
};
@ -8404,280 +8374,7 @@ TaskChallengeGame.prototype.rgbToHex = function(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
};
// ========================================
// AI Tasks Tab Management
// ========================================
TaskChallengeGame.prototype.setupAITasksTabListeners = function() {
// Test Connection Button
const testConnectionBtn = document.getElementById('test-connection');
if (testConnectionBtn) {
testConnectionBtn.onclick = async () => {
const btn = testConnectionBtn;
const originalText = btn.textContent;
btn.textContent = 'Testing...';
btn.disabled = true;
try {
if (this.aiTaskManager) {
const isConnected = await this.aiTaskManager.testConnection();
this.updateConnectionStatus();
if (isConnected) {
this.flashMessageManager.showMessage('✅ Connected to Ollama successfully!');
} else {
this.flashMessageManager.showMessage('❌ Cannot connect to Ollama. Check if it\'s running.');
}
} else {
this.flashMessageManager.showMessage('⚠️ AI Manager not initialized');
}
} catch (error) {
this.flashMessageManager.showMessage('❌ Connection test failed: ' + error.message);
} finally {
btn.textContent = originalText;
btn.disabled = false;
}
};
}
// Generate Test Task Button
const generateTestBtn = document.getElementById('generate-test-task');
if (generateTestBtn) {
generateTestBtn.onclick = async () => {
const btn = generateTestBtn;
const preview = document.getElementById('test-task-output');
const originalText = btn.textContent;
if (!preview) {
console.error('Test task output element not found');
this.flashMessageManager.showMessage('❌ UI element not found');
return;
}
btn.textContent = 'Generating...';
btn.disabled = true;
preview.className = 'task-preview generating';
preview.textContent = 'AI is creating a personalized edging task...';
try {
if (this.aiTaskManager) {
const task = await this.aiTaskManager.generateEdgingTask();
preview.className = 'task-preview';
preview.textContent = task.instruction || task.description || task;
this.flashMessageManager.showMessage('🎯 Test task generated successfully!');
} else {
throw new Error('AI Manager not initialized');
}
} catch (error) {
preview.className = 'task-preview error';
preview.textContent = `Error generating task: ${error.message}`;
this.flashMessageManager.showMessage('❌ Failed to generate test task');
} finally {
btn.textContent = originalText;
btn.disabled = false;
}
};
}
// Model Selection Change
const modelSelect = document.getElementById('ai-model');
if (modelSelect) {
modelSelect.onchange = (e) => {
const model = e.target.value;
if (this.aiTaskManager) {
this.aiTaskManager.updateSettings({ model: model });
this.flashMessageManager.showMessage(`🔄 Switched to model: ${model}`);
}
};
}
// Temperature Slider
const tempSlider = document.getElementById('ai-temperature');
const tempValue = document.getElementById('temp-value');
if (tempSlider && tempValue) {
tempSlider.oninput = (e) => {
const temperature = parseFloat(e.target.value);
tempValue.textContent = temperature.toFixed(1);
if (this.aiTaskManager) {
this.aiTaskManager.updateSettings({ temperature: temperature });
}
};
}
// Max Tokens Input
const maxTokensInput = document.getElementById('ai-max-tokens');
if (maxTokensInput) {
maxTokensInput.onchange = (e) => {
const maxTokens = parseInt(e.target.value);
if (this.aiTaskManager) {
this.aiTaskManager.updateSettings({ maxTokens: maxTokens });
}
};
}
// Difficulty Level Change
const difficultySelect = document.getElementById('task-difficulty');
if (difficultySelect) {
difficultySelect.onchange = (e) => {
const difficulty = e.target.value;
if (this.aiTaskManager) {
this.aiTaskManager.updateSettings({ difficulty: difficulty });
this.flashMessageManager.showMessage(`🎯 Difficulty set to: ${difficulty}`);
}
};
}
// Personal Preferences Textarea
const prefsTextarea = document.getElementById('personal-preferences');
if (prefsTextarea) {
prefsTextarea.oninput = this.debounce((e) => {
const preferences = e.target.value;
if (this.aiTaskManager) {
this.aiTaskManager.updateSettings({ personalPreferences: preferences });
}
}, 500);
}
// Enable AI Toggle
const aiToggle = document.getElementById('enable-ai');
if (aiToggle) {
aiToggle.onchange = (e) => {
const enabled = e.target.checked;
if (this.aiTaskManager) {
this.aiTaskManager.updateSettings({ enabled: enabled });
this.flashMessageManager.showMessage(`🤖 AI Tasks ${enabled ? 'enabled' : 'disabled'}`);
// Update UI based on toggle
const configSection = document.querySelector('.ai-config');
if (configSection) {
configSection.style.opacity = enabled ? '1' : '0.6';
}
}
};
}
// Auto-generate Toggle
const autoGenToggle = document.getElementById('auto-generate');
if (autoGenToggle) {
autoGenToggle.onchange = (e) => {
const autoGenerate = e.target.checked;
if (this.aiTaskManager) {
this.aiTaskManager.updateSettings({ autoGenerate: autoGenerate });
this.flashMessageManager.showMessage(`🔄 Auto-generate ${autoGenerate ? 'enabled' : 'disabled'}`);
}
};
}
};
TaskChallengeGame.prototype.loadAITasksTab = function() {
if (!this.aiTaskManager) return;
const settings = this.aiTaskManager.getSettings();
// Load model selection
const modelSelect = document.getElementById('ai-model');
if (modelSelect) {
modelSelect.value = settings.model || 'llama3.2';
}
// Load temperature
const tempSlider = document.getElementById('ai-temperature');
const tempValue = document.getElementById('temp-value');
if (tempSlider && tempValue) {
tempSlider.value = settings.temperature || 0.7;
tempValue.textContent = (settings.temperature || 0.7).toFixed(1);
}
// Load max tokens
const maxTokensInput = document.getElementById('ai-max-tokens');
if (maxTokensInput) {
maxTokensInput.value = settings.maxTokens || 200;
}
// Load difficulty
const difficultySelect = document.getElementById('task-difficulty');
if (difficultySelect) {
difficultySelect.value = settings.difficulty || 'medium';
}
// Load personal preferences
const prefsTextarea = document.getElementById('personal-preferences');
if (prefsTextarea) {
prefsTextarea.value = settings.personalPreferences || '';
}
// Load toggles
const aiToggle = document.getElementById('enable-ai');
if (aiToggle) {
aiToggle.checked = settings.enabled !== false;
}
const autoGenToggle = document.getElementById('auto-generate');
if (autoGenToggle) {
autoGenToggle.checked = settings.autoGenerate === true;
}
// Update UI state
const configSection = document.querySelector('.ai-config');
if (configSection) {
configSection.style.opacity = settings.enabled !== false ? '1' : '0.6';
}
// Update connection status (async)
this.updateConnectionStatus().catch(error => {
console.error('Failed to update connection status:', error);
});
};
TaskChallengeGame.prototype.updateConnectionStatus = async function() {
const statusValue = document.getElementById('connection-status');
const modelStatus = document.getElementById('model-status');
if (!statusValue || !this.aiTaskManager) {
if (statusValue) {
statusValue.textContent = 'Not Available';
statusValue.className = 'status-value disconnected';
}
if (modelStatus) {
modelStatus.textContent = 'N/A';
modelStatus.className = 'status-value';
}
return;
}
try {
// Set loading state
statusValue.textContent = 'Checking...';
statusValue.className = 'status-value';
// Force a fresh connection test
const isConnected = await this.aiTaskManager.testConnection();
const settings = this.aiTaskManager.getSettings();
if (isConnected) {
statusValue.textContent = 'Connected';
statusValue.className = 'status-value connected';
if (modelStatus) {
modelStatus.textContent = settings.model || this.aiTaskManager.currentModel || 'wizardlm-uncensored:13b';
modelStatus.className = 'status-value';
}
} else {
statusValue.textContent = 'Disconnected';
statusValue.className = 'status-value disconnected';
if (modelStatus) {
modelStatus.textContent = 'N/A';
modelStatus.className = 'status-value';
}
}
} catch (error) {
console.error('Error updating connection status:', error);
statusValue.textContent = 'Error';
statusValue.className = 'status-value disconnected';
if (modelStatus) {
modelStatus.textContent = 'N/A';
modelStatus.className = 'status-value';
}
}
};
// Utility function for debouncing input
TaskChallengeGame.prototype.debounce = function(func, wait) {

View File

@ -17,7 +17,7 @@ class PopupImageManager {
maxInterval: 120000, // 2 minutes
displayDuration: 5000, // 5 seconds
history: [],
maxHistorySize: 20
maxHistorySize: 50 // Increased from 20 to allow more variety
};
this.init();
@ -68,8 +68,26 @@ class PopupImageManager {
return;
}
console.log('🔍 Current periodicSystem state:', this.periodicSystem);
const { minInterval, maxInterval } = this.periodicSystem;
const interval = Math.random() * (maxInterval - minInterval) + minInterval;
// Validate intervals before calculation
if (typeof minInterval !== 'number' || typeof maxInterval !== 'number' ||
isNaN(minInterval) || isNaN(maxInterval) ||
minInterval <= 0 || maxInterval <= 0) {
console.error('❌ Invalid periodic popup intervals:', {
minInterval,
maxInterval,
minType: typeof minInterval,
maxType: typeof maxInterval
});
console.log('🔄 Resetting to default intervals');
this.periodicSystem.minInterval = 30000; // 30 seconds
this.periodicSystem.maxInterval = 120000; // 2 minutes
return this.scheduleNextPeriodicPopup();
}
const interval = minInterval > maxInterval ? minInterval : Math.random() * (maxInterval - minInterval) + minInterval;
this.periodicSystem.interval = setTimeout(async () => {
try {
@ -91,13 +109,106 @@ class PopupImageManager {
return;
}
const image = await this.getRandomPeriodicImage();
if (!image) {
// Get the number of images to show based on configuration
const imageCount = this.getImageCount();
console.log(`🖼️ Showing ${imageCount} periodic popup images`);
const images = await this.getMultipleRandomPeriodicImages(imageCount);
if (images.length === 0) {
console.log('⚠️ No images available for periodic popup');
return;
}
this.displayPeriodicPopup(image);
this.displayMultiplePeriodicPopups(images);
}
/**
* Get the number of images to show based on configuration
*/
getImageCount() {
const config = this.config || {};
if (config.countMode === 'fixed') {
return Math.max(1, config.imageCount || 1);
} else if (config.countMode === 'range') {
const min = Math.max(1, config.minCount || 1);
const max = Math.max(min, config.maxCount || 3);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Default to 1 if no configuration
return 1;
}
/**
* Get multiple random images for periodic popups
*/
async getMultipleRandomPeriodicImages(count) {
const allImages = await this.getLinkedImages();
if (allImages.length === 0) {
return [];
}
// Filter out disabled images and recent history
const disabledImages = this.dataManager.get('disabledImages') || [];
const { history } = this.periodicSystem;
// Dynamically adjust history size based on available images
// Keep history to about 30% of total images, minimum 20, maximum 100
const totalImages = allImages.length;
const optimalHistorySize = Math.min(100, Math.max(20, Math.floor(totalImages * 0.3)));
if (this.periodicSystem.maxHistorySize !== optimalHistorySize) {
this.periodicSystem.maxHistorySize = optimalHistorySize;
console.log(`📊 Adjusted history size to ${optimalHistorySize} (30% of ${totalImages} total images)`);
}
const availableImages = allImages.filter(img => {
const imagePath = typeof img === 'string' ? img : img.src;
return !disabledImages.includes(imagePath) && !history.includes(imagePath);
});
if (availableImages.length === 0) {
console.log('⚠️ No available images (all disabled or recently shown)');
console.log('🔄 Clearing history to allow image reuse');
this.periodicSystem.history = [];
// Retry with cleared history
const retryAvailableImages = allImages.filter(img => {
const imagePath = typeof img === 'string' ? img : img.src;
return !disabledImages.includes(imagePath);
});
if (retryAvailableImages.length === 0) {
console.log('❌ No images available even after clearing history');
return [];
}
// Use the retry images
availableImages.length = 0;
availableImages.push(...retryAvailableImages);
console.log(`✅ Found ${availableImages.length} images after clearing history`);
}
// Get unique random images
const selectedImages = [];
const imagesToChooseFrom = [...availableImages];
for (let i = 0; i < Math.min(count, imagesToChooseFrom.length); i++) {
const randomIndex = Math.floor(Math.random() * imagesToChooseFrom.length);
const selectedImage = imagesToChooseFrom.splice(randomIndex, 1)[0];
selectedImages.push(selectedImage);
// Add to history
const imagePath = typeof selectedImage === 'string' ? selectedImage : selectedImage.src;
this.periodicSystem.history.push(imagePath);
// Manage history size
if (this.periodicSystem.history.length > this.periodicSystem.maxHistorySize) {
this.periodicSystem.history.shift();
}
}
return selectedImages;
}
/**
@ -242,7 +353,211 @@ class PopupImageManager {
}
/**
* Display periodic popup
* Display multiple periodic popups with positioning
*/
displayMultiplePeriodicPopups(images) {
const config = this.config || {};
const positioning = config.positioning || 'center';
const allowOverlap = config.allowOverlap !== false;
console.log(`🎯 Positioning ${images.length} images using '${positioning}' mode`);
images.forEach((imageData, index) => {
const position = this.calculatePopupPosition(positioning, index, images.length, allowOverlap);
this.displayPositionedPeriodicPopup(imageData, position, index);
});
}
/**
* Calculate position for a popup based on positioning mode
*/
calculatePopupPosition(positioning, index, total, allowOverlap) {
const config = this.config || {};
const viewportWidth = config.viewportWidth || window.innerWidth;
const viewportHeight = config.viewportHeight || window.innerHeight;
// Popup dimensions (approximate)
const popupWidth = 400;
const popupHeight = 300;
let position = { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' };
switch (positioning) {
case 'random':
if (!allowOverlap && total > 1) {
// Grid distribution with some randomness for non-overlapping positions
const cols = Math.ceil(Math.sqrt(total));
const rows = Math.ceil(total / cols);
const col = index % cols;
const row = Math.floor(index / cols);
// Calculate available space
const maxLeft = Math.max(100, viewportWidth - popupWidth - 50);
const maxTop = Math.max(100, viewportHeight - popupHeight - 50);
const sectionWidth = maxLeft / cols;
const sectionHeight = maxTop / rows;
// Add some randomness within each section
const randomOffsetX = Math.random() * Math.min(100, sectionWidth * 0.3);
const randomOffsetY = Math.random() * Math.min(100, sectionHeight * 0.3);
const left = Math.max(50, (col * sectionWidth) + randomOffsetX);
const top = Math.max(50, (row * sectionHeight) + randomOffsetY);
position = {
top: `${top}px`,
left: `${left}px`,
transform: 'none'
};
} else {
// Completely random positioning (overlapping allowed)
const maxLeft = Math.max(50, viewportWidth - popupWidth);
const maxTop = Math.max(50, viewportHeight - popupHeight);
const randomLeft = 50 + Math.random() * Math.max(0, maxLeft - 50);
const randomTop = 50 + Math.random() * Math.max(0, maxTop - 50);
position = {
top: `${randomTop}px`,
left: `${randomLeft}px`,
transform: 'none'
};
}
break;
case 'grid':
const cols = Math.ceil(Math.sqrt(total));
const rows = Math.ceil(total / cols);
const col = index % cols;
const row = Math.floor(index / cols);
const gridLeft = (viewportWidth / cols) * col + (viewportWidth / cols - popupWidth) / 2;
const gridTop = (viewportHeight / rows) * row + (viewportHeight / rows - popupHeight) / 2;
position = {
top: `${Math.max(0, gridTop)}px`,
left: `${Math.max(0, gridLeft)}px`,
transform: 'none'
};
break;
case 'corners':
const corners = [
{ top: '10%', left: '10%' },
{ top: '10%', right: '10%' },
{ bottom: '10%', left: '10%' },
{ bottom: '10%', right: '10%' }
];
position = corners[index % corners.length] || corners[0];
break;
case 'center':
default:
// Center with offset for multiple images
if (total > 1) {
if (!allowOverlap) {
// Spread them out more when overlapping is disabled
const spacing = Math.min(200, (viewportWidth - popupWidth) / total);
const offset = (index - (total - 1) / 2) * spacing;
position = {
top: '50%',
left: `calc(50% + ${offset}px)`,
transform: 'translate(-50%, -50%)'
};
} else {
// Smaller offset when overlapping is allowed
const offset = (index - (total - 1) / 2) * 50;
position = {
top: '50%',
left: `calc(50% + ${offset}px)`,
transform: 'translate(-50%, -50%)'
};
}
} else {
// Single image, center it perfectly
position = {
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
};
}
break;
}
return position;
}
/**
* Display a positioned periodic popup
*/
displayPositionedPeriodicPopup(imageData, position, index) {
const imageSrc = this.getImageSrc(imageData);
const imageName = typeof imageData === 'string' ? 'Random Image' : (imageData.originalName || 'Random Image');
const popup = document.createElement('div');
popup.className = 'periodic-popup-positioned';
// Apply positioning with !important to override any CSS conflicts
popup.style.cssText = `
position: fixed !important;
z-index: ${10000 + index} !important;
top: ${position.top} !important;
left: ${position.left} !important;
${position.transform ? `transform: ${position.transform} !important;` : ''}
`;
console.log(`🎯 Applied position for popup ${index + 1}:`, position);
popup.innerHTML = `
<div class="periodic-popup-content" onclick="event.stopPropagation()">
<div class="periodic-popup-header">
<span class="periodic-popup-title">💫 Random Visual ${index + 1}</span>
<button class="periodic-popup-close" onclick="window.popupImageManager.hidePeriodicPopup(this.closest('.periodic-popup-positioned'))">×</button>
</div>
<div class="periodic-popup-image-wrapper">
<img src="${imageSrc}" alt="Random Popup" class="periodic-popup-image">
</div>
<div class="periodic-popup-footer">
<small>${imageName}</small>
</div>
</div>
`;
this.ensurePeriodicStyles();
document.body.appendChild(popup);
// Animate in with slight delay for each popup
setTimeout(() => {
popup.classList.add('periodic-visible');
}, 50 + (index * 100));
// Auto-hide after configured duration
setTimeout(() => {
this.hidePeriodicPopup(popup);
}, this.getDisplayDuration());
}
/**
* Get display duration based on configuration
*/
getDisplayDuration() {
const config = this.config || {};
if (config.durationMode === 'fixed') {
return (config.displayDuration || 5) * 1000;
} else if (config.durationMode === 'range') {
const min = Math.max(1, config.minDuration || 3);
const max = Math.max(min, config.maxDuration || 10);
const randomDuration = Math.floor(Math.random() * (max - min + 1)) + min;
return randomDuration * 1000;
}
// Default to 5 seconds
return 5000;
}
/**
* Display periodic popup (legacy single popup method)
*/
displayPeriodicPopup(imageData) {
const imageSrc = this.getImageSrc(imageData);
@ -311,14 +626,15 @@ class PopupImageManager {
styles.textContent = `
.periodic-popup-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10001;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
width: auto;
height: auto;
}
.periodic-popup-container.periodic-visible {
@ -332,13 +648,31 @@ class PopupImageManager {
}
.periodic-popup-backdrop {
width: 100%;
height: 100%;
width: auto;
height: auto;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 8px;
}
.periodic-popup-positioned {
position: fixed;
background: rgba(0, 0, 0, 0.8);
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
overflow: hidden;
max-width: 400px;
max-height: 500px;
opacity: 1;
transition: opacity 0.3s ease;
}
.periodic-popup-positioned.periodic-hiding {
opacity: 0;
pointer-events: none;
}
.periodic-popup-content {
@ -438,9 +772,22 @@ class PopupImageManager {
* Update periodic popup settings
*/
updatePeriodicSettings(settings) {
if (settings.minInterval) this.periodicSystem.minInterval = settings.minInterval * 1000;
if (settings.maxInterval) this.periodicSystem.maxInterval = settings.maxInterval * 1000;
if (settings.displayDuration) this.periodicSystem.displayDuration = settings.displayDuration * 1000;
console.log('🔧 Received settings:', settings);
if (typeof settings.minInterval === 'number' && !isNaN(settings.minInterval) && settings.minInterval > 0) {
this.periodicSystem.minInterval = settings.minInterval * 1000;
}
if (typeof settings.maxInterval === 'number' && !isNaN(settings.maxInterval) && settings.maxInterval > 0) {
this.periodicSystem.maxInterval = settings.maxInterval * 1000;
}
if (typeof settings.displayDuration === 'number' && !isNaN(settings.displayDuration) && settings.displayDuration > 0) {
this.periodicSystem.displayDuration = settings.displayDuration * 1000;
}
// Ensure minInterval is not greater than maxInterval
if (this.periodicSystem.minInterval > this.periodicSystem.maxInterval) {
this.periodicSystem.minInterval = this.periodicSystem.maxInterval;
}
console.log('🔧 Periodic popup settings updated:', {
minInterval: this.periodicSystem.minInterval / 1000 + 's',
@ -481,14 +828,29 @@ class PopupImageManager {
// Update periodic system timing if frequency/duration config is provided
if (newConfig.frequency) {
this.periodicSystem.minInterval = newConfig.frequency.min;
this.periodicSystem.maxInterval = newConfig.frequency.max;
console.log('Updated periodic system frequency:', newConfig.frequency);
console.log('🔧 Updating periodic system from config frequency:', newConfig.frequency);
if (typeof newConfig.frequency.min === 'number' && newConfig.frequency.min > 0) {
this.periodicSystem.minInterval = newConfig.frequency.min * 1000; // Convert to milliseconds
}
if (typeof newConfig.frequency.max === 'number' && newConfig.frequency.max > 0) {
this.periodicSystem.maxInterval = newConfig.frequency.max * 1000; // Convert to milliseconds
}
console.log('Updated periodic system frequency:', {
min: this.periodicSystem.minInterval / 1000 + 's',
max: this.periodicSystem.maxInterval / 1000 + 's'
});
}
if (newConfig.duration) {
this.periodicSystem.displayDuration = newConfig.duration.min; // Use min duration for consistency
console.log('Updated periodic system duration:', newConfig.duration);
console.log('🔧 Updating periodic system from config duration:', newConfig.duration);
if (typeof newConfig.duration.min === 'number' && newConfig.duration.min > 0) {
this.periodicSystem.displayDuration = newConfig.duration.min * 1000; // Convert to milliseconds
}
console.log('Updated periodic system duration:', this.periodicSystem.displayDuration / 1000 + 's');
}
console.log('Popup image config updated:', newConfig);

View File

@ -40,19 +40,6 @@ class VideoLibrary {
// Content container
this.libraryContent = document.getElementById('library-content');
// Debug: Log which elements were found
console.log(`📁 VideoLibrary elements initialized:`, {
gridViewBtn: !!this.gridViewBtn,
listViewBtn: !!this.listViewBtn,
sortSelect: !!this.sortSelect,
sortDirectionBtn: !!this.sortDirectionBtn,
searchInput: !!this.searchInput,
refreshBtn: !!this.refreshBtn,
createPlaylistBtn: !!this.createPlaylistBtn,
selectModeBtn: !!this.selectModeBtn,
libraryContent: !!this.libraryContent
});
if (!this.libraryContent) {
console.error(`📁 ❌ library-content element not found!`);
}
@ -258,16 +245,7 @@ class VideoLibrary {
}
}
// Debug: Show some sample video names and paths
if (this.videos.length > 0) {
console.log(`📁 Sample videos loaded:`);
this.videos.slice(0, 5).forEach((video, index) => {
console.log(` ${index + 1}. "${video.name}" (${video.category}) - ${video.path}`);
});
if (this.videos.length > 5) {
console.log(` ... and ${this.videos.length - 5} more videos`);
}
}
// Apply current filters and display
this.applyFiltersAndSort();
@ -399,7 +377,7 @@ class VideoLibrary {
}
createVideoElement(video) {
console.log('🔍 DEBUG: createVideoElement called for:', video.name);
const duration = this.formatDuration(video.duration);
const fileSize = this.formatFileSize(video.size);
const isSelected = this.selectedVideos.has(video.path);
@ -410,7 +388,7 @@ class VideoLibrary {
if (this.currentView === 'grid') {
return `
<div class="video-card ${selectionClass}" data-video-path="${video.path}" style="background: red !important; border: 3px solid lime !important; height: auto !important; padding: 0 !important; margin: 0 !important; min-height: unset !important; overflow: hidden; border-radius: 10px;">
<div class="video-card ${selectionClass}" data-video-path="${video.path}">
${this.isSelectMode ? `<div class="video-selection-checkbox ${isSelected ? 'checked' : ''}">✓</div>` : ''}
<div class="video-thumbnail" data-video-path="${video.path}" style="height: 80px !important; width: 100% !important; position: relative; overflow: hidden;">
${thumbnailSrc ?

View File

@ -1,365 +0,0 @@
/**
* AI Task Manager - Ollama Integration for Gooner Training Academy
* Generates NSFW edging tasks using local AI models
*/
class AITaskManager {
constructor(dataManager) {
this.dataManager = dataManager;
this.ollamaUrl = 'http://localhost:11434';
this.isAvailable = false;
this.availableModels = [];
this.currentModel = 'wizardlm-uncensored:13b'; // Default NSFW-friendly model
this.isGenerating = false;
this.init();
}
async init() {
console.log('AITaskManager initializing...');
console.log('Testing basic fetch capability...');
// Test if fetch works at all
try {
const testResponse = await fetch('http://localhost:11434/api/tags');
console.log('Basic fetch test result:', testResponse.status);
} catch (testError) {
console.error('Basic fetch test failed:', testError);
}
await this.checkAvailability();
await this.loadModels();
this.loadConfig();
console.log('AITaskManager initialized:', this.isAvailable ? 'Ollama available' : 'Ollama not available');
}
async checkAvailability() {
try {
console.log('Testing Ollama connection to:', this.ollamaUrl);
// Create a manual timeout controller for better browser compatibility
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
const response = await fetch(`${this.ollamaUrl}/api/tags`, {
method: 'GET',
signal: controller.signal,
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
clearTimeout(timeoutId);
console.log('Response status:', response.status);
console.log('Response ok:', response.ok);
this.isAvailable = response.ok;
if (response.ok) {
const data = await response.json();
console.log('Available models from API:', data.models?.map(m => m.name));
}
console.log('Ollama connection test result:', this.isAvailable ? 'SUCCESS' : 'FAILED');
return this.isAvailable;
} catch (error) {
console.error('Ollama connection error details:', error);
console.log('Error name:', error.name);
console.log('Error message:', error.message);
this.isAvailable = false;
return false;
}
}
async loadModels() {
if (!this.isAvailable) {
console.log('Skipping model loading - Ollama not available');
return [];
}
try {
console.log('Loading models from Ollama...');
const response = await fetch(`${this.ollamaUrl}/api/tags`, {
method: 'GET',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.availableModels = data.models || [];
console.log('Loaded models:', this.availableModels.map(m => m.name));
// Check if our preferred NSFW models are available
const modelNames = this.availableModels.map(m => m.name);
const preferredModels = ['wizardlm-uncensored:13b', 'llama3.1:8b-instruct', 'dolphin-mistral:7b', 'wizardlm-uncensored:7b', 'llama3.2'];
console.log('Checking preferred models:', preferredModels);
console.log('Available model names:', modelNames);
for (const preferred of preferredModels) {
if (modelNames.includes(preferred)) {
console.log('Found preferred model:', preferred);
this.currentModel = preferred;
break;
}
}
// If no preferred models found, use the first available model
if (this.availableModels.length > 0 && !modelNames.includes(this.currentModel)) {
this.currentModel = this.availableModels[0].name;
console.log('Using first available model:', this.currentModel);
}
console.log('Selected model:', this.currentModel);
return this.availableModels;
} catch (error) {
console.error('Error loading models:', error);
this.availableModels = [];
return [];
}
}
loadConfig() {
const savedConfig = this.dataManager.get('aiTaskConfig');
this.config = {
enabled: false,
model: this.currentModel,
temperature: 0.8,
maxTokens: 300,
userPreferences: {
experience: 'intermediate', // beginner, intermediate, advanced
intensity: 'medium', // low, medium, high, extreme
duration: 5, // minutes
style: 'instructional', // instructional, descriptive, commanding
kinks: [], // user-selected interests
limits: [] // user-defined hard limits
},
...savedConfig
};
}
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
this.dataManager.set('aiTaskConfig', this.config);
if (newConfig.model) {
this.currentModel = newConfig.model;
}
}
// Alias method for UI compatibility
updateSettings(newSettings) {
return this.updateConfig(newSettings);
}
// Alias method for UI compatibility
getSettings() {
return this.getConfig();
}
getConfig() {
return { ...this.config };
}
async generateEdgingTask(customPrefs = {}) {
if (!this.isAvailable) {
throw new Error('AI not available. Please ensure Ollama is running and models are installed.');
}
if (this.isGenerating) {
throw new Error('Already generating a task. Please wait...');
}
this.isGenerating = true;
try {
const prefs = { ...this.config.userPreferences, ...customPrefs };
const prompt = this.buildEdgingPrompt(prefs);
console.log('Generating AI task with model:', this.currentModel);
const response = await fetch(`${this.ollamaUrl}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.currentModel,
prompt: prompt,
stream: false,
options: {
temperature: this.config.temperature,
num_predict: this.config.maxTokens,
stop: ['User:', 'Human:', 'Assistant:']
}
})
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status}`);
}
const data = await response.json();
const taskText = data.response.trim();
if (!taskText || taskText.length < 20) {
throw new Error('Generated task was too short or empty');
}
// Create task object compatible with existing game system
const aiTask = {
id: `ai-task-${Date.now()}`,
type: 'ai-generated',
category: 'edging',
instruction: taskText,
duration: prefs.duration * 60000, // Convert to milliseconds
difficulty: prefs.intensity,
source: 'ollama',
model: this.currentModel,
generated: new Date().toISOString(),
preferences: prefs
};
console.log('AI Task Generated:', aiTask.instruction.substring(0, 100) + '...');
return aiTask;
} catch (error) {
console.error('Error generating AI task:', error);
throw error;
} finally {
this.isGenerating = false;
}
}
buildEdgingPrompt(prefs) {
const basePrompt = `You are an expert in creating edging challenges. Generate a detailed ${prefs.duration}-minute edging task with ${prefs.intensity} intensity for someone with ${prefs.experience} experience.
The task should include:
- Clear step-by-step instructions
- Specific timing and rhythm guidance
- Techniques for building arousal without climax
- Commands for start, stop, and pause moments
- Breathing and focus instructions
- Progressive intensity building
Style: ${prefs.style}
Duration: ${prefs.duration} minutes exactly
Intensity: ${prefs.intensity}
Experience Level: ${prefs.experience}
Generate only the task instructions, no introduction or explanation:
Task Instructions:`;
return basePrompt;
}
async generateConsequenceTask(skippedTask, severity = 'medium') {
if (!this.isAvailable) return null;
const prompt = `Create a consequence task for someone who skipped an edging challenge. This should be a punishment task with ${severity} severity that teaches discipline and makes them regret skipping.
The consequence should:
- Be more challenging than the original task
- Include elements of denial or frustration
- Have a longer duration (at least 7-10 minutes)
- Include specific punishments or restrictions
- Make the user understand the cost of skipping
Generate only the consequence task instructions:
Consequence Task:`;
try {
const response = await fetch(`${this.ollamaUrl}/api/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.currentModel,
prompt: prompt,
stream: false,
options: {
temperature: 0.9,
num_predict: this.config.maxTokens
}
})
});
const data = await response.json();
return {
id: `ai-consequence-${Date.now()}`,
type: 'ai-consequence',
instruction: data.response.trim(),
duration: 600000, // 10 minutes
difficulty: 'punishment',
source: 'ollama',
isConsequence: true
};
} catch (error) {
console.error('Error generating consequence task:', error);
return null;
}
}
async testConnection() {
console.log('Starting connection test...');
const available = await this.checkAvailability();
if (!available) {
console.log('Connection test failed: Ollama service not available');
return false;
}
console.log('Ollama available, loading models...');
await this.loadModels();
if (this.availableModels.length === 0) {
console.log('Connection test failed: No models installed');
return false;
}
console.log(`Found ${this.availableModels.length} models, using: ${this.currentModel}`);
try {
// Quick test generation to verify the model works
console.log('Testing model with simple task generation...');
const testTask = await this.generateEdgingTask({
duration: 1,
intensity: 'low',
experience: 'beginner'
});
console.log('Connection test successful! Generated test task.');
return true;
} catch (error) {
console.error('Connection test failed during task generation:', error);
return false;
}
}
getAvailableModels() {
return this.availableModels;
}
isReady() {
return this.isAvailable && this.availableModels.length > 0;
}
getStatus() {
return {
available: this.isAvailable,
generating: this.isGenerating,
model: this.currentModel,
modelCount: this.availableModels.length,
config: this.config
};
}
}

View File

@ -1685,6 +1685,135 @@ body.cinema-mode {
opacity: 1;
}
/* ===== THEATER MODE ACTIVE ===== */
body.theater-mode-active {
overflow: hidden;
}
body.theater-mode-active .cinema-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 2000;
background: rgba(42, 42, 58, 0.95);
backdrop-filter: blur(10px);
opacity: 0;
transform: translateY(-100%);
transition: all 0.3s ease;
}
body.theater-mode-active .cinema-header:hover,
body.theater-mode-active .cinema-header.show-on-hover {
opacity: 1;
transform: translateY(0);
}
body.theater-mode-active .cinema-main {
display: block;
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
}
body.theater-mode-active .main-content-area {
padding: 0;
height: 100vh;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
body.theater-mode-active .video-player-section {
width: 100vw;
height: 100vh;
margin: 0;
border-radius: 0;
box-shadow: none;
background: #000;
}
body.theater-mode-active .video-container {
width: 100vw;
height: 100vh;
min-height: 100vh;
border-radius: 0;
}
body.theater-mode-active .main-video {
width: 100vw;
height: 100vh;
object-fit: contain;
}
/* Auto-hide header in theater mode */
body.theater-mode-active .cinema-header.auto-hide {
opacity: 0;
transform: translateY(-100%);
transition-delay: 3s;
}
/* Show header on mouse movement near top */
body.theater-mode-active .header-hover-zone {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 80px;
z-index: 1999;
pointer-events: none;
}
/* Theater mode controls styling */
body.theater-mode-active .video-controls {
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.9));
backdrop-filter: blur(5px);
}
/* Theater mode escape hint */
.theater-escape-hint {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.9);
color: #fff;
padding: 12px 16px;
border-radius: 8px;
font-size: 0.9rem;
z-index: 2001;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 107, 157, 0.3);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.theater-escape-hint kbd {
background: rgba(255, 107, 157, 0.2);
border: 1px solid rgba(255, 107, 157, 0.4);
border-radius: 4px;
padding: 2px 6px;
font-size: 0.8rem;
margin: 0 2px;
color: #ff6b9d;
font-weight: bold;
}
body.theater-mode-active .theater-escape-hint {
opacity: 1;
transform: translateY(0);
}
body.theater-mode-active .theater-escape-hint.fade-out {
opacity: 0;
transform: translateY(-20px);
transition-delay: 3s;
}
/* ===== FULLSCREEN STYLES ===== */
.video-container:-webkit-full-screen {
width: 100vw;

View File

@ -4654,157 +4654,7 @@ input[type="number"]:focus, input[type="text"]:focus, select:focus {
animation: popupFadeOut 0.3s ease-in;
}
/* ======================================
AI Tasks Tab Styles
====================================== */
.ai-status-display {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
border-left: 4px solid #17a2b8;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
font-weight: 500;
color: #495057;
}
.status-value {
font-weight: bold;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
background: rgba(23, 162, 184, 0.1);
color: #17a2b8;
}
.status-value.connected {
background: rgba(40, 167, 69, 0.1);
color: #28a745;
}
.status-value.disconnected {
background: rgba(220, 53, 69, 0.1);
color: #dc3545;
}
.ai-test-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 15px;
}
.ai-test-buttons .btn {
flex: 1;
min-width: 120px;
padding: 10px 15px;
font-size: 14px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.task-preview {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
min-height: 100px;
max-height: 200px;
overflow-y: auto;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px;
line-height: 1.5;
color: #495057;
white-space: pre-wrap;
margin-top: 10px;
}
.task-preview.generating {
background: #fff3cd;
color: #856404;
display: flex;
align-items: center;
justify-content: center;
}
.task-preview.error {
background: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
}
.help-content {
background: #e7f3ff;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #007bff;
}
.help-content ol {
margin: 10px 0;
padding-left: 20px;
}
.help-content ul {
margin: 5px 0;
padding-left: 20px;
}
.help-content code {
background: rgba(0, 0, 0, 0.1);
padding: 2px 5px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.help-content a {
color: #007bff;
text-decoration: none;
}
.help-content a:hover {
text-decoration: underline;
}
/* Responsive styles for AI tab */
@media (max-width: 768px) {
.ai-test-buttons {
flex-direction: column;
}
.ai-test-buttons .btn {
min-width: 100%;
}
.status-item {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
.status-value {
align-self: flex-end;
}
}
/* Audio Controls Styling */
.audio-controls {

View File

@ -964,7 +964,7 @@
<script src="src/features/webcam/webcamManager.js"></script>
<script src="src/features/stats/playerStats.js"></script>
<script src="src/features/ui/flashMessageManager.js"></script>
<script src="src/features/tasks/aiTaskManager.js"></script>
<script src="src/features/tasks/interactiveTaskManager.js"></script>
<!-- Data Scripts -->