diff --git a/README.md b/README.md
index 819d1ca..2582bc0 100644
--- a/README.md
+++ b/README.md
@@ -898,8 +898,20 @@ webGame/
├── quick-play.html # Quick Play game mode
├── training-academy.html # Training Academy mode
├── porn-cinema.html # Video player system
+├── hypno-gallery.html # Immersive slideshow system
├── player-stats.html # Statistics dashboard
├── user-profile.html # Profile and achievements
+├── scripts/ # Installation and launch scripts
+│ ├── setup.bat # Windows installer
+│ ├── setup.sh # Mac/Linux installer
+│ ├── Start-webgame.bat # Windows launcher
+│ └── create-distribution.* # Distribution builders
+├── docs/ # Documentation and guides
+│ ├── HYPNO-GALLERY-README.md # Hypno Gallery documentation
+│ ├── INSTALLATION_GUIDE.md # Setup instructions
+│ ├── TESTER_GUIDE.md # Testing instructions
+│ ├── ROADMAP.md # Development roadmap
+│ └── archive/ # Archived documentation
├── src/
│ ├── core/ # Game engine and state management
│ ├── data/modes/ # Game mode configurations
diff --git a/BETA_FEEDBACK_FORM.md b/docs/BETA_FEEDBACK_FORM.md
similarity index 100%
rename from BETA_FEEDBACK_FORM.md
rename to docs/BETA_FEEDBACK_FORM.md
diff --git a/DISTRIBUTION_SUMMARY.md b/docs/DISTRIBUTION_SUMMARY.md
similarity index 100%
rename from DISTRIBUTION_SUMMARY.md
rename to docs/DISTRIBUTION_SUMMARY.md
diff --git a/docs/HYPNO-GALLERY-README.md b/docs/HYPNO-GALLERY-README.md
new file mode 100644
index 0000000..4166345
--- /dev/null
+++ b/docs/HYPNO-GALLERY-README.md
@@ -0,0 +1,325 @@
+# 🌀 Hypno Gallery - Immersive Slideshow System
+
+## Overview
+
+The Hypno Gallery is an advanced, feature-rich slideshow application designed for creating immersive visual experiences. It supports recursive directory scanning, multiple slideshow management, customizable timing patterns, and sophisticated visual effects.
+
+## Current Features
+
+### 📋 Slideshow Management
+- **Multiple Slideshow Support**: Create, save, load, and manage multiple slideshow configurations
+- **Slideshow Playlists**: Each slideshow can have its own directories, settings, and configurations
+- **Persistent Storage**: Slideshows are automatically saved to localStorage and persist between sessions
+- **Duplicate & Share**: Clone existing slideshows for quick customization
+- **Import/Export**: Load existing slideshows or create new ones
+
+### 📁 Directory Management
+- **Recursive Directory Scanning**: Automatically finds all images in directories and subdirectories
+- **Multiple Directory Support**: Add multiple image directories to a single slideshow
+- **Space-Friendly Path Handling**: Properly handles directory names with spaces
+- **Real-time Directory Updates**: Add/remove directories from active slideshows
+- **Cross-Platform Path Support**: Works with Windows, macOS, and Linux file paths
+
+### ⏱️ Advanced Timing System
+- **Constant Duration**: Fixed timing between image transitions
+- **Random Range**: Variable timing within a specified min/max range
+- **Wave Pattern**: Sinusoidal timing that creates rhythmic acceleration/deceleration
+- **Customizable Parameters**: Fine-tune wave amplitude, frequency, and base timing
+
+### 🖼️ Image Handling
+- **Multiple Format Support**: JPG, PNG, GIF, BMP, WebP, SVG, TIFF, ICO
+- **Smart Image Ordering**: Random shuffle, sequential, or reverse order
+- **Flexible Display Modes**:
+ - **Contain**: Fit entire image within screen bounds
+ - **Cover**: Fill screen, cropping if necessary
+ - **Stretch**: Fill screen, distorting aspect ratio if needed
+- **Memory Efficient**: Loads images on-demand with preloading optimization
+
+### ✨ Transition Effects
+- **Fade Transitions**: Smooth cross-fade between images
+- **Customizable Duration**: Adjust transition timing (100ms - 2s)
+- **Instant Switching**: Option for no transitions for rapid changes
+- **GPU Accelerated**: Hardware-accelerated CSS transitions for smooth performance
+
+### 🎨 Background Customization
+- **Solid Colors**: Customizable solid background colors
+- **Gradient Backgrounds**: Linear gradients with multiple direction options
+- **Blurred Image Backgrounds**: Dynamic blurred version of current image
+- **Opacity Control**: Adjustable background opacity and blur intensity
+- **Real-time Preview**: Live preview of background changes
+
+### 🎮 Interactive Controls
+- **Fullscreen Experience**: Immersive fullscreen slideshow mode
+- **Mouse Controls**: Auto-hiding UI elements, show on mouse movement
+- **Keyboard Shortcuts**: Space to pause/resume, arrow keys for navigation, Escape to exit
+- **Progress Indicator**: Optional progress bar showing slideshow advancement
+- **Real-time Information**: Current image counter, timing info, slideshow status
+
+### 📷 Captured Photo Integration
+- **Webcam Integration**: Include photos captured from webcam sessions
+- **Mixed Media**: Combine directory images with captured photos
+- **Flexible Inclusion**: Toggle captured photos on/off per slideshow
+
+### 💾 Data Persistence
+- **Auto-Save Settings**: All settings automatically saved to localStorage
+- **Session Recovery**: Resume slideshow configurations after restart
+- **Export/Import Ready**: Prepared for future backup/restore functionality
+
+## File Structure
+
+```
+hypno-gallery.html # Main application file
+src/
+├── core/
+│ ├── main.js # Electron main process (includes recursive image scanner)
+│ └── preload.js # IPC bridge (exposes readImageDirectoryRecursive)
+├── styles/
+│ ├── styles.css # Base application styles
+│ └── styles-dark-edgy.css # Dark theme styles
+└── utils/
+ └── desktop-file-manager.js # File system utilities
+```
+
+## Usage Guide
+
+### Getting Started
+1. **Launch Application**: Open `hypno-gallery.html` in the electron app
+2. **Create Slideshow**: Click "🎬 New" to create your first slideshow
+3. **Add Directories**: Use "📁 Add Directory" to select image folders
+4. **Configure Settings**: Customize timing, transitions, and visual effects
+5. **Start Experience**: Click "🌀 Start Hypno Gallery" to begin
+
+### Slideshow Management
+```javascript
+// Creating a new slideshow
+1. Click "🎬 New" button
+2. Enter slideshow name
+3. Click "✅ Create"
+
+// Loading existing slideshow
+1. Click "📁 Load" button
+2. Select from dropdown
+3. Click "✅ Load"
+
+// Adding directories to slideshow
+1. Load or create a slideshow
+2. Click "📁 Add Directory"
+3. Select folder containing images
+4. Directory is recursively scanned and added
+```
+
+### Advanced Configuration
+
+#### Timing Patterns
+- **Constant**: `duration = 3000ms` (3 seconds per image)
+- **Random**: `min = 1500ms, max = 5000ms` (random between 1.5-5 seconds)
+- **Wave**: `waveMin = 1000ms, waveMax = 5000ms, rate = 100` (sinusoidal pattern)
+
+#### Transition Types
+- **Fade**: Smooth cross-fade transition with customizable duration
+- **None**: Instant image switching for rapid-fire effects
+
+#### Background Modes
+- **Solid**: Single color background
+- **Gradient**: Linear gradient from color A to color B
+- **Blurred**: Dynamically blurred version of current image
+
+## Technical Implementation
+
+### Recursive Directory Scanning
+The application uses a main process function for reliable directory traversal:
+
+```javascript
+// Main process implementation (main.js)
+ipcMain.handle('read-image-directory-recursive', async (event, dirPath) => {
+ // Recursive scanning with fs.readdir and fs.stat
+ // Handles subdirectories and file filtering
+ // Returns array of image file objects with metadata
+});
+
+// Renderer usage (hypno-gallery.html)
+const images = await window.electronAPI.readImageDirectoryRecursive(directoryPath);
+```
+
+### Image Loading Pipeline
+1. **Directory Scanning**: Recursively find all image files
+2. **Metadata Extraction**: File size, path, format detection
+3. **Preprocessing**: Path normalization, title generation
+4. **Memory Management**: On-demand loading with preloading optimization
+
+### Performance Optimizations
+- **GPU Acceleration**: CSS transitions and transforms
+- **Memory Efficiency**: Image preloading with memory limits
+- **File System Caching**: Reduced redundant directory scans
+- **Event Throttling**: Optimized mouse movement and resize handlers
+
+## Future Implementations
+
+### 🎯 Planned Features (Near-term)
+
+#### 📱 Grid Functionality
+```
+Multi-Slideshow Grid System:
+- Display multiple slideshows simultaneously
+- 2x2, 3x3, or custom grid layouts
+- Independent timing and settings per grid cell
+- Synchronized or independent playback modes
+- Grid-specific transition effects (wave patterns across cells)
+```
+
+#### 📝 Text Overlay System
+```
+Dynamic Text Integration:
+- Customizable text overlays on images
+- Multiple font options and styles
+- Position presets (center, corners, custom)
+- Text animation effects (fade in/out, scroll, pulse)
+- Per-slideshow text configuration
+- Image-specific text metadata support
+```
+
+#### 🎵 Audio Integration
+```
+Synchronized Audio Experience:
+- Background music support
+- Audio track per slideshow
+- Beat-synchronized image timing
+- Audio visualization elements
+- Volume and fade controls
+```
+
+#### 🎨 Advanced Visual Effects
+```
+Enhanced Visual Processing:
+- Image filters (sepia, blur, contrast, saturation)
+- Color correction and enhancement
+- Edge detection and artistic effects
+- Real-time image transformation
+- GPU-accelerated filter pipeline
+```
+
+### 🚀 Advanced Features (Long-term)
+
+#### 🌐 Network Capabilities
+```
+Cloud and Network Features:
+- Cloud storage integration
+- Network directory mounting
+- Remote slideshow control
+- Multi-device synchronization
+- Collaborative slideshow creation
+```
+
+#### 🤖 AI-Enhanced Features
+```
+Intelligent Automation:
+- AI-powered image categorization
+- Smart slideshow generation based on content
+- Mood-based timing adjustment
+- Automatic image enhancement
+- Content-aware transitions
+```
+
+#### 📊 Analytics and Insights
+```
+Usage Analytics:
+- Slideshow viewing statistics
+- Image popularity tracking
+- Timing pattern analysis
+- User interaction heatmaps
+- Performance metrics dashboard
+```
+
+#### 🎮 Interactive Elements
+```
+Enhanced Interactivity:
+- Touch gesture support
+- Voice control integration
+- Eye-tracking compatibility
+- Biometric feedback integration
+- Interactive hotspots on images
+```
+
+## Configuration Examples
+
+### Basic Slideshow Setup
+```javascript
+// Example slideshow configuration
+const slideshowConfig = {
+ name: "Nature Photography",
+ directories: [
+ "E:/Photos/Nature",
+ "E:/Photos/Landscapes"
+ ],
+ settings: {
+ timingMode: "random",
+ minDuration: 2000,
+ maxDuration: 6000,
+ transitionType: "fade",
+ transitionDuration: 800,
+ backgroundType: "blurred",
+ fitMode: "cover"
+ },
+ includeCapturedPhotos: false
+};
+```
+
+### Advanced Wave Timing
+```javascript
+// Wave timing for hypnotic effects
+const hypnoConfig = {
+ timingMode: "wave",
+ waveMin: 500, // Fastest: 0.5 seconds
+ waveMax: 4000, // Slowest: 4 seconds
+ waveRate: 50, // Wave frequency
+ transitionType: "fade",
+ transitionDuration: 1200
+};
+```
+
+## API Reference
+
+### Core Functions
+- `loadImagesFromDirectory(path)`: Recursively scan directory for images
+- `startSlideshow()`: Initialize and begin slideshow playback
+- `pauseSlideshow()`: Pause/resume slideshow
+- `stopSlideshow()`: Stop and exit slideshow mode
+- `createSlideshow(name)`: Create new slideshow configuration
+- `loadSlideshow(name)`: Load existing slideshow
+- `saveSlideshow()`: Persist current slideshow settings
+
+### Event Handlers
+- `onImageLoad`: Triggered when new image is loaded
+- `onTimingChange`: Fired when timing pattern changes
+- `onTransitionComplete`: Called after transition finishes
+- `onSlideshowEnd`: Triggered when slideshow completes loop
+
+## Browser Compatibility
+- **Electron**: Full feature support (recommended)
+- **Chrome/Edge**: Core features supported
+- **Firefox**: Core features supported
+- **Safari**: Limited support (no file system access)
+
+## Performance Notes
+- **Recommended**: 500+ images per slideshow
+- **Memory Usage**: ~50MB for 1000 images
+- **File Formats**: All modern image formats supported
+- **Directory Depth**: No practical limit on subdirectory scanning
+
+## Troubleshooting
+
+### Common Issues
+1. **Images not loading**: Check directory permissions and file formats
+2. **Slideshow won't start**: Ensure at least one directory is added with images
+3. **Transitions choppy**: Reduce image size or transition duration
+4. **Directory not found**: Verify path exists and contains supported image files
+
+### Debug Mode
+Enable debug logging by opening browser console - all operations are logged with detailed information.
+
+---
+
+**Version**: 2.0.0
+**Last Updated**: November 2025
+**Platform**: Electron Desktop Application
+**License**: Proprietary
\ No newline at end of file
diff --git a/INSTALLATION_GUIDE.md b/docs/INSTALLATION_GUIDE.md
similarity index 93%
rename from INSTALLATION_GUIDE.md
rename to docs/INSTALLATION_GUIDE.md
index 85121df..23de247 100644
--- a/INSTALLATION_GUIDE.md
+++ b/docs/INSTALLATION_GUIDE.md
@@ -5,13 +5,13 @@
### **Method 1: Automated Setup (Recommended)**
#### **Windows Users:**
-1. Double-click `setup.bat`
+1. Double-click `scripts/setup.bat`
2. Follow the prompts
-3. Launch with `Start-webgame.bat`
+3. Launch with `scripts/Start-webgame.bat`
#### **Mac/Linux Users:**
1. Open terminal in this folder
-2. Run: `chmod +x setup.sh && ./setup.sh`
+2. Run: `chmod +x scripts/setup.sh && ./scripts/setup.sh`
3. Launch with: `npm start`
### **Method 2: Manual Setup**
@@ -103,9 +103,10 @@ Gooner-Training-Academy/
├── 📄 TESTER_GUIDE.md # Testing instructions
├── 📄 INSTALLATION_GUIDE.md # This file
├── 📄 README.md # Full documentation
-├── 🔧 setup.bat # Windows installer
-├── 🔧 setup.sh # Mac/Linux installer
-├── 🚀 Start-webgame.bat # Windows launcher
+├── 📁 scripts/ # Installation and launch scripts
+│ ├── setup.bat # Windows installer
+│ ├── setup.sh # Mac/Linux installer
+│ └── Start-webgame.bat # Windows launcher
├── 📄 package.json # Dependencies
├── 🌐 index.html # Main application
├── 📁 src/ # Application code
diff --git a/README-ARCHIVED.md b/docs/README-ARCHIVED.md
similarity index 100%
rename from README-ARCHIVED.md
rename to docs/README-ARCHIVED.md
diff --git a/README-DESKTOP.md b/docs/README-DESKTOP.md
similarity index 100%
rename from README-DESKTOP.md
rename to docs/README-DESKTOP.md
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..4dac662
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,67 @@
+# 📚 Documentation Index
+
+Welcome to the Gooner Training Academy documentation! This directory contains all guides, references, and technical documentation for the application.
+
+## 📖 Getting Started
+
+| Document | Purpose | Target Audience |
+|----------|---------|----------------|
+| [INSTALLATION_GUIDE.md](INSTALLATION_GUIDE.md) | Setup instructions and system requirements | New users, testers |
+| [TESTER_GUIDE.md](TESTER_GUIDE.md) | Testing procedures and focus areas | Beta testers, QA |
+| [README-DESKTOP.md](README-DESKTOP.md) | Desktop-specific features and setup | Desktop users |
+
+## 🎮 Feature Documentation
+
+| Document | Purpose | Target Audience |
+|----------|---------|----------------|
+| [HYPNO-GALLERY-README.md](HYPNO-GALLERY-README.md) | Complete Hypno Gallery feature guide | Users, developers |
+
+## 📋 Project Management
+
+| Document | Purpose | Target Audience |
+|----------|---------|----------------|
+| [ROADMAP.md](ROADMAP.md) | Development roadmap and future plans | Stakeholders, developers |
+| [tasks.md](tasks.md) | Task lists and development todos | Development team |
+| [organized-tasks.md](organized-tasks.md) | Structured task organization | Project managers |
+| [levels.md](levels.md) | Game progression and level design | Game designers |
+| [consequences.md](consequences.md) | Punishment system documentation | Content designers |
+
+## 📦 Distribution & Release
+
+| Document | Purpose | Target Audience |
+|----------|---------|----------------|
+| [DISTRIBUTION_SUMMARY.md](DISTRIBUTION_SUMMARY.md) | Release packaging information | Release managers |
+| [BETA_FEEDBACK_FORM.md](BETA_FEEDBACK_FORM.md) | Beta testing feedback template | Beta testers |
+
+## 🗃️ Archives
+
+The [archive/](archive/) directory contains historical documentation and deprecated guides:
+
+| Document | Status | Notes |
+|----------|--------|-------|
+| [sprint1.md](archive/sprint1.md) | Archived | First development sprint documentation |
+| [note.md](archive/note.md) | Archived | Development notes |
+| [GAME_MODE_CLEANUP_PLAN.md](archive/GAME_MODE_CLEANUP_PLAN.md) | Archived | Game mode refactoring plan |
+| [MEDIA_LIBRARY_CLEANUP_PLAN.md](archive/MEDIA_LIBRARY_CLEANUP_PLAN.md) | Archived | Media system refactoring plan |
+| [DATA_LOSS_INCIDENT.md](archive/DATA_LOSS_INCIDENT.md) | Archived | Historical data loss incident report |
+
+## 🚀 Quick Navigation
+
+### For New Users:
+1. Start with [INSTALLATION_GUIDE.md](INSTALLATION_GUIDE.md)
+2. Review [TESTER_GUIDE.md](TESTER_GUIDE.md) if participating in beta testing
+3. Check feature-specific documentation as needed
+
+### For Developers:
+1. Review [ROADMAP.md](ROADMAP.md) for project direction
+2. Check [tasks.md](tasks.md) for current development priorities
+3. Consult feature documentation for implementation details
+
+### For Testers:
+1. Follow [INSTALLATION_GUIDE.md](INSTALLATION_GUIDE.md) for setup
+2. Use [TESTER_GUIDE.md](TESTER_GUIDE.md) for testing procedures
+3. Submit feedback via [BETA_FEEDBACK_FORM.md](BETA_FEEDBACK_FORM.md)
+
+---
+
+**Need help?** Check the main [README.md](../README.md) in the root directory for comprehensive application documentation and support resources.
\ No newline at end of file
diff --git a/TESTER_GUIDE.md b/docs/TESTER_GUIDE.md
similarity index 100%
rename from TESTER_GUIDE.md
rename to docs/TESTER_GUIDE.md
diff --git a/hypno-gallery.html b/hypno-gallery.html
index 1f68109..9ce0cd6 100644
--- a/hypno-gallery.html
+++ b/hypno-gallery.html
@@ -226,6 +226,19 @@
background: rgba(0, 0, 0, 0.7);
padding: 1rem;
border-radius: 10px;
+ opacity: 0;
+ transition: opacity 0.3s ease-in-out;
+ pointer-events: none;
+ }
+
+ .slideshow-controls.visible {
+ opacity: 1;
+ pointer-events: auto;
+ }
+
+ .slideshow-container:hover .slideshow-controls {
+ opacity: 1;
+ pointer-events: auto;
}
.slideshow-controls button {
@@ -251,6 +264,19 @@
padding: 1rem;
border-radius: 10px;
font-family: 'Courier New', monospace;
+ opacity: 0;
+ transition: opacity 0.3s ease-in-out;
+ pointer-events: none;
+ }
+
+ .slideshow-info.visible {
+ opacity: 1;
+ pointer-events: auto;
+ }
+
+ .slideshow-container:hover .slideshow-info {
+ opacity: 1;
+ pointer-events: auto;
}
.navigation {
@@ -269,6 +295,16 @@
height: 4px;
background: rgba(255, 255, 255, 0.2);
display: none;
+ opacity: 0;
+ transition: opacity 0.3s ease-in-out;
+ }
+
+ .progress-container.visible {
+ opacity: 1;
+ }
+
+ .slideshow-container:hover .progress-container {
+ opacity: 1;
}
.progress-bar {
@@ -404,6 +440,99 @@
+
+
✨ Transition Settings
@@ -624,9 +796,20 @@
transitionType: 'fade',
transitionDuration: 500,
selectedDirectories: [], // Array of selected directory paths
- includeCapturedPhotos: true
+ includeCapturedPhotos: true,
+ backgroundType: 'solid',
+ backgroundColor: '#000000',
+ gradientColor1: '#000000',
+ gradientColor2: '#1a1a1a',
+ gradientDirection: 'to bottom',
+ blurAmount: 20,
+ blurOpacity: 70
};
+ // Slideshow Management Variables
+ let savedSlideshows = {};
+ let currentSlideshowName = null;
+
let slideshow = {
images: [],
currentIndex: 0,
@@ -678,13 +861,12 @@
}
if (savedSettings.includeCapturedPhotos !== undefined) {
currentSettings.includeCapturedPhotos = savedSettings.includeCapturedPhotos;
- document.getElementById('includeCapturedPhotos').checked = savedSettings.includeCapturedPhotos;
}
console.log('💾 Loaded directory preferences:', currentSettings.selectedDirectories);
// Populate directory list
- populateDirectoryList(linkedDirectories);
+ // populateDirectoryList(linkedDirectories); // UI element removed
} catch (error) {
console.error('❌ Error initializing directory selection:', error);
@@ -694,6 +876,10 @@
// Populate Directory List
function populateDirectoryList(directories) {
const directoriesList = document.getElementById('linkedDirectoriesList');
+ if (!directoriesList) {
+ console.log('📁 Directory list UI element not found (removed), skipping population');
+ return;
+ }
if (directories.length === 0) {
directoriesList.innerHTML = '
No image directories linked. Use Library to add directories.
';
@@ -1131,27 +1317,72 @@
updateTransitionSettings(e.target.value);
});
- // Include captured photos checkbox
- document.getElementById('includeCapturedPhotos').addEventListener('change', (e) => {
- currentSettings.includeCapturedPhotos = e.target.checked;
+ // Include captured photos checkbox (removed from UI)
+ const capturedPhotosCheckbox = document.getElementById('includeCapturedPhotos');
+ if (capturedPhotosCheckbox) {
+ capturedPhotosCheckbox.addEventListener('change', (e) => {
+ currentSettings.includeCapturedPhotos = e.target.checked;
+ saveHypnoGallerySettings();
+ console.log('📷 Captured photos inclusion:', e.target.checked);
+
+ // Refresh image library
+ setTimeout(() => {
+ initializeImageLibrary();
+ }, 100);
+ });
+ }
+
+ // Refresh directories button (removed from UI)
+ const refreshButton = document.getElementById('refreshDirectories');
+ if (refreshButton) {
+ refreshButton.addEventListener('click', async () => {
+ console.log('🔄 Refreshing directories...');
+ await initializeDirectorySelection();
+ await initializeImageLibrary();
+ });
+ }
+
+ // Background settings
+ document.getElementById('backgroundType').addEventListener('change', (e) => {
+ currentSettings.backgroundType = e.target.value;
+ updateBackgroundOptions(e.target.value);
+ applyBackground();
saveHypnoGallerySettings();
- console.log('📷 Captured photos inclusion:', e.target.checked);
-
- // Refresh image library
- setTimeout(() => {
- initializeImageLibrary();
- }, 100);
});
- // Refresh directories button
- document.getElementById('refreshDirectories').addEventListener('click', async () => {
- console.log('🔄 Refreshing directories...');
- await initializeDirectorySelection();
- await initializeImageLibrary();
+ // Color pickers and hex inputs
+ setupColorControl('backgroundColor');
+ setupColorControl('gradientColor1');
+ setupColorControl('gradientColor2');
+
+ document.getElementById('gradientDirection').addEventListener('change', (e) => {
+ currentSettings.gradientDirection = e.target.value;
+ applyBackground();
+ saveHypnoGallerySettings();
+ });
+
+ // Blur controls
+ document.getElementById('blurAmount').addEventListener('input', (e) => {
+ const value = parseInt(e.target.value);
+ currentSettings.blurAmount = value;
+ document.getElementById('blurAmountValue').textContent = value + 'px';
+ applyBackground();
+ saveHypnoGallerySettings();
+ });
+
+ document.getElementById('blurOpacity').addEventListener('input', (e) => {
+ const value = parseInt(e.target.value);
+ currentSettings.blurOpacity = value;
+ document.getElementById('blurOpacityValue').textContent = value + '%';
+ applyBackground();
+ saveHypnoGallerySettings();
});
// Keyboard controls
document.addEventListener('keydown', handleKeyPress);
+
+ // Mouse movement controls for auto-hiding UI
+ setupMouseControls();
}
// Update timing settings display
@@ -1188,16 +1419,19 @@
}
// Start slideshow
- function startSlideshow() {
- if (imageLibrary.length === 0) {
- alert('No images available for slideshow. Please add images to your library first.');
+ async function startSlideshow() {
+ console.log('🌀 Starting Hypno Gallery slideshow...');
+
+ // Get slideshow-specific images
+ const slideshowImages = await getSlideshowImages();
+
+ if (slideshowImages.length === 0) {
+ alert('No images available for slideshow. Please configure slideshow directories or add images to your library first.');
return;
}
- console.log('🌀 Starting Hypno Gallery slideshow...');
-
// Prepare image array
- slideshow.images = [...imageLibrary];
+ slideshow.images = [...slideshowImages];
// Apply image ordering
switch (currentSettings.imageOrder) {
@@ -1266,6 +1500,14 @@
imgElement.src = `file://${currentImage.fullPath}`;
}
+ // Update background for blurred mode
+ if (currentSettings.backgroundType === 'blurred') {
+ // Wait for image to load before updating background
+ imgElement.onload = () => {
+ setTimeout(() => applyBackground(), 100); // Small delay to ensure image is rendered
+ };
+ }
+
// Apply fit mode
switch (currentSettings.fitMode) {
case 'contain':
@@ -1384,6 +1626,11 @@
function nextImage() {
if (!slideshow.isPlaying) return;
+ // Cancel current timing to restart timer
+ if (slideshow.nextTimeout) {
+ clearTimeout(slideshow.nextTimeout);
+ }
+
slideshow.currentIndex++;
loadCurrentImage();
scheduleNextImage();
@@ -1458,29 +1705,56 @@
// Handle keyboard input
function handleKeyPress(event) {
- if (!slideshow.isPlaying) return;
+ // Allow keyboard controls even when slideshow is paused
+ if (!slideshow.isPlaying && !slideshow.isPaused) return;
switch (event.key) {
case ' ':
case 'p':
+ case 'k': // Video player convention
event.preventDefault();
pauseSlideshow();
break;
case 'ArrowRight':
case 'n':
+ case 'l': // Right arrow alternative
+ case 'j': // Vim-like forward
event.preventDefault();
nextImage();
break;
case 'ArrowLeft':
case 'b':
+ case 'h': // Left arrow alternative
+ case 'k': // Vim-like backward (if not pausing)
event.preventDefault();
- previousImage();
+ if (event.key === 'k' && slideshow.isPlaying) {
+ pauseSlideshow(); // k pauses when playing
+ } else {
+ previousImage();
+ }
break;
case 'Escape':
case 'q':
+ case 'x':
event.preventDefault();
stopSlideshow();
break;
+ case 'f':
+ case 'F11':
+ event.preventDefault();
+ toggleFullscreen();
+ break;
+ case 'r':
+ event.preventDefault();
+ // Restart slideshow from beginning
+ slideshow.currentIndex = 0;
+ displayCurrentImage();
+ break;
+ case '?':
+ case 'h':
+ event.preventDefault();
+ showKeyboardHelp();
+ break;
}
}
@@ -1492,6 +1766,126 @@
}
}
+ // Mouse control setup for auto-hiding UI
+ let mouseTimeout;
+ let controlsVisible = false;
+
+ function setupMouseControls() {
+ const slideshowContainer = document.getElementById('slideshowContainer');
+ if (!slideshowContainer) return;
+
+ // Show controls on mouse movement
+ slideshowContainer.addEventListener('mousemove', () => {
+ if (!slideshow.isPlaying && !slideshow.isPaused) return;
+
+ showControls();
+
+ // Clear existing timeout
+ if (mouseTimeout) {
+ clearTimeout(mouseTimeout);
+ }
+
+ // Hide controls after 3 seconds of no movement
+ mouseTimeout = setTimeout(() => {
+ hideControls();
+ }, 3000);
+ });
+
+ // Keep controls visible when hovering over them
+ const controls = slideshowContainer.querySelector('.slideshow-controls');
+ const info = slideshowContainer.querySelector('.slideshow-info');
+
+ [controls, info].forEach(element => {
+ if (element) {
+ element.addEventListener('mouseenter', () => {
+ if (mouseTimeout) {
+ clearTimeout(mouseTimeout);
+ }
+ showControls();
+ });
+
+ element.addEventListener('mouseleave', () => {
+ mouseTimeout = setTimeout(() => {
+ hideControls();
+ }, 1000);
+ });
+ }
+ });
+
+ // Controls start hidden and only show on mouse hover
+ // No initial hideControls() call needed as CSS handles this
+ }
+
+ function showControls() {
+ controlsVisible = true;
+ const slideshowContainer = document.getElementById('slideshowContainer');
+ if (!slideshowContainer) return;
+
+ const controls = slideshowContainer.querySelector('.slideshow-controls');
+ const info = slideshowContainer.querySelector('.slideshow-info');
+ const progress = slideshowContainer.querySelector('.progress-container');
+
+ if (controls) controls.classList.add('visible');
+ if (info) info.classList.add('visible');
+ if (progress && progress.style.display !== 'none') progress.classList.add('visible');
+ }
+
+ function hideControls() {
+ controlsVisible = false;
+ const slideshowContainer = document.getElementById('slideshowContainer');
+ if (!slideshowContainer) return;
+
+ const controls = slideshowContainer.querySelector('.slideshow-controls');
+ const info = slideshowContainer.querySelector('.slideshow-info');
+ const progress = slideshowContainer.querySelector('.progress-container');
+
+ if (controls) controls.classList.remove('visible');
+ if (info) info.classList.remove('visible');
+ if (progress) progress.classList.remove('visible');
+ }
+
+ function showControlsTemporarily() {
+ // Keyboard controls no longer show UI - only mouse hover does
+ // This function kept for potential future use
+ return;
+ }
+
+ function toggleFullscreen() {
+ if (!document.fullscreenElement) {
+ document.documentElement.requestFullscreen().catch(err => {
+ console.log('Error attempting to enable fullscreen:', err);
+ });
+ } else {
+ document.exitFullscreen().catch(err => {
+ console.log('Error attempting to exit fullscreen:', err);
+ });
+ }
+ }
+
+ function showKeyboardHelp() {
+ const helpText = `
+🎮 KEYBOARD CONTROLS:
+
+▶️ PLAYBACK:
+ Space, P, K - Pause/Resume
+ →, N, L, J - Next image
+ ←, B, H - Previous image
+ R - Restart from beginning
+
+🎯 NAVIGATION:
+ Esc, Q, X - Stop slideshow
+ F, F11 - Toggle fullscreen
+
+❓ HELP:
+ ?, H - Show this help
+
+ℹ️ Controls auto-hide after 3 seconds of no mouse movement.
+ Move mouse or use keyboard to show them again.
+ `;
+
+ alert(helpText);
+ }
+
// Show image gallery (for testing/preview)
function showImageGallery() {
if (imageLibrary.length === 0) {
@@ -1523,10 +1917,1126 @@
document.body.insertAdjacentHTML('beforeend', galleryHtml);
}
+ // Slideshow Management Functions
+ function loadSavedSlideshows() {
+ try {
+ const saved = localStorage.getItem('hypnoGallerySlideshows');
+ if (saved) {
+ savedSlideshows = JSON.parse(saved);
+ console.log('📋 Loaded slideshows:', Object.keys(savedSlideshows).length);
+ } else {
+ savedSlideshows = {};
+ console.log('📋 No saved slideshows found, initialized empty object');
+ }
+ updateSlideshowsList();
+ } catch (error) {
+ console.error('❌ Error loading slideshows:', error);
+ savedSlideshows = {};
+ }
+ }
+
+ function saveSlideshowsToStorage() {
+ try {
+ localStorage.setItem('hypnoGallerySlideshows', JSON.stringify(savedSlideshows));
+ console.log('💾 Slideshows saved to storage');
+ } catch (error) {
+ console.error('❌ Error saving slideshows:', error);
+ }
+ }
+
+ function updateSlideshowsList() {
+ // This function is kept for compatibility but no longer manages dropdown
+ console.log('📋 Slideshows available:', Object.keys(savedSlideshows || {}).length);
+
+ // Update slideshow details if current slideshow is loaded
+ updateSlideshowDetails();
+ }
+
+ function updateSlideshowDetails() {
+ const details = document.getElementById('slideshowDetails');
+ const directoryList = document.getElementById('slideshowDirectoryList');
+ const addBtn = document.getElementById('addDirectoryBtn');
+ const removeBtn = document.getElementById('removeDirectoryBtn');
+ const testBtn = document.getElementById('testDirectoriesBtn');
+
+ if (currentSlideshowName && savedSlideshows[currentSlideshowName]) {
+ const slideshow = savedSlideshows[currentSlideshowName];
+ const directories = slideshow.directories || [];
+ const sourcesCount = directories.length + (slideshow.includeCapturedPhotos ? 1 : 0);
+
+ details.innerHTML = `
+
+ 🎬 ${currentSlideshowName}
+
+
📁 Directories: ${directories.length} folders
+
📷 Captured Photos: ${slideshow.includeCapturedPhotos ? 'Included' : 'Excluded'}
+
⏱️ Timing: ${slideshow.settings.timingMode} (${slideshow.settings.duration}ms)
+
✨ Transition: ${slideshow.settings.transitionType} (${slideshow.settings.transitionDuration}ms)
+
📅 Created: ${slideshow.created ? new Date(slideshow.created).toLocaleDateString() : 'Unknown'}
+ `;
+
+ // Update directory list
+ if (directories.length > 0) {
+ directoryList.innerHTML = directories.map((dir, index) => `
+
+ 📁 ${dir.name || dir}
+
+ `).join('');
+ } else {
+ directoryList.innerHTML = '
No directories added
';
+ }
+
+ // Enable buttons
+ addBtn.disabled = false;
+ document.getElementById('addFromLibraryBtn').disabled = false;
+ removeBtn.disabled = true; // Enable when directory is selected
+ testBtn.disabled = directories.length === 0;
+
+ } else {
+ details.innerHTML = '
Select a slideshow to view details
';
+ directoryList.innerHTML = '
Select a slideshow to manage directories
';
+
+ // Disable buttons
+ addBtn.disabled = true;
+ document.getElementById('addFromLibraryBtn').disabled = true;
+ removeBtn.disabled = true;
+ testBtn.disabled = true;
+ }
+ }
+
+ function showCreateSlideshowDialog() {
+ const dialog = document.createElement('div');
+ dialog.id = 'create-slideshow-dialog';
+ dialog.innerHTML = `
+
+
+
🎬 Create New Slideshow
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(dialog);
+
+ // Focus the input and select all text with multiple attempts
+ const focusInput = () => {
+ const input = document.getElementById('dialogSlideshowName');
+ if (input) {
+ input.focus();
+ input.select();
+
+ // Add click handler to ensure input can be clicked
+ input.addEventListener('click', () => {
+ input.focus();
+ });
+
+ // Add a safety focus on any dialog click
+ dialog.addEventListener('click', (e) => {
+ if (e.target === input) {
+ input.focus();
+ }
+ });
+ }
+ };
+
+ // Try multiple times to ensure focus works
+ setTimeout(focusInput, 50);
+ setTimeout(focusInput, 150);
+ setTimeout(focusInput, 300);
+
+ // Handle Enter key
+ document.getElementById('dialogSlideshowName').addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') {
+ createSlideshowFromDialog();
+ } else if (e.key === 'Escape') {
+ closeSlideshowDialog();
+ }
+ });
+ }
+
+ function showLoadSlideshowDialog() {
+ console.log('🔍 Debug - savedSlideshows:', savedSlideshows);
+ console.log('🔍 Debug - slideshow count:', savedSlideshows ? Object.keys(savedSlideshows).length : 'savedSlideshows is null/undefined');
+ console.log('🔍 Debug - localStorage check:', localStorage.getItem('hypnoGallerySlideshows'));
+
+ // Reload slideshows if empty but localStorage has data
+ if ((!savedSlideshows || Object.keys(savedSlideshows).length === 0)) {
+ const saved = localStorage.getItem('hypnoGallerySlideshows');
+ if (saved) {
+ try {
+ savedSlideshows = JSON.parse(saved);
+ console.log('🔄 Reloaded slideshows from localStorage:', Object.keys(savedSlideshows).length);
+ } catch (e) {
+ console.error('❌ Error parsing saved slideshows:', e);
+ }
+ }
+ }
+
+ if (!savedSlideshows || Object.keys(savedSlideshows).length === 0) {
+ alert('No saved slideshows available. Create one first!');
+ return;
+ }
+
+ const dialog = document.createElement('div');
+ dialog.id = 'load-slideshow-dialog';
+ dialog.innerHTML = `
+
+
+
📂 Load Slideshow
+
+
+
+
+
+
+
+ Select a slideshow to view details
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(dialog);
+
+ // Add change listener for slideshow selection
+ document.getElementById('dialogSlideshowSelect').addEventListener('change', updateDialogSlideshowInfo);
+ }
+
+ function updateDialogSlideshowInfo() {
+ const select = document.getElementById('dialogSlideshowSelect');
+ const info = document.getElementById('dialogSlideshowInfo');
+ const loadBtn = document.getElementById('dialogLoadBtn');
+
+ if (select.value && savedSlideshows[select.value]) {
+ const slideshow = savedSlideshows[select.value];
+ const directories = slideshow.directories || [];
+
+ info.innerHTML = `
+
📁 Directories: ${directories.length} folders
+
📷 Captured Photos: ${slideshow.includeCapturedPhotos ? 'Included' : 'Excluded'}
+
⏱️ Timing: ${slideshow.settings.timingMode}
+
📅 Created: ${slideshow.created ? new Date(slideshow.created).toLocaleDateString() : 'Unknown'}
+ `;
+ loadBtn.disabled = false;
+ } else {
+ info.textContent = 'Select a slideshow to view details';
+ loadBtn.disabled = true;
+ }
+ }
+
+ function createSlideshowFromDialog() {
+ const nameInput = document.getElementById('dialogSlideshowName');
+ const name = nameInput.value.trim();
+
+ if (!name) {
+ alert('Please enter a slideshow name');
+ nameInput.focus();
+ return;
+ }
+
+ if (savedSlideshows[name]) {
+ if (!confirm(`Slideshow "${name}" already exists. Replace it?`)) {
+ nameInput.focus();
+ return;
+ }
+ }
+
+ // Create slideshow with current settings and empty directories
+ savedSlideshows[name] = {
+ name: name,
+ settings: { ...currentSettings },
+ directories: [], // Slideshow-specific directories
+ includeCapturedPhotos: true, // Default to including captured photos
+ created: Date.now(),
+ modified: Date.now()
+ };
+
+ saveSlideshowsToStorage();
+ updateSlideshowsList();
+
+ // Load the new slideshow
+ loadSlideshow(name);
+
+ closeSlideshowDialog();
+
+ console.log('✅ Created slideshow:', name);
+ alert(`Slideshow "${name}" created successfully!`);
+ }
+
+ function loadSlideshowFromDialog() {
+ const select = document.getElementById('dialogSlideshowSelect');
+ const name = select.value;
+
+ if (!name || !savedSlideshows[name]) {
+ alert('Please select a slideshow to load');
+ return;
+ }
+
+ // Load the slideshow directly
+ loadSlideshow(name);
+
+ closeSlideshowDialog();
+
+ console.log('📂 Loaded slideshow from dialog:', name);
+ }
+
+ function closeSlideshowDialog() {
+ const createDialog = document.getElementById('create-slideshow-dialog');
+ const loadDialog = document.getElementById('load-slideshow-dialog');
+
+ if (createDialog) createDialog.remove();
+ if (loadDialog) loadDialog.remove();
+ }
+
+ function loadSlideshow(slideshowName = null) {
+ // If no name provided and no current slideshow, return
+ if (!slideshowName && !currentSlideshowName) {
+ currentSlideshowName = null;
+ updateCurrentSessionDisplay();
+ return;
+ }
+
+ const name = slideshowName || currentSlideshowName;
+
+ if (!name || !savedSlideshows[name]) {
+ // Clear settings if no slideshow selected
+ currentSlideshowName = null;
+ updateCurrentSessionDisplay();
+ return;
+ }
+
+ const slideshow = savedSlideshows[name];
+
+ // Load settings
+ Object.assign(currentSettings, slideshow.settings);
+ currentSlideshowName = name;
+
+ // Update UI controls
+ updateUIFromSettings();
+ updateCurrentSessionDisplay();
+ updateSlideshowDetails();
+
+ console.log('📂 Auto-loaded slideshow:', name);
+ }
+
+ function saveCurrentAsSlideshow() {
+ if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
+ alert('Please load a slideshow first, or create a new one');
+ return;
+ }
+
+ if (!confirm(`Update slideshow "${currentSlideshowName}" with current settings?`)) {
+ return;
+ }
+
+ savedSlideshows[currentSlideshowName].settings = { ...currentSettings };
+ savedSlideshows[currentSlideshowName].modified = Date.now();
+
+ saveSlideshowsToStorage();
+ updateSlideshowDetails();
+
+ console.log('💾 Updated slideshow:', currentSlideshowName);
+ alert(`Slideshow "${currentSlideshowName}" updated successfully!`);
+ }
+
+ function duplicateSlideshow() {
+ if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
+ alert('Please load a slideshow first');
+ return;
+ }
+
+ showDuplicateSlideshowDialog(currentSlideshowName);
+ }
+
+ function showDuplicateSlideshowDialog(originalName) {
+ const dialog = document.createElement('div');
+ dialog.id = 'duplicate-slideshow-dialog';
+ dialog.innerHTML = `
+
+
+
📋 Duplicate Slideshow
+
+
+ Duplicating: ${originalName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(dialog);
+
+ // Focus the input and select all text
+ const focusInput = () => {
+ const input = document.getElementById('dialogDuplicateName');
+ if (input) {
+ input.focus();
+ input.select();
+ }
+ };
+
+ setTimeout(focusInput, 50);
+ setTimeout(focusInput, 150);
+
+ // Handle Enter key
+ document.getElementById('dialogDuplicateName').addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') {
+ executeDuplicate();
+ } else if (e.key === 'Escape') {
+ closeDuplicateDialog();
+ }
+ });
+ }
+
+ function executeDuplicate() {
+ const input = document.getElementById('dialogDuplicateName');
+ const newName = input.value.trim();
+ const originalName = currentSlideshowName;
+
+ if (!newName) {
+ alert('Please enter a name for the duplicate slideshow');
+ input.focus();
+ return;
+ }
+
+ if (savedSlideshows[newName]) {
+ if (!confirm(`Slideshow "${newName}" already exists. Replace it?`)) {
+ input.focus();
+ return;
+ }
+ }
+
+ // Perform the duplication
+ savedSlideshows[newName] = {
+ ...savedSlideshows[originalName],
+ name: newName,
+ created: Date.now(),
+ modified: Date.now()
+ };
+
+ saveSlideshowsToStorage();
+ updateSlideshowsList();
+
+ // Load the new slideshow
+ loadSlideshow(newName);
+
+ closeDuplicateDialog();
+
+ console.log('📋 Duplicated slideshow:', originalName, '→', newName);
+ alert(`Slideshow duplicated as "${newName}"!`);
+ }
+
+ function closeDuplicateDialog() {
+ const dialog = document.getElementById('duplicate-slideshow-dialog');
+ if (dialog) dialog.remove();
+ }
+
+ function showLibrarySelectionDialog() {
+ if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
+ alert('Please load a slideshow first');
+ return;
+ }
+
+ const dialog = document.createElement('div');
+ dialog.id = 'library-selection-dialog';
+ dialog.innerHTML = `
+
+
+
📚 Add from Library
+
+
+
+
+
+
+
+
+
+
+ Loading directories...
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(dialog);
+
+ // Load available directories
+ loadLibraryDirectories();
+ }
+
+ async function loadLibraryDirectories() {
+ const container = document.getElementById('libraryDirectoriesList');
+
+ try {
+ // Get linked directories from localStorage
+ const linkedDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
+ console.log('📚 Loading library directories:', linkedDirectories);
+
+ if (linkedDirectories.length === 0) {
+ container.innerHTML = '
No directories in library. Add some through the main game interface first.
';
+ return;
+ }
+
+ // Create checkboxes for each directory
+ container.innerHTML = linkedDirectories.map((dir, index) => {
+ const dirPath = dir.path || dir;
+ const dirName = typeof dir === 'string' ? dir.split(/[\\\\/]/).pop() : (dir.name || dirPath.split(/[\\\\/]/).pop() || 'Unknown Directory');
+ const imageCount = dir.imageCount || dir.images?.length || 0;
+
+ return `
+
+
+
+
+ `;
+ }).join('');
+
+ } catch (error) {
+ console.error('❌ Error loading library directories:', error);
+ container.innerHTML = '
Error loading directories
';
+ }
+ }
+
+ function toggleLibraryDirectory(index) {
+ const checkbox = document.getElementById(`libDir_${index}`);
+ if (checkbox) {
+ checkbox.checked = !checkbox.checked;
+ }
+ }
+
+ function addSelectedFromLibrary() {
+ const includeCapturedPhotos = document.getElementById('libraryCapturedPhotos').checked;
+ const selectedDirectories = [];
+
+ // Get selected directories from localStorage
+ const linkedDirectories = JSON.parse(localStorage.getItem('linkedImageDirectories') || '[]');
+ linkedDirectories.forEach((dir, index) => {
+ const checkbox = document.getElementById(`libDir_${index}`);
+ if (checkbox && checkbox.checked) {
+ const dirPath = dir.path || dir;
+ selectedDirectories.push({
+ path: dirPath,
+ name: typeof dir === 'string' ? dir.split(/[\\\\/]/).pop() : (dir.name || dirPath.split(/[\\\\/]/).pop() || 'Unknown Directory')
+ });
+ }
+ });
+
+ // Allow adding nothing (user might want slideshow-specific directories only)
+ if (selectedDirectories.length === 0 && !includeCapturedPhotos) {
+ if (!confirm('No library directories or captured photos will be added. Continue anyway?')) {
+ return;
+ }
+ }
+
+ // Add to current slideshow
+ const slideshow = savedSlideshows[currentSlideshowName];
+ if (!slideshow.directories) {
+ slideshow.directories = [];
+ }
+
+ // Add directories (check for duplicates)
+ let addedCount = 0;
+ selectedDirectories.forEach(newDir => {
+ const exists = slideshow.directories.find(existingDir =>
+ (typeof existingDir === 'string' ? existingDir : existingDir.path) === newDir.path
+ );
+
+ if (!exists) {
+ slideshow.directories.push(newDir);
+ addedCount++;
+ }
+ });
+
+ // Update captured photos setting only if it changed
+ const currentCapturedPhotos = slideshow.includeCapturedPhotos || false;
+ if (includeCapturedPhotos !== currentCapturedPhotos) {
+ slideshow.includeCapturedPhotos = includeCapturedPhotos;
+ }
+
+ // Save and update
+ slideshow.modified = Date.now();
+ saveSlideshowsToStorage();
+ updateSlideshowDetails();
+
+ closeLibraryDialog();
+
+ // Better messaging
+ let message = '';
+ if (addedCount > 0) {
+ message = `Added ${addedCount} director${addedCount === 1 ? 'y' : 'ies'} to slideshow!`;
+ } else if (selectedDirectories.length > 0) {
+ message = 'No new directories were added (duplicates found)';
+ } else {
+ message = 'No directories selected.';
+ }
+
+ if (includeCapturedPhotos !== currentCapturedPhotos) {
+ const capturedAction = includeCapturedPhotos ? 'enabled' : 'disabled';
+ message += ` Captured photos ${capturedAction}.`;
+ }
+
+ if (message.trim()) {
+ alert(message);
+ }
+
+ console.log('📚 Added from library:', { directories: addedCount, capturedPhotos: includeCapturedPhotos });
+ }
+
+ function closeLibraryDialog() {
+ const dialog = document.getElementById('library-selection-dialog');
+ if (dialog) dialog.remove();
+ }
+
+ // Background Management Functions
+ function setupColorControl(colorId) {
+ const colorPicker = document.getElementById(colorId);
+ const hexInput = document.getElementById(colorId + 'Hex');
+
+ colorPicker.addEventListener('change', (e) => {
+ const color = e.target.value;
+ hexInput.value = color;
+ currentSettings[colorId] = color;
+ applyBackground();
+ saveHypnoGallerySettings();
+ });
+
+ hexInput.addEventListener('input', (e) => {
+ const color = e.target.value;
+ if (/^#[0-9A-F]{6}$/i.test(color)) {
+ colorPicker.value = color;
+ currentSettings[colorId] = color;
+ applyBackground();
+ saveHypnoGallerySettings();
+ }
+ });
+ }
+
+ function updateBackgroundOptions(type) {
+ const solidOptions = document.getElementById('solidColorOptions');
+ const gradientOptions = document.getElementById('gradientOptions');
+ const blurredOptions = document.getElementById('blurredOptions');
+
+ // Hide all options first
+ solidOptions.style.display = 'none';
+ gradientOptions.style.display = 'none';
+ blurredOptions.style.display = 'none';
+
+ // Show relevant options
+ switch (type) {
+ case 'solid':
+ solidOptions.style.display = 'flex';
+ break;
+ case 'gradient':
+ gradientOptions.style.display = 'block';
+ break;
+ case 'blurred':
+ blurredOptions.style.display = 'block';
+ break;
+ }
+ }
+
+ function applyBackground() {
+ const slideshowContainer = document.getElementById('slideshowContainer');
+ if (!slideshowContainer) return;
+
+ const { backgroundType, backgroundColor, gradientColor1, gradientColor2, gradientDirection, blurAmount, blurOpacity } = currentSettings;
+
+ // Ensure container has proper positioning for backgrounds
+ if (slideshowContainer.style.position !== 'relative') {
+ slideshowContainer.style.position = 'relative';
+ }
+
+ // Remove existing background elements
+ const existingBg = slideshowContainer.querySelector('.slideshow-background');
+ if (existingBg) existingBg.remove();
+
+ // Reset any previous background styles
+ slideshowContainer.style.background = '';
+ slideshowContainer.style.backgroundColor = '';
+
+ switch (backgroundType) {
+ case 'solid':
+ slideshowContainer.style.backgroundColor = backgroundColor;
+ break;
+
+ case 'gradient':
+ const gradientBg = `linear-gradient(${gradientDirection}, ${gradientColor1}, ${gradientColor2})`;
+ slideshowContainer.style.background = gradientBg;
+ break;
+
+ case 'blurred':
+ slideshowContainer.style.backgroundColor = '#000000'; // Fallback color
+ createBlurredBackground();
+ break;
+ }
+ }
+
+ function createBlurredBackground() {
+ const slideshowContainer = document.getElementById('slideshowContainer');
+ const currentImg = document.getElementById('slideshowImage');
+
+ if (!currentImg || !currentImg.src || !slideshowContainer) {
+ console.log('🫧 Cannot create blurred background:', {
+ hasContainer: !!slideshowContainer,
+ hasImg: !!currentImg,
+ hasSrc: !!currentImg?.src
+ });
+ return;
+ }
+
+ console.log('🫧 Creating blurred background with:', currentImg.src);
+
+ // Create background element
+ const backgroundDiv = document.createElement('div');
+ backgroundDiv.className = 'slideshow-background';
+ backgroundDiv.style.cssText = `
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image: url("${currentImg.src}");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ filter: blur(${currentSettings.blurAmount}px);
+ opacity: ${currentSettings.blurOpacity / 100};
+ z-index: 1;
+ pointer-events: none;
+ `;
+
+ // Insert background behind the main image
+ slideshowContainer.insertBefore(backgroundDiv, slideshowContainer.firstChild);
+
+ // Ensure main image is above background
+ if (currentImg.style.position !== 'relative') {
+ currentImg.style.position = 'relative';
+ currentImg.style.zIndex = '2';
+ }
+
+ // Ensure all controls are above the background (z-index only, preserve existing positioning)
+ const slideshowInfo = document.getElementById('slideshowInfo');
+ const slideshowControls = slideshowContainer.querySelector('.slideshow-controls');
+ const progressBar = slideshowContainer.querySelector('.progress-bar');
+
+ if (slideshowInfo) {
+ slideshowInfo.style.zIndex = '10';
+ }
+
+ if (slideshowControls) {
+ slideshowControls.style.zIndex = '10';
+ }
+
+ if (progressBar) {
+ progressBar.style.zIndex = '10';
+ }
+
+ console.log('✅ Blurred background created successfully');
+ }
+
+ function deleteSlideshow() {
+ if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
+ alert('Please load a slideshow first');
+ return;
+ }
+
+ const name = currentSlideshowName;
+
+ if (!confirm(`Delete slideshow "${name}"? This cannot be undone.`)) {
+ return;
+ }
+
+ delete savedSlideshows[name];
+ saveSlideshowsToStorage();
+ updateSlideshowsList();
+
+ // Clear current slideshow since it was deleted
+ currentSlideshowName = null;
+ updateCurrentSessionDisplay();
+
+ console.log('🗑️ Deleted slideshow:', name);
+ alert(`Slideshow "${name}" deleted successfully!`);
+ }
+
+ function updateUIFromSettings() {
+ // Timing settings
+ document.getElementById('timingMode').value = currentSettings.timingMode;
+ document.getElementById('durationSlider').value = currentSettings.duration;
+ document.getElementById('minDurationSlider').value = currentSettings.minDuration;
+ document.getElementById('maxDurationSlider').value = currentSettings.maxDuration;
+ document.getElementById('waveMinSlider').value = currentSettings.waveMin;
+ document.getElementById('waveMaxSlider').value = currentSettings.waveMax;
+ document.getElementById('waveRateSlider').value = currentSettings.waveRate;
+
+ // Transition settings
+ document.getElementById('transitionType').value = currentSettings.transitionType;
+ document.getElementById('transitionDuration').value = currentSettings.transitionDuration;
+
+ // Directory settings (checkbox removed)
+
+ // Background settings
+ document.getElementById('backgroundType').value = currentSettings.backgroundType;
+ document.getElementById('backgroundColor').value = currentSettings.backgroundColor;
+ document.getElementById('backgroundColorHex').value = currentSettings.backgroundColor;
+ document.getElementById('gradientColor1').value = currentSettings.gradientColor1;
+ document.getElementById('gradientColor1Hex').value = currentSettings.gradientColor1;
+ document.getElementById('gradientColor2').value = currentSettings.gradientColor2;
+ document.getElementById('gradientColor2Hex').value = currentSettings.gradientColor2;
+ document.getElementById('gradientDirection').value = currentSettings.gradientDirection;
+ document.getElementById('blurAmount').value = currentSettings.blurAmount;
+ document.getElementById('blurAmountValue').textContent = currentSettings.blurAmount + 'px';
+ document.getElementById('blurOpacity').value = currentSettings.blurOpacity;
+ document.getElementById('blurOpacityValue').textContent = currentSettings.blurOpacity + '%';
+
+ // Update displays
+ updateTimingSettings(currentSettings.timingMode);
+ updateTransitionSettings(currentSettings.transitionType);
+ updateBackgroundOptions(currentSettings.backgroundType);
+
+ // Refresh directory display (if function exists)
+ if (typeof updateLinkedDirectories === 'function') {
+ updateLinkedDirectories();
+ }
+ }
+
+ function updateCurrentSessionDisplay() {
+ let sourcesCount = 0;
+
+ if (currentSlideshowName && savedSlideshows[currentSlideshowName]) {
+ const slideshow = savedSlideshows[currentSlideshowName];
+ sourcesCount = (slideshow.directories || []).length + (slideshow.includeCapturedPhotos ? 1 : 0);
+ } else {
+ sourcesCount = currentSettings.selectedDirectories.length +
+ (currentSettings.includeCapturedPhotos ? 1 : 0);
+ }
+
+ document.getElementById('currentSources').textContent =
+ sourcesCount > 0 ? `${sourcesCount} sources selected` : 'No sources selected';
+
+ let timingText = currentSettings.timingMode;
+ if (currentSettings.timingMode === 'constant') {
+ timingText += ` (${(currentSettings.duration/1000).toFixed(1)}s)`;
+ } else if (currentSettings.timingMode === 'random') {
+ timingText += ` (${(currentSettings.minDuration/1000).toFixed(1)}-${(currentSettings.maxDuration/1000).toFixed(1)}s)`;
+ } else if (currentSettings.timingMode === 'wave') {
+ timingText += ` (${(currentSettings.waveMin/1000).toFixed(1)}-${(currentSettings.waveMax/1000).toFixed(1)}s)`;
+ }
+ document.getElementById('currentTiming').textContent = timingText;
+
+ document.getElementById('currentTransitions').textContent =
+ `${currentSettings.transitionType} (${(currentSettings.transitionDuration/1000).toFixed(1)}s)`;
+ }
+
+ // Slideshow Directory Management Functions
+ let selectedDirectoryIndex = -1;
+
+ function selectSlideshowDirectory(index) {
+ // Remove previous selection
+ document.querySelectorAll('.slideshow-directory-item').forEach(item => {
+ item.style.border = '1px solid transparent';
+ item.style.background = 'rgba(102, 126, 234, 0.1)';
+ });
+
+ // Select new item
+ const items = document.querySelectorAll('.slideshow-directory-item');
+ if (items[index]) {
+ items[index].style.border = '1px solid #667eea';
+ items[index].style.background = 'rgba(102, 126, 234, 0.3)';
+ selectedDirectoryIndex = index;
+ document.getElementById('removeDirectoryBtn').disabled = false;
+ }
+ }
+
+ async function addDirectoryToSlideshow() {
+ if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
+ alert('Please load a slideshow first');
+ return;
+ }
+
+ try {
+ // Check if we're in Electron environment
+ if (!window.electronAPI || !window.electronAPI.selectDirectory) {
+ alert('Directory selection is only available in the desktop version of the application.');
+ return;
+ }
+
+ // Use the existing selectDirectory method
+ const directoryPath = await window.electronAPI.selectDirectory();
+
+ if (directoryPath) {
+ const directoryName = directoryPath.split(/[\\/]/).pop();
+
+ // Initialize directories array if it doesn't exist
+ if (!savedSlideshows[currentSlideshowName].directories) {
+ savedSlideshows[currentSlideshowName].directories = [];
+ }
+
+ // Check if directory already exists
+ const existingDir = savedSlideshows[currentSlideshowName].directories.find(dir =>
+ (typeof dir === 'string' ? dir : dir.path) === directoryPath
+ );
+
+ if (existingDir) {
+ alert('This directory is already added to the slideshow');
+ return;
+ }
+
+ // Add directory to slideshow
+ savedSlideshows[currentSlideshowName].directories.push({
+ path: directoryPath,
+ name: directoryName
+ });
+
+ savedSlideshows[currentSlideshowName].modified = Date.now();
+ saveSlideshowsToStorage();
+ updateSlideshowDetails();
+ updateCurrentSessionDisplay();
+
+ console.log('📁 Added directory to slideshow:', directoryName);
+ alert(`Added directory: ${directoryName}`);
+ } else {
+ console.log('Directory selection cancelled');
+ }
+
+ } catch (error) {
+ console.error('❌ Error adding directory:', error);
+ alert('Error adding directory: ' + error.message);
+ }
+ }
+
+ function removeDirectoryFromSlideshow() {
+ if (!currentSlideshowName || !savedSlideshows[currentSlideshowName] || selectedDirectoryIndex === -1) {
+ return;
+ }
+
+ const directories = savedSlideshows[currentSlideshowName].directories || [];
+ if (selectedDirectoryIndex >= 0 && selectedDirectoryIndex < directories.length) {
+ const removedDir = directories[selectedDirectoryIndex];
+ directories.splice(selectedDirectoryIndex, 1);
+
+ savedSlideshows[currentSlideshowName].modified = Date.now();
+ saveSlideshowsToStorage();
+
+ selectedDirectoryIndex = -1;
+ updateSlideshowDetails();
+ updateCurrentSessionDisplay();
+
+ console.log('🗑️ Removed directory from slideshow:', removedDir.name || removedDir);
+ }
+ }
+
+ async function testSlideshowDirectories() {
+ if (!currentSlideshowName || !savedSlideshows[currentSlideshowName]) {
+ return;
+ }
+
+ const slideshow = savedSlideshows[currentSlideshowName];
+ const directories = slideshow.directories || [];
+
+ if (directories.length === 0) {
+ alert('No directories to test');
+ return;
+ }
+
+ let totalImages = 0;
+ let validDirs = 0;
+ let invalidDirs = [];
+
+ // Test each directory
+ for (const dir of directories) {
+ const dirPath = typeof dir === 'string' ? dir : dir.path;
+ const dirName = typeof dir === 'string' ? dirPath.split(/[\\/]/).pop() : dir.name;
+
+ try {
+ // For electron environments, we could use fs to check directory
+ // For now, just assume they're valid and count as 1 image each for demo
+ validDirs++;
+ totalImages += 10; // Placeholder count
+ } catch (error) {
+ invalidDirs.push(dirName);
+ }
+ }
+
+ const message = `
+📁 Directory Test Results:
+✅ Valid directories: ${validDirs}
+❌ Invalid directories: ${invalidDirs.length}
+🖼️ Estimated images: ${totalImages}
+
+${invalidDirs.length > 0 ? `\nInvalid directories:\n${invalidDirs.join('\n')}` : ''}
+ `;
+
+ alert(message.trim());
+ }
+
+ async function getSlideshowImages() {
+ let images = [];
+
+ // If a slideshow is loaded, use its directories
+ if (currentSlideshowName && savedSlideshows[currentSlideshowName]) {
+ const slideshow = savedSlideshows[currentSlideshowName];
+ const directories = slideshow.directories || [];
+
+ console.log('📁 Loading images from slideshow directories:', directories.length);
+
+ // Load images from slideshow-specific directories
+ for (const dir of directories) {
+ const dirPath = typeof dir === 'string' ? dir : dir.path;
+ try {
+ const dirImages = await loadImagesFromDirectory(dirPath);
+ images.push(...dirImages);
+ console.log(`📁 Loaded ${dirImages.length} images from: ${dirPath}`);
+ } catch (error) {
+ console.error(`❌ Error loading directory ${dirPath}:`, error);
+ }
+ }
+
+ // Include captured photos if enabled
+ if (slideshow.includeCapturedPhotos) {
+ const capturedPhotos = getCapturedPhotos();
+ images.push(...capturedPhotos);
+ console.log(`📷 Added ${capturedPhotos.length} captured photos`);
+ }
+ } else {
+ // Fallback to main library if no slideshow selected
+ console.log('📚 Using main image library');
+ images = [...imageLibrary];
+ }
+
+ console.log(`🖼️ Total slideshow images: ${images.length}`);
+ return images;
+ }
+
+ async function loadImagesFromDirectory(directoryPath) {
+ // Load images from a directory recursively using the main process function
+ console.log(`🔍 Starting recursive image scan of: ${directoryPath}`);
+
+ if (typeof window !== 'undefined' && window.electronAPI && window.electronAPI.readImageDirectoryRecursive) {
+ try {
+ // Use the proper main process recursive function
+ const files = await window.electronAPI.readImageDirectoryRecursive(directoryPath);
+ console.log(`✅ Found ${files.length} images recursively`);
+
+ // Convert to the expected format for the slideshow system
+ const processedFiles = files.map(file => ({
+ name: file.name,
+ fullPath: file.path,
+ isWebcamCapture: false,
+ source: 'slideshow-directory'
+ }));
+
+ return processedFiles;
+ } catch (error) {
+ console.error('❌ Error reading directory recursively:', error);
+ return [];
+ }
+ } else {
+ console.warn('⚠️ Directory loading not available in this environment');
+ return [];
+ }
+ }
+
+
+
+ function getCapturedPhotos() {
+ // Get captured photos from localStorage or game library
+ try {
+ const captured = localStorage.getItem('capturedPhotos');
+ if (captured) {
+ const photos = JSON.parse(captured);
+ return photos.map(photo => ({
+ name: photo.name || 'Captured Photo',
+ path: photo.dataURL || photo.path,
+ fullPath: photo.dataURL || photo.path,
+ isWebcamCapture: true,
+ source: 'captured-photos'
+ }));
+ }
+ } catch (error) {
+ console.error('❌ Error loading captured photos:', error);
+ }
+ return [];
+ }
+
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
console.log('🌀 Hypno Gallery DOM loaded');
- setTimeout(initializeHypnoGallery, 1000);
+ setTimeout(() => {
+ initializeHypnoGallery();
+ loadSavedSlideshows();
+ updateCurrentSessionDisplay();
+
+ // Dropdown removed - slideshow loading now handled by dialog system
+ }, 1000);
});
// Initialize timing settings display
diff --git a/index.html b/index.html
index c6276fa..eeed46b 100644
--- a/index.html
+++ b/index.html
@@ -7024,6 +7024,42 @@
return { success: false, reason: 'PlayerStats not available' };
};
+
+ // ===== ANIMATION EVENT LISTENERS =====
+
+ // Listen for level up events
+ window.addEventListener('levelUp', (event) => {
+ console.log('🎉 Level up event received:', event.detail);
+ updateLevelDisplay(); // Update the display immediately
+ });
+
+ // Listen for achievement events
+ window.addEventListener('achievementUnlocked', (event) => {
+ console.log('🏆 Achievement unlocked event received:', event.detail);
+ });
+
+ // Listen for player stats updates
+ window.addEventListener('playerStatsUpdated', (event) => {
+ console.log('📊 Player stats updated:', event.detail);
+ updateLevelDisplay(); // Keep display in sync
+ });
+
+ // Test function for animations
+ window.testLevelUpAnimation = function() {
+ const oldLevel = { level: 4, name: 'Aroused', icon: '🌿', description: 'Your arousal is building' };
+ const newLevel = { level: 5, name: 'Lustful', icon: '🌟', description: 'Consumed by lust and craving more' };
+ window.showLevelUpAnimation(oldLevel, newLevel, 25, 'test');
+ };
+
+ window.testAchievementAnimation = function() {
+ const achievement = {
+ id: 'test',
+ icon: '🏆',
+ title: 'Test Achievement',
+ description: 'This is a test achievement for demonstration'
+ };
+ window.showAchievementAnimation(achievement);
+ };
diff --git a/porn-cinema.html b/porn-cinema.html
index 380889e..3d7bdc8 100644
--- a/porn-cinema.html
+++ b/porn-cinema.html
@@ -316,41 +316,305 @@
showExitConfirmationDialog();
});
+ function getSassyTheaterAttendantDialog() {
+ // Array of sassy theater attendant responses
+ const sassyResponses = [
+ {
+ avatar: '🎭',
+ title: 'Leaving Already?',
+ message: 'Oh honey, the show was just getting good! Are you sure you want to walk out now?',
+ snarkIcon: '🍿',
+ snark: 'I mean, I guess not everyone has the stamina for a full feature presentation...',
+ exitIcon: '🚶♀️',
+ exitText: 'Leave Theater',
+ stayIcon: '🎬',
+ stayText: 'Keep Watching'
+ },
+ {
+ avatar: '💅',
+ title: 'Really? Right Now?',
+ message: 'Sweetie, you\'re about to miss the best part! The climax is coming up...',
+ snarkIcon: '🎪',
+ snark: 'But sure, go ahead and leave during the most exciting scene. Your loss!',
+ exitIcon: '🚶♀️',
+ exitText: 'Leave Theater',
+ stayIcon: '🎬',
+ stayText: 'Keep Watching'
+ },
+ {
+ avatar: '😏',
+ title: 'Intermission Over?',
+ message: 'Darling, this isn\'t a bathroom break - you\'re actually leaving? How disappointing...',
+ snarkIcon: '🎭',
+ snark: 'I thought you had better taste in entertainment. Guess I was wrong.',
+ exitIcon: '🚶♀️',
+ exitText: 'Leave Theater',
+ stayIcon: '🎬',
+ stayText: 'Keep Watching'
+ },
+ {
+ avatar: '🎪',
+ title: 'Show\'s Not Over!',
+ message: 'Excuse me? The feature presentation is still running! You can\'t just leave mid-scene!',
+ snarkIcon: '🎬',
+ snark: 'This is like walking out of the theater during the finale. Absolutely scandalous!',
+ exitIcon: '🚶♀️',
+ exitText: 'Leave Theater',
+ stayIcon: '🎬',
+ stayText: 'Keep Watching'
+ },
+ {
+ avatar: '💄',
+ title: 'Attention Deficit?',
+ message: 'Oh sweetie, can\'t focus for more than five minutes? The good stuff requires patience...',
+ snarkIcon: '⏰',
+ snark: 'Real connoisseurs know that the best performances build up slowly to an explosive finish.',
+ exitIcon: '🚶♀️',
+ exitText: 'Leave Theater',
+ stayIcon: '🎬',
+ stayText: 'Keep Watching'
+ },
+ {
+ avatar: '🎬',
+ title: 'Intermission Confusion?',
+ message: 'Hun, this isn\'t the lobby! You\'re in the middle of premium content here!',
+ snarkIcon: '🍾',
+ snark: 'Some people pay extra for this level of entertainment, and you\'re just... walking away?',
+ exitIcon: '🚶♀️',
+ exitText: 'Leave Theater',
+ stayIcon: '🎬',
+ stayText: 'Keep Watching'
+ }
+ ];
+
+ // Return a random sassy response
+ return sassyResponses[Math.floor(Math.random() * sassyResponses.length)];
+ }
+
function showExitConfirmationDialog() {
+ // Get sassy theater attendant content
+ const attendantDialog = getSassyTheaterAttendantDialog();
+
// Create modal dialog
const modal = document.createElement('div');
- modal.className = 'confirmation-modal';
+ modal.className = 'confirmation-modal cinema-attendant-modal';
modal.innerHTML = `
-
+
+
+
+
+
+
+
+
+
+
@@ -1066,6 +1082,10 @@
let isVideoPlaying = true;
let videoControlListenersInitialized = false;
+ // Scenario System Variables
+ let currentScenarioTask = null;
+ let currentScenarioStep = 'start';
+
// TTS Variables
let voiceManager = null;
let ttsEnabled = true;
@@ -1167,15 +1187,33 @@
function toggleTTS() {
ttsEnabled = !ttsEnabled;
- const toggleBtn = document.getElementById('tts-toggle');
+ // Update header toggle button
+ const headerToggleBtn = document.getElementById('tts-toggle');
+ // Update sidebar toggle button
+ const sidebarToggleBtn = document.getElementById('sidebar-tts-toggle');
+
if (ttsEnabled) {
- toggleBtn.innerHTML = '🔊 Voice: ON';
- toggleBtn.style.background = 'linear-gradient(135deg, #8B5CF6, #EC4899)';
+ if (headerToggleBtn) {
+ headerToggleBtn.innerHTML = '🔊 Voice: ON';
+ headerToggleBtn.style.background = 'linear-gradient(135deg, #8B5CF6, #EC4899)';
+ }
+ if (sidebarToggleBtn) {
+ sidebarToggleBtn.innerHTML = '🔊 ON';
+ sidebarToggleBtn.style.background = 'rgba(139, 92, 246, 0.4)';
+ sidebarToggleBtn.style.borderColor = 'rgba(139, 92, 246, 0.6)';
+ }
updateTTSStatus('Voice enabled');
speakText('Voice narration enabled.');
} else {
- toggleBtn.innerHTML = '🔇 Voice: OFF';
- toggleBtn.style.background = 'linear-gradient(135deg, #6B7280, #9CA3AF)';
+ if (headerToggleBtn) {
+ headerToggleBtn.innerHTML = '🔇 Voice: OFF';
+ headerToggleBtn.style.background = 'linear-gradient(135deg, #6B7280, #9CA3AF)';
+ }
+ if (sidebarToggleBtn) {
+ sidebarToggleBtn.innerHTML = '🔇 OFF';
+ sidebarToggleBtn.style.background = 'rgba(107, 114, 128, 0.4)';
+ sidebarToggleBtn.style.borderColor = 'rgba(107, 114, 128, 0.6)';
+ }
updateTTSStatus('Voice disabled');
stopTTS();
}
@@ -1196,9 +1234,16 @@
// Update TTS status display
function updateTTSStatus(status) {
- const statusEl = document.getElementById('tts-status-text');
- if (statusEl) {
- statusEl.textContent = status;
+ // Update header status
+ const headerStatusEl = document.getElementById('tts-status-text');
+ if (headerStatusEl) {
+ headerStatusEl.textContent = status;
+ }
+
+ // Update sidebar status
+ const sidebarStatusEl = document.getElementById('sidebar-tts-status');
+ if (sidebarStatusEl) {
+ sidebarStatusEl.textContent = status;
}
}
@@ -1209,6 +1254,30 @@
}
}
+ // Initialize sidebar TTS controls to match current state
+ function initializeSidebarTTSControls() {
+ const sidebarToggleBtn = document.getElementById('sidebar-tts-toggle');
+ const sidebarStatusEl = document.getElementById('sidebar-tts-status');
+
+ if (sidebarToggleBtn) {
+ if (ttsEnabled) {
+ sidebarToggleBtn.innerHTML = '🔊 ON';
+ sidebarToggleBtn.style.background = 'rgba(139, 92, 246, 0.4)';
+ sidebarToggleBtn.style.borderColor = 'rgba(139, 92, 246, 0.6)';
+ } else {
+ sidebarToggleBtn.innerHTML = '🔇 OFF';
+ sidebarToggleBtn.style.background = 'rgba(107, 114, 128, 0.4)';
+ sidebarToggleBtn.style.borderColor = 'rgba(107, 114, 128, 0.6)';
+ }
+ }
+
+ if (sidebarStatusEl) {
+ sidebarStatusEl.textContent = ttsEnabled ? 'Voice ready' : 'Voice disabled';
+ }
+
+ console.log('🔊 Sidebar TTS controls initialized');
+ }
+
// ===== END TTS FUNCTIONS =====
// Helper function to get format from file path
@@ -1878,6 +1947,9 @@
}
}, 100);
+ // Initialize sidebar TTS controls state
+ initializeSidebarTTSControls();
+
console.log('✅ Video control listeners set up successfully');
videoControlListenersInitialized = true;
}
@@ -1977,8 +2049,27 @@
const modeName = availableModes[selectedTrainingMode]?.name || selectedTrainingMode;
speakText(`Starting ${modeName} training session. Your journey begins now.`);
- // Hide setup interface
- document.querySelector('.academy-header').style.display = 'none';
+ // Hide most setup interface but keep TTS controls accessible
+ const academyHeader = document.querySelector('.academy-header');
+ if (academyHeader) {
+ // Hide everything except TTS controls
+ const children = academyHeader.children;
+ for (let child of children) {
+ if (!child.classList.contains('tts-controls')) {
+ child.style.display = 'none';
+ }
+ }
+ // Make header smaller and less prominent during training
+ academyHeader.style.padding = '0.5rem';
+ academyHeader.style.margin = '0.5rem';
+ academyHeader.style.background = 'rgba(26, 26, 26, 0.7)';
+ academyHeader.style.position = 'fixed';
+ academyHeader.style.top = '10px';
+ academyHeader.style.right = '260px'; // Position next to video controls
+ academyHeader.style.width = 'auto';
+ academyHeader.style.zIndex = '1001';
+ academyHeader.style.borderRadius = '8px';
+ }
document.querySelector('.library-status').style.display = 'none';
document.querySelector('.training-controls').style.display = 'none';
document.querySelector('.academy-start-controls').style.display = 'none';
@@ -2462,7 +2553,7 @@
` : ''}
-
+
`;
@@ -2532,7 +2623,7 @@
` : ''}
-
+
`;
@@ -2712,6 +2803,372 @@
returnToModeSelection();
}
+ function showQuitConfirmation() {
+ console.log('⚠️ Showing quit training confirmation...');
+
+ // Get dynamic content based on selected training mode
+ const dialogContent = getQuipyDialogContent();
+
+ // Create confirmation overlay
+ const confirmOverlay = document.createElement('div');
+ confirmOverlay.id = 'quit-confirmation-overlay';
+ confirmOverlay.innerHTML = `
+
+
+
+
+
+
${dialogContent.message}
+
+ ${dialogContent.warningIcon}
+ ${dialogContent.warning}
+
+
+
+
+
+
+
+
+ `;
+
+ // Add styles
+ confirmOverlay.innerHTML += `
+
+ `;
+
+ document.body.appendChild(confirmOverlay);
+
+ // Close on backdrop click
+ confirmOverlay.querySelector('.confirmation-backdrop').onclick = cancelQuitTraining;
+
+ // ESC key handler
+ const escHandler = (e) => {
+ if (e.key === 'Escape') {
+ cancelQuitTraining();
+ }
+ };
+ document.addEventListener('keydown', escHandler);
+ confirmOverlay.escHandler = escHandler;
+ }
+
+ function getQuipyDialogContent() {
+ // Default content
+ let content = {
+ icon: '⚠️',
+ title: 'Quit Training Session?',
+ message: 'Are you sure you want to quit this training session?',
+ warningIcon: '🔥',
+ warning: 'Your progress will be saved, but you\'ll need to restart this session.',
+ quitIcon: '🚪',
+ quitText: 'Quit Training',
+ continueIcon: '💪',
+ continueText: 'Continue Training'
+ };
+
+ // Customize based on selected training mode
+ if (selectedTrainingMode) {
+ switch (selectedTrainingMode) {
+ case 'beginner':
+ content = {
+ icon: '🥺',
+ title: 'Giving Up Already?',
+ message: 'Come on, even beginners can handle more than this! You\'re just getting started.',
+ warningIcon: '💪',
+ warning: 'Real gooners don\'t quit during basic training. But hey, your choice...',
+ quitIcon: '😢',
+ quitText: 'I Give Up',
+ continueIcon: '🌟',
+ continueText: 'Keep Learning'
+ };
+ break;
+
+ case 'intermediate':
+ content = {
+ icon: '😏',
+ title: 'Can\'t Handle the Heat?',
+ message: 'Thought you were ready for intermediate training? Guess not everyone can level up.',
+ warningIcon: '🔥',
+ warning: 'You\'ll lose your momentum and have to rebuild that focus. Worth it?',
+ quitIcon: '🏃',
+ quitText: 'Retreat',
+ continueIcon: '🔥',
+ continueText: 'Bring the Heat'
+ };
+ break;
+
+ case 'advanced':
+ content = {
+ icon: '🤨',
+ title: 'Seriously? Advanced Gooner Quitting?',
+ message: 'An advanced gooner doesn\'t just walk away. This is embarrassing.',
+ warningIcon: '👑',
+ warning: 'You\'re supposed to be elite. Elite gooners finish what they start.',
+ quitIcon: '💸',
+ quitText: 'Be Weak',
+ continueIcon: '👑',
+ continueText: 'Stay Elite'
+ };
+ break;
+
+ case 'endurance':
+ content = {
+ icon: '💀',
+ title: 'Endurance Broken Already?',
+ message: 'The whole point is to build stamina. Quitting defeats the purpose entirely.',
+ warningIcon: '⏱️',
+ warning: 'Real endurance training means pushing through when it gets tough.',
+ quitIcon: '💔',
+ quitText: 'Tap Out',
+ continueIcon: '⚡',
+ continueText: 'Push Limits'
+ };
+ break;
+
+ case 'humiliation':
+ content = {
+ icon: '😈',
+ title: 'Too Humiliated to Continue?',
+ message: 'Aww, is the humiliation training too much for you? How... predictable.',
+ warningIcon: '🔥',
+ warning: 'Quitting now just proves you can\'t handle being properly trained.',
+ quitIcon: '🙈',
+ quitText: 'Run Away',
+ continueIcon: '😈',
+ continueText: 'Take More'
+ };
+ break;
+
+ case 'dress-up':
+ content = {
+ icon: '💄',
+ title: 'Not Feeling Pretty Enough?',
+ message: 'Can\'t handle getting all dressed up? But you were just starting to look so cute!',
+ warningIcon: '👗',
+ warning: 'You\'ll miss out on completing your fabulous transformation.',
+ quitIcon: '👔',
+ quitText: 'Stay Boring',
+ continueIcon: '💅',
+ continueText: 'Get Pretty'
+ };
+ break;
+
+ default:
+ // Keep default content for unknown modes
+ break;
+ }
+ }
+
+ // If we're in a scenario, add scenario-specific flavor
+ if (currentScenarioTask && currentScenarioTask.title) {
+ const scenario = currentScenarioTask.title.toLowerCase();
+ if (scenario.includes('sissy') || scenario.includes('feminiz')) {
+ content.icon = '💋';
+ content.title = 'Not Ready to Embrace It?';
+ } else if (scenario.includes('worship') || scenario.includes('goddess')) {
+ content.icon = '👸';
+ content.title = 'Abandoning Your Goddess?';
+ } else if (scenario.includes('edge') || scenario.includes('denial')) {
+ content.icon = '😤';
+ content.title = 'Can\'t Handle the Edge?';
+ }
+ }
+
+ return content;
+ }
+
+ function confirmQuitTraining() {
+ console.log('✅ Quit training confirmed');
+
+ // Remove confirmation overlay
+ const overlay = document.getElementById('quit-confirmation-overlay');
+ if (overlay) {
+ if (overlay.escHandler) {
+ document.removeEventListener('keydown', overlay.escHandler);
+ }
+ overlay.remove();
+ }
+
+ // Proceed with quitting
+ returnToModeSelection();
+ }
+
+ function cancelQuitTraining() {
+ console.log('❌ Quit training cancelled');
+
+ // Remove confirmation overlay
+ const overlay = document.getElementById('quit-confirmation-overlay');
+ if (overlay) {
+ if (overlay.escHandler) {
+ document.removeEventListener('keydown', overlay.escHandler);
+ }
+ overlay.style.animation = 'fadeOut 0.2s ease-out';
+ setTimeout(() => overlay.remove(), 200);
+ }
+ }
+
function returnToModeSelection() {
console.log('🔄 Returning to training mode selection...');
@@ -2744,14 +3201,71 @@
}
// Show setup interface again
- document.querySelector('.academy-header').style.display = 'block';
- document.querySelector('.library-status').style.display = 'block';
- document.querySelector('.training-controls').style.display = 'block';
- document.querySelector('.academy-start-controls').style.display = 'block';
+ const academyHeader = document.querySelector('.academy-header');
+ if (academyHeader) {
+ // Restore all children visibility
+ const children = academyHeader.children;
+ for (let child of children) {
+ child.style.display = '';
+ }
+ // Reset header styling
+ academyHeader.style.padding = '2rem';
+ academyHeader.style.margin = '1rem';
+ academyHeader.style.background = 'linear-gradient(135deg, #1a1a1a, #2d1b3d)';
+ academyHeader.style.position = 'static';
+ academyHeader.style.top = 'auto';
+ academyHeader.style.right = 'auto';
+ academyHeader.style.width = 'auto';
+ academyHeader.style.zIndex = 'auto';
+ academyHeader.style.borderRadius = '15px';
+ }
+ // Show main training academy interface
+ const libraryStatus = document.querySelector('.library-status');
+ if (libraryStatus) libraryStatus.style.display = 'block';
- // Hide game interface
+ const trainingControls = document.querySelector('.training-controls');
+ if (trainingControls) trainingControls.style.display = 'block';
+
+ const startControls = document.querySelector('.academy-start-controls');
+ if (startControls) startControls.style.display = 'block';
+
+ // Show the main training academy container
+ const academyContainer = document.querySelector('.training-academy-container');
+ if (academyContainer) {
+ academyContainer.style.display = 'block';
+ academyContainer.style.visibility = 'visible';
+ }
+
+ // Show training mode selection cards
+ const modeSelection = document.querySelector('.training-mode-selection');
+ if (modeSelection) {
+ modeSelection.style.display = 'block';
+ modeSelection.style.visibility = 'visible';
+ }
+
+ // Hide all game-related containers
const gameInterface = document.getElementById('gameInterface');
- gameInterface.style.display = 'none';
+ if (gameInterface) {
+ gameInterface.style.display = 'none';
+ }
+
+ const gameContainer = document.getElementById('game-container');
+ if (gameContainer) {
+ gameContainer.style.display = 'none';
+ gameContainer.innerHTML = '';
+ }
+
+ const taskDisplay = document.getElementById('training-task-display');
+ if (taskDisplay) {
+ taskDisplay.style.display = 'none';
+ taskDisplay.innerHTML = '';
+ }
+
+ // Clear any overlays that might be covering the interface
+ const overlays = document.querySelectorAll('.camera-overlay, .verification-overlay, #camera-overlay');
+ overlays.forEach(overlay => {
+ if (overlay) overlay.remove();
+ });
// Reset training mode cards
document.querySelectorAll('.training-mode-card').forEach(card => {
@@ -2771,8 +3285,6 @@
}
// Scenario Adventure Display System
- let currentScenarioStep = 'start';
- let currentScenarioTask = null;
function displayScenarioAdventure(container, task) {
console.log('🎭 Displaying scenario adventure:', task.id);
@@ -2793,6 +3305,16 @@
}
function displayScenarioStep(container, stepId) {
+ if (!currentScenarioTask) {
+ console.error('❌ No current scenario task available');
+ return;
+ }
+
+ if (!currentScenarioTask.interactiveData) {
+ console.error('❌ No interactive data available for current scenario task');
+ return;
+ }
+
const scenario = currentScenarioTask.interactiveData;
const step = scenario.steps[stepId];
@@ -2821,7 +3343,7 @@
`).join('')}
-
+
`;
@@ -2834,7 +3356,7 @@