Implement level display system replacing status panels

Features:
- Replace TOTAL XP and SESSION TIME panels with unified level display
- Show current level name and number (Virgin Level 1  Omnipotent Level 20)
- Display total XP with progress bar toward next level
- Real-time updates every 5 seconds and on PlayerStats changes

 UI Improvements:
- Cyberpunk-styled level display card matching billboard aesthetic
- Level name prominently displayed with level number
- XP progress bar showing advancement toward next level
- Responsive design maintaining home page layout

 Technical Implementation:
- Level calculation system using 100 XP per level progression
- Integration with existing PlayerStats XP tracking system
- Performance-optimized updates with event-driven refresh
- Fallback to localStorage if PlayerStats unavailable

 Level System:
- 20 adult-themed levels from Virgin to Omnipotent
- 100 XP required per level advancement
- Progress tracking shows current level XP and next level requirement
- Visual progress indicator for level advancement motivation
This commit is contained in:
dilgenfritz 2025-11-05 22:18:50 -06:00
parent 9a8d4b5432
commit c361c308c1
11 changed files with 5009 additions and 726 deletions

View File

@ -59,8 +59,9 @@
- ✅ **Ultra-Compact Controls**: Added 60px width volume slider with CSS override system
- ✅ **Complete Integration**: Focus interruption videos playing successfully with proper controls
- **✅ XP System Implementation (October 2025)**
- **Main Game**: 1 XP per task, 5 XP for 10-task streaks, no XP for quit sessions
- **Scenario Games**: Time-based XP (1 XP per 2 minutes), focus session bonuses (3 XP per 30 seconds), webcam bonuses, photo rewards
- **Quick-Play**: 1 XP for tasks completed under 5 minutes, 2 XP for tasks 5-10 minutes, 3 XP for tasks over 10 minutes
- **Scenario Games**: Time-based XP (1 XP per 2 minutes), webcam bonuses(x2 Multiplier per minute), photo rewards 2 XP per picture
- **Porn Cinema**: 1 XP per 5 minutes of video watched
## 📋 Feature Backlog

49
consequences.md Normal file
View File

@ -0,0 +1,49 @@
Eat your own precum while watching your wife's photos and imagining her with better men for 8 minutes
Do 20 push-ups while wearing your wife's panties
Practice begging on your knees for 10 minutes: 'Please let other men have my wife, I'm not worthy of her'
Gently push a finger inside your ass and hold it there for 1 minute.
Slap your cock 10 times while looking at your wife's photos and saying 'I don't deserve her' after each slap
Look at your wife's sexiest pictures while imagining her showing them to alpha males and chanting 'I'm a pathetic cuckold' for 12 minutes
Find your wife's hottest photos and post them on chatpic for real men until it has 1 download
Find your wife's sexiest lingerie, put it on, and practice crawling on all fours like a submissive pet for 8 minutes
Put on your wife's dress or skirt and pose femininely in the mirror for 10 minutes
Look at your wife's most revealing photos and post them on erome for other men to see until it has 50 views
Find your wife's dirtiest panties and sniff them while stroking for 5 minutes
Jerk off while imagining you're a cum dump for your bull, begging for him to fill you up.
Lick your wife's dirty panties while edging and repeating 'I'm not man enough for her' for 6 minutes
Push two fingers in your ass and fuck yourself while begging to be used like a sissy for 5 minutes
Find your wife's sexiest outfit and wear it while practicing sucking motions on a dildo for 12 minutes
Sniff your wife's worn shoes while jerking off and imagining her walking to meet her lover for 8 minutes
Finger your ass with one hand while slapping your cock with the other for 4 minutes
Wear your wife's bra and panties while doing jumping jacks for 3 minutes
Lick the inside of your wife's high heels while edging and saying 'She walks to better men' for 2 minutes
Push three fingers in your ass and stretch yourself while begging to be filled by a real man for 2 minutes
Do 30 squats while wearing your wife's thong and chanting 'I'm a sissy cuck' with each squat
Lick your wife's dirty socks while edging and repeating 'Real men deserve her pussy I deserve her feet' for 8 minutes
Wear your wife's shortest dress and practice walking slutty while stroking for 12 minutes
Do planks for 2 minutes while wearing your wife's sports and saying 'She's stronger than me'

View File

@ -38,6 +38,18 @@
</div>
<div class="game-container">
<!-- Side Characters -->
<div class="character-side left" style="background-image: url('assets/1.png'); top: 15%;"></div>
<div class="character-side right" style="background-image: url('assets/11.png'); top: 20%;"></div>
<div class="character-side left" style="background-image: url('assets/3.png'); top: 35%; left: -40px;"></div>
<div class="character-side right" style="background-image: url('assets/4.png'); top: 40%; right: -40px;"></div>
<div class="character-side left" style="background-image: url('assets/5.png'); top: 55%; left: -30px;"></div>
<div class="character-side right" style="background-image: url('assets/6.png'); top: 60%; right: -35px;"></div>
<div class="character-side left" style="background-image: url('assets/7.png'); top: 75%; left: -35px;"></div>
<div class="character-side right" style="background-image: url('assets/8.png'); top: 80%; right: -30px;"></div>
<div class="character-side left" style="background-image: url('assets/9.png'); top: 90%; left: -25px;"></div>
<div class="character-side right" style="background-image: url('assets/10.png'); top: 95%; right: -25px;"></div>
<!-- Game Header -->
<header class="game-header hero-header">
<div class="hero-background"></div>
@ -53,61 +65,73 @@
<span class="tagline-subtitle">• Advanced Training System • v3.0</span>
</p>
<!-- Feature Highlights -->
<!-- Feature Highlights - Now Main Action Buttons -->
<div class="hero-features">
<div class="hero-feature">
<button class="hero-feature btn-feature" id="training-academy-btn">
<span class="feature-icon">🎬</span>
<span class="feature-text">Multi-Screen Training</span>
</div>
<div class="hero-feature">
<span class="feature-text">Training Academy</span>
</button>
<button class="hero-feature btn-feature" id="quick-play-btn">
<span class="feature-icon">📊</span>
<span class="feature-text">Progress Analytics</span>
</div>
<div class="hero-feature">
<span class="feature-text">Quick Play</span>
</button>
<button class="hero-feature btn-feature" id="porn-cinema-btn">
<span class="feature-icon">🏆</span>
<span class="feature-text">Achievement System</span>
</div>
<div class="hero-feature">
<span class="feature-icon">🎵</span>
<span class="feature-text">Immersive Audio</span>
</div>
<span class="feature-text">Porn Cinema</span>
</button>
<button class="hero-feature btn-feature" id="library-btn">
<span class="cassie-icon"></span>
<span class="feature-text">Library</span>
</button>
</div>
</div>
<!-- Status Panel -->
<div class="hero-status-panel">
<!-- Overall XP Display -->
<div class="status-card overall-xp-display">
<div class="status-label">Total XP</div>
<div class="status-value">
<span id="overall-xp-header" class="xp-number">0</span>
<span class="xp-suffix">XP</span>
<!-- Level and XP Display -->
<div class="status-card level-display">
<div class="status-label">Current Level</div>
<div class="status-value level-info">
<div class="level-title" id="current-level-name">Virgin</div>
<div class="level-number">Level <span id="current-level-number">1</span></div>
</div>
<div class="status-bar">
<div class="status-progress" id="overall-xp-progress" style="width: 0%"></div>
</div>
</div>
<!-- Session Timer -->
<div class="status-card timer-compact">
<div class="status-label">Session Time</div>
<div class="status-value">
<span id="timer" class="timer-display">00:00</span>
</div>
<div class="timer-status-text" id="timer-status"></div>
</div>
<!-- Quick Stats -->
<div class="status-card quick-stats">
<div class="status-label">Quick Stats</div>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-number" id="header-sessions">0</span>
<span class="stat-label">Sessions</span>
<div class="level-xp-info">
<div class="xp-display">
<span id="level-xp-header" class="xp-number">0</span>
<span class="xp-suffix">XP</span>
</div>
<div class="stat-item">
<span class="stat-number" id="header-streak">0</span>
<span class="stat-label">Streak</span>
<div class="level-progress-container">
<div class="status-bar">
<div class="status-progress" id="level-xp-progress" style="width: 0%"></div>
</div>
<div class="level-progress-text">
<span id="level-progress-current">0</span> / <span id="level-progress-next">100</span> XP
</div>
</div>
</div>
</div>
</div>
<!-- Cyberpunk Video Billboard -->
<div class="video-billboard-container">
<div class="video-billboard">
<div class="billboard-frame">
<video id="billboard-video" muted playsinline preload="metadata">
<source id="billboard-video-source" src="" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="billboard-overlay">
<div class="billboard-controls">
<button id="billboard-mute" class="billboard-btn" title="Toggle Mute">🔇</button>
<button id="billboard-pause" class="billboard-btn" title="Toggle Pause">⏸️</button>
</div>
</div>
<div class="neon-border">
<div class="neon-edge top"></div>
<div class="neon-edge bottom"></div>
<div class="neon-edge left"></div>
<div class="neon-edge right"></div>
</div>
</div>
</div>
@ -147,82 +171,13 @@
<main class="game-content">
<!-- Start Screen -->
<div id="start-screen" class="screen active">
<h2>Ready to Start?</h2>
<p>Complete tasks, but beware of skipping - there are consequences!</p>
<!-- Main Action Buttons -->
<!-- Secondary Action Buttons -->
<div class="main-actions">
<button id="start-btn" class="btn btn-primary">Start Game</button>
<button id="quick-play-btn" class="btn btn-primary">⚡ Quick Play</button>
<button id="training-academy-btn" class="btn btn-primary">🎓 Training Academy</button>
<button id="porn-cinema-btn" class="btn btn-primary">🎬 Porn Cinema</button>
<button id="player-stats-btn" class="btn btn-secondary">📊 Player Stats</button>
<button id="user-profile-btn" class="btn btn-secondary">👤 Profile</button>
<button id="manage-tasks-btn" class="btn btn-secondary">Manage Tasks</button>
<button id="library-btn" class="btn btn-secondary">📚 Library</button>
<button id="manage-annoyance-btn" class="btn btn-secondary">😈 Annoyance</button>
</div>
<!-- Game Mode Selection -->
<div class="game-mode-selection">
<h3>Game Mode</h3>
<div class="game-mode-options">
<div class="game-mode-option">
<input type="radio" id="mode-complete-all" name="gameMode" value="complete-all" checked>
<label for="mode-complete-all">
<strong>🎯 Complete All Tasks</strong>
<p>Finish every task in the game</p>
</label>
</div>
<div class="game-mode-option">
<input type="radio" id="mode-timed" name="gameMode" value="timed">
<label for="mode-timed">
<strong>⏱️ Timed Challenge</strong>
<p>Complete as many tasks as possible within the time limit</p>
<div class="mode-config" id="timed-config" style="display: none;">
<label>Time Limit:
<select id="time-limit-select">
<option value="300">5 minutes</option>
<option value="600">10 minutes</option>
<option value="900">15 minutes</option>
<option value="1200">20 minutes</option>
<option value="1800">30 minutes</option>
<option value="custom">Custom...</option>
</select>
</label>
<div id="custom-time-input" style="display: none; margin-top: 10px;">
<label>Custom time (minutes):
<input type="number" id="custom-time-value" min="1" max="180" value="15" style="width: 60px;">
</label>
</div>
</div>
</label>
</div>
<div class="game-mode-option">
<input type="radio" id="mode-xp-target" name="gameMode" value="xp-target">
<label for="mode-xp-target">
<strong>🏆 XP Target</strong>
<p>Reach the target XP to win</p>
<div class="mode-config" id="xp-target-config" style="display: none;">
<label>Target XP:
<select id="xp-target-select">
<option value="100">100 XP</option>
<option value="200">200 XP</option>
<option value="300">300 XP</option>
<option value="custom">Custom...</option>
</select>
</label>
<div id="custom-xp-input" style="display: none; margin-top: 10px;">
<label>Custom target XP:
<input type="number" id="custom-xp-value" min="50" max="10000" value="300" style="width: 80px;">
</label>
</div>
</div>
</label>
</div>
</div>
</div>
<!-- Options Menu -->
<div class="options-section">
<button id="options-menu-btn" class="btn btn-tertiary">⚙️ Options</button>
@ -318,7 +273,6 @@
<!-- Other Options -->
<div class="option-item other-controls">
<button id="stats-btn" class="btn btn-option" title="View Statistics">📊 Stats</button>
<button id="help-btn" class="btn btn-option" title="Keyboard Shortcuts & Help">❓ Help</button>
</div>
</div>
@ -326,58 +280,6 @@
</div>
</div>
<!-- Task Management Screen -->
<div id="task-management-screen" class="screen">
<h2>Manage Your Tasks</h2>
<!-- Add New Task Section -->
<div class="task-editor-section">
<h3>Add New Task</h3>
<div class="task-input-group">
<label for="new-task-text">Task Description:</label>
<textarea id="new-task-text" placeholder="Enter your task description..." rows="3"></textarea>
</div>
<div class="task-input-group">
<label for="new-task-type">Task Type:</label>
<select id="new-task-type">
<option value="main">Main Task</option>
<option value="consequence">Consequence Task</option>
</select>
</div>
<div class="task-input-group" id="difficulty-input-group">
<label for="new-task-difficulty">Difficulty:</label>
<select id="new-task-difficulty">
<option value="Easy">🟢 Easy (1 point)</option>
<option value="Medium" selected>🟡 Medium (3 points)</option>
<option value="Hard">🔴 Hard (5 points)</option>
</select>
</div>
<button id="add-task-btn" class="btn btn-success">Add Task</button>
</div>
<!-- Existing Tasks Section -->
<div class="task-list-section">
<h3>Your Tasks</h3>
<div class="task-tabs">
<button id="main-tasks-tab" class="tab-btn active">Main Tasks</button>
<button id="consequence-tasks-tab" class="tab-btn">Consequence Tasks</button>
</div>
<div id="main-tasks-list" class="task-list active">
<!-- Main tasks will be populated here -->
</div>
<div id="consequence-tasks-list" class="task-list">
<!-- Consequence tasks will be populated here -->
</div>
</div>
<div class="management-buttons">
<button id="back-to-start-btn" class="btn btn-secondary">Back to Start</button>
<button id="reset-tasks-btn" class="btn btn-warning">Reset to Defaults</button>
</div>
</div>
<!-- Image Management Screen -->
<div id="image-management-screen" class="screen">
<h2>🖼️ Image Library Management</h2>
@ -1937,6 +1839,7 @@
<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>
<script src="src/features/webcam/webcamManager.js"></script>
<script src="src/features/tts/voiceManager.js"></script>
@ -3218,9 +3121,669 @@
}
}
// ===== LEVEL CALCULATION SYSTEM =====
const levelData = [
{ level: 1, name: "Virgin", xpRequired: 0 },
{ level: 2, name: "Curious", xpRequired: 100 },
{ level: 3, name: "Eager", xpRequired: 200 },
{ level: 4, name: "Aroused", xpRequired: 300 },
{ level: 5, name: "Lustful", xpRequired: 400 },
{ level: 6, name: "Passionate", xpRequired: 500 },
{ level: 7, name: "Addicted", xpRequired: 600 },
{ level: 8, name: "Obsessed", xpRequired: 700 },
{ level: 9, name: "Deviant", xpRequired: 800 },
{ level: 10, name: "Kinky", xpRequired: 900 },
{ level: 11, name: "Perverted", xpRequired: 1000 },
{ level: 12, name: "Depraved", xpRequired: 1100 },
{ level: 13, name: "Dominant", xpRequired: 1200 },
{ level: 14, name: "Submissive", xpRequired: 1300 },
{ level: 15, name: "Hedonist", xpRequired: 1400 },
{ level: 16, name: "Insatiable", xpRequired: 1500 },
{ level: 17, name: "Transcendent", xpRequired: 1600 },
{ level: 18, name: "Enlightened", xpRequired: 1700 },
{ level: 19, name: "Godlike", xpRequired: 1800 },
{ level: 20, name: "Omnipotent", xpRequired: 1900 }
];
function calculateLevel(totalXP) {
// Find the highest level that the user has reached
let currentLevel = levelData[0];
for (let i = levelData.length - 1; i >= 0; i--) {
if (totalXP >= levelData[i].xpRequired) {
currentLevel = levelData[i];
break;
}
}
// Calculate progress toward next level
const nextLevel = levelData.find(l => l.level === currentLevel.level + 1);
const currentLevelXP = totalXP - currentLevel.xpRequired;
const xpNeededForNext = nextLevel ? nextLevel.xpRequired - currentLevel.xpRequired : 100;
const progressPercent = nextLevel ? (currentLevelXP / xpNeededForNext) * 100 : 100;
return {
level: currentLevel.level,
name: currentLevel.name,
totalXP: totalXP,
currentLevelXP: currentLevelXP,
xpNeededForNext: xpNeededForNext,
progressPercent: Math.min(progressPercent, 100),
nextLevelName: nextLevel ? nextLevel.name : 'Max Level'
};
}
function updateLevelDisplay() {
// Get total XP from PlayerStats if available
let totalXP = 0;
if (window.playerStats && window.playerStats.stats) {
totalXP = window.playerStats.stats.totalXP || 0;
} else {
// Fallback to localStorage
const savedStats = localStorage.getItem('playerStats');
if (savedStats) {
try {
const stats = JSON.parse(savedStats);
totalXP = stats.totalXP || 0;
} catch (e) {
console.warn('Could not parse saved stats for level display');
}
}
}
const levelInfo = calculateLevel(totalXP);
// Update the display elements
const levelNameEl = document.getElementById('current-level-name');
const levelNumberEl = document.getElementById('current-level-number');
const levelXPEl = document.getElementById('level-xp-header');
const progressEl = document.getElementById('level-xp-progress');
const progressCurrentEl = document.getElementById('level-progress-current');
const progressNextEl = document.getElementById('level-progress-next');
if (levelNameEl) levelNameEl.textContent = levelInfo.name;
if (levelNumberEl) levelNumberEl.textContent = levelInfo.level;
if (levelXPEl) levelXPEl.textContent = levelInfo.totalXP.toLocaleString();
if (progressEl) progressEl.style.width = levelInfo.progressPercent + '%';
if (progressCurrentEl) progressCurrentEl.textContent = levelInfo.currentLevelXP;
if (progressNextEl) progressNextEl.textContent = levelInfo.xpNeededForNext;
console.log(`📊 Level Display Updated: ${levelInfo.name} (Level ${levelInfo.level}) - ${levelInfo.totalXP} XP`);
}
// Update level display when page loads and periodically
function initializeLevelDisplay() {
updateLevelDisplay();
// Update every 5 seconds to catch XP changes
setInterval(updateLevelDisplay, 5000);
// Also update when PlayerStats changes (if available)
if (window.addEventListener) {
window.addEventListener('playerStatsUpdated', updateLevelDisplay);
}
}
// ===== VIDEO BILLBOARD FUNCTIONALITY =====
let billboardVideoSystem = {
videos: [],
currentVideoIndex: 0,
isPlaying: true,
isMuted: true,
playTimeout: null,
async initialize() {
console.log('🎬 Initializing Video Billboard...');
// Try to load videos directly using the same method as Quick Play
await this.loadVideosDirectly();
// If no videos found, try fallback methods
if (this.videos.length === 0) {
await this.tryFallbackVideos();
}
if (this.videos.length === 0) {
console.warn('⚠️ No videos available for billboard');
return;
}
console.log(`🎬 Billboard loaded ${this.videos.length} videos`);
this.setupVideoElement();
this.setupControls();
this.startVideoRotation();
},
async loadVideosDirectly() {
try {
// Use the same video loading logic as Quick Play
console.log('🎬 Loading videos using Quick Play method...');
// Get linked directories from localStorage (same as Quick Play)
let linkedDirs;
try {
linkedDirs = JSON.parse(localStorage.getItem('linkedVideoDirectories') || '[]');
if (!Array.isArray(linkedDirs)) {
linkedDirs = [];
}
} catch (e) {
console.log('Error parsing linkedVideoDirectories, resetting to empty array:', e);
linkedDirs = [];
}
let linkedIndividualVideos;
try {
linkedIndividualVideos = JSON.parse(localStorage.getItem('linkedIndividualVideos') || '[]');
if (!Array.isArray(linkedIndividualVideos)) {
linkedIndividualVideos = [];
}
} catch (e) {
console.log('Error parsing linkedIndividualVideos, resetting to empty array:', e);
linkedIndividualVideos = [];
}
console.log(`🎬 Found ${linkedDirs.length} linked directories, ${linkedIndividualVideos.length} individual videos`);
// Load videos from linked directories using Electron API (same as Quick Play)
if (window.electronAPI && linkedDirs.length > 0) {
const videoExtensions = /\.(mp4|webm|avi|mov|mkv|wmv|flv|m4v)$/i;
for (const dir of linkedDirs) {
try {
console.log(`🎬 Scanning directory: ${dir.path}`);
// Use video-specific directory reading for better results
let files = [];
if (window.electronAPI.readVideoDirectory) {
files = await window.electronAPI.readVideoDirectory(dir.path);
} else if (window.electronAPI.readVideoDirectoryRecursive) {
files = await window.electronAPI.readVideoDirectoryRecursive(dir.path);
} else if (window.electronAPI.readDirectory) {
const allFiles = await window.electronAPI.readDirectory(dir.path);
files = allFiles.filter(file => videoExtensions.test(file.name));
}
if (files && files.length > 0) {
console.log(`🎬 Found ${files.length} video files in ${dir.path}`);
files.forEach(file => {
this.videos.push({
name: file.name,
path: file.path,
filePath: file.path,
size: file.size || 0,
duration: file.duration || 0,
directory: dir.path
});
});
} else {
console.log(`🎬 No video files found in ${dir.path}`);
}
} catch (error) {
console.error(`❌ Error loading videos from directory ${dir.path}:`, error);
continue;
}
}
}
// Add individual linked video files
linkedIndividualVideos.forEach(video => {
this.videos.push({
name: video.name || 'Unknown Video',
path: video.path,
filePath: video.path,
size: video.size || 0,
directory: 'individual'
});
});
console.log(`🎬 Billboard total videos loaded: ${this.videos.length}`);
} catch (error) {
console.error('❌ Error loading videos directly:', error);
}
},
async tryFallbackVideos() {
// Try the same fallback methods as Quick Play
console.log('🎬 Trying fallback video discovery methods...');
// Try unified storage (Quick Play method)
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
let allVideos = unifiedData.allVideos || [];
console.log(`🎬 Got ${allVideos.length} videos from unified storage`);
// Try legacy storage (Quick Play method)
if (allVideos.length === 0) {
const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}');
allVideos = Object.values(storedVideos).flat();
console.log(`🎬 Got ${allVideos.length} videos from legacy storage`);
}
// Try VideoLibrary localStorage (Quick Play method)
if (allVideos.length === 0) {
const videoLibraryData = JSON.parse(localStorage.getItem('videoLibrary') || '[]');
allVideos = Array.isArray(videoLibraryData) ? videoLibraryData : [];
console.log(`🎬 Got ${allVideos.length} videos from VideoLibrary localStorage`);
}
// Try to access the global VideoLibrary if available
if (allVideos.length === 0 && window.VideoLibrary && window.VideoLibrary.videos) {
allVideos = window.VideoLibrary.videos;
console.log(`🎬 Got ${allVideos.length} videos from global VideoLibrary.videos`);
}
// Try desktop file manager
if (allVideos.length === 0 && window.desktopFileManager) {
try {
if (typeof window.desktopFileManager.getAllVideos === 'function') {
allVideos = window.desktopFileManager.getAllVideos();
console.log(`🎬 Got ${allVideos.length} videos from DesktopFileManager`);
}
} catch (dmError) {
console.warn('🎬 Error getting videos from DesktopFileManager:', dmError);
}
}
if (allVideos.length > 0) {
this.videos = allVideos.slice(0, 50); // Limit to first 50 for performance
console.log(`🎬 Loaded ${this.videos.length} videos from fallback methods`);
return;
}
console.log('🎬 No video sources found, creating demo placeholder');
this.createDemoVideo();
},
async getVideosFromLibrarySystem() {
try {
// Try to access the global video library data that's being loaded
if (window.game && window.game.fileManager && window.game.fileManager.allLinkedVideos) {
const linkedVideos = window.game.fileManager.allLinkedVideos;
if (linkedVideos && linkedVideos.length > 0) {
console.log(`🎬 Found ${linkedVideos.length} videos from game fileManager`);
return linkedVideos;
}
}
// Try desktop file manager
if (window.desktopFileManager && window.desktopFileManager.allLinkedVideos) {
const linkedVideos = window.desktopFileManager.allLinkedVideos;
if (linkedVideos && linkedVideos.length > 0) {
console.log(`🎬 Found ${linkedVideos.length} videos from desktop fileManager`);
return linkedVideos;
}
}
// Try to check if we can find any active video data being processed
const libraryTab = document.querySelector('#lib-video-gallery');
if (libraryTab && libraryTab.children.length > 0) {
console.log('🎬 Found videos in library gallery, attempting to extract data...');
return this.extractVideosFromGallery();
}
return null;
} catch (error) {
console.warn('⚠️ Error accessing library system videos:', error);
return null;
}
},
extractVideosFromGallery() {
const videos = [];
const galleryItems = document.querySelectorAll('#lib-video-gallery .video-item');
galleryItems.forEach((item, index) => {
const videoElement = item.querySelector('video');
const titleElement = item.querySelector('.video-title');
if (videoElement && videoElement.src) {
let src = videoElement.src;
// Remove the file:// prefix if present
if (src.startsWith('file://')) {
src = src.substring(7);
}
videos.push({
name: titleElement ? titleElement.textContent : `Video ${index + 1}`,
path: src,
filePath: src
});
}
});
console.log(`🎬 Extracted ${videos.length} videos from gallery`);
return videos;
},
createDemoVideo() {
// Create a demo placeholder that shows the billboard is working
const demoContainer = document.querySelector('.billboard-frame');
if (demoContainer) {
const video = document.getElementById('billboard-video');
if (video) {
video.style.display = 'none';
}
// Create demo content
const demoDiv = document.createElement('div');
demoDiv.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, #1a0033, #330066, #8a2be2);
background-size: 400% 400%;
animation: gradientShift 3s ease infinite;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: 'Audiowide', monospace;
text-align: center;
font-size: 14px;
z-index: 1;
`;
demoDiv.innerHTML = `
<div>
<div style="font-size: 18px; margin-bottom: 10px;">🎬</div>
<div>CYBERPUNK BILLBOARD</div>
<div style="font-size: 10px; margin-top: 5px; opacity: 0.7;">Awaiting Video Library</div>
</div>
`;
// Add animation keyframes if not already present
if (!document.querySelector('#billboard-demo-styles')) {
const style = document.createElement('style');
style.id = 'billboard-demo-styles';
style.textContent = `
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
`;
document.head.appendChild(style);
}
demoContainer.appendChild(demoDiv);
console.log('🎬 Created demo billboard content');
}
},
setupVideoElement() {
const video = document.getElementById('billboard-video');
if (!video) return;
// Set performance optimizations (like Quick Play)
video.muted = this.isMuted;
video.volume = 0.3;
video.playsInline = true;
video.preload = 'metadata';
// Add performance CSS properties via JavaScript
video.style.backfaceVisibility = 'hidden';
video.style.transform = 'translateZ(0)';
video.style.willChange = 'opacity';
// Set up event listeners for smooth playback
video.addEventListener('loadstart', () => {
console.log('🎬 Billboard video loading started');
});
video.addEventListener('loadedmetadata', () => {
console.log('🎬 Billboard metadata loaded');
});
video.addEventListener('canplay', () => {
console.log('🎬 Billboard video ready to play');
});
video.addEventListener('error', (e) => {
console.error('❌ Billboard video error:', e);
this.loadRandomVideo();
});
video.addEventListener('ended', () => {
// Immediately load next video when current one ends
this.loadRandomVideo();
});
// Handle stalled playback
video.addEventListener('stalled', () => {
console.warn('⚠️ Billboard video stalled, reloading...');
setTimeout(() => this.loadRandomVideo(), 1000);
});
// Handle waiting/buffering
video.addEventListener('waiting', () => {
console.log('🎬 Billboard video buffering...');
});
console.log('🎬 Billboard video element configured for smooth playback');
},
setupControls() {
const muteBtn = document.getElementById('billboard-mute');
const pauseBtn = document.getElementById('billboard-pause');
if (muteBtn) {
muteBtn.addEventListener('click', () => this.toggleMute());
}
if (pauseBtn) {
pauseBtn.addEventListener('click', () => this.togglePause());
}
this.updateControlButtons();
},
updateControlButtons() {
const muteBtn = document.getElementById('billboard-mute');
const pauseBtn = document.getElementById('billboard-pause');
if (muteBtn) {
muteBtn.textContent = this.isMuted ? '🔇' : '🔊';
muteBtn.title = this.isMuted ? 'Unmute' : 'Mute';
}
if (pauseBtn) {
pauseBtn.textContent = this.isPlaying ? '⏸️' : '▶️';
pauseBtn.title = this.isPlaying ? 'Pause' : 'Play';
}
},
async startVideoRotation() {
if (this.videos.length === 0) return;
this.loadRandomVideo();
},
async loadRandomVideo() {
if (this.videos.length === 0) return;
// Clear any existing timeout
if (this.playTimeout) {
clearTimeout(this.playTimeout);
}
// Select random video
this.currentVideoIndex = Math.floor(Math.random() * this.videos.length);
const selectedVideo = this.videos[this.currentVideoIndex];
const video = document.getElementById('billboard-video');
const source = document.getElementById('billboard-video-source');
if (!video || !source || !selectedVideo) return;
try {
// Fade out current video smoothly
video.style.opacity = '0';
// Wait for fade out, then load new video
setTimeout(() => {
const videoPath = selectedVideo.path || selectedVideo.filePath;
if (videoPath) {
console.log(`🎬 Billboard loading: ${selectedVideo.name || selectedVideo.title || 'Unknown'}`);
// Use source element for better loading (like Quick Play)
source.src = `file://${videoPath}`;
video.load(); // Force reload with new source
// Handle metadata loaded event
video.onloadedmetadata = () => {
// Set random start time for variety
const duration = video.duration;
if (duration > 10) {
const maxStart = Math.max(0, duration - 10); // Ensure we have at least 10 seconds to play
const randomStart = Math.random() * maxStart;
video.currentTime = randomStart;
}
};
// Handle when video can start playing
video.oncanplay = () => {
// Fade in smoothly
video.style.opacity = '1';
if (this.isPlaying) {
// Use a small delay to ensure smooth playback
setTimeout(() => {
video.play().catch(e => {
console.warn('Billboard autoplay prevented:', e);
});
}, 100);
}
// Set timeout for next video (8 seconds)
this.playTimeout = setTimeout(() => {
this.loadRandomVideo();
}, 8000);
};
// Handle loading errors
video.onerror = () => {
console.warn('Billboard video loading error, trying next video');
setTimeout(() => this.loadRandomVideo(), 1000);
};
}
}, 500); // Wait for fade out
} catch (error) {
console.error('❌ Error loading billboard video:', error);
// Try next video after a delay
setTimeout(() => this.loadRandomVideo(), 2000);
}
},
toggleMute() {
const video = document.getElementById('billboard-video');
if (!video) return;
this.isMuted = !this.isMuted;
video.muted = this.isMuted;
this.updateControlButtons();
console.log(`🎬 Billboard ${this.isMuted ? 'muted' : 'unmuted'}`);
},
togglePause() {
const video = document.getElementById('billboard-video');
if (!video) return;
this.isPlaying = !this.isPlaying;
if (this.isPlaying) {
video.play().catch(e => console.warn('Billboard play prevented:', e));
// Resume rotation
if (!this.playTimeout) {
this.playTimeout = setTimeout(() => {
this.loadRandomVideo();
}, 8000);
}
} else {
video.pause();
// Stop rotation
if (this.playTimeout) {
clearTimeout(this.playTimeout);
this.playTimeout = null;
}
}
this.updateControlButtons();
console.log(`🎬 Billboard ${this.isPlaying ? 'resumed' : 'paused'}`);
},
// Method to refresh videos when they become available
async refreshVideos() {
console.log('🎬 Refreshing billboard videos...');
const videos = await this.getVideosFromLibrarySystem();
if (videos && videos.length > 0) {
this.videos = videos.slice(0, 50);
console.log(`🎬 Billboard refreshed with ${this.videos.length} videos`);
// Remove demo content if it exists
const demoContent = document.querySelector('.billboard-frame > div');
if (demoContent && demoContent.innerHTML.includes('CYBERPUNK BILLBOARD')) {
demoContent.remove();
const video = document.getElementById('billboard-video');
if (video) {
video.style.display = 'block';
}
}
// Start playing if we weren't before
if (this.videos.length > 0) {
this.startVideoRotation();
return true;
}
}
return false;
}
};
// Global function to refresh billboard when videos become available
window.refreshBillboard = function() {
if (billboardVideoSystem) {
billboardVideoSystem.refreshVideos();
}
};
function initializeBillboardVideo() {
// Wait a bit for other systems to load, then try multiple times
let attempts = 0;
const maxAttempts = 5;
const tryInitialize = async () => {
attempts++;
console.log(`🎬 Billboard initialization attempt ${attempts}/${maxAttempts}`);
await billboardVideoSystem.initialize();
// If we still have no videos and haven't reached max attempts, try again
if (billboardVideoSystem.videos.length === 0 && attempts < maxAttempts) {
console.log(`🎬 No videos found on attempt ${attempts}, retrying in 3 seconds...`);
setTimeout(tryInitialize, 3000);
} else if (billboardVideoSystem.videos.length > 0) {
console.log(`🎬 Billboard successfully initialized with ${billboardVideoSystem.videos.length} videos`);
} else {
console.log('🎬 Billboard initialized with demo content after all attempts');
}
};
// Start the first attempt after a delay
setTimeout(tryInitialize, 5000);
}
// Initialize everything when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
// Initialize PlayerStats first
if (typeof PlayerStats !== 'undefined' && !window.playerStats) {
window.playerStats = new PlayerStats();
console.log('📊 PlayerStats initialized on home page');
}
initializeVideoPlayer();
// Initialize desktop file manager if in Electron environment
@ -3283,15 +3846,7 @@
});
}
// Set up player stats button (only once)
const playerStatsBtn = document.getElementById('player-stats-btn');
if (playerStatsBtn && !playerStatsBtn.hasAttribute('data-handler-attached')) {
playerStatsBtn.setAttribute('data-handler-attached', 'true');
playerStatsBtn.addEventListener('click', () => {
console.log('📊 Opening Player Stats...');
window.location.href = 'player-stats.html';
});
}
// Set up user profile button (only once)
const userProfileBtn = document.getElementById('user-profile-btn');
@ -3364,6 +3919,19 @@
}
});
}
// Initialize Video Billboard
initializeBillboardVideo();
// Initialize Level Display
initializeLevelDisplay();
// Set up a listener for when videos are loaded in the library
setTimeout(() => {
if (window.refreshBillboard) {
window.refreshBillboard();
}
}, 10000); // Check again after 10 seconds for loaded videos
}, 1000);
});

251
levels.md Normal file
View File

@ -0,0 +1,251 @@
# 🎯 Level System - webGame
## Overview
The webGame uses an XP-based progression system where players advance through levels by earning Experience Points (XP) across all game modes. Each level requires 100 XP to advance, providing a steady and rewarding progression curve.
**Base Formula:** Level = (Total XP ÷ 100) + 1
---
## XP Sources
### 📺 Porn Cinema
- **Watch Time XP**: 1 XP per 5 minutes of viewing
- **Video Completion**: Videos must reach 90% completion to count toward stats
### ⚡ Quick Play
- **Task Completion XP**:
- 1 XP for tasks completed in <5 minutes
- 2 XP for tasks completed in 5-10 minutes
- 3 XP for tasks completed in >10 minutes
- **Session Time Bonus**: Additional XP for longer play sessions
- **Incomplete Tasks**: Players still earn XP even for incomplete tasks
### 🎭 Scenario Games
- **Base Time XP**: 1 XP per 2 minutes of gameplay
- **Webcam Bonus**: 1 XP per minute during webcam activities (x2 multiplier)
- **Photo Rewards**: 2 XP per photo taken during scenarios
---
## Level Breakdown (1-20)
### 🌱 **Level 1** - *Virgin*
- **XP Required**: 0-99 XP
- **Description**: Welcome to webGame! Your first taste of pleasure awaits.
- **Unlocks**:
- Access to all basic game modes
- Profile customization
- Basic statistics tracking
### 🌿 **Level 2** - *Curious*
- **XP Required**: 100-199 XP
- **Description**: Your curiosity is growing! Start exploring your desires.
- **Unlocks**:
- Achievement "First Steps" available
- Enhanced stat tracking
- Basic playlist creation
### 🌱 **Level 3** - *Eager*
- **XP Required**: 200-299 XP
- **Description**: An eager participant! You're developing your appetites.
- **Unlocks**:
- Achievement "Early Bird" available
- Extended session tracking
- Profile bio customization
### 🌿 **Level 4** - *Aroused*
- **XP Required**: 300-399 XP
- **Description**: Your arousal is building! Keep feeding your desires.
- **Unlocks**:
- Advanced statistics dashboard
- Streak tracking begins
- Achievement "Marathon Viewer" available
### 🌟 **Level 5** - *Lustful*
- **XP Required**: 400-499 XP
- **Description**: Consumed by lust and craving more experiences!
- **Unlocks**:
- Achievement "Level Up" unlocked
- Enhanced profile features
- Priority support features
### 🔥 **Level 6** - *Passionate*
- **XP Required**: 500-599 XP
- **Description**: Your passion burns hot with unbridled desire.
- **Unlocks**:
- Achievement "XP Master" unlocked
- Advanced playlist management
- Custom themes (future feature)
### ⭐ **Level 7** - *Addicted*
- **XP Required**: 600-699 XP
- **Description**: Hopelessly addicted to the rush of pleasure!
- **Unlocks**:
- Achievement "Consistency King" available
- Extended activity history
- Advanced export options
### 🎯 **Level 8** - *Obsessed*
- **XP Required**: 700-799 XP
- **Description**: Your obsession drives you to new heights of ecstasy.
- **Unlocks**:
- Achievement "Playlist Curator" available
- Enhanced statistics breakdown
- Advanced profile customization
### 🏆 **Level 9** - *Deviant*
- **XP Required**: 800-899 XP
- **Description**: A true deviant exploring the depths of desire!
- **Unlocks**:
- Multiple achievement categories
- Detailed performance analytics
- Achievement showcase features
### 💎 **Level 10** - *Kinky*
- **XP Required**: 900-999 XP
- **Description**: Welcome to the kinky elite! Your fetishes are flourishing.
- **Unlocks**:
- Elite status badge
- Advanced features access
- Achievement "Video Collector" available
### 🌟 **Level 11** - *Perverted*
- **XP Required**: 1000-1099 XP
- **Description**: A seasoned pervert with extensive experience in debauchery.
- **Unlocks**:
- Veteran status recognition
- Historical data archives
- Legacy achievement tracking
### 🔮 **Level 12** - *Depraved*
- **XP Required**: 1100-1199 XP
- **Description**: You've mastered the art of depravity and corruption!
- **Unlocks**:
- Master tier privileges
- Advanced customization options
- Exclusive features access
### 👑 **Level 13** - *Dominant*
- **XP Required**: 1200-1299 XP
- **Description**: A dominant force commanding respect and submission!
- **Unlocks**:
- Champion status badge
- Premium feature previews
- Community recognition features
### 🚀 **Level 14** - *Hedonistic*
- **XP Required**: 1300-1399 XP
- **Description**: Pure hedonism guides your every pleasure-seeking move!
- **Unlocks**:
- Legendary badge and privileges
- Beta feature access
- Advanced analytics dashboard
### 🌌 **Level 15** - *Decadent*
- **XP Required**: 1400-1499 XP
- **Description**: You've reached decadent levels of indulgence!
- **Unlocks**:
- Cosmic tier recognition
- Exclusive content access
- Advanced progression tracking
### ⚡ **Level 16** - *Insatiable*
- **XP Required**: 1500-1599 XP
- **Description**: Your insatiable appetite knows no bounds!
- **Unlocks**:
- Transcendent status
- Ultimate customization options
- Developer preview features
### 🔥 **Level 17** - *Sinful*
- **XP Required**: 1600-1699 XP
- **Description**: Deliciously sinful dedication to carnal pleasures!
- **Unlocks**:
- Immortal tier privileges
- Lifetime achievement tracking
- Exclusive immortal features
### 🌠 **Level 18** - *Forbidden*
- **XP Required**: 1700-1799 XP
- **Description**: You've achieved forbidden status in the realm of desire!
- **Unlocks**:
- Celestial recognition
- Advanced achievement categories
- Special celestial features
### 🏛️ **Level 19** - *Godlike*
- **XP Required**: 1800-1899 XP
- **Description**: Godlike levels of sexual prowess and commitment!
- **Unlocks**:
- Godlike status badge
- Ultimate privileges
- All premium features unlocked
### 👁️ **Level 20** - *Omnipotent*
- **XP Required**: 1900+ XP
- **Description**: You are the ultimate master of pleasure and desire!
- **Unlocks**:
- Omnipotent status (highest achievable)
- All features and privileges
- Exclusive omnipotent recognition
- Developer acknowledgment
- Infinite progression tracking
---
## 🏆 Achievement Integration
### Level-Based Achievements
- **Level Up** - Reach Level 5 (400+ XP)
- **XP Master** - Earn 500+ total XP (Level 6)
- **Elite Status** - Reach Level 10 (900+ XP)
- **Legendary** - Reach Level 14 (1300+ XP)
- **Omnipotent** - Reach Level 20 (1900+ XP)
### Progression Achievements
- **First Steps** - Watch your first video
- **Early Bird** - Watch 10 videos
- **Marathon Viewer** - Watch for 2+ hours total
- **Playlist Curator** - Create 5 playlists
- **Consistency King** - Maintain a 7-day streak
- **Video Collector** - Watch 100 different videos
---
## 📊 Progression Tips
### Efficient XP Earning
1. **Consistent Daily Activity**: Maintain streaks for bonus achievements
2. **Complete Tasks**: Finish Quick Play tasks for maximum XP
3. **Watch Full Videos**: Reach 90% completion in Cinema mode
4. **Engage with Scenarios**: Use webcam features for bonus XP
5. **Take Photos**: Earn 2 XP per photo in scenario modes
### Level Milestones
- **Levels 1-5**: Sexual awakening and building desires (500 XP)
- **Levels 6-10**: Corruption and exploring kinks (500 XP)
- **Levels 11-15**: Embracing perversion and depravity (500 XP)
- **Levels 16-20**: Transcendent mastery of carnal arts (400+ XP)
---
## 🔮 Future Expansions
### Planned Features
- **Level 25+**: Extended progression for dedicated users
- **Prestige System**: Reset levels for additional bonuses
- **Seasonal Events**: Temporary XP multipliers and special achievements
- **Community Features**: Level-based social interactions
- **Custom Rewards**: Personalized unlocks based on play style
### Level Cap Expansion
The system is designed to easily extend beyond Level 20, with potential for:
- **Level 50**: Ultra-Legendary status
- **Level 100**: Mythical achievements
- **Infinite Progression**: No level cap for ultimate dedication
---
*Last Updated: November 5, 2025*
*Version: 1.0 - Initial Level System*

File diff suppressed because it is too large Load Diff

View File

@ -79,9 +79,9 @@ class TaskChallengeGame {
// Scenario-specific XP tracking (separate from main game)
scenarioXp: {
timeBased: 0, // 1 XP per 2 minutes
focusBonuses: 0, // 3 XP per 30 seconds of focus
webcamBonuses: 0, // 3 XP per 30 seconds of webcam mirror
photoRewards: 0, // 1 XP per photo taken
focusBonuses: 0, // 3 XP per 30 seconds of focus (unchanged)
webcamBonuses: 0, // x2 multiplier per minute of webcam
photoRewards: 0, // 2 XP per photo taken
stepCompletion: 0, // 5 XP per scenario step (existing)
total: 0 // Sum of all scenario XP
},
@ -1795,14 +1795,15 @@ class TaskChallengeGame {
safeAddListener('track-selector', 'change', (e) => this.changeTrack(parseInt(e.target.value)));
safeAddListener('volume-slider', 'input', (e) => this.changeVolume(parseInt(e.target.value)));
// Task management
safeAddListener('manage-tasks-btn', 'click', () => this.showTaskManagement());
// Task management - Now handled in Quick Play
// safeAddListener('manage-tasks-btn', 'click', () => this.showTaskManagement());
safeAddListener('back-to-start-btn', 'click', () => this.showScreen('start-screen'));
safeAddListener('add-task-btn', 'click', () => this.addNewTask());
safeAddListener('reset-tasks-btn', 'click', () => this.resetToDefaultTasks());
safeAddListener('main-tasks-tab', 'click', () => this.showTaskTab('main'));
safeAddListener('consequence-tasks-tab', 'click', () => this.showTaskTab('consequence'));
safeAddListener('new-task-type', 'change', () => this.toggleDifficultyDropdown());
// Legacy task management functions - disabled as task management moved to Quick Play
// safeAddListener('add-task-btn', 'click', () => this.addNewTask());
// safeAddListener('reset-tasks-btn', 'click', () => this.resetToDefaultTasks());
// safeAddListener('main-tasks-tab', 'click', () => this.showTaskTab('main'));
// safeAddListener('consequence-tasks-tab', 'click', () => this.showTaskTab('consequence'));
// safeAddListener('new-task-type', 'change', () => this.toggleDifficultyDropdown());
// Data management
safeAddListener('export-btn', 'click', () => this.exportData());
@ -2470,29 +2471,29 @@ class TaskChallengeGame {
}
updateScenarioWebcamXp() {
// Award 3 XP per 30 seconds during webcam mirror activities
// Award XP for webcam mirror activity (x2 multiplier = 1 XP per minute)
if (!this.gameState.scenarioTracking.isInWebcamActivity || this.gameState.isPaused) return;
const now = Date.now();
const timeSinceLastAward = now - this.gameState.scenarioTracking.lastWebcamXpAwarded;
const thirtySecondsMs = 30 * 1000; // 30 seconds in milliseconds
const oneMinuteMs = 60 * 1000; // 1 minute in milliseconds
if (timeSinceLastAward >= thirtySecondsMs) {
const periods = Math.floor(timeSinceLastAward / thirtySecondsMs);
const xpToAward = periods * 3;
if (timeSinceLastAward >= oneMinuteMs) {
const periods = Math.floor(timeSinceLastAward / oneMinuteMs);
const xpToAward = periods * 1; // 1 XP per minute (x2 multiplier of base rate)
this.gameState.scenarioXp.webcamBonuses += xpToAward;
this.gameState.scenarioTracking.lastWebcamXpAwarded = now;
this.updateScenarioTotalXp();
console.log(`📹 Webcam bonus XP: +${xpToAward} (${periods} x 30s periods)`);
console.log(`📹 Webcam bonus XP: +${xpToAward} (${periods} x 1min periods, x2 multiplier)`);
}
}
awardScenarioPhotoXp() {
// Award 1 XP per photo taken during scenarios
this.gameState.scenarioXp.photoRewards += 1;
// Award 2 XP per photo taken during scenarios (ROADMAP requirement)
this.gameState.scenarioXp.photoRewards += 2;
this.gameState.scenarioTracking.totalPhotosThisSession += 1;
this.updateScenarioTotalXp();
console.log(`📸 Photo XP: +1 (Total photos this session: ${this.gameState.scenarioTracking.totalPhotosThisSession})`);
console.log(`📸 Photo XP: +2 (Total photos this session: ${this.gameState.scenarioTracking.totalPhotosThisSession})`);
}
awardScenarioStepXp() {

View File

@ -8,88 +8,273 @@ const mainGameData = {
mainTasks: [
{
id: 1,
text: "Watch porn and stroke slowly for 2 minutes, no cumming",
difficulty: "Easy"
text: "Watch porn and stroke slowly, no cumming",
baseDuration: 5,
tags: []
},
{
id: 2,
text: "Edge while watching BBC content sitting in chair for 3 minutes",
difficulty: "Easy"
text: "Edge while watching BBC content sitting in chair",
baseDuration: 6,
tags: ["bbc"]
},
{
id: 3,
text: "Change your panties and get on your knees for 1 minute",
difficulty: "Medium"
text: "Change your panties and get on your knees",
baseDuration: 5,
tags: ["sissy"]
},
{
id: 4,
text: "Watch cuckold videos standing up and edge for 4 minutes without release",
difficulty: "Medium"
text: "Watch cuckold videos standing up and edge without release",
baseDuration: 8,
tags: ["cuckold"]
},
{
id: 5,
text: "Goon to compilation videos sitting for 6 minutes, multiple edges",
difficulty: "Easy"
text: "Goon to compilation videos sitting, multiple edges",
baseDuration: 10,
tags: ["compilation"]
},
{
id: 6,
text: "Watch interracial in the captain morgan pose, stroke for 7 minutes",
difficulty: "Medium"
text: "Watch interracial in the captain morgan pose, stroke",
baseDuration: 10,
tags: ["interracial"]
},
{
id: 7,
text: "Edge to humiliation content while kneeling down for 4 minutes",
difficulty: "Easy"
text: "Edge to humiliation content while kneeling down",
baseDuration: 8,
tags: ["humiliation"]
},
{
id: 8,
text: "Watch sissy hypno standing while stroking with your non-dominant hand for 3 minutes",
difficulty: "Medium"
text: "Watch sissy hypno standing while stroking with your non-dominant hand",
baseDuration: 6,
tags: ["sissy", "hypno"]
},
{
id: 9,
text: "Goon to gangbang videos sitting in chair for 5 minutes",
difficulty: "Easy"
text: "Goon to gangbang videos sitting in chair",
baseDuration: 9,
tags: ["gangbang"]
},
{
id: 10,
text: "Watch MILF porn while standing, stroke for 2 minutes",
difficulty: "Easy"
text: "Watch MILF porn while standing, stroke",
baseDuration: 5,
tags: ["milf"]
},
{
id: 11,
text: "Edge to cuckold captions standing for 5 minutes with multiple stops",
difficulty: "Easy"
text: "Edge to cuckold captions standing with multiple stops",
baseDuration: 9,
tags: ["cuckold", "captions"]
},
{
id: 12,
text: "Watch trans porn while stroking sitting and standing alternately for 6 minutes",
difficulty: "Medium"
text: "Squeeze your balls while watching sissy porn",
baseDuration: 5,
tags: ["cbt", "sissy"]
},
{
id: 13,
text: "Goon session: 15 minutes of continuous stroking sitting to random videos",
difficulty: "Hard"
text: "Slap your cock 10 times while watching humiliation content",
baseDuration: 5,
tags: ["cbt", "humiliation"]
},
{
id: 14,
text: "Watch hotwife content while edging on knees for 2 minutes",
difficulty: "Hard"
text: "Call yourself a pathetic gooner while stroking",
baseDuration: 8,
tags: ["humiliation", "verbal"]
},
{
id: 15,
text: "Get naked and do 5 minutes of edging standing to BBC compilations",
difficulty: "Hard"
text: "Pinch your nipples and balls while edging to BBC content",
baseDuration: 5,
tags: ["cbt", "bbc"]
},
{
id: 16,
text: "Change your panties and then do 5 minutes alternating sitting/standing while watching porn",
difficulty: "Hard"
text: "Admit you're addicted to porn out loud while edging",
baseDuration: 6,
tags: ["humiliation", "verbal"]
},
{
id: 17,
text: "Put panties and a bra on and do 15 minutes of edging in doggy position to humiliation content",
difficulty: "Hard"
text: "Flick your cock head 15 times then edge without cumming",
baseDuration: 8,
tags: ["cbt"]
},
{
id: 18,
text: "Watch trans porn while stroking sitting and standing alternately",
baseDuration: 10,
tags: ["trans"]
},
{
id: 19,
text: "Continuous stroking sitting to random videos",
baseDuration: 9,
tags: []
},
{
id: 20,
text: "Watch hotwife content while edging on knees",
baseDuration: 5,
tags: ["cuckold"]
},
{
id: 21,
text: "Get naked and edge standing to BBC compilations",
baseDuration: 9,
tags: ["bbc", "compilation"]
},
{
id: 22,
text: "Change your panties and then stand while watching porn",
baseDuration: 9,
tags: ["sissy"]
},
{
id: 23,
text: "Put panties and a bra on and edge in doggy position to humiliation content",
baseDuration: 9,
tags: ["sissy", "humiliation"]
},
{
id: 24,
text: "Stroke with your left hand only while watching BBC",
baseDuration: 6,
tags: ["bbc"]
},
{
id: 25,
text: "Edge to sissy captions while on your knees",
baseDuration: 8,
tags: ["sissy", "captions"]
},
{
id: 26,
text: "Slap your balls 5 times then edge to interracial porn",
baseDuration: 9,
tags: ["cbt", "interracial"]
},
{
id: 27,
text: "Watch gangbang videos while alternating hands every 30 seconds",
baseDuration: 8,
tags: ["gangbang"]
},
{
id: 28,
text: "Edge to femdom content while lying on your back",
baseDuration: 10,
tags: ["femdom"]
},
{
id: 29,
text: "Stroke while looking at yourself in the mirror and calling yourself a slut",
baseDuration: 5,
tags: ["humiliation", "verbal"]
},
{
id: 30,
text: "Slap your cock 10 times while watching humiliation videos",
baseDuration: 5,
tags: ["cbt", "humiliation"]
},
{
id: 31,
text: "Practice moaning like a girl while edging to sissy content",
baseDuration: 8,
tags: ["sissy", "vocal", "feminization"]
},
{
id: 32,
text: "Edge to sissy hypno while wearing your wife's panties",
baseDuration: 8,
tags: ["sissy", "hypno"]
},
{
id: 33,
text: "Slap your thighs 20 times then stroke to BBC content",
baseDuration: 6,
tags: ["cbt", "bbc"]
},
{
id: 34,
text: "Stroke while reciting \"I'm a pathetic cuckold\" 10 times to humiliation content",
baseDuration: 6,
tags: ["humiliation", "verbal", "cuckold"]
},
{
id: 35,
text: "Edge to cuckold videos while doing squats",
baseDuration: 6,
tags: ["cuckold"]
},
{
id: 36,
text: "Watch BBC content while crawling on all fours and stroking",
baseDuration: 5,
tags: ["bbc", "pet-play"]
},
{
id: 37,
text: "Slap your ass 15 times then edge to sissy content",
baseDuration: 8,
tags: ["cbt", "sissy"]
},
{
id: 38,
text: "Stroke to gangbang videos while switching hands every minute",
baseDuration: 9,
tags: ["gangbang"]
},
{
id: 39,
text: "Edge to humiliation content while wearing your wife's bra",
baseDuration: 8,
tags: ["humiliation", "sissy"]
},
{
id: 40,
text: "Stroke to BBC while chanting \"bigger is better\"",
baseDuration: 5,
tags: ["bbc", "verbal", "humiliation"]
},
{
id: 41,
text: "Stroke to compilation videos while on your knees",
baseDuration: 9,
tags: ["compilation"]
},
{
id: 42,
text: "Edge to sissy captions while touching your nipples",
baseDuration: 6,
tags: ["sissy", "captions", "nipple-play"]
},
{
id: 43,
text: "Watch interracial while alternating fast and slow strokes every 30 seconds",
baseDuration: 8,
tags: ["interracial"]
},
{
id: 44,
text: "Watch humiliation content while stroking with just your fingertips",
baseDuration: 8,
tags: ["humiliation"]
},
{
id: 45,
text: "Edge to sissy hypno while practicing feminine poses",
baseDuration: 9,
tags: ["sissy", "hypno", "feminization"]
}
],
@ -97,56 +282,194 @@ const mainGameData = {
consequenceTasks: [
{
id: 101,
text: "Find your wife's dirtiest panties and sniff them while stroking for 5 minutes"
text: "Eat your own precum while watching your wife's photos and imagining her with better men",
baseDuration: 8,
tags: ["humiliation", "cuckold"]
},
{
id: 102,
text: "Find your wife's sexiest photo, imagine sending it to her ex-boyfriend, and edge while thinking about it for 8 minutes"
text: "Do 20 push-ups while wearing your wife's panties",
baseDuration: 5,
tags: ["exercise", "sissy", "humiliation"]
},
{
id: 103,
text: "Go through your wife's photos, pick the sexiest one, and imagine her with another man for 10 minutes"
text: "Practice begging on your knees: 'Please let other men have my wife, I'm not worthy of her'",
baseDuration: 10,
tags: ["begging", "cuckold", "humiliation"]
},
{
id: 104,
text: "Look at your wife's most revealing photos and post them on erome for other men to see for 7 minutes"
text: "Gently push a finger inside your ass and hold it there",
baseDuration: 5,
tags: ["anal", "humiliation"]
},
{
id: 105,
text: "Put on your wife's dress or skirt and pose femininely in the mirror for 10 minutes"
text: "Slap your cock 10 times while looking at your wife's photos and saying 'I don't deserve her' after each slap",
baseDuration: 5,
tags: ["cbt", "cuckold", "humiliation"]
},
{
id: 106,
text: "Find your wife's hottest bikini photos and imagine sharing them with strangers while stroking for 8 minutes"
text: "Look at your wife's sexiest pictures while imagining her showing them to alpha males and chanting 'I'm a pathetic cuckold'",
baseDuration: 10,
tags: ["cuckold", "humiliation", "mental"]
},
{
id: 107,
text: "Look at your wife's photos and imagine telling other men how good she is in bed for 12 minutes"
text: "Find your wife's hottest photos and post them on chatpic for real men until it has 1 download",
baseDuration: 10,
tags: ["online-exposure", "cuckold"]
},
{
id: 108,
text: "Find your wife's sexiest lingerie, put it on, and practice crawling on all fours like a submissive pet for 8 minutes"
text: "Find your wife's sexiest lingerie, put it on, and practice crawling on all fours like a submissive pet",
baseDuration: 8,
tags: ["sissy", "pet-play", "humiliation"]
},
{
id: 109,
text: "Go through your wife's photos and send her nudes to your coworkers while repeating 'She deserves better' 100 times"
text: "Put on your wife's dress or skirt and pose femininely in the mirror",
baseDuration: 10,
tags: ["sissy", "posing"]
},
{
id: 110,
text: "Look at your wife's most intimate photos and imagine her sending them to other men behind your back for 15 minutes"
text: "Look at your wife's most revealing photos and post them on erome for other men to see until it has 50 views",
baseDuration: 10,
tags: ["online-exposure", "cuckold"]
},
{
id: 111,
text: "Find your wife's hottest photos and post them on chatpic for real men for 10 minutes"
text: "Find your wife's dirtiest panties and sniff them while stroking",
baseDuration: 5,
tags: ["panty-worship", "humiliation", "scent"]
},
{
id: 112,
text: "Look at your wife's sexiest pictures while imagining her showing them to alpha males and chanting 'I'm a pathetic cuckold' for 12 minutes"
text: "Jerk off while imagining you're a cum dump for your bull, begging for him to fill you up",
baseDuration: 5,
tags: ["fantasy", "humiliation", "cuckold"]
},
{
id: 113,
text: "Lick your wife's dirty panties while edging and repeating 'I'm not man enough for her'",
baseDuration: 6,
tags: ["panty-worship", "humiliation"]
},
{
id: 114,
text: "Push two fingers in your ass and fuck yourself while begging to be used like a sissy",
baseDuration: 5,
tags: ["anal", "sissy", "humiliation"]
},
{
id: 115,
text: "Find your wife's sexiest outfit and wear it while practicing sucking motions on a dildo",
baseDuration: 10,
tags: ["sissy", "oral-training", "toy"]
},
{
id: 116,
text: "Sniff your wife's worn shoes while jerking off and imagining her walking to meet her lover",
baseDuration: 8,
tags: ["foot-worship", "scent", "cuckold", "fantasy"]
},
{
id: 117,
text: "Finger your ass with one hand while slapping your cock with the other",
baseDuration: 6,
tags: ["anal", "cbt", "dual-stimulation"]
},
{
id: 118,
text: "Wear your wife's bra and panties while doing jumping jacks",
baseDuration: 5,
tags: ["exercise", "sissy", "humiliation"]
},
{
id: 119,
text: "Lick the inside of your wife's high heels while edging and saying 'She walks to better men'",
baseDuration: 5,
tags: ["foot-worship", "humiliation"]
},
{
id: 120,
text: "Push three fingers in your ass and stretch yourself while begging to be filled by a real man",
baseDuration: 5,
tags: ["anal", "humiliation"]
},
{
id: 121,
text: "Do 30 squats while wearing your wife's thong and chanting 'I'm a sissy cuck' with each squat",
baseDuration: 5,
tags: ["exercise", "sissy", "humiliation"]
},
{
id: 122,
text: "Lick your wife's dirty socks while edging and repeating 'Real men deserve her pussy I deserve her feet'",
baseDuration: 8,
tags: ["foot-worship", "humiliation"]
},
{
id: 123,
text: "Wear your wife's shortest dress and practice walking slutty while stroking",
baseDuration: 10,
tags: ["sissy"]
},
{
id: 124,
text: "Do planks while wearing your wife's sports bra and saying 'She's stronger than me'",
baseDuration: 5,
tags: ["exercise", "sissy", "humiliation"]
}
],
// Configuration for main game mode
config: {
// Duration scaling system
durationSettings: {
short: {
name: "Short",
description: "1-5 minute sessions",
multiplier: 0.5,
minDuration: 1,
maxDuration: 5
},
medium: {
name: "Medium",
description: "5-10 minute sessions",
multiplier: 1.0,
minDuration: 5,
maxDuration: 10
},
long: {
name: "Long",
description: "15-20 minute sessions",
multiplier: 2.5,
minDuration: 15,
maxDuration: 20
}
},
// Function to calculate actual duration based on setting
calculateDuration: function(baseDuration, setting = 'medium') {
const config = this.durationSettings[setting];
if (!config) return baseDuration;
let calculatedDuration = Math.round(baseDuration * config.multiplier);
// Enforce min/max bounds
if (calculatedDuration < config.minDuration) {
calculatedDuration = config.minDuration;
} else if (calculatedDuration > config.maxDuration) {
calculatedDuration = config.maxDuration;
}
return calculatedDuration;
},
difficultySystem: {
easy: { points: 10, timeBonus: 5 },
medium: { points: 20, timeBonus: 10 },

View File

@ -42,8 +42,7 @@ class PlayerStats {
return {
// Core Viewing Stats
totalWatchTime: 0, // milliseconds
videosWatched: 0,
sessions: 0,
videosWatched: 0, // Only videos completed 90%+
// Engagement Stats
playlistsCreated: 0,
@ -53,8 +52,6 @@ class PlayerStats {
// Behavioral Stats
mostWatchedVideo: { path: null, name: null, count: 0 },
longestSingleSession: 0,
videosCompleted: 0,
videosSkipped: 0,
// Achievement Stats
@ -62,6 +59,15 @@ class PlayerStats {
longestStreak: 0,
bingeSessions: 0,
// XP and Game Progress
totalXP: 0,
quickPlaySessions: 0,
quickPlayTasksCompleted: 0,
quickPlayXP: 0,
quickPlayTimeSpent: 0, // milliseconds
scenarioGameXP: 0,
pornCinemaXP: 0,
// Detailed Tracking
videoPlayCounts: {}, // { videoPath: count }
videoWatchTimes: {}, // { videoPath: totalTime }
@ -83,10 +89,10 @@ class PlayerStats {
}
initializeSession() {
this.stats.sessions++;
this.updateDailyStats('sessions', 1);
// Initialize daily stats tracking
this.updateDailyStats('videos', 0); // Initialize today's entry
this.saveStats();
console.log(`📊 Session #${this.stats.sessions} started`);
console.log('📊 Stats tracking initialized');
}
// ===== VIDEO PLAYBACK TRACKING =====
@ -108,10 +114,7 @@ class PlayerStats {
// Update video play count
this.incrementVideoPlayCount(video);
// Update stats
this.stats.videosWatched++;
this.currentSession.videosWatched++;
this.updateDailyStats('videos', 1);
// Note: videosWatched will only be incremented on 90%+ completion
// Set first/last play dates
const now = new Date().toISOString();
@ -120,6 +123,9 @@ class PlayerStats {
}
this.stats.lastPlayDate = now;
// Start XP tracking interval (every 30 seconds)
this.startXPTracking();
this.saveStats();
}
@ -128,6 +134,7 @@ class PlayerStats {
console.log('📊 Video resumed');
this.currentSession.isVideoPlaying = true;
this.currentSession.videoWatchStartTime = Date.now();
this.startXPTracking(); // Restart XP tracking when video resumes
}
}
@ -137,6 +144,7 @@ class PlayerStats {
this.recordCurrentWatchTime();
this.currentSession.isVideoPlaying = false;
this.currentSession.videoWatchStartTime = null;
this.stopXPTracking(); // Stop XP tracking when video pauses
}
}
@ -147,13 +155,22 @@ class PlayerStats {
this.recordCurrentWatchTime();
}
// Track completion vs skip
// Only count as "watched" if 90%+ completed
if (completionPercentage >= 90) {
this.stats.videosCompleted++;
this.stats.videosWatched++;
this.currentSession.videosWatched++;
this.updateDailyStats('videos', 1);
console.log('📊 Video completed (90%+) - counted as watched');
} else if (completionPercentage < 10) {
this.stats.videosSkipped++;
console.log('📊 Video skipped (<10%) - counted as skipped');
}
// XP is now awarded continuously in recordCurrentWatchTime()
// Stop XP tracking
this.stopXPTracking();
// Reset current video tracking
this.currentSession.isVideoPlaying = false;
this.currentSession.currentVideoStartTime = null;
@ -182,9 +199,61 @@ class PlayerStats {
this.updateDailyStats('watchTime', watchTime);
console.log(`📊 Recorded ${this.formatDuration(watchTime)} of watch time`);
// Reset the watch start time for next interval
this.currentSession.videoWatchStartTime = Date.now();
}
}
// Check for XP awards separately from watch time recording
checkForXPAwards() {
const totalWatchMinutes = this.stats.totalWatchTime / (1000 * 60);
const totalXPEarned = Math.floor(totalWatchMinutes / 5);
const currentPornCinemaXP = this.stats.pornCinemaXP || 0;
if (totalXPEarned > currentPornCinemaXP) {
const newXP = totalXPEarned - currentPornCinemaXP;
this.awardXP(newXP, 'porncinema');
console.log(`🏆 Awarded ${newXP} XP for reaching ${totalWatchMinutes.toFixed(1)} total minutes watched`);
}
}
// ===== XP TRACKING DURING PLAYBACK =====
startXPTracking() {
// Clear any existing interval
this.stopXPTracking();
// Start new interval to track watch time every 30 seconds
this.xpTrackingInterval = setInterval(() => {
if (this.currentSession.isVideoPlaying) {
this.recordCurrentWatchTime();
this.saveStats();
}
}, 30000); // 30 seconds
// Separate interval to check for XP awards every 5 minutes
this.xpAwardInterval = setInterval(() => {
if (this.currentSession.isVideoPlaying) {
this.checkForXPAwards();
}
}, 300000); // 5 minutes
console.log('📊 Started XP tracking interval');
}
stopXPTracking() {
if (this.xpTrackingInterval) {
clearInterval(this.xpTrackingInterval);
this.xpTrackingInterval = null;
}
if (this.xpAwardInterval) {
clearInterval(this.xpAwardInterval);
this.xpAwardInterval = null;
}
console.log('📊 Stopped XP tracking interval');
}
incrementVideoPlayCount(video) {
const path = video.path;
if (!this.stats.videoPlayCounts[path]) {
@ -216,6 +285,55 @@ class PlayerStats {
this.saveStats();
}
// ===== XP TRACKING =====
awardXP(amount, source = 'general') {
console.log(`🏆 Awarded ${amount} XP from ${source}`);
this.stats.totalXP += amount;
// Track by source
switch (source) {
case 'quickplay':
this.stats.quickPlayXP += amount;
break;
case 'scenario':
this.stats.scenarioGameXP += amount;
break;
case 'porncinema':
this.stats.pornCinemaXP += amount;
break;
}
this.updateDailyStats('xp', amount);
this.saveStats();
}
onQuickPlaySessionComplete(sessionData) {
console.log('📊 Quick Play session completed:', sessionData);
this.stats.quickPlaySessions++;
this.stats.quickPlayTasksCompleted += sessionData.tasksCompleted || 0;
this.stats.quickPlayTimeSpent += sessionData.timeSpent || 0;
if (sessionData.xpEarned) {
this.awardXP(sessionData.xpEarned, 'quickplay');
}
this.saveStats();
}
onTaskCompleted(taskData) {
console.log('📊 Task completed:', taskData);
if (taskData.xpEarned) {
this.awardXP(taskData.xpEarned, taskData.source || 'general');
}
// Update daily task completion stats
this.updateDailyStats('tasksCompleted', 1);
this.saveStats();
}
// ===== DAILY STATS TRACKING =====
updateDailyStats(type, value) {
@ -225,7 +343,8 @@ class PlayerStats {
this.stats.dailyStats[today] = {
watchTime: 0,
videos: 0,
sessions: 0
xp: 0,
tasksCompleted: 0
};
}
@ -259,53 +378,32 @@ class PlayerStats {
this.stats.longestStreak = maxStreak;
}
// ===== SESSION TRACKING =====
endSession() {
// Record any current watch time
if (this.currentSession.isVideoPlaying) {
this.recordCurrentWatchTime();
}
// Update longest session
const sessionLength = this.currentSession.watchTime;
if (sessionLength > this.stats.longestSingleSession) {
this.stats.longestSingleSession = sessionLength;
}
// Check for binge session (2+ hours)
if (sessionLength >= 2 * 60 * 60 * 1000) {
this.stats.bingeSessions++;
}
console.log(`📊 Session ended: ${this.formatDuration(sessionLength)} watch time, ${this.currentSession.videosWatched} videos`);
this.saveStats();
}
// ===== UTILITY METHODS =====
// ===== DAILY STATS TRACKING =====
getFormattedStats() {
return {
// Viewing Stats
totalWatchTime: this.formatDuration(this.stats.totalWatchTime),
averageSessionLength: this.formatDuration(this.stats.totalWatchTime / Math.max(this.stats.sessions, 1)),
videosWatched: this.stats.videosWatched.toLocaleString(),
sessions: this.stats.sessions.toLocaleString(),
videosWatched: this.stats.videosWatched.toLocaleString(), // Only 90%+ completed
// Game Progress Stats
totalXP: this.stats.totalXP.toLocaleString(),
quickPlayXP: this.stats.quickPlayXP.toLocaleString(),
scenarioGameXP: this.stats.scenarioGameXP.toLocaleString(),
pornCinemaXP: this.stats.pornCinemaXP.toLocaleString(),
quickPlaySessions: this.stats.quickPlaySessions.toLocaleString(),
quickPlayTasksCompleted: this.stats.quickPlayTasksCompleted.toLocaleString(),
quickPlayTimeSpent: this.formatDuration(this.stats.quickPlayTimeSpent),
// General Stats
playlistsCreated: this.stats.playlistsCreated.toLocaleString(),
videosCompleted: this.stats.videosCompleted.toLocaleString(),
longestSession: this.formatDuration(this.stats.longestSingleSession),
videosSkipped: this.stats.videosSkipped.toLocaleString(),
daysActive: this.stats.daysActive.length.toLocaleString(),
longestStreak: this.stats.longestStreak.toLocaleString(),
mostWatchedVideo: this.stats.mostWatchedVideo.name || 'None yet',
completionRate: this.getCompletionRate()
mostWatchedVideo: this.stats.mostWatchedVideo.name || 'None yet'
};
}
getCompletionRate() {
const total = this.stats.videosCompleted + this.stats.videosSkipped;
if (total === 0) return '0%';
return Math.round((this.stats.videosCompleted / total) * 100) + '%';
}
formatDuration(milliseconds) {
if (!milliseconds || milliseconds === 0) return '0m';
@ -340,9 +438,10 @@ class PlayerStats {
}
}
// Auto-cleanup on page unload
// Auto-cleanup on page unload - record any current watch time
window.addEventListener('beforeunload', () => {
if (window.playerStats) {
window.playerStats.endSession();
if (window.playerStats && window.playerStats.currentSession.isVideoPlaying) {
window.playerStats.recordCurrentWatchTime();
window.playerStats.saveStats();
}
});

View File

@ -366,6 +366,16 @@ body {
min-width: 32px;
}
.cassie-icon {
display: inline-block;
width: 2.5rem;
height: 2.5rem;
background-image: url('../../assets/cassie.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.feature-text {
font-size: var(--font-base);
color: var(--text-primary);
@ -454,6 +464,58 @@ body {
margin-top: var(--space-sm);
}
/* Level Display Styles */
.level-display {
min-width: 280px;
}
.level-info {
flex-direction: column;
gap: var(--space-xs);
align-items: center;
margin-bottom: var(--space-base);
}
.level-title {
font-size: var(--font-xxl);
color: var(--color-primary);
font-weight: 700;
text-shadow: 0 0 15px rgba(138, 43, 226, 0.6);
text-transform: uppercase;
letter-spacing: 1px;
}
.level-number {
font-size: var(--font-lg);
color: var(--text-secondary);
font-weight: 500;
}
.level-xp-info {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.xp-display {
display: flex;
align-items: baseline;
justify-content: center;
gap: var(--space-xs);
}
.level-progress-container {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.level-progress-text {
font-size: var(--font-sm);
color: var(--text-tertiary);
text-align: center;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@ -6407,4 +6469,362 @@ button#start-mirror-btn:disabled {
color: var(--color-accent);
font-size: var(--font-sm);
font-weight: 600;
}
/* ===============================
CHARACTER GRAPHICS SYSTEM
=============================== */
/* Corner Characters */
.character-corner {
position: fixed;
z-index: 5;
pointer-events: none;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.character-corner.top-left {
top: 0;
left: 0;
width: 200px;
height: 250px;
}
.character-corner.top-right {
top: 0;
right: 0;
width: 200px;
height: 250px;
}
.character-corner.bottom-left {
bottom: 0;
left: 0;
width: 200px;
height: 250px;
}
.character-corner.bottom-right {
bottom: 0;
right: 0;
width: 200px;
height: 250px;
}
/* Side Characters */
.character-side {
position: fixed;
z-index: 3;
pointer-events: none;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.character-side.left {
left: -50px;
top: 50%;
transform: translateY(-50%);
width: 150px;
height: 300px;
}
.character-side.right {
right: -50px;
top: 50%;
transform: translateY(-50%);
width: 150px;
height: 300px;
}
/* Background Characters */
.character-background {
position: absolute;
z-index: 1;
opacity: 0.15;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
pointer-events: none;
}
/* Button Characters - Small mascots for buttons */
.character-button-mascot {
position: absolute;
width: 40px;
height: 40px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
top: -10px;
right: -10px;
z-index: 10;
pointer-events: none;
}
/* Floating Characters - Animated floating elements */
.character-floating {
position: fixed;
z-index: 2;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
pointer-events: none;
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
/* Specific character implementations */
.character-cassie-corner {
background-image: url('../../assets/characters/cassie-corner.png');
}
.character-mai-side {
background-image: url('../../assets/characters/mai-side.png');
}
.character-asuka-background {
background-image: url('../../assets/characters/asuka-background.png');
}
/* ===== CYBERPUNK VIDEO BILLBOARD ===== */
.video-billboard-container {
position: absolute;
top: 15%;
right: 50px;
width: 500px;
height: 320px;
z-index: 10;
}
.video-billboard {
width: 100%;
height: 100%;
position: relative;
border-radius: 12px;
overflow: hidden;
background: linear-gradient(135deg, #1a0033, #330066);
box-shadow:
0 0 30px rgba(138, 43, 226, 0.4),
inset 0 0 20px rgba(218, 112, 214, 0.1);
}
.billboard-frame {
position: relative;
width: 100%;
height: 100%;
border-radius: 8px;
overflow: hidden;
}
#billboard-video {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
border-radius: 8px;
transition: opacity 0.5s ease-in-out;
opacity: 1;
/* Optimize video performance */
backface-visibility: hidden;
transform: translateZ(0);
will-change: opacity;
}
.billboard-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
to bottom,
rgba(138, 43, 226, 0.1) 0%,
transparent 30%,
transparent 70%,
rgba(0, 0, 0, 0.3) 100%
);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.video-billboard:hover .billboard-overlay {
opacity: 1;
}
.billboard-controls {
position: absolute;
bottom: 15px;
right: 15px;
display: flex;
gap: 10px;
pointer-events: all;
}
.billboard-btn {
background: rgba(26, 26, 26, 0.8);
border: 1px solid rgba(138, 43, 226, 0.5);
color: #ffffff;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.billboard-btn:hover {
background: rgba(138, 43, 226, 0.3);
border-color: rgba(138, 43, 226, 0.8);
box-shadow: 0 0 15px rgba(138, 43, 226, 0.5);
transform: translateY(-2px);
}
.billboard-btn:active {
transform: translateY(0);
box-shadow: 0 0 10px rgba(138, 43, 226, 0.3);
}
/* Neon Border Effects */
.neon-border {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
border-radius: 8px;
}
/* Continuous neon frame edges */
.neon-edge {
position: absolute;
background: linear-gradient(90deg,
rgba(0, 255, 255, 0.8) 0%,
rgba(255, 0, 255, 0.8) 25%,
rgba(255, 255, 0, 0.8) 50%,
rgba(0, 255, 0, 0.8) 75%,
rgba(138, 43, 226, 0.8) 100%
);
box-shadow:
0 0 10px rgba(138, 43, 226, 0.6),
0 0 20px rgba(138, 43, 226, 0.4),
0 0 30px rgba(138, 43, 226, 0.2);
}
.neon-edge.top {
top: -2px;
left: 0;
right: 0;
height: 4px;
border-radius: 8px 8px 0 0;
animation: neon-flow-horizontal 3s linear infinite;
}
.neon-edge.bottom {
bottom: -2px;
left: 0;
right: 0;
height: 4px;
border-radius: 0 0 8px 8px;
animation: neon-flow-horizontal-reverse 3s linear infinite;
}
.neon-edge.left {
left: -2px;
top: 0;
bottom: 0;
width: 4px;
border-radius: 8px 0 0 8px;
background: linear-gradient(0deg,
rgba(0, 255, 255, 0.8) 0%,
rgba(255, 0, 255, 0.8) 25%,
rgba(255, 255, 0, 0.8) 50%,
rgba(0, 255, 0, 0.8) 75%,
rgba(138, 43, 226, 0.8) 100%
);
animation: neon-flow-vertical 3s linear infinite;
}
.neon-edge.right {
right: -2px;
top: 0;
bottom: 0;
width: 4px;
border-radius: 0 8px 8px 0;
background: linear-gradient(0deg,
rgba(0, 255, 255, 0.8) 0%,
rgba(255, 0, 255, 0.8) 25%,
rgba(255, 255, 0, 0.8) 50%,
rgba(0, 255, 0, 0.8) 75%,
rgba(138, 43, 226, 0.8) 100%
);
animation: neon-flow-vertical-reverse 3s linear infinite;
}
/* Remove the corner elements completely */
.neon-corner {
display: none;
}
/* Neon Animation Keyframes */
@keyframes neon-flow-horizontal {
0% { transform: translateX(-100%); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateX(100%); opacity: 0; }
}
@keyframes neon-flow-horizontal-reverse {
0% { transform: translateX(100%); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateX(-100%); opacity: 0; }
}
@keyframes neon-flow-vertical {
0% { transform: translateY(-100%); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateY(100%); opacity: 0; }
}
@keyframes neon-flow-vertical-reverse {
0% { transform: translateY(100%); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateY(-100%); opacity: 0; }
}
/* Responsive adjustments */
@media (max-width: 1200px) {
.video-billboard-container {
width: 450px;
height: 280px;
right: 30px;
}
}
@media (max-width: 992px) {
.video-billboard-container {
width: 380px;
height: 240px;
right: 20px;
top: 20%;
}
.billboard-controls {
bottom: 10px;
right: 10px;
gap: 8px;
}
.billboard-btn {
padding: 6px 10px;
font-size: 12px;
}
}

91
tasks.md Normal file
View File

@ -0,0 +1,91 @@
# Main Game Tasks
Watch porn and stroke slowly for 2 minutes, no cumming
Edge while watching BBC content sitting in chair for 3 minutes
Change your panties and get on your knees for 1 minute
Watch cuckold videos standing up and edge for 4 minutes without release
Goon to compilation videos sitting for 6 minutes, multiple edges
Watch interracial in the captain morgan pose, stroke for 7 minutes
Edge to humiliation content while kneeling down for 4 minutes
Watch sissy hypno standing while stroking with your non-dominant hand for 3 minutes
Goon to gangbang videos sitting in chair for 5 minutes
Watch MILF porn while standing, stroke for 2 minutes
Edge to cuckold captions standing for 5 minutes with multiple stops
Squeeze your balls while watching sissy porn for 2 minutes
Slap your cock 10 times while watching humiliation content
Call yourself a pathetic gooner while stroking for 4 minutes
Pinch your nipples and balls while edging to BBC content for 1 minutes
Admit you're addicted to porn out loud while edging for 3 minutes
Flick your cock head 15 times then edge without cumming for 4 minutes
Watch trans porn while stroking sitting and standing alternately for 6 minutes
5 minutes of continuous stroking sitting to random videos
Watch hotwife content while edging on knees for 2 minutes
Get naked and do 5 minutes of edging standing to BBC compilations
Change your panties and then do 5 minutes standing while watching porn
Put panties and a bra on and do 5 minutes of edging in doggy position to humiliation content
Stroke with your left hand only while watching BBC for 3 minutes
Edge to sissy captions while on your knees for 4 minutes
Slap your balls 5 times then edge to interracial porn for 5 minutes
Watch gangbang videos while alternating hands every 30 seconds for 4 minutes
Edge to femdom content while lying on your back for 6 minutes
Stroke while looking at yourself in the mirror and calling yourself a slut for 2 minutes
Slap your cock 10 times while watching humiliation videos
Practice moaning like a girl while edging to sissy content for 4 minutes
Edge to sissy hypno while wearing your wife's panties for 4 minutes
Slap your thighs 20 times then stroke to BBC content for 3 minutes
Stroke while reciting "I'm a pathetic cuckold" 10 times to humiliation content
Edge to cuckold videos while doing squats for 3 minutes
Watch BBC content while crawling on all fours and stroking for 2 minutes
Slap your ass 15 times then edge to sissy content for 4 minutes
Stroke to gangbang videos while switching hands every minute for 5 minutes
Edge to humiliation content while wearing your wife's bra for 4 minutes
Stroke to BBC while chanting "bigger is better" for 2 minutes
Stroke to compilation videos while on your knees for 5 minutes
Edge to sissy captions while touching your nipples for 3 minutes
Watch interracial while alternating fast and slow strokes every 30 seconds for 4 minutes
Watch humiliation content while stroking with just your fingertips for 4 minutes
Edge to sissy hypno while practicing feminine poses for 5 minutes

View File

@ -148,7 +148,7 @@
.quick-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
@ -162,10 +162,15 @@
}
.quick-stat-value {
font-size: 2rem;
font-size: 1.4rem;
font-weight: bold;
color: var(--primary-light);
display: block;
line-height: 1.2;
min-height: 2em;
display: flex;
align-items: center;
justify-content: center;
}
.quick-stat-label {
@ -327,7 +332,11 @@
}
.quick-stats {
grid-template-columns: repeat(2, 1fr);
grid-template-columns: 1fr;
}
.quick-stat-value {
font-size: 1.2rem;
}
.achievement-grid {
@ -350,6 +359,76 @@
align-items: center;
gap: 10px;
}
/* Detailed Stats Styles */
.stat-detail {
background: var(--background);
border-radius: 8px;
padding: 15px;
text-align: center;
border: 1px solid var(--border);
}
.stat-detail-value {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-light);
display: block;
margin-bottom: 5px;
}
.stat-detail-label {
font-size: 0.85rem;
color: var(--text-secondary);
}
.xp-mode {
background: var(--background);
border-radius: 8px;
padding: 15px;
text-align: center;
border: 1px solid var(--border);
}
.xp-mode-value {
font-size: 1.3rem;
font-weight: bold;
color: var(--warning);
display: block;
margin-bottom: 5px;
}
.xp-mode-label {
font-size: 0.85rem;
color: var(--text-secondary);
}
.reset-section {
background: var(--surface);
border-radius: 15px;
padding: 30px;
border: 1px solid var(--error);
margin-top: 30px;
}
.reset-section h3 {
color: var(--error);
font-size: 1.5rem;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.btn-danger {
background: var(--error);
color: white;
}
.btn-danger:hover {
background: #DC2626;
transform: translateY(-2px);
}
</style>
</head>
<body>
@ -393,7 +472,7 @@
</div>
<div class="stats-overview">
<h3>📊 Statistics Overview</h3>
<h3>📊 Player Statistics</h3>
<div class="quick-stats">
<div class="quick-stat">
@ -404,6 +483,10 @@
<span class="quick-stat-value" id="videos-watched">0</span>
<div class="quick-stat-label">Videos Watched</div>
</div>
<div class="quick-stat">
<span class="quick-stat-value" id="total-xp">0</span>
<div class="quick-stat-label">Total XP</div>
</div>
<div class="quick-stat">
<span class="quick-stat-value" id="current-level">1</span>
<div class="quick-stat-label">Current Level</div>
@ -412,6 +495,10 @@
<span class="quick-stat-value" id="current-streak">0</span>
<div class="quick-stat-label">Day Streak</div>
</div>
<div class="quick-stat">
<span class="quick-stat-value" id="playlists-created">0</span>
<div class="quick-stat-label">Playlists Created</div>
</div>
</div>
<div class="level-progress">
@ -423,6 +510,74 @@
<div class="level-fill" id="level-fill" style="width: 0%"></div>
<div class="level-text" id="level-text">0%</div>
</div>
<div class="level-description" style="margin-top: 15px; text-align: center; padding: 10px; background: rgba(139, 92, 246, 0.1); border-radius: 8px;">
<div id="level-desc" style="font-style: italic; color: var(--text-secondary);">Your first taste of pleasure awaits</div>
</div>
</div>
<!-- Detailed Statistics Section -->
<div class="detailed-stats" style="margin-top: 30px;">
<h4 style="margin-bottom: 20px; color: var(--primary-light);">📈 Detailed Statistics</h4>
<div class="stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
<div class="stat-detail">
<div class="stat-detail-value" id="days-active">0</div>
<div class="stat-detail-label">Days Active</div>
</div>
<div class="stat-detail">
<div class="stat-detail-value" id="longest-streak">0</div>
<div class="stat-detail-label">Longest Streak</div>
</div>
<div class="stat-detail">
<div class="stat-detail-value" id="videos-added-playlists">0</div>
<div class="stat-detail-label">Videos Added to Playlists</div>
</div>
<div class="stat-detail">
<div class="stat-detail-value" id="videos-skipped">0</div>
<div class="stat-detail-label">Videos Skipped</div>
</div>
<div class="stat-detail">
<div class="stat-detail-value" id="binge-sessions">0</div>
<div class="stat-detail-label">Binge Sessions (2+ hrs)</div>
</div>
<div class="stat-detail">
<div class="stat-detail-value" id="most-watched-video">None</div>
<div class="stat-detail-label">Most Watched Video</div>
</div>
</div>
<!-- Game Mode XP Breakdown -->
<div class="xp-breakdown" style="margin-top: 25px;">
<h4 style="margin-bottom: 15px; color: var(--primary-light);">🎮 XP Breakdown by Game Mode</h4>
<div class="xp-modes" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;">
<div class="xp-mode">
<div class="xp-mode-value" id="quick-play-xp">0</div>
<div class="xp-mode-label">Quick Play XP</div>
</div>
<div class="xp-mode">
<div class="xp-mode-value" id="porn-cinema-xp">0</div>
<div class="xp-mode-label">Cinema XP</div>
</div>
<div class="xp-mode">
<div class="xp-mode-value" id="scenario-xp">0</div>
<div class="xp-mode-label">Scenario XP</div>
</div>
</div>
</div>
<!-- Activity Timeline -->
<div class="activity-info" style="margin-top: 25px; background: var(--background); padding: 20px; border-radius: 10px;">
<h4 style="margin-bottom: 15px; color: var(--primary-light);">📅 Activity Timeline</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
<div>
<strong>First Play Date:</strong>
<div id="first-play-date" style="color: var(--text-secondary);">N/A</div>
</div>
<div>
<strong>Last Activity:</strong>
<div id="last-activity" style="color: var(--text-secondary);">N/A</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -435,19 +590,29 @@
</div>
<div class="export-section">
<h3>💾 Profile Data</h3>
<h3>💾 Profile & Statistics Data</h3>
<p>Manage your profile and statistics data</p>
<div class="action-buttons">
<button class="btn btn-secondary" onclick="viewDetailedStats()">📈 Detailed Statistics</button>
<button class="btn btn-secondary" onclick="exportProfile()">📤 Export Profile</button>
<button class="btn btn-secondary" onclick="importProfile()">📥 Import Profile</button>
<button class="btn btn-secondary" onclick="exportProfile()"><EFBFBD> Export Profile</button>
<button class="btn btn-secondary" onclick="importProfile()"><EFBFBD> Import Profile</button>
<button class="btn btn-secondary" onclick="exportStats()"><EFBFBD> Export Statistics</button>
<button class="btn btn-primary" onclick="saveProfile()">💾 Save Changes</button>
</div>
</div>
<div class="reset-section">
<h3>⚠️ Danger Zone</h3>
<p>These actions cannot be undone. Please be careful!</p>
<div class="action-buttons">
<button class="btn btn-danger" onclick="resetStats()">🗑️ Reset All Statistics</button>
<button class="btn btn-danger" onclick="resetProfile()">👤 Reset Profile</button>
</div>
</div>
<div class="action-buttons">
<a href="index.html" class="btn btn-secondary">🏠 Back to Main Menu</a>
<a href="player-stats.html" class="btn btn-secondary">📊 Detailed Stats</a>
<a href="porn-cinema.html" class="btn btn-secondary">🎬 Cinema</a>
<a href="quick-play.html" class="btn btn-secondary">⚡ Quick Play</a>
</div>
<script src="src/features/stats/playerStats.js"></script>
@ -507,42 +672,108 @@
}
updateStats() {
if (!this.playerStats) return;
if (!this.playerStats) {
// Initialize playerStats if not available
window.playerStats = new PlayerStats();
this.playerStats = window.playerStats;
}
const stats = this.playerStats.getStats();
const stats = this.playerStats.getFormattedStats();
const rawStats = this.playerStats.getRawStats();
// Update quick stats
document.getElementById('total-watch-time').textContent = this.formatTime(stats.totalWatchTime);
document.getElementById('total-watch-time').textContent = stats.totalWatchTime;
document.getElementById('videos-watched').textContent = stats.videosWatched;
document.getElementById('current-streak').textContent = stats.streaks.current;
document.getElementById('total-xp').textContent = rawStats.totalXP || 0;
document.getElementById('current-streak').textContent = stats.currentStreak;
document.getElementById('playlists-created').textContent = stats.playlistsCreated;
// Calculate level based on XP
const level = this.calculateLevel(stats.totalWatchTime);
document.getElementById('current-level').textContent = level;
const level = this.calculateLevel(rawStats.totalXP || 0);
const levelInfo = this.getLevelInfo(level);
document.getElementById('current-level').textContent = `${level} - ${levelInfo.name}`;
// Update level description
document.getElementById('level-desc').textContent = levelInfo.description;
// Update level progress
this.updateLevelProgress(stats.totalWatchTime);
this.updateLevelProgress(rawStats.totalXP || 0);
// Update detailed stats
document.getElementById('days-active').textContent = stats.daysActive;
document.getElementById('longest-streak').textContent = stats.longestStreak;
document.getElementById('videos-added-playlists').textContent = rawStats.videosAddedToPlaylists || 0;
document.getElementById('videos-skipped').textContent = rawStats.videosSkipped || 0;
document.getElementById('binge-sessions').textContent = rawStats.bingeSessions || 0;
document.getElementById('most-watched-video').textContent = stats.mostWatchedVideo || 'None';
// Update XP breakdown
document.getElementById('quick-play-xp').textContent = rawStats.quickPlayXP || 0;
document.getElementById('porn-cinema-xp').textContent = rawStats.pornCinemaXP || 0;
document.getElementById('scenario-xp').textContent = rawStats.scenarioGameXP || 0;
// Update activity timeline
document.getElementById('first-play-date').textContent = rawStats.firstPlayDate ?
new Date(rawStats.firstPlayDate).toLocaleDateString() : 'N/A';
document.getElementById('last-activity').textContent = rawStats.lastPlayDate ?
new Date(rawStats.lastPlayDate).toLocaleDateString() : 'N/A';
// Update achievements
this.updateAchievements(stats);
this.updateAchievements(rawStats);
}
calculateLevel(watchTimeSeconds) {
// Level up every hour of watch time
return Math.floor(watchTimeSeconds / 3600) + 1;
calculateLevel(totalXp) {
// Level up every 100 XP
return Math.floor(totalXp / 100) + 1;
}
updateLevelProgress(watchTimeSeconds) {
const currentLevel = this.calculateLevel(watchTimeSeconds);
const currentLevelStart = (currentLevel - 1) * 3600;
const nextLevelStart = currentLevel * 3600;
const progressInLevel = watchTimeSeconds - currentLevelStart;
getLevelInfo(level) {
const levelData = {
1: { name: 'Virgin', icon: '🌱', description: 'Your first taste of pleasure awaits' },
2: { name: 'Curious', icon: '🌿', description: 'Start exploring your desires' },
3: { name: 'Eager', icon: '🌱', description: 'Developing your appetites' },
4: { name: 'Aroused', icon: '🌿', description: 'Your arousal is building' },
5: { name: 'Lustful', icon: '🌟', description: 'Consumed by lust and craving more' },
6: { name: 'Passionate', icon: '🔥', description: 'Your passion burns hot with desire' },
7: { name: 'Addicted', icon: '⭐', description: 'Hopelessly addicted to pleasure' },
8: { name: 'Obsessed', icon: '🎯', description: 'Your obsession drives you to new heights' },
9: { name: 'Deviant', icon: '🏆', description: 'A true deviant exploring desire' },
10: { name: 'Kinky', icon: '💎', description: 'Welcome to the kinky elite' },
11: { name: 'Perverted', icon: '🌟', description: 'A seasoned pervert with experience' },
12: { name: 'Depraved', icon: '🔮', description: 'Mastered the art of depravity' },
13: { name: 'Dominant', icon: '👑', description: 'A dominant force commanding respect' },
14: { name: 'Hedonistic', icon: '🚀', description: 'Pure hedonism guides your moves' },
15: { name: 'Decadent', icon: '🌌', description: 'Reached decadent levels of indulgence' },
16: { name: 'Insatiable', icon: '⚡', description: 'Your appetite knows no bounds' },
17: { name: 'Sinful', icon: '🔥', description: 'Deliciously sinful dedication' },
18: { name: 'Forbidden', icon: '🌠', description: 'Achieved forbidden status' },
19: { name: 'Godlike', icon: '🏛️', description: 'Godlike levels of sexual prowess' },
20: { name: 'Omnipotent', icon: '👁️', description: 'Ultimate master of pleasure' }
};
// For levels beyond 20, continue with Omnipotent
if (level > 20) {
return {
name: `Omnipotent ${level}`,
icon: '👁️',
description: 'Beyond mortal comprehension'
};
}
return levelData[level] || { name: 'Unknown', icon: '❓', description: 'Mysterious level' };
}
updateLevelProgress(totalXp) {
const currentLevel = this.calculateLevel(totalXp);
const currentLevelStart = (currentLevel - 1) * 100;
const nextLevelStart = currentLevel * 100;
const progressInLevel = totalXp - currentLevelStart;
const levelDuration = nextLevelStart - currentLevelStart;
const progressPercent = (progressInLevel / levelDuration) * 100;
document.getElementById('level-fill').style.width = progressPercent + '%';
document.getElementById('level-text').textContent = Math.round(progressPercent) + '%';
document.getElementById('level-xp').textContent = `${Math.round(progressInLevel)} / ${levelDuration} seconds`;
document.getElementById('level-xp').textContent = `${progressInLevel} / ${levelDuration} XP`;
}
initializeAchievements() {
@ -550,58 +781,72 @@
{
id: 'first-video',
icon: '🎬',
title: 'First Steps',
title: 'First Taste',
description: 'Watch your first video',
condition: stats => stats.videosWatched >= 1
condition: stats => (stats.videosWatched || 0) >= 1
},
{
id: 'early-bird',
icon: '🌅',
title: 'Early Bird',
title: 'Eager Explorer',
description: 'Watch 10 videos',
condition: stats => stats.videosWatched >= 10
condition: stats => (stats.videosWatched || 0) >= 10
},
{
id: 'marathon',
icon: '🏃',
title: 'Marathon Viewer',
description: 'Watch for 2+ hours in one session',
condition: stats => stats.sessions.longest >= 7200
title: 'Marathon Session',
description: 'Watch for 2+ hours total',
condition: stats => (stats.totalWatchTime || 0) >= 7200
},
{
id: 'curator',
icon: '📚',
title: 'Playlist Curator',
title: 'Pleasure Curator',
description: 'Create 5 playlists',
condition: stats => stats.playlistsCreated >= 5
condition: stats => (stats.playlistsCreated || 0) >= 5
},
{
id: 'consistent',
icon: '🔥',
title: 'Consistency King',
title: 'Addiction King',
description: 'Maintain a 7-day streak',
condition: stats => stats.streaks.longest >= 7
condition: stats => (stats.longestStreak || 0) >= 7
},
{
id: 'completionist',
icon: '',
title: 'Completionist',
description: 'Complete 50 videos (90%+)',
condition: stats => stats.videosCompleted >= 50
id: 'xp-master',
icon: '',
title: 'Passionate Soul',
description: 'Earn 500 total XP (Level 6)',
condition: stats => (stats.totalXP || 0) >= 500
},
{
id: 'night-owl',
icon: '🦉',
title: 'Night Owl',
description: 'Watch after midnight',
condition: stats => stats.sessions.total > 0 // Simplified for demo
id: 'level-up',
icon: '<27>',
title: 'Lustful Awakening',
description: 'Reach Level 5 (Lustful)',
condition: stats => Math.floor((stats.totalXP || 0) / 100) + 1 >= 5
},
{
id: 'kinky-elite',
icon: '💎',
title: 'Kinky Elite',
description: 'Reach Level 10 (Kinky)',
condition: stats => Math.floor((stats.totalXP || 0) / 100) + 1 >= 10
},
{
id: 'depraved-master',
icon: '🔮',
title: 'Depraved Master',
description: 'Reach Level 12 (Depraved)',
condition: stats => Math.floor((stats.totalXP || 0) / 100) + 1 >= 12
},
{
id: 'collector',
icon: '💎',
title: 'Video Collector',
icon: '🎭',
title: 'Desire Collector',
description: 'Watch 100 different videos',
condition: stats => stats.videosWatched >= 100
condition: stats => (stats.videosWatched || 0) >= 100
}
];
}
@ -693,10 +938,6 @@
}
}
function viewDetailedStats() {
window.location.href = 'player-stats.html';
}
function exportProfile() {
const profile = localStorage.getItem('userProfile');
const stats = localStorage.getItem('playerStats');
@ -714,6 +955,59 @@
a.download = `webgame-profile-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
if (window.userProfile) {
window.userProfile.showNotification('Profile exported successfully!', 'success');
}
}
function exportStats() {
if (!window.playerStats) {
alert('No statistics available to export');
return;
}
const exportData = window.playerStats.exportStats();
const dataStr = JSON.stringify(exportData, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `player-stats-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
if (window.userProfile) {
window.userProfile.showNotification('Statistics exported successfully!', 'success');
}
}
function resetStats() {
if (confirm('Are you sure you want to reset all statistics? This action cannot be undone.')) {
if (window.playerStats) {
window.playerStats.resetStats();
if (window.userProfile) {
window.userProfile.updateStats();
window.userProfile.showNotification('Statistics reset successfully!', 'success');
}
}
}
}
function resetProfile() {
if (confirm('Are you sure you want to reset your profile? This action cannot be undone.')) {
localStorage.removeItem('userProfile');
if (window.userProfile) {
window.userProfile.profile = window.userProfile.loadProfile();
window.userProfile.updateDisplay();
window.userProfile.showNotification('Profile reset successfully!', 'success');
}
}
}
function importProfile() {