Game Mode:
-
Final Score: points
+
Final XP: XP
Final Time:
Tasks Completed:
Tasks Skipped:
diff --git a/src/core/game.js b/src/core/game.js
index f32a3b3..7c9dd8e 100644
--- a/src/core/game.js
+++ b/src/core/game.js
@@ -66,11 +66,13 @@ class TaskChallengeGame {
usedConsequenceTasks: [],
usedTaskImages: [], // Track which task images have been shown
usedConsequenceImages: [], // Track which consequence images have been shown
- gameMode: 'complete-all', // Game mode: 'complete-all', 'timed', 'score-target'
+ gameMode: 'complete-all', // Game mode: 'complete-all', 'timed', 'xp-target'
timeLimit: 300, // Time limit in seconds (5 minutes default)
- scoreTarget: 1000, // Score target (default 1000 points)
+ xpTarget: 300, // XP target (default 300 XP)
+ xp: 0, // Current session XP earned
+ taskCompletionXp: 0, // XP earned from completing tasks (tracked separately)
+ sessionStartTime: null, // When the current session started
currentStreak: 0, // Track consecutive completed regular tasks
- totalStreakBonuses: 0, // Track total streak bonus points earned
lastStreakMilestone: 0, // Track the last streak milestone reached
focusInterruptionChance: 0 // Percentage chance (0-50) for focus-hold interruptions in scenarios
};
@@ -163,6 +165,8 @@ class TaskChallengeGame {
setTimeout(() => {
this.hideLoadingOverlay();
this.isInitialized = true;
+ // Initialize stats display including overall XP
+ this.updateStats();
}, 500);
}, 1000);
}
@@ -1685,7 +1689,6 @@ class TaskChallengeGame {
if (this.fileManager) {
await this.fileManager.scanDirectoryForAudio('background');
await this.fileManager.scanDirectoryForAudio('ambient');
- await this.fileManager.scanDirectoryForAudio('effects');
console.log('Desktop audio discovery completed');
} else {
console.log('Web mode - audio discovery skipped');
@@ -1874,52 +1877,79 @@ class TaskChallengeGame {
masterVolumeDisplay.textContent = e.target.value + '%';
});
- // Category volume controls
+ // Category volume controls - all now control the same background audio
taskAudioSlider.addEventListener('input', (e) => {
const volume = parseInt(e.target.value) / 100;
- this.audioManager.setCategoryVolume('tasks', volume);
+ this.audioManager.setCategoryVolume('background', volume);
+ // Update all displays since they all represent the same setting now
taskAudioDisplay.textContent = e.target.value + '%';
+ punishmentAudioDisplay.textContent = e.target.value + '%';
+ rewardAudioDisplay.textContent = e.target.value + '%';
+ // Sync all sliders
+ punishmentAudioSlider.value = e.target.value;
+ rewardAudioSlider.value = e.target.value;
});
punishmentAudioSlider.addEventListener('input', (e) => {
const volume = parseInt(e.target.value) / 100;
- this.audioManager.setCategoryVolume('punishments', volume);
+ this.audioManager.setCategoryVolume('background', volume);
+ // Update all displays
+ taskAudioDisplay.textContent = e.target.value + '%';
punishmentAudioDisplay.textContent = e.target.value + '%';
+ rewardAudioDisplay.textContent = e.target.value + '%';
+ // Sync all sliders
+ taskAudioSlider.value = e.target.value;
+ rewardAudioSlider.value = e.target.value;
});
rewardAudioSlider.addEventListener('input', (e) => {
const volume = parseInt(e.target.value) / 100;
- this.audioManager.setCategoryVolume('rewards', volume);
+ this.audioManager.setCategoryVolume('background', volume);
+ // Update all displays
+ taskAudioDisplay.textContent = e.target.value + '%';
+ punishmentAudioDisplay.textContent = e.target.value + '%';
rewardAudioDisplay.textContent = e.target.value + '%';
+ // Sync all sliders
+ taskAudioSlider.value = e.target.value;
+ punishmentAudioSlider.value = e.target.value;
});
- // Enable/disable toggles
+ // Enable/disable toggles - all now control the same background audio
enableTaskAudio.addEventListener('change', (e) => {
- this.audioManager.setCategoryEnabled('tasks', e.target.checked);
+ this.audioManager.setCategoryEnabled('background', e.target.checked);
+ // Sync all toggles since they represent the same setting
+ enablePunishmentAudio.checked = e.target.checked;
+ enableRewardAudio.checked = e.target.checked;
});
enablePunishmentAudio.addEventListener('change', (e) => {
- this.audioManager.setCategoryEnabled('punishments', e.target.checked);
+ this.audioManager.setCategoryEnabled('background', e.target.checked);
+ // Sync all toggles
+ enableTaskAudio.checked = e.target.checked;
+ enableRewardAudio.checked = e.target.checked;
});
enableRewardAudio.addEventListener('change', (e) => {
- this.audioManager.setCategoryEnabled('rewards', e.target.checked);
+ this.audioManager.setCategoryEnabled('background', e.target.checked);
+ // Sync all toggles
+ enableTaskAudio.checked = e.target.checked;
+ enablePunishmentAudio.checked = e.target.checked;
});
// Periodic popup controls
this.setupPeriodicPopupControls();
- // Preview buttons
+ // Preview buttons - all now preview background audio
previewTaskAudio.addEventListener('click', () => {
- this.audioManager.previewAudio('tasks', 'teasing');
+ this.audioManager.playBackgroundAudio({ fadeIn: 300 });
});
previewPunishmentAudio.addEventListener('click', () => {
- this.audioManager.previewAudio('punishments', 'denial');
+ this.audioManager.playBackgroundAudio({ fadeIn: 300 });
});
previewRewardAudio.addEventListener('click', () => {
- this.audioManager.previewAudio('rewards', 'completion');
+ this.audioManager.playBackgroundAudio({ fadeIn: 300 });
});
// Debug button
@@ -1944,22 +1974,43 @@ class TaskChallengeGame {
document.getElementById('master-volume').value = masterVolume;
document.getElementById('master-volume-display').textContent = masterVolume + '%';
- const taskVolume = Math.round(settings.categories.tasks.volume * 100);
- document.getElementById('task-audio-volume').value = taskVolume;
- document.getElementById('task-audio-volume-display').textContent = taskVolume + '%';
+ // Use background audio settings for all audio controls since we simplified to one category
+ const backgroundVolume = Math.round((settings.categories.background?.volume || 0.7) * 100);
- const punishmentVolume = Math.round(settings.categories.punishments.volume * 100);
- document.getElementById('punishment-audio-volume').value = punishmentVolume;
- document.getElementById('punishment-audio-volume-display').textContent = punishmentVolume + '%';
+ // Update all audio volume controls to use the same background audio setting
+ const audioVolumeElements = [
+ 'task-audio-volume',
+ 'punishment-audio-volume',
+ 'reward-audio-volume'
+ ];
+ const audioDisplayElements = [
+ 'task-audio-volume-display',
+ 'punishment-audio-volume-display',
+ 'reward-audio-volume-display'
+ ];
- const rewardVolume = Math.round(settings.categories.rewards.volume * 100);
- document.getElementById('reward-audio-volume').value = rewardVolume;
- document.getElementById('reward-audio-volume-display').textContent = rewardVolume + '%';
+ audioVolumeElements.forEach(id => {
+ const element = document.getElementById(id);
+ if (element) element.value = backgroundVolume;
+ });
- // Update toggles
- document.getElementById('enable-task-audio').checked = settings.categories.tasks.enabled;
- document.getElementById('enable-punishment-audio').checked = settings.categories.punishments.enabled;
- document.getElementById('enable-reward-audio').checked = settings.categories.rewards.enabled;
+ audioDisplayElements.forEach(id => {
+ const element = document.getElementById(id);
+ if (element) element.textContent = backgroundVolume + '%';
+ });
+
+ // Update toggles - all use the same background audio enabled setting
+ const backgroundEnabled = settings.categories.background?.enabled || false;
+ const audioToggleElements = [
+ 'enable-task-audio',
+ 'enable-punishment-audio',
+ 'enable-reward-audio'
+ ];
+
+ audioToggleElements.forEach(id => {
+ const element = document.getElementById(id);
+ if (element) element.checked = backgroundEnabled;
+ });
}
setupPeriodicPopupControls() {
@@ -2112,15 +2163,15 @@ class TaskChallengeGame {
console.log('⚠️ Time limit select not found');
}
- const scoreTargetSelect = document.getElementById('score-target-select');
- if (scoreTargetSelect) {
- console.log('🏆 Found score target select, adding listener');
- scoreTargetSelect.addEventListener('change', () => {
- console.log('🏆 Score target select changed');
- this.handleScoreTargetChange();
+ const xpTargetSelect = document.getElementById('xp-target-select');
+ if (xpTargetSelect) {
+ console.log('⭐ Found XP target select, adding listener');
+ xpTargetSelect.addEventListener('change', () => {
+ console.log('⭐ XP target select changed');
+ this.handleXpTargetChange();
});
} else {
- console.log('⚠️ Score target select not found');
+ console.log('⚠️ XP target select not found');
}
// Add listeners for custom input changes (if elements exist)
@@ -2131,10 +2182,10 @@ class TaskChallengeGame {
});
}
- const customScoreValue = document.getElementById('custom-score-value');
- if (customScoreValue) {
- customScoreValue.addEventListener('input', () => {
- this.handleCustomScoreChange();
+ const customXpValue = document.getElementById('custom-xp-value');
+ if (customXpValue) {
+ customXpValue.addEventListener('input', () => {
+ this.handleCustomXpChange();
});
}
@@ -2152,14 +2203,14 @@ class TaskChallengeGame {
// Show/hide configuration options based on selected mode (if elements exist)
const timedConfig = document.getElementById('timed-config');
- const scoreTargetConfig = document.getElementById('score-target-config');
+ const xpTargetConfig = document.getElementById('xp-target-config');
// Hide all configs first
if (timedConfig) {
timedConfig.style.display = 'none';
}
- if (scoreTargetConfig) {
- scoreTargetConfig.style.display = 'none';
+ if (xpTargetConfig) {
+ xpTargetConfig.style.display = 'none';
}
// Show appropriate config
@@ -2167,16 +2218,16 @@ class TaskChallengeGame {
timedConfig.style.display = 'block';
console.log('⏱️ Showing timed configuration options');
this.handleTimeLimitChange();
- } else if (selectedMode === 'score-target' && scoreTargetConfig) {
- scoreTargetConfig.style.display = 'block';
- console.log('🏆 Showing score target configuration options');
- this.handleScoreTargetChange();
+ } else if (selectedMode === 'xp-target' && xpTargetConfig) {
+ xpTargetConfig.style.display = 'block';
+ console.log('⭐ Showing XP target configuration options');
+ this.handleXpTargetChange();
}
console.log(`Game state updated:`, {
gameMode: this.gameState.gameMode,
timeLimit: this.gameState.timeLimit,
- scoreTarget: this.gameState.scoreTarget
+ xpTarget: this.gameState.xpTarget
});
}
@@ -2208,31 +2259,31 @@ class TaskChallengeGame {
}
}
- handleScoreTargetChange() {
- const scoreTargetSelect = document.getElementById('score-target-select');
- const customScoreInput = document.getElementById('custom-score-input');
+ handleXpTargetChange() {
+ const xpTargetSelect = document.getElementById('xp-target-select');
+ const customXpInput = document.getElementById('custom-xp-input');
- if (!scoreTargetSelect) {
- console.log('⚠️ Score target select element not found');
+ if (!xpTargetSelect) {
+ console.log('⚠️ XP target select element not found');
return;
}
- const selectedValue = scoreTargetSelect.value;
- console.log(`🏆 Score target selection: ${selectedValue}`);
+ const selectedValue = xpTargetSelect.value;
+ console.log(`⭐ XP target selection: ${selectedValue}`);
- if (customScoreInput) {
+ if (customXpInput) {
if (selectedValue === 'custom') {
- customScoreInput.style.display = 'block';
- console.log('🏆 Showing custom score input');
- this.handleCustomScoreChange();
+ customXpInput.style.display = 'block';
+ console.log('⭐ Showing custom XP input');
+ this.handleCustomXpChange();
} else {
- customScoreInput.style.display = 'none';
- this.gameState.scoreTarget = parseInt(selectedValue);
- console.log(`🏆 Score target set to: ${this.gameState.scoreTarget} points`);
+ customXpInput.style.display = 'none';
+ this.gameState.xpTarget = parseInt(selectedValue);
+ console.log(`⭐ XP target set to: ${this.gameState.xpTarget} XP`);
}
} else if (selectedValue !== 'custom') {
- this.gameState.scoreTarget = parseInt(selectedValue);
- console.log(`🏆 Score target set to: ${this.gameState.scoreTarget} points`);
+ this.gameState.xpTarget = parseInt(selectedValue);
+ console.log(`⭐ XP target set to: ${this.gameState.xpTarget} XP`);
}
}
@@ -2245,14 +2296,91 @@ class TaskChallengeGame {
}
}
- handleCustomScoreChange() {
- const customScoreValue = document.getElementById('custom-score-value');
- if (customScoreValue) {
- const score = parseInt(customScoreValue.value) || 300;
- this.gameState.scoreTarget = score;
- console.log(`Custom score target set to ${score} points`);
+ handleCustomXpChange() {
+ const customXpValue = document.getElementById('custom-xp-value');
+ if (customXpValue) {
+ const xp = parseInt(customXpValue.value) || 100;
+ this.gameState.xpTarget = xp;
+ console.log(`Custom XP target set to ${xp} XP`);
}
}
+
+ // XP Calculation Methods
+ calculateTimeBasedXp() {
+ if (!this.gameState.sessionStartTime) return 0;
+
+ const currentTime = Date.now();
+ const sessionDuration = (currentTime - this.gameState.sessionStartTime) / 60000; // Convert to minutes
+ return Math.floor(sessionDuration / 2); // 1 XP per 2 minutes
+ }
+
+ calculateActivityBonusXp() {
+ let bonusXp = 0;
+
+ // Focus session bonus: 5 XP per minute
+ const focusMinutes = (this.gameState.focusSessionTime || 0) / 60000;
+ bonusXp += Math.floor(focusMinutes * 5);
+
+ // Webcam mirror bonus: 5 XP per minute
+ const webcamMinutes = (this.gameState.webcamMirrorTime || 0) / 60000;
+ bonusXp += Math.floor(webcamMinutes * 5);
+
+ // Photo bonus: 1 XP per photo
+ bonusXp += this.gameState.photosTaken || 0;
+
+ return bonusXp;
+ }
+
+ updateXp() {
+ // XP comes only from task completion - no time or activity restrictions
+ const taskCompletionXp = this.gameState.taskCompletionXp || 0;
+
+ // Set current session XP to task completion XP only
+ this.gameState.xp = taskCompletionXp;
+
+ // Debug logging to track XP sources
+ if (taskCompletionXp > 0) {
+ console.log(`💰 XP Update - Task XP: ${taskCompletionXp}`);
+ }
+
+ this.updateStats();
+
+ // Check if XP target is reached for xp-target mode
+ if (this.gameState.gameMode === 'xp-target' && taskCompletionXp >= this.gameState.xpTarget) {
+ console.log(`⭐ XP target of ${this.gameState.xpTarget} reached! Current XP: ${taskCompletionXp}`);
+ this.endGame('target-reached');
+ }
+
+ return taskCompletionXp;
+ }
+
+ trackFocusSession(isActive) {
+ if (isActive && !this.gameState._focusSessionStart) {
+ this.gameState._focusSessionStart = Date.now();
+ } else if (!isActive && this.gameState._focusSessionStart) {
+ const duration = Date.now() - this.gameState._focusSessionStart;
+ this.gameState.focusSessionTime = (this.gameState.focusSessionTime || 0) + duration;
+ delete this.gameState._focusSessionStart;
+ this.updateXp();
+ }
+ }
+
+ trackWebcamMirror(isActive) {
+ if (isActive && !this.gameState._webcamMirrorStart) {
+ this.gameState._webcamMirrorStart = Date.now();
+ } else if (!isActive && this.gameState._webcamMirrorStart) {
+ const duration = Date.now() - this.gameState._webcamMirrorStart;
+ this.gameState.webcamMirrorTime = (this.gameState.webcamMirrorTime || 0) + duration;
+ delete this.gameState._webcamMirrorStart;
+ this.updateXp();
+ }
+ }
+
+ incrementPhotosTaken() {
+ this.gameState.photosTaken = (this.gameState.photosTaken || 0) + 1;
+ this.updateXp();
+ console.log(`📷 Photo taken! Total photos: ${this.gameState.photosTaken}, XP gained: +1`);
+ }
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
@@ -2656,24 +2784,43 @@ class TaskChallengeGame {
}
changeTheme(themeName) {
- // Remove all existing theme classes (old theme names from CSS)
- const themeClasses = ['theme-sunset', 'theme-forest', 'theme-midnight', 'theme-pastel', 'theme-neon', 'theme-autumn', 'theme-monochrome'];
- document.body.classList.remove(...themeClasses);
-
- // Add new theme class (ocean is default, no class needed)
- if (themeName !== 'ocean') {
- document.body.classList.add(`theme-${themeName}`);
+ // Remove any existing theme link
+ const existingThemeLink = document.getElementById('dynamic-theme');
+ if (existingThemeLink) {
+ existingThemeLink.remove();
}
+ // Remove old theme classes if they exist
+ const oldThemeClasses = ['theme-sunset', 'theme-forest', 'theme-midnight', 'theme-pastel', 'theme-neon', 'theme-autumn', 'theme-monochrome'];
+ document.body.classList.remove(...oldThemeClasses);
+
+ // Load the appropriate theme CSS file
+ if (themeName && themeName !== 'balanced-purple') {
+ const themeLink = document.createElement('link');
+ themeLink.id = 'dynamic-theme';
+ themeLink.rel = 'stylesheet';
+ themeLink.href = `${themeName}-theme.css`;
+ document.head.appendChild(themeLink);
+ }
+ // balanced-purple is our default theme loaded in the HTML, so no need to load additional CSS
+
// Save theme preference
localStorage.setItem('gameTheme', themeName);
+
+ console.log(`Theme changed to: ${themeName}`);
}
loadSavedTheme() {
- // Clear any problematic theme and force default
- localStorage.removeItem('gameTheme');
- const savedTheme = 'ocean'; // Force ocean theme for readability
- document.getElementById('theme-dropdown').value = savedTheme;
+ // Load saved theme or default to balanced-purple
+ const savedTheme = localStorage.getItem('gameTheme') || 'balanced-purple';
+
+ // Make sure the dropdown reflects the saved theme
+ const themeDropdown = document.getElementById('theme-dropdown');
+ if (themeDropdown) {
+ themeDropdown.value = savedTheme;
+ }
+
+ // Apply the theme
this.changeTheme(savedTheme);
}
@@ -2783,7 +2930,7 @@ class TaskChallengeGame {
this.musicManager.initializeVolumeUI();
// Update theme
- const theme = this.dataManager.getSetting('theme') || 'ocean';
+ const theme = this.dataManager.getSetting('theme') || 'balanced-purple';
this.changeTheme(theme);
} catch (error) {
@@ -3783,14 +3930,12 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
// Update tab buttons
const backgroundTab = document.getElementById('background-audio-tab');
const ambientTab = document.getElementById('ambient-audio-tab');
- const effectsTab = document.getElementById('effects-audio-tab');
const backgroundGallery = document.getElementById('background-audio-gallery');
const ambientGallery = document.getElementById('ambient-audio-gallery');
- const effectsGallery = document.getElementById('effects-audio-gallery');
// Remove active class from all tabs and galleries
- [backgroundTab, ambientTab, effectsTab].forEach(tab => tab && tab.classList.remove('active'));
- [backgroundGallery, ambientGallery, effectsGallery].forEach(gallery => gallery && gallery.classList.remove('active'));
+ [backgroundTab, ambientTab].forEach(tab => tab && tab.classList.remove('active'));
+ [backgroundGallery, ambientGallery].forEach(gallery => gallery && gallery.classList.remove('active'));
// Add active class to selected tab and gallery
if (tabType === 'background') {
@@ -3799,9 +3944,6 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
} else if (tabType === 'ambient') {
ambientTab && ambientTab.classList.add('active');
ambientGallery && ambientGallery.classList.add('active');
- } else if (tabType === 'effects') {
- effectsTab && effectsTab.classList.add('active');
- effectsGallery && effectsGallery.classList.add('active');
}
// Update gallery controls to work with current tab
@@ -3844,18 +3986,6 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
}
};
}
-
- const importEffectsBtn = document.getElementById('import-effects-audio-btn');
- if (importEffectsBtn) {
- importEffectsBtn.onclick = async () => {
- if (this.fileManager) {
- await this.fileManager.selectAndImportAudio('effects');
- this.loadAudioGallery(); // Refresh the gallery to show new audio
- } else {
- this.showNotification('Desktop file manager not available', 'warning');
- }
- };
- }
// Audio storage info button
const audioStorageInfoBtn = document.getElementById('audio-storage-info-btn');
@@ -3863,6 +3993,12 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
audioStorageInfoBtn.onclick = () => this.showAudioStorageInfo();
}
+ // Scan directories button
+ const scanAudioDirectoriesBtn = document.getElementById('scan-audio-directories-btn');
+ if (scanAudioDirectoriesBtn) {
+ scanAudioDirectoriesBtn.onclick = () => this.scanAudioDirectories();
+ }
+
// Tab buttons
const backgroundAudioTab = document.getElementById('background-audio-tab');
if (backgroundAudioTab) {
@@ -3874,11 +4010,6 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
ambientAudioTab.onclick = () => this.switchAudioTab('ambient');
}
- const effectsAudioTab = document.getElementById('effects-audio-tab');
- if (effectsAudioTab) {
- effectsAudioTab.onclick = () => this.switchAudioTab('effects');
- }
-
// Gallery control buttons - assign onclick directly to avoid expensive DOM operations
const selectAllAudioBtn = document.getElementById('select-all-audio-btn');
if (selectAllAudioBtn) {
@@ -3913,20 +4044,18 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
loadAudioGallery() {
const backgroundGallery = document.getElementById('background-audio-gallery');
const ambientGallery = document.getElementById('ambient-audio-gallery');
- const effectsGallery = document.getElementById('effects-audio-gallery');
- if (!backgroundGallery || !ambientGallery || !effectsGallery) {
+ if (!backgroundGallery || !ambientGallery) {
console.error('Audio gallery elements not found');
return;
}
// Get custom audio from storage
- const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [], effects: [] };
+ const customAudio = this.dataManager.get('customAudio') || { background: [], ambient: [] };
// Load each category
this.loadAudioCategory('background', backgroundGallery, customAudio.background);
this.loadAudioCategory('ambient', ambientGallery, customAudio.ambient);
- this.loadAudioCategory('effects', effectsGallery, customAudio.effects);
// Update audio count
this.updateAudioCount();
@@ -3938,6 +4067,11 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
if (this.musicManager) {
this.musicManager.refreshCustomTracks();
}
+
+ // Refresh the audio manager to include new audio files
+ if (this.audioManager) {
+ this.audioManager.refreshAudioLibrary();
+ }
}
loadAudioCategory(category, gallery, audioFiles) {
@@ -3953,7 +4087,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
${audio.title}
${audio.name}
-
+