feat: Phase 2 - Advanced Flash Message System & Annoyance Management
- Added comprehensive FlashMessageManager class with full CRUD operations - Implemented 4-tab Annoyance Management interface (Messages, Appearance, Behavior, Import/Export) - Enhanced message management: add/edit/delete/enable/disable with categories and priorities - Advanced appearance controls: position, animation, colors, fonts, opacity - Sophisticated behavior settings: timing, variation, event-based triggers - Complete import/export system with merge/replace modes and reset options - Added 20 default encouraging messages across 4 categories - Integrated flash messages into game events (task completion, streaks, skips) - Added responsive CSS with animations and professional styling - Enhanced data persistence and error handling - Game integration: messages start/pause/resume with game state Phase 1-2 Complete: Core infrastructure + Advanced management ready for Phase 3
This commit is contained in:
parent
f1f88d5f23
commit
6fad5af73f
|
|
@ -0,0 +1,569 @@
|
|||
/**
|
||||
* Flash Message Manager - Handles displaying encouraging messages during gameplay
|
||||
* Part of the Annoyance system for customizable user motivation
|
||||
*/
|
||||
class FlashMessageManager {
|
||||
constructor(dataManager) {
|
||||
this.dataManager = dataManager;
|
||||
this.isActive = false;
|
||||
this.isPaused = false;
|
||||
this.currentTimeout = null;
|
||||
this.messageElement = null;
|
||||
this.config = null;
|
||||
this.messages = [];
|
||||
this.lastMessageIndex = -1;
|
||||
this.messageQueue = [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Load configuration and messages
|
||||
this.loadConfiguration();
|
||||
this.loadMessages();
|
||||
this.createMessageElement();
|
||||
|
||||
console.log('FlashMessageManager initialized');
|
||||
console.log(`Loaded ${this.messages.length} flash messages`);
|
||||
}
|
||||
|
||||
loadConfiguration() {
|
||||
// Get saved config or use defaults
|
||||
const savedConfig = this.dataManager.get('flashMessageConfig');
|
||||
this.config = {
|
||||
...gameData.defaultFlashConfig,
|
||||
...(savedConfig || {})
|
||||
};
|
||||
}
|
||||
|
||||
loadMessages() {
|
||||
// Get custom messages or use defaults
|
||||
const customMessages = this.dataManager.get('customFlashMessages');
|
||||
|
||||
if (customMessages && customMessages.length > 0) {
|
||||
this.messages = customMessages.filter(msg => msg.enabled !== false);
|
||||
} else {
|
||||
// Use default messages
|
||||
this.messages = gameData.defaultFlashMessages.filter(msg => msg.enabled !== false);
|
||||
}
|
||||
}
|
||||
|
||||
createMessageElement() {
|
||||
// Create the overlay element for flash messages
|
||||
this.messageElement = document.createElement('div');
|
||||
this.messageElement.id = 'flash-message-overlay';
|
||||
this.messageElement.className = 'flash-message-overlay';
|
||||
this.messageElement.style.display = 'none';
|
||||
|
||||
// Apply default styles
|
||||
this.applyMessageStyles();
|
||||
|
||||
// Add to body
|
||||
document.body.appendChild(this.messageElement);
|
||||
}
|
||||
|
||||
applyMessageStyles() {
|
||||
if (!this.messageElement) return;
|
||||
|
||||
// Base styles that are always applied
|
||||
Object.assign(this.messageElement.style, {
|
||||
position: 'fixed',
|
||||
display: 'none',
|
||||
fontSize: this.config.fontSize,
|
||||
fontWeight: this.config.fontWeight,
|
||||
color: this.config.color,
|
||||
backgroundColor: this.config.backgroundColor,
|
||||
borderRadius: this.config.borderRadius,
|
||||
padding: this.config.padding,
|
||||
maxWidth: this.config.maxWidth,
|
||||
zIndex: this.config.zIndex,
|
||||
textAlign: 'center',
|
||||
wordWrap: 'break-word',
|
||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.3)',
|
||||
backdropFilter: 'blur(5px)',
|
||||
border: '2px solid rgba(255, 255, 255, 0.2)',
|
||||
transition: 'opacity 0.5s ease-in-out, transform 0.5s ease-in-out'
|
||||
});
|
||||
|
||||
// Position-based styles
|
||||
this.applyPositionStyles();
|
||||
}
|
||||
|
||||
applyPositionStyles() {
|
||||
if (!this.messageElement) return;
|
||||
|
||||
// Reset position styles
|
||||
this.messageElement.style.top = '';
|
||||
this.messageElement.style.bottom = '';
|
||||
this.messageElement.style.left = '';
|
||||
this.messageElement.style.right = '';
|
||||
this.messageElement.style.transform = '';
|
||||
|
||||
switch (this.config.position) {
|
||||
case 'top-left':
|
||||
this.messageElement.style.top = '20px';
|
||||
this.messageElement.style.left = '20px';
|
||||
break;
|
||||
case 'top-center':
|
||||
this.messageElement.style.top = '20px';
|
||||
this.messageElement.style.left = '50%';
|
||||
this.messageElement.style.transform = 'translateX(-50%)';
|
||||
break;
|
||||
case 'top-right':
|
||||
this.messageElement.style.top = '20px';
|
||||
this.messageElement.style.right = '20px';
|
||||
break;
|
||||
case 'center-left':
|
||||
this.messageElement.style.top = '50%';
|
||||
this.messageElement.style.left = '20px';
|
||||
this.messageElement.style.transform = 'translateY(-50%)';
|
||||
break;
|
||||
case 'center':
|
||||
default:
|
||||
this.messageElement.style.top = '50%';
|
||||
this.messageElement.style.left = '50%';
|
||||
this.messageElement.style.transform = 'translate(-50%, -50%)';
|
||||
break;
|
||||
case 'center-right':
|
||||
this.messageElement.style.top = '50%';
|
||||
this.messageElement.style.right = '20px';
|
||||
this.messageElement.style.transform = 'translateY(-50%)';
|
||||
break;
|
||||
case 'bottom-left':
|
||||
this.messageElement.style.bottom = '20px';
|
||||
this.messageElement.style.left = '20px';
|
||||
break;
|
||||
case 'bottom-center':
|
||||
this.messageElement.style.bottom = '20px';
|
||||
this.messageElement.style.left = '50%';
|
||||
this.messageElement.style.transform = 'translateX(-50%)';
|
||||
break;
|
||||
case 'bottom-right':
|
||||
this.messageElement.style.bottom = '20px';
|
||||
this.messageElement.style.right = '20px';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
if (!this.config.enabled || this.messages.length === 0) {
|
||||
console.log('Flash messages disabled or no messages available');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isActive = true;
|
||||
this.isPaused = false;
|
||||
this.scheduleNext();
|
||||
console.log('Flash message system started');
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.isActive = false;
|
||||
this.isPaused = false;
|
||||
this.clearCurrentTimeout();
|
||||
this.hideCurrentMessage();
|
||||
console.log('Flash message system stopped');
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (this.isActive) {
|
||||
this.isPaused = true;
|
||||
this.clearCurrentTimeout();
|
||||
this.hideCurrentMessage();
|
||||
console.log('Flash message system paused');
|
||||
}
|
||||
}
|
||||
|
||||
resume() {
|
||||
if (this.isActive && this.isPaused) {
|
||||
this.isPaused = false;
|
||||
this.scheduleNext();
|
||||
console.log('Flash message system resumed');
|
||||
}
|
||||
}
|
||||
|
||||
scheduleNext() {
|
||||
if (!this.isActive || this.isPaused) return;
|
||||
|
||||
// Clear any existing timeout
|
||||
this.clearCurrentTimeout();
|
||||
|
||||
// Schedule the next message
|
||||
const delay = this.config.intervalDelay + (Math.random() * 10000 - 5000); // ±5 seconds variation
|
||||
this.currentTimeout = setTimeout(() => {
|
||||
this.showRandomMessage();
|
||||
this.scheduleNext(); // Schedule the next one
|
||||
}, Math.max(delay, 5000)); // Minimum 5 seconds between messages
|
||||
}
|
||||
|
||||
clearCurrentTimeout() {
|
||||
if (this.currentTimeout) {
|
||||
clearTimeout(this.currentTimeout);
|
||||
this.currentTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
showRandomMessage() {
|
||||
if (!this.isActive || this.isPaused || this.messages.length === 0) return;
|
||||
|
||||
// Pick a random message (avoid repeating the last one if possible)
|
||||
let messageIndex;
|
||||
if (this.messages.length > 1) {
|
||||
do {
|
||||
messageIndex = Math.floor(Math.random() * this.messages.length);
|
||||
} while (messageIndex === this.lastMessageIndex);
|
||||
} else {
|
||||
messageIndex = 0;
|
||||
}
|
||||
|
||||
const message = this.messages[messageIndex];
|
||||
this.lastMessageIndex = messageIndex;
|
||||
|
||||
this.showMessage(message);
|
||||
}
|
||||
|
||||
showMessage(messageObj, customConfig = {}) {
|
||||
if (!this.messageElement) return;
|
||||
|
||||
// Apply any custom configuration temporarily
|
||||
const tempConfig = { ...this.config, ...customConfig };
|
||||
|
||||
// Set the message text
|
||||
this.messageElement.textContent = messageObj.text || messageObj;
|
||||
|
||||
// Apply styles (in case config changed)
|
||||
this.applyMessageStyles();
|
||||
|
||||
// Show the message with animation
|
||||
this.messageElement.style.display = 'block';
|
||||
this.messageElement.style.opacity = '0';
|
||||
|
||||
// Trigger animation based on config
|
||||
requestAnimationFrame(() => {
|
||||
this.applyShowAnimation(tempConfig.animation);
|
||||
});
|
||||
|
||||
// Hide the message after the display duration
|
||||
setTimeout(() => {
|
||||
this.hideCurrentMessage(tempConfig.animation);
|
||||
}, tempConfig.displayDuration);
|
||||
|
||||
console.log(`Showed flash message: "${messageObj.text || messageObj}"`);
|
||||
}
|
||||
|
||||
applyShowAnimation(animationType = 'fade') {
|
||||
if (!this.messageElement) return;
|
||||
|
||||
switch (animationType) {
|
||||
case 'slide':
|
||||
this.messageElement.style.opacity = '1';
|
||||
this.messageElement.style.transform =
|
||||
this.messageElement.style.transform.replace('translateY(50px)', '');
|
||||
break;
|
||||
case 'bounce':
|
||||
this.messageElement.style.opacity = '1';
|
||||
this.messageElement.style.animation = 'flashBounceIn 0.6s ease-out';
|
||||
break;
|
||||
case 'pulse':
|
||||
this.messageElement.style.opacity = '1';
|
||||
this.messageElement.style.animation = 'flashPulseIn 0.8s ease-out';
|
||||
break;
|
||||
case 'fade':
|
||||
default:
|
||||
this.messageElement.style.opacity = '1';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hideCurrentMessage(animationType = 'fade') {
|
||||
if (!this.messageElement) return;
|
||||
|
||||
switch (animationType) {
|
||||
case 'slide':
|
||||
this.messageElement.style.transform += ' translateY(-50px)';
|
||||
this.messageElement.style.opacity = '0';
|
||||
break;
|
||||
case 'bounce':
|
||||
case 'pulse':
|
||||
this.messageElement.style.animation = 'flashFadeOut 0.5s ease-in';
|
||||
break;
|
||||
case 'fade':
|
||||
default:
|
||||
this.messageElement.style.opacity = '0';
|
||||
break;
|
||||
}
|
||||
|
||||
// Hide the element completely after animation
|
||||
setTimeout(() => {
|
||||
if (this.messageElement) {
|
||||
this.messageElement.style.display = 'none';
|
||||
this.messageElement.style.animation = '';
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Manual message triggering for specific events
|
||||
triggerEventMessage(eventType, customMessage = null) {
|
||||
if (!this.isActive || this.isPaused) return;
|
||||
|
||||
let message = customMessage;
|
||||
|
||||
if (!message) {
|
||||
// Find messages for specific event types
|
||||
const eventMessages = this.messages.filter(msg => {
|
||||
switch (eventType) {
|
||||
case 'taskComplete':
|
||||
return msg.category === 'achievement';
|
||||
case 'taskSkip':
|
||||
return msg.category === 'persistence';
|
||||
case 'gameStart':
|
||||
return msg.category === 'motivational';
|
||||
case 'streak':
|
||||
return msg.category === 'achievement';
|
||||
default:
|
||||
return msg.category === 'encouraging';
|
||||
}
|
||||
});
|
||||
|
||||
if (eventMessages.length > 0) {
|
||||
message = eventMessages[Math.floor(Math.random() * eventMessages.length)];
|
||||
} else {
|
||||
// Fallback to any available message
|
||||
message = this.messages[Math.floor(Math.random() * this.messages.length)];
|
||||
}
|
||||
}
|
||||
|
||||
if (message) {
|
||||
// Clear current schedule temporarily to show event message
|
||||
this.clearCurrentTimeout();
|
||||
this.showMessage(message);
|
||||
|
||||
// Resume normal scheduling after a short delay
|
||||
setTimeout(() => {
|
||||
if (this.isActive && !this.isPaused) {
|
||||
this.scheduleNext();
|
||||
}
|
||||
}, this.config.displayDuration + 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration management
|
||||
updateConfig(newConfig) {
|
||||
this.config = { ...this.config, ...newConfig };
|
||||
this.dataManager.set('flashMessageConfig', this.config);
|
||||
|
||||
// Apply new styles
|
||||
this.applyMessageStyles();
|
||||
|
||||
// Restart if active to apply new timing
|
||||
if (this.isActive) {
|
||||
this.stop();
|
||||
this.start();
|
||||
}
|
||||
|
||||
console.log('Flash message config updated:', newConfig);
|
||||
}
|
||||
|
||||
updateMessages(newMessages) {
|
||||
this.messages = newMessages.filter(msg => msg.enabled !== false);
|
||||
this.dataManager.set('customFlashMessages', newMessages);
|
||||
console.log(`Updated messages: ${this.messages.length} active messages`);
|
||||
}
|
||||
|
||||
// Enhanced message management
|
||||
addMessage(messageData) {
|
||||
const customMessages = this.dataManager.get('customFlashMessages') || [...gameData.defaultFlashMessages];
|
||||
const newMessage = {
|
||||
id: Date.now(),
|
||||
text: messageData.text,
|
||||
category: messageData.category || 'custom',
|
||||
priority: messageData.priority || 'normal',
|
||||
enabled: true,
|
||||
isCustom: true,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
customMessages.push(newMessage);
|
||||
this.updateMessages(customMessages);
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
editMessage(messageId, updates) {
|
||||
const customMessages = this.dataManager.get('customFlashMessages') || [...gameData.defaultFlashMessages];
|
||||
const messageIndex = customMessages.findIndex(msg => msg.id === messageId);
|
||||
|
||||
if (messageIndex !== -1) {
|
||||
customMessages[messageIndex] = { ...customMessages[messageIndex], ...updates };
|
||||
this.updateMessages(customMessages);
|
||||
return customMessages[messageIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
deleteMessage(messageId) {
|
||||
const customMessages = this.dataManager.get('customFlashMessages') || [...gameData.defaultFlashMessages];
|
||||
const filteredMessages = customMessages.filter(msg => msg.id !== messageId);
|
||||
this.updateMessages(filteredMessages);
|
||||
return filteredMessages.length < customMessages.length;
|
||||
}
|
||||
|
||||
toggleMessageEnabled(messageId) {
|
||||
const customMessages = this.dataManager.get('customFlashMessages') || [...gameData.defaultFlashMessages];
|
||||
const messageIndex = customMessages.findIndex(msg => msg.id === messageId);
|
||||
|
||||
if (messageIndex !== -1) {
|
||||
customMessages[messageIndex].enabled = !customMessages[messageIndex].enabled;
|
||||
this.updateMessages(customMessages);
|
||||
return customMessages[messageIndex].enabled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Advanced filtering and categorization
|
||||
getMessagesByCategory(category) {
|
||||
if (category === 'all') return this.getAllMessages();
|
||||
return this.getAllMessages().filter(msg => msg.category === category);
|
||||
}
|
||||
|
||||
getMessageStats() {
|
||||
const allMessages = this.getAllMessages();
|
||||
const enabledMessages = allMessages.filter(msg => msg.enabled !== false);
|
||||
const disabledMessages = allMessages.filter(msg => msg.enabled === false);
|
||||
|
||||
const categories = {};
|
||||
allMessages.forEach(msg => {
|
||||
const cat = msg.category || 'custom';
|
||||
categories[cat] = (categories[cat] || 0) + 1;
|
||||
});
|
||||
|
||||
return {
|
||||
total: allMessages.length,
|
||||
enabled: enabledMessages.length,
|
||||
disabled: disabledMessages.length,
|
||||
custom: allMessages.filter(msg => msg.isCustom).length,
|
||||
categories: categories
|
||||
};
|
||||
}
|
||||
|
||||
// Import/Export functionality
|
||||
exportMessages(includeDisabled = true, customOnly = false) {
|
||||
const allMessages = this.getAllMessages();
|
||||
let messagesToExport = allMessages;
|
||||
|
||||
if (!includeDisabled) {
|
||||
messagesToExport = messagesToExport.filter(msg => msg.enabled !== false);
|
||||
}
|
||||
|
||||
if (customOnly) {
|
||||
messagesToExport = messagesToExport.filter(msg => msg.isCustom);
|
||||
}
|
||||
|
||||
const exportData = {
|
||||
messages: messagesToExport,
|
||||
config: this.getConfig(),
|
||||
exportedAt: new Date().toISOString(),
|
||||
version: "2.0"
|
||||
};
|
||||
|
||||
return JSON.stringify(exportData, null, 2);
|
||||
}
|
||||
|
||||
importMessages(jsonData, mode = 'merge') {
|
||||
try {
|
||||
const importData = JSON.parse(jsonData);
|
||||
const importedMessages = importData.messages || [];
|
||||
|
||||
let finalMessages = [];
|
||||
|
||||
if (mode === 'replace') {
|
||||
// Replace all messages
|
||||
finalMessages = importedMessages.map(msg => ({
|
||||
...msg,
|
||||
id: msg.id || Date.now() + Math.random(),
|
||||
importedAt: new Date().toISOString()
|
||||
}));
|
||||
} else {
|
||||
// Merge with existing messages
|
||||
const existingMessages = this.getAllMessages();
|
||||
const existingIds = new Set(existingMessages.map(msg => msg.id));
|
||||
|
||||
finalMessages = [...existingMessages];
|
||||
|
||||
importedMessages.forEach(msg => {
|
||||
if (!existingIds.has(msg.id)) {
|
||||
finalMessages.push({
|
||||
...msg,
|
||||
id: msg.id || Date.now() + Math.random(),
|
||||
importedAt: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.updateMessages(finalMessages);
|
||||
|
||||
// Optionally import config
|
||||
if (importData.config) {
|
||||
this.updateConfig(importData.config);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
imported: importedMessages.length,
|
||||
total: finalMessages.length
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
resetToDefaults() {
|
||||
this.dataManager.set('customFlashMessages', null);
|
||||
this.dataManager.set('flashMessageConfig', null);
|
||||
this.loadConfiguration();
|
||||
this.loadMessages();
|
||||
return {
|
||||
messages: this.getAllMessages().length,
|
||||
config: this.getConfig()
|
||||
};
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
isEnabled() {
|
||||
return this.config.enabled;
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
getMessages() {
|
||||
return [...this.messages];
|
||||
}
|
||||
|
||||
getAllMessages() {
|
||||
// Return both enabled and disabled messages for management
|
||||
const customMessages = this.dataManager.get('customFlashMessages');
|
||||
return customMessages || [...gameData.defaultFlashMessages];
|
||||
}
|
||||
|
||||
// Preview method for testing
|
||||
previewMessage(messageObj, customConfig = {}) {
|
||||
const wasActive = this.isActive;
|
||||
this.isActive = true; // Temporarily enable to show preview
|
||||
this.showMessage(messageObj, customConfig);
|
||||
this.isActive = wasActive; // Restore previous state
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
destroy() {
|
||||
this.stop();
|
||||
if (this.messageElement && this.messageElement.parentNode) {
|
||||
this.messageElement.parentNode.removeChild(this.messageElement);
|
||||
}
|
||||
this.messageElement = null;
|
||||
}
|
||||
}
|
||||
711
game.js
711
game.js
|
|
@ -38,6 +38,10 @@ class TaskChallengeGame {
|
|||
this.audioDiscoveryComplete = false;
|
||||
this.audioManagementListenersAttached = false;
|
||||
this.musicManager = new MusicManager(this.dataManager);
|
||||
|
||||
// Initialize Flash Message System
|
||||
this.flashMessageManager = new FlashMessageManager(this.dataManager);
|
||||
|
||||
this.initializeEventListeners();
|
||||
this.setupKeyboardShortcuts();
|
||||
this.setupWindowResizeHandling();
|
||||
|
|
@ -391,6 +395,9 @@ class TaskChallengeGame {
|
|||
// Audio management - only the main button, others will be attached when screen is shown
|
||||
document.getElementById('manage-audio-btn').addEventListener('click', () => this.showAudioManagement());
|
||||
|
||||
// Annoyance management - main button and basic controls
|
||||
document.getElementById('manage-annoyance-btn').addEventListener('click', () => this.showAnnoyanceManagement());
|
||||
|
||||
// Load saved theme
|
||||
this.loadSavedTheme();
|
||||
}
|
||||
|
|
@ -2478,6 +2485,9 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
this.loadNextTask();
|
||||
console.log('Showing game screen...');
|
||||
this.showScreen('game-screen');
|
||||
console.log('Starting flash message system...');
|
||||
this.flashMessageManager.start();
|
||||
this.flashMessageManager.triggerEventMessage('gameStart');
|
||||
console.log('Updating stats...');
|
||||
this.updateStats();
|
||||
console.log('startGame() completed successfully');
|
||||
|
|
@ -2820,6 +2830,11 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
this.gameState.score += streakBonus;
|
||||
this.gameState.totalStreakBonuses += streakBonus;
|
||||
this.showStreakBonusNotification(this.gameState.currentStreak, streakBonus);
|
||||
// Trigger special streak message
|
||||
this.flashMessageManager.triggerEventMessage('streak');
|
||||
} else {
|
||||
// Trigger regular completion message
|
||||
this.flashMessageManager.triggerEventMessage('taskComplete');
|
||||
}
|
||||
|
||||
// Check for score target win condition
|
||||
|
|
@ -2857,6 +2872,9 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
this.gameState.currentStreak = 0;
|
||||
this.gameState.lastStreakMilestone = 0;
|
||||
|
||||
// Trigger skip message
|
||||
this.flashMessageManager.triggerEventMessage('taskSkip');
|
||||
|
||||
// Load a consequence task
|
||||
this.gameState.isConsequenceTask = true;
|
||||
this.loadNextTask();
|
||||
|
|
@ -2871,6 +2889,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
this.gameState.pausedTime = Date.now();
|
||||
this.stopTimer();
|
||||
this.musicManager.pause();
|
||||
this.flashMessageManager.pause();
|
||||
this.showScreen('paused-screen');
|
||||
|
||||
// Auto-save the paused game state
|
||||
|
|
@ -2884,6 +2903,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
this.gameState.isPaused = false;
|
||||
this.startTimer();
|
||||
this.musicManager.resume();
|
||||
this.flashMessageManager.resume();
|
||||
this.showScreen('game-screen');
|
||||
}
|
||||
|
||||
|
|
@ -2894,6 +2914,7 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
endGame(reason = 'complete-all') {
|
||||
this.gameState.isRunning = false;
|
||||
this.stopTimer();
|
||||
this.flashMessageManager.stop();
|
||||
this.showFinalStats(reason);
|
||||
this.showScreen('game-over-screen');
|
||||
}
|
||||
|
|
@ -3891,6 +3912,696 @@ class MusicManager {
|
|||
}
|
||||
}
|
||||
|
||||
// Annoyance Management Methods - Phase 2: Advanced Message Management
|
||||
TaskChallengeGame.prototype.showAnnoyanceManagement = function() {
|
||||
this.showScreen('annoyance-management-screen');
|
||||
this.setupAnnoyanceManagementEventListeners();
|
||||
this.loadAnnoyanceSettings();
|
||||
this.showAnnoyanceTab('messages'); // Default to messages tab
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.setupAnnoyanceManagementEventListeners = function() {
|
||||
// Back button
|
||||
const backBtn = document.getElementById('back-to-start-from-annoyance-btn');
|
||||
if (backBtn) {
|
||||
backBtn.onclick = () => this.showScreen('start-screen');
|
||||
}
|
||||
|
||||
// Save settings button
|
||||
const saveBtn = document.getElementById('save-annoyance-settings');
|
||||
if (saveBtn) {
|
||||
saveBtn.onclick = () => this.saveAllAnnoyanceSettings();
|
||||
}
|
||||
|
||||
// Tab navigation
|
||||
document.getElementById('messages-tab').onclick = () => this.showAnnoyanceTab('messages');
|
||||
document.getElementById('appearance-tab').onclick = () => this.showAnnoyanceTab('appearance');
|
||||
document.getElementById('behavior-tab').onclick = () => this.showAnnoyanceTab('behavior');
|
||||
document.getElementById('import-export-tab').onclick = () => this.showAnnoyanceTab('import-export');
|
||||
|
||||
this.setupMessagesTabListeners();
|
||||
this.setupAppearanceTabListeners();
|
||||
this.setupBehaviorTabListeners();
|
||||
this.setupImportExportTabListeners();
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.showAnnoyanceTab = function(tabName) {
|
||||
// Update tab buttons
|
||||
document.querySelectorAll('.annoyance-tab').forEach(tab => tab.classList.remove('active'));
|
||||
document.getElementById(`${tabName}-tab`).classList.add('active');
|
||||
|
||||
// Update tab content
|
||||
document.querySelectorAll('.annoyance-tab-content').forEach(content => content.classList.remove('active'));
|
||||
document.getElementById(`${tabName}-tab-content`).classList.add('active');
|
||||
|
||||
// Load tab-specific content
|
||||
switch(tabName) {
|
||||
case 'messages':
|
||||
this.loadMessagesTab();
|
||||
break;
|
||||
case 'appearance':
|
||||
this.loadAppearanceTab();
|
||||
break;
|
||||
case 'behavior':
|
||||
this.loadBehaviorTab();
|
||||
break;
|
||||
case 'import-export':
|
||||
this.loadImportExportTab();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Messages Tab Management
|
||||
TaskChallengeGame.prototype.setupMessagesTabListeners = function() {
|
||||
const enabledCheckbox = document.getElementById('flash-messages-enabled');
|
||||
const addBtn = document.getElementById('add-new-message-btn');
|
||||
const closeEditorBtn = document.getElementById('close-editor-btn');
|
||||
const saveMessageBtn = document.getElementById('save-message-btn');
|
||||
const previewCurrentBtn = document.getElementById('preview-current-message-btn');
|
||||
const cancelEditBtn = document.getElementById('cancel-edit-btn');
|
||||
const categoryFilter = document.getElementById('category-filter');
|
||||
const showDisabledCheckbox = document.getElementById('show-disabled-messages');
|
||||
const messageTextarea = document.getElementById('message-text');
|
||||
|
||||
if (enabledCheckbox) {
|
||||
enabledCheckbox.onchange = (e) => this.updateFlashMessageSetting('enabled', e.target.checked);
|
||||
}
|
||||
|
||||
if (addBtn) {
|
||||
addBtn.onclick = () => this.showMessageEditor();
|
||||
}
|
||||
|
||||
if (closeEditorBtn) {
|
||||
closeEditorBtn.onclick = () => this.hideMessageEditor();
|
||||
}
|
||||
|
||||
if (saveMessageBtn) {
|
||||
saveMessageBtn.onclick = () => this.saveCurrentMessage();
|
||||
}
|
||||
|
||||
if (previewCurrentBtn) {
|
||||
previewCurrentBtn.onclick = () => this.previewCurrentMessage();
|
||||
}
|
||||
|
||||
if (cancelEditBtn) {
|
||||
cancelEditBtn.onclick = () => this.hideMessageEditor();
|
||||
}
|
||||
|
||||
if (categoryFilter) {
|
||||
categoryFilter.onchange = () => this.refreshMessageList();
|
||||
}
|
||||
|
||||
if (showDisabledCheckbox) {
|
||||
showDisabledCheckbox.onchange = () => this.refreshMessageList();
|
||||
}
|
||||
|
||||
if (messageTextarea) {
|
||||
messageTextarea.oninput = () => this.updateCharacterCount();
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.loadMessagesTab = function() {
|
||||
const config = this.flashMessageManager.getConfig();
|
||||
const enabledCheckbox = document.getElementById('flash-messages-enabled');
|
||||
if (enabledCheckbox) enabledCheckbox.checked = config.enabled;
|
||||
|
||||
this.refreshMessageList();
|
||||
this.hideMessageEditor(); // Ensure editor is closed by default
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.refreshMessageList = function() {
|
||||
const categoryFilter = document.getElementById('category-filter');
|
||||
const showDisabled = document.getElementById('show-disabled-messages').checked;
|
||||
const selectedCategory = categoryFilter.value;
|
||||
|
||||
let messages = this.flashMessageManager.getMessagesByCategory(selectedCategory);
|
||||
|
||||
if (!showDisabled) {
|
||||
messages = messages.filter(msg => msg.enabled !== false);
|
||||
}
|
||||
|
||||
this.renderMessageList(messages);
|
||||
this.updateMessageStats();
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.renderMessageList = function(messages) {
|
||||
const listElement = document.getElementById('message-list');
|
||||
|
||||
if (messages.length === 0) {
|
||||
listElement.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No messages match your criteria.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
listElement.innerHTML = messages.map(msg => `
|
||||
<div class="message-item ${msg.enabled === false ? 'disabled' : ''}" data-message-id="${msg.id}">
|
||||
<div class="message-content">
|
||||
<div class="message-text">${msg.text}</div>
|
||||
<div class="message-meta">
|
||||
<span class="message-category ${msg.category}">${this.getCategoryEmoji(msg.category)} ${msg.category || 'Custom'}</span>
|
||||
<span>Priority: ${msg.priority || 'Normal'}</span>
|
||||
${msg.isCustom ? '<span>Custom</span>' : '<span>Default</span>'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-actions">
|
||||
<div class="message-toggle ${msg.enabled !== false ? 'enabled' : ''}"
|
||||
onclick="game.toggleMessage(${msg.id})"></div>
|
||||
<button class="btn btn-info btn-small" onclick="game.editMessage(${msg.id})">Edit</button>
|
||||
<button class="btn btn-success btn-small" onclick="game.previewMessage(${msg.id})">Preview</button>
|
||||
${msg.isCustom ? `<button class="btn btn-danger btn-small" onclick="game.deleteMessage(${msg.id})">Delete</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.updateMessageStats = function() {
|
||||
const stats = this.flashMessageManager.getMessageStats();
|
||||
const statsElement = document.getElementById('message-stats');
|
||||
if (statsElement) {
|
||||
statsElement.textContent = `${stats.total} messages (${stats.enabled} enabled, ${stats.disabled} disabled)`;
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.showMessageEditor = function(messageData = null) {
|
||||
const editor = document.getElementById('message-editor');
|
||||
const title = document.getElementById('editor-title');
|
||||
const textarea = document.getElementById('message-text');
|
||||
const category = document.getElementById('message-category');
|
||||
const priority = document.getElementById('message-priority');
|
||||
|
||||
if (messageData) {
|
||||
title.textContent = 'Edit Message';
|
||||
textarea.value = messageData.text || '';
|
||||
category.value = messageData.category || 'custom';
|
||||
priority.value = messageData.priority || 'normal';
|
||||
editor.dataset.editingId = messageData.id;
|
||||
} else {
|
||||
title.textContent = 'Add New Message';
|
||||
textarea.value = '';
|
||||
category.value = 'custom';
|
||||
priority.value = 'normal';
|
||||
delete editor.dataset.editingId;
|
||||
}
|
||||
|
||||
editor.style.display = 'block';
|
||||
textarea.focus();
|
||||
this.updateCharacterCount();
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.hideMessageEditor = function() {
|
||||
const editor = document.getElementById('message-editor');
|
||||
editor.style.display = 'none';
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.saveCurrentMessage = function() {
|
||||
const textarea = document.getElementById('message-text');
|
||||
const category = document.getElementById('message-category');
|
||||
const priority = document.getElementById('message-priority');
|
||||
const editor = document.getElementById('message-editor');
|
||||
|
||||
const messageText = textarea.value.trim();
|
||||
if (!messageText) {
|
||||
this.showNotification('Please enter a message text!', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
text: messageText,
|
||||
category: category.value,
|
||||
priority: priority.value
|
||||
};
|
||||
|
||||
if (editor.dataset.editingId) {
|
||||
// Edit existing message
|
||||
const updated = this.flashMessageManager.editMessage(parseInt(editor.dataset.editingId), messageData);
|
||||
if (updated) {
|
||||
this.showNotification('✅ Message updated successfully!', 'success');
|
||||
} else {
|
||||
this.showNotification('❌ Failed to update message', 'error');
|
||||
}
|
||||
} else {
|
||||
// Add new message
|
||||
const newMessage = this.flashMessageManager.addMessage(messageData);
|
||||
this.showNotification('✅ Message added successfully!', 'success');
|
||||
}
|
||||
|
||||
this.hideMessageEditor();
|
||||
this.refreshMessageList();
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.previewCurrentMessage = function() {
|
||||
const textarea = document.getElementById('message-text');
|
||||
const messageText = textarea.value.trim();
|
||||
|
||||
if (!messageText) {
|
||||
this.showNotification('Please enter a message to preview!', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
this.flashMessageManager.previewMessage({ text: messageText });
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.updateCharacterCount = function() {
|
||||
const textarea = document.getElementById('message-text');
|
||||
const counter = document.getElementById('char-count');
|
||||
const currentLength = textarea.value.length;
|
||||
const maxLength = 200;
|
||||
|
||||
counter.textContent = currentLength;
|
||||
|
||||
const counterElement = counter.parentElement;
|
||||
counterElement.className = 'char-counter';
|
||||
|
||||
if (currentLength > maxLength * 0.9) {
|
||||
counterElement.className += ' warning';
|
||||
}
|
||||
if (currentLength > maxLength) {
|
||||
counterElement.className += ' error';
|
||||
}
|
||||
};
|
||||
|
||||
// Message action methods
|
||||
TaskChallengeGame.prototype.toggleMessage = function(messageId) {
|
||||
const enabled = this.flashMessageManager.toggleMessageEnabled(messageId);
|
||||
this.refreshMessageList();
|
||||
this.showNotification(`Message ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.editMessage = function(messageId) {
|
||||
const messages = this.flashMessageManager.getAllMessages();
|
||||
const message = messages.find(msg => msg.id === messageId);
|
||||
if (message) {
|
||||
this.showMessageEditor(message);
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.previewMessage = function(messageId) {
|
||||
const messages = this.flashMessageManager.getAllMessages();
|
||||
const message = messages.find(msg => msg.id === messageId);
|
||||
if (message) {
|
||||
this.flashMessageManager.previewMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.deleteMessage = function(messageId) {
|
||||
const messages = this.flashMessageManager.getAllMessages();
|
||||
const message = messages.find(msg => msg.id === messageId);
|
||||
|
||||
if (message && confirm(`Delete this message?\n\n"${message.text}"`)) {
|
||||
const deleted = this.flashMessageManager.deleteMessage(messageId);
|
||||
if (deleted) {
|
||||
this.showNotification('✅ Message deleted', 'success');
|
||||
this.refreshMessageList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.getCategoryEmoji = function(category) {
|
||||
const emojis = {
|
||||
motivational: '💪',
|
||||
encouraging: '🌟',
|
||||
achievement: '🏆',
|
||||
persistence: '🔥',
|
||||
custom: '✨'
|
||||
};
|
||||
return emojis[category] || '✨';
|
||||
};
|
||||
|
||||
// Appearance Tab Management
|
||||
TaskChallengeGame.prototype.setupAppearanceTabListeners = function() {
|
||||
const controls = {
|
||||
position: document.getElementById('message-position'),
|
||||
animation: document.getElementById('animation-style'),
|
||||
fontSize: document.getElementById('font-size'),
|
||||
opacity: document.getElementById('message-opacity'),
|
||||
textColor: document.getElementById('text-color'),
|
||||
backgroundColor: document.getElementById('background-color'),
|
||||
resetBtn: document.getElementById('reset-appearance-btn'),
|
||||
previewBtn: document.getElementById('preview-appearance-btn')
|
||||
};
|
||||
|
||||
if (controls.position) {
|
||||
controls.position.onchange = (e) => this.updateFlashMessageSetting('position', e.target.value);
|
||||
}
|
||||
|
||||
if (controls.animation) {
|
||||
controls.animation.onchange = (e) => this.updateFlashMessageSetting('animation', e.target.value);
|
||||
}
|
||||
|
||||
if (controls.fontSize) {
|
||||
controls.fontSize.oninput = (e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
document.getElementById('font-size-display').textContent = `${value}px`;
|
||||
this.updateFlashMessageSetting('fontSize', `${value}px`);
|
||||
};
|
||||
}
|
||||
|
||||
if (controls.opacity) {
|
||||
controls.opacity.oninput = (e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
document.getElementById('opacity-display').textContent = `${value}%`;
|
||||
const bgColor = this.hexToRgba(controls.backgroundColor.value, value / 100);
|
||||
this.updateFlashMessageSetting('backgroundColor', bgColor);
|
||||
};
|
||||
}
|
||||
|
||||
if (controls.textColor) {
|
||||
controls.textColor.onchange = (e) => this.updateFlashMessageSetting('color', e.target.value);
|
||||
}
|
||||
|
||||
if (controls.backgroundColor) {
|
||||
controls.backgroundColor.onchange = (e) => {
|
||||
const opacity = parseInt(controls.opacity.value) / 100;
|
||||
const bgColor = this.hexToRgba(e.target.value, opacity);
|
||||
this.updateFlashMessageSetting('backgroundColor', bgColor);
|
||||
};
|
||||
}
|
||||
|
||||
if (controls.resetBtn) {
|
||||
controls.resetBtn.onclick = () => this.resetAppearanceToDefaults();
|
||||
}
|
||||
|
||||
if (controls.previewBtn) {
|
||||
controls.previewBtn.onclick = () => this.previewAppearanceStyle();
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.loadAppearanceTab = function() {
|
||||
const config = this.flashMessageManager.getConfig();
|
||||
|
||||
const controls = {
|
||||
position: document.getElementById('message-position'),
|
||||
animation: document.getElementById('animation-style'),
|
||||
fontSize: document.getElementById('font-size'),
|
||||
opacity: document.getElementById('message-opacity'),
|
||||
textColor: document.getElementById('text-color'),
|
||||
backgroundColor: document.getElementById('background-color')
|
||||
};
|
||||
|
||||
if (controls.position) controls.position.value = config.position;
|
||||
if (controls.animation) controls.animation.value = config.animation;
|
||||
if (controls.fontSize) {
|
||||
const fontSize = parseInt(config.fontSize) || 24;
|
||||
controls.fontSize.value = fontSize;
|
||||
document.getElementById('font-size-display').textContent = `${fontSize}px`;
|
||||
}
|
||||
|
||||
// Extract opacity from backgroundColor if it's rgba
|
||||
let opacity = 90;
|
||||
let bgHex = '#007bff';
|
||||
if (config.backgroundColor.includes('rgba')) {
|
||||
const rgbaMatch = config.backgroundColor.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([01]?\.?\d*)\)/);
|
||||
if (rgbaMatch) {
|
||||
opacity = Math.round(parseFloat(rgbaMatch[4]) * 100);
|
||||
bgHex = this.rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.opacity) {
|
||||
controls.opacity.value = opacity;
|
||||
document.getElementById('opacity-display').textContent = `${opacity}%`;
|
||||
}
|
||||
if (controls.textColor) controls.textColor.value = config.color || '#ffffff';
|
||||
if (controls.backgroundColor) controls.backgroundColor.value = bgHex;
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.resetAppearanceToDefaults = function() {
|
||||
const defaults = gameData.defaultFlashConfig;
|
||||
this.flashMessageManager.updateConfig({
|
||||
position: defaults.position,
|
||||
animation: defaults.animation,
|
||||
fontSize: defaults.fontSize,
|
||||
color: defaults.color,
|
||||
backgroundColor: defaults.backgroundColor
|
||||
});
|
||||
this.loadAppearanceTab();
|
||||
this.showNotification('✅ Appearance reset to defaults', 'success');
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.previewAppearanceStyle = function() {
|
||||
const messages = this.flashMessageManager.getMessages();
|
||||
if (messages.length > 0) {
|
||||
const sampleMessage = messages[0];
|
||||
this.flashMessageManager.previewMessage(sampleMessage);
|
||||
} else {
|
||||
this.flashMessageManager.previewMessage({ text: "This is a preview of your style settings!" });
|
||||
}
|
||||
};
|
||||
|
||||
// Behavior Tab Management
|
||||
TaskChallengeGame.prototype.setupBehaviorTabListeners = function() {
|
||||
const controls = {
|
||||
duration: document.getElementById('display-duration'),
|
||||
interval: document.getElementById('interval-delay'),
|
||||
variation: document.getElementById('time-variation'),
|
||||
eventBased: document.getElementById('event-based-messages'),
|
||||
pauseOnHover: document.getElementById('pause-on-hover'),
|
||||
testBtn: document.getElementById('test-behavior-btn')
|
||||
};
|
||||
|
||||
if (controls.duration) {
|
||||
controls.duration.oninput = (e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
document.getElementById('duration-display').textContent = `${(value / 1000).toFixed(1)}s`;
|
||||
this.updateFlashMessageSetting('displayDuration', value);
|
||||
};
|
||||
}
|
||||
|
||||
if (controls.interval) {
|
||||
controls.interval.oninput = (e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
document.getElementById('interval-display').textContent = `${Math.round(value / 1000)}s`;
|
||||
this.updateFlashMessageSetting('intervalDelay', value);
|
||||
};
|
||||
}
|
||||
|
||||
if (controls.variation) {
|
||||
controls.variation.oninput = (e) => {
|
||||
const value = parseInt(e.target.value);
|
||||
document.getElementById('variation-display').textContent = `±${Math.round(value / 1000)}s`;
|
||||
this.updateFlashMessageSetting('timeVariation', value);
|
||||
};
|
||||
}
|
||||
|
||||
if (controls.eventBased) {
|
||||
controls.eventBased.onchange = (e) => this.updateFlashMessageSetting('eventBasedMessages', e.target.checked);
|
||||
}
|
||||
|
||||
if (controls.pauseOnHover) {
|
||||
controls.pauseOnHover.onchange = (e) => this.updateFlashMessageSetting('pauseOnHover', e.target.checked);
|
||||
}
|
||||
|
||||
if (controls.testBtn) {
|
||||
controls.testBtn.onclick = () => this.testCurrentBehaviorSettings();
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.loadBehaviorTab = function() {
|
||||
const config = this.flashMessageManager.getConfig();
|
||||
|
||||
const durationSlider = document.getElementById('display-duration');
|
||||
const intervalSlider = document.getElementById('interval-delay');
|
||||
const variationSlider = document.getElementById('time-variation');
|
||||
const eventBasedCheckbox = document.getElementById('event-based-messages');
|
||||
const pauseOnHoverCheckbox = document.getElementById('pause-on-hover');
|
||||
|
||||
if (durationSlider) {
|
||||
durationSlider.value = config.displayDuration;
|
||||
document.getElementById('duration-display').textContent = `${(config.displayDuration / 1000).toFixed(1)}s`;
|
||||
}
|
||||
if (intervalSlider) {
|
||||
intervalSlider.value = config.intervalDelay;
|
||||
document.getElementById('interval-display').textContent = `${Math.round(config.intervalDelay / 1000)}s`;
|
||||
}
|
||||
if (variationSlider) {
|
||||
const variation = config.timeVariation || 5000;
|
||||
variationSlider.value = variation;
|
||||
document.getElementById('variation-display').textContent = `±${Math.round(variation / 1000)}s`;
|
||||
}
|
||||
if (eventBasedCheckbox) {
|
||||
eventBasedCheckbox.checked = config.eventBasedMessages !== false;
|
||||
}
|
||||
if (pauseOnHoverCheckbox) {
|
||||
pauseOnHoverCheckbox.checked = config.pauseOnHover || false;
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.testCurrentBehaviorSettings = function() {
|
||||
this.showNotification('🧪 Testing behavior settings with 3 quick messages...', 'info');
|
||||
|
||||
let count = 0;
|
||||
const showTestMessage = () => {
|
||||
if (count >= 3) return;
|
||||
|
||||
const messages = this.flashMessageManager.getMessages();
|
||||
if (messages.length > 0) {
|
||||
const message = messages[Math.floor(Math.random() * messages.length)];
|
||||
this.flashMessageManager.previewMessage(message);
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count < 3) {
|
||||
setTimeout(showTestMessage, 2000);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.showNotification('✅ Behavior test complete!', 'success');
|
||||
}, this.flashMessageManager.getConfig().displayDuration + 500);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(showTestMessage, 500);
|
||||
};
|
||||
|
||||
// Import/Export Tab Management
|
||||
TaskChallengeGame.prototype.setupImportExportTabListeners = function() {
|
||||
const exportAllBtn = document.getElementById('export-all-messages-btn');
|
||||
const exportEnabledBtn = document.getElementById('export-enabled-messages-btn');
|
||||
const exportCustomBtn = document.getElementById('export-custom-messages-btn');
|
||||
const importBtn = document.getElementById('import-messages-btn');
|
||||
const importFile = document.getElementById('import-messages-file');
|
||||
const resetDefaultsBtn = document.getElementById('reset-to-defaults-btn');
|
||||
const clearAllBtn = document.getElementById('clear-all-messages-btn');
|
||||
|
||||
if (exportAllBtn) {
|
||||
exportAllBtn.onclick = () => this.exportMessages('all');
|
||||
}
|
||||
if (exportEnabledBtn) {
|
||||
exportEnabledBtn.onclick = () => this.exportMessages('enabled');
|
||||
}
|
||||
if (exportCustomBtn) {
|
||||
exportCustomBtn.onclick = () => this.exportMessages('custom');
|
||||
}
|
||||
if (importBtn) {
|
||||
importBtn.onclick = () => importFile.click();
|
||||
}
|
||||
if (importFile) {
|
||||
importFile.onchange = (e) => this.handleMessageImport(e);
|
||||
}
|
||||
if (resetDefaultsBtn) {
|
||||
resetDefaultsBtn.onclick = () => this.resetMessagesToDefaults();
|
||||
}
|
||||
if (clearAllBtn) {
|
||||
clearAllBtn.onclick = () => this.clearAllMessages();
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.loadImportExportTab = function() {
|
||||
// No specific loading needed for this tab
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.exportMessages = function(type) {
|
||||
let includeDisabled = true;
|
||||
let customOnly = false;
|
||||
let filename = 'flash_messages_all.json';
|
||||
|
||||
switch(type) {
|
||||
case 'enabled':
|
||||
includeDisabled = false;
|
||||
filename = 'flash_messages_enabled.json';
|
||||
break;
|
||||
case 'custom':
|
||||
customOnly = true;
|
||||
filename = 'flash_messages_custom.json';
|
||||
break;
|
||||
}
|
||||
|
||||
const exportData = this.flashMessageManager.exportMessages(includeDisabled, customOnly);
|
||||
this.downloadFile(exportData, filename, 'application/json');
|
||||
this.showNotification(`✅ Messages exported to ${filename}`, 'success');
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.handleMessageImport = function(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const importMode = document.querySelector('input[name="importMode"]:checked').value;
|
||||
const result = this.flashMessageManager.importMessages(e.target.result, importMode);
|
||||
|
||||
if (result.success) {
|
||||
this.showNotification(`✅ Successfully imported ${result.imported} messages (${result.total} total)`, 'success');
|
||||
if (this.getCurrentAnnoyanceTab() === 'messages') {
|
||||
this.refreshMessageList();
|
||||
}
|
||||
} else {
|
||||
this.showNotification(`❌ Import failed: ${result.error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showNotification('❌ Invalid file format', 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
// Clear the file input
|
||||
event.target.value = '';
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.resetMessagesToDefaults = function() {
|
||||
if (confirm('Reset to default messages? This will remove all custom messages and cannot be undone.')) {
|
||||
const result = this.flashMessageManager.resetToDefaults();
|
||||
this.showNotification(`✅ Reset to ${result.messages} default messages`, 'success');
|
||||
if (this.getCurrentAnnoyanceTab() === 'messages') {
|
||||
this.refreshMessageList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.clearAllMessages = function() {
|
||||
if (confirm('Clear ALL messages? This will remove every message and cannot be undone. You will need to import messages to use the flash message system.')) {
|
||||
this.flashMessageManager.updateMessages([]);
|
||||
this.showNotification('⚠️ All messages cleared', 'warning');
|
||||
if (this.getCurrentAnnoyanceTab() === 'messages') {
|
||||
this.refreshMessageList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Utility Methods
|
||||
TaskChallengeGame.prototype.updateFlashMessageSetting = function(setting, value) {
|
||||
const currentConfig = this.flashMessageManager.getConfig();
|
||||
const newConfig = { ...currentConfig, [setting]: value };
|
||||
this.flashMessageManager.updateConfig(newConfig);
|
||||
console.log(`Updated flash message setting: ${setting} = ${value}`);
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.saveAllAnnoyanceSettings = function() {
|
||||
this.showNotification('✅ All annoyance settings saved!', 'success');
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.loadAnnoyanceSettings = function() {
|
||||
// This method is called when the annoyance screen first loads
|
||||
// Individual tabs will load their specific settings when shown
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.getCurrentAnnoyanceTab = function() {
|
||||
const activeTab = document.querySelector('.annoyance-tab.active');
|
||||
return activeTab ? activeTab.id.replace('-tab', '') : 'messages';
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.downloadFile = function(content, filename, mimeType) {
|
||||
const blob = new Blob([content], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.hexToRgba = function(hex, alpha) {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.rgbToHex = function(r, g, b) {
|
||||
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||||
};
|
||||
|
||||
// Initialize game when page loads
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.game = new TaskChallengeGame();
|
||||
|
|
|
|||
147
gameData.js
147
gameData.js
|
|
@ -141,5 +141,150 @@ const gameData = {
|
|||
discoveredConsequenceImages: [], // Will be populated automatically
|
||||
|
||||
// Placeholder images for tasks that don't have specific images
|
||||
defaultImage: "images/placeholder.jpg"
|
||||
defaultImage: "images/placeholder.jpg",
|
||||
|
||||
// Flash Message System - Default encouraging messages
|
||||
defaultFlashMessages: [
|
||||
// Motivational messages
|
||||
{
|
||||
id: 1,
|
||||
text: "You're doing amazing! Keep going!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Every task completed makes you stronger!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "Progress, not perfection!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: "You've got this! Stay focused!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
text: "Small steps lead to big changes!",
|
||||
category: "motivational",
|
||||
enabled: true
|
||||
},
|
||||
// Encouraging messages
|
||||
{
|
||||
id: 6,
|
||||
text: "Your dedication is inspiring!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
text: "Look how far you've come already!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
text: "You're building great habits!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
text: "Believe in yourself - you're capable of amazing things!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
text: "Your future self will thank you!",
|
||||
category: "encouraging",
|
||||
enabled: true
|
||||
},
|
||||
// Achievement messages
|
||||
{
|
||||
id: 11,
|
||||
text: "Great job completing that task!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
text: "You're on fire! Keep the streak alive!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
text: "Another win in the books!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
text: "Excellence in action!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
text: "You're crushing your goals!",
|
||||
category: "achievement",
|
||||
enabled: true
|
||||
},
|
||||
// Persistence messages
|
||||
{
|
||||
id: 16,
|
||||
text: "Don't give up now - you're so close!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
text: "Every challenge is an opportunity to grow!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
text: "Push through - greatness awaits!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
text: "You're stronger than any excuse!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
text: "Champions are made in moments like this!",
|
||||
category: "persistence",
|
||||
enabled: true
|
||||
}
|
||||
],
|
||||
|
||||
// Flash message configuration defaults
|
||||
defaultFlashConfig: {
|
||||
enabled: true,
|
||||
displayDuration: 3000, // 3 seconds
|
||||
intervalDelay: 45000, // 45 seconds
|
||||
position: 'center',
|
||||
animation: 'fade',
|
||||
fontSize: '24px',
|
||||
fontWeight: 'bold',
|
||||
color: '#ffffff',
|
||||
backgroundColor: 'rgba(0, 123, 255, 0.9)',
|
||||
borderRadius: '15px',
|
||||
padding: '20px 30px',
|
||||
maxWidth: '400px',
|
||||
zIndex: 10000
|
||||
}
|
||||
};
|
||||
246
index.html
246
index.html
|
|
@ -61,6 +61,7 @@
|
|||
<button id="manage-tasks-btn" class="btn btn-secondary">Manage Tasks</button>
|
||||
<button id="manage-images-btn" class="btn btn-secondary">Manage Images</button>
|
||||
<button id="manage-audio-btn" class="btn btn-secondary">🎵 Manage Audio</button>
|
||||
<button id="manage-annoyance-btn" class="btn btn-secondary">😈 Annoyance</button>
|
||||
</div>
|
||||
|
||||
<!-- Game Mode Selection -->
|
||||
|
|
@ -341,6 +342,250 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Annoyance Management Screen -->
|
||||
<div id="annoyance-management-screen" class="screen">
|
||||
<h2>😈 Annoyance Management</h2>
|
||||
<p>Configure flash messages and motivational features to enhance your experience!</p>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="annoyance-tabs">
|
||||
<button id="messages-tab" class="annoyance-tab active">💬 Messages</button>
|
||||
<button id="appearance-tab" class="annoyance-tab">🎨 Appearance</button>
|
||||
<button id="behavior-tab" class="annoyance-tab">⚡ Behavior</button>
|
||||
<button id="import-export-tab" class="annoyance-tab">📁 Import/Export</button>
|
||||
</div>
|
||||
|
||||
<!-- Messages Tab -->
|
||||
<div id="messages-tab-content" class="annoyance-tab-content active">
|
||||
<div class="annoyance-section">
|
||||
<div class="section-header">
|
||||
<h3>💬 Message Management</h3>
|
||||
<div class="header-controls">
|
||||
<label>
|
||||
<input type="checkbox" id="flash-messages-enabled" checked>
|
||||
Enable Flash Messages
|
||||
</label>
|
||||
<button id="add-new-message-btn" class="btn btn-success btn-small">+ Add Message</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Editor -->
|
||||
<div id="message-editor" class="message-editor" style="display: none;">
|
||||
<div class="editor-header">
|
||||
<h4 id="editor-title">Add New Message</h4>
|
||||
<button id="close-editor-btn" class="btn btn-outline btn-small">✕ Close</button>
|
||||
</div>
|
||||
<div class="editor-form">
|
||||
<div class="form-group">
|
||||
<label>Message Text:</label>
|
||||
<textarea id="message-text" placeholder="Enter your motivational message..." maxlength="200" rows="3"></textarea>
|
||||
<div class="char-counter">
|
||||
<span id="char-count">0</span>/200 characters
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Category:</label>
|
||||
<select id="message-category">
|
||||
<option value="motivational">💪 Motivational</option>
|
||||
<option value="encouraging">🌟 Encouraging</option>
|
||||
<option value="achievement">🏆 Achievement</option>
|
||||
<option value="persistence">🔥 Persistence</option>
|
||||
<option value="custom">✨ Custom</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Priority:</label>
|
||||
<select id="message-priority">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="high">High</option>
|
||||
<option value="low">Low</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
<button id="save-message-btn" class="btn btn-primary">Save Message</button>
|
||||
<button id="preview-current-message-btn" class="btn btn-info">Preview</button>
|
||||
<button id="cancel-edit-btn" class="btn btn-secondary">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message List -->
|
||||
<div class="message-list-section">
|
||||
<div class="list-header">
|
||||
<div class="list-filters">
|
||||
<label>Filter by Category:
|
||||
<select id="category-filter">
|
||||
<option value="all">All Categories</option>
|
||||
<option value="motivational">💪 Motivational</option>
|
||||
<option value="encouraging">🌟 Encouraging</option>
|
||||
<option value="achievement">🏆 Achievement</option>
|
||||
<option value="persistence">🔥 Persistence</option>
|
||||
<option value="custom">✨ Custom</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" id="show-disabled-messages"> Show Disabled
|
||||
</label>
|
||||
</div>
|
||||
<div class="list-stats">
|
||||
<span id="message-stats">20 messages (18 enabled, 2 disabled)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="message-list" class="message-list">
|
||||
<!-- Messages will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Appearance Tab -->
|
||||
<div id="appearance-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3>🎨 Visual Appearance</h3>
|
||||
<div class="appearance-controls">
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>Position:</label>
|
||||
<select id="message-position">
|
||||
<option value="center">Center</option>
|
||||
<option value="top-center">Top Center</option>
|
||||
<option value="bottom-center">Bottom Center</option>
|
||||
<option value="top-left">Top Left</option>
|
||||
<option value="top-right">Top Right</option>
|
||||
<option value="bottom-left">Bottom Left</option>
|
||||
<option value="bottom-right">Bottom Right</option>
|
||||
<option value="center-left">Center Left</option>
|
||||
<option value="center-right">Center Right</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Animation:</label>
|
||||
<select id="animation-style">
|
||||
<option value="fade">Fade</option>
|
||||
<option value="slide">Slide</option>
|
||||
<option value="bounce">Bounce</option>
|
||||
<option value="pulse">Pulse</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>Font Size: <span id="font-size-display">24px</span></label>
|
||||
<input type="range" id="font-size" min="16" max="48" value="24" step="2">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Opacity: <span id="opacity-display">90%</span></label>
|
||||
<input type="range" id="message-opacity" min="50" max="100" value="90" step="5">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>Text Color:</label>
|
||||
<input type="color" id="text-color" value="#ffffff">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Background Color:</label>
|
||||
<input type="color" id="background-color" value="#007bff">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<button id="reset-appearance-btn" class="btn btn-outline">Reset to Defaults</button>
|
||||
<button id="preview-appearance-btn" class="btn btn-info">Preview Style</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Behavior Tab -->
|
||||
<div id="behavior-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3>⚡ Behavior Settings</h3>
|
||||
<div class="behavior-controls">
|
||||
<div class="control-group">
|
||||
<label>Display Duration: <span id="duration-display">3.0s</span></label>
|
||||
<input type="range" id="display-duration" min="1000" max="10000" value="3000" step="500">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Interval Between Messages: <span id="interval-display">45s</span></label>
|
||||
<input type="range" id="interval-delay" min="10000" max="300000" value="45000" step="5000">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Random Variation: <span id="variation-display">±5s</span></label>
|
||||
<input type="range" id="time-variation" min="0" max="30000" value="5000" step="1000">
|
||||
<small class="help-text">Adds random time variation to prevent predictability</small>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>
|
||||
<input type="checkbox" id="event-based-messages" checked>
|
||||
Enable Event-Based Messages
|
||||
</label>
|
||||
<small class="help-text">Show special messages for task completion, streaks, etc.</small>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>
|
||||
<input type="checkbox" id="pause-on-hover">
|
||||
Pause Timer on Message Hover
|
||||
</label>
|
||||
<small class="help-text">Pause message fade when hovering (useful for reading)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<button id="test-behavior-btn" class="btn btn-success">Test Current Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import/Export Tab -->
|
||||
<div id="import-export-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3><EFBFBD> Import & Export</h3>
|
||||
<div class="import-export-controls">
|
||||
<div class="control-section">
|
||||
<h4>💾 Export Messages</h4>
|
||||
<div class="export-options">
|
||||
<button id="export-all-messages-btn" class="btn btn-primary">Export All Messages</button>
|
||||
<button id="export-enabled-messages-btn" class="btn btn-secondary">Export Enabled Only</button>
|
||||
<button id="export-custom-messages-btn" class="btn btn-info">Export Custom Only</button>
|
||||
</div>
|
||||
<p class="help-text">Export your messages as a JSON file for backup or sharing</p>
|
||||
</div>
|
||||
<div class="control-section">
|
||||
<h4><EFBFBD> Import Messages</h4>
|
||||
<div class="import-options">
|
||||
<button id="import-messages-btn" class="btn btn-success">Import Messages</button>
|
||||
<input type="file" id="import-messages-file" accept=".json" style="display: none;">
|
||||
<div class="import-mode">
|
||||
<label>Import Mode:</label>
|
||||
<div class="radio-group">
|
||||
<label><input type="radio" name="importMode" value="merge" checked> Merge with existing</label>
|
||||
<label><input type="radio" name="importMode" value="replace"> Replace all messages</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-text">Import messages from a JSON file</p>
|
||||
</div>
|
||||
<div class="control-section">
|
||||
<h4>🔄 Reset Options</h4>
|
||||
<div class="reset-options">
|
||||
<button id="reset-to-defaults-btn" class="btn btn-warning">Reset to Default Messages</button>
|
||||
<button id="clear-all-messages-btn" class="btn btn-danger">Clear All Messages</button>
|
||||
</div>
|
||||
<p class="help-text danger">⚠️ Reset operations cannot be undone!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="management-buttons">
|
||||
<button id="back-to-start-from-annoyance-btn" class="btn btn-secondary">Back to Start</button>
|
||||
<button id="save-annoyance-settings" class="btn btn-primary">Save All Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Game Screen -->
|
||||
<div id="game-screen" class="screen">
|
||||
<div class="task-container">
|
||||
|
|
@ -421,6 +666,7 @@
|
|||
</div>
|
||||
|
||||
<script src="gameData.js"></script>
|
||||
<script src="flashMessageManager.js"></script>
|
||||
<script src="desktop-file-manager.js"></script>
|
||||
<script src="game.js"></script>
|
||||
<!-- Statistics Modal -->
|
||||
|
|
|
|||
567
styles.css
567
styles.css
|
|
@ -2099,4 +2099,571 @@ body.theme-monochrome {
|
|||
|
||||
.task-image-container {
|
||||
transition: min-height 0.3s ease;
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
FLASH MESSAGE SYSTEM
|
||||
=========================== */
|
||||
|
||||
/* Flash message overlay */
|
||||
.flash-message-overlay {
|
||||
position: fixed;
|
||||
display: none;
|
||||
pointer-events: none; /* Don't block clicks */
|
||||
font-family: 'Arial', sans-serif;
|
||||
user-select: none; /* Prevent text selection */
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Flash message animations */
|
||||
@keyframes flashBounceIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.3) translate(-50%, -50%);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.05) translate(-50%, -50%);
|
||||
}
|
||||
70% {
|
||||
transform: scale(0.9) translate(-50%, -50%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flashPulseIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(1) translate(-50%, -50%);
|
||||
}
|
||||
20% {
|
||||
opacity: 0.8;
|
||||
transform: scale(1.1) translate(-50%, -50%);
|
||||
}
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(0.95) translate(-50%, -50%);
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: scale(1.05) translate(-50%, -50%);
|
||||
}
|
||||
80% {
|
||||
opacity: 1;
|
||||
transform: scale(0.98) translate(-50%, -50%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flashFadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
/* Flash message responsive styles */
|
||||
@media (max-width: 768px) {
|
||||
.flash-message-overlay {
|
||||
max-width: 90% !important;
|
||||
padding: 15px 20px !important;
|
||||
font-size: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.flash-message-overlay {
|
||||
max-width: 95% !important;
|
||||
padding: 12px 16px !important;
|
||||
font-size: 16px !important;
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
ANNOYANCE MANAGEMENT SCREEN
|
||||
=========================== */
|
||||
|
||||
/* Tab Navigation */
|
||||
.annoyance-tabs {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
margin-bottom: 20px;
|
||||
background: #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.annoyance-tab {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.annoyance-tab:hover {
|
||||
background: #dee2e6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.annoyance-tab.active {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Tab Content */
|
||||
.annoyance-tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.annoyance-tab-content.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Section Styling */
|
||||
.annoyance-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* Message Editor */
|
||||
.message-editor {
|
||||
background: white;
|
||||
border: 2px solid #007bff;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.editor-header h4 {
|
||||
margin: 0;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.editor-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.form-group textarea:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-group select {
|
||||
padding: 10px 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.form-group select:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.char-counter {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.char-counter.warning {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.char-counter.error {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Message List */
|
||||
.message-list-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
background: #f8f9fa;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.list-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.list-filters label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.list-filters select {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.list-stats {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.message-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.message-item.disabled {
|
||||
opacity: 0.6;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.message-category {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 2px 8px;
|
||||
background: #e9ecef;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-category.motivational { background: #d4edda; color: #155724; }
|
||||
.message-category.encouraging { background: #d1ecf1; color: #0c5460; }
|
||||
.message-category.achievement { background: #fff3cd; color: #856404; }
|
||||
.message-category.persistence { background: #f8d7da; color: #721c24; }
|
||||
.message-category.custom { background: #e2e3e5; color: #383d41; }
|
||||
|
||||
.message-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message-toggle {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: #ccc;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.message-toggle.enabled {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.message-toggle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.message-toggle.enabled::after {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
/* Control Layouts */
|
||||
.control-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.control-group input[type="range"] {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: #ddd;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-group input[type="range"]:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.control-group input[type="range"]::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #007bff;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.control-group input[type="range"]::-webkit-slider-thumb:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.control-group input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-group input[type="color"] {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.control-group input[type="color"]:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.help-text.danger {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
/* Import/Export Sections */
|
||||
.control-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.control-section h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.export-options, .import-options, .reset-options {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.import-mode {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.radio-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.annoyance-tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-row, .control-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.message-actions {
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.export-options, .import-options, .reset-options {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.annoyance-section {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue