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:
dilgenfritz 2025-09-28 21:33:54 -05:00
parent f1f88d5f23
commit 6fad5af73f
5 changed files with 2239 additions and 1 deletions

569
flashMessageManager.js Normal file
View File

@ -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
View File

@ -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();

View File

@ -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
}
};

View File

@ -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 -->

View File

@ -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;
}
}