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:
parent
9a8d4b5432
commit
c361c308c1
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
||||
922
index.html
922
index.html
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
2934
quick-play.html
2934
quick-play.html
File diff suppressed because it is too large
Load Diff
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue