feat: Add dynamic aspect ratio sizing to punishment popups
- Modified PopupImageManager to calculate popup size based on image aspect ratio - Added size configuration options (min/max width/height, viewport ratios) - Enhanced popup positioning to handle variable-sized popups with collision detection - Added comprehensive size controls to Annoyance Management interface - Implemented asynchronous image loading to determine proper dimensions - Added padding between popups to prevent visual overlap - Updated default configuration with size constraints and viewport ratio settings
This commit is contained in:
parent
6fad5af73f
commit
22a1642faa
311
game.js
311
game.js
|
|
@ -42,6 +42,9 @@ class TaskChallengeGame {
|
|||
// Initialize Flash Message System
|
||||
this.flashMessageManager = new FlashMessageManager(this.dataManager);
|
||||
|
||||
// Initialize Popup Image System (Punishment for skips)
|
||||
this.popupImageManager = new PopupImageManager(this.dataManager);
|
||||
|
||||
this.initializeEventListeners();
|
||||
this.setupKeyboardShortcuts();
|
||||
this.setupWindowResizeHandling();
|
||||
|
|
@ -2875,6 +2878,9 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
// Trigger skip message
|
||||
this.flashMessageManager.triggerEventMessage('taskSkip');
|
||||
|
||||
// Trigger punishment popups for skipping
|
||||
this.popupImageManager.triggerPunishmentPopups();
|
||||
|
||||
// Load a consequence task
|
||||
this.gameState.isConsequenceTask = true;
|
||||
this.loadNextTask();
|
||||
|
|
@ -3937,11 +3943,13 @@ TaskChallengeGame.prototype.setupAnnoyanceManagementEventListeners = function()
|
|||
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('popup-images-tab').onclick = () => this.showAnnoyanceTab('popup-images');
|
||||
document.getElementById('import-export-tab').onclick = () => this.showAnnoyanceTab('import-export');
|
||||
|
||||
this.setupMessagesTabListeners();
|
||||
this.setupAppearanceTabListeners();
|
||||
this.setupBehaviorTabListeners();
|
||||
this.setupPopupImagesTabListeners();
|
||||
this.setupImportExportTabListeners();
|
||||
};
|
||||
|
||||
|
|
@ -3965,6 +3973,9 @@ TaskChallengeGame.prototype.showAnnoyanceTab = function(tabName) {
|
|||
case 'behavior':
|
||||
this.loadBehaviorTab();
|
||||
break;
|
||||
case 'popup-images':
|
||||
this.loadPopupImagesSettings();
|
||||
break;
|
||||
case 'import-export':
|
||||
this.loadImportExportTab();
|
||||
break;
|
||||
|
|
@ -4451,6 +4462,306 @@ TaskChallengeGame.prototype.testCurrentBehaviorSettings = function() {
|
|||
setTimeout(showTestMessage, 500);
|
||||
};
|
||||
|
||||
// Popup Images Tab Management
|
||||
TaskChallengeGame.prototype.setupPopupImagesTabListeners = function() {
|
||||
// Enable/disable toggle
|
||||
const enabledCheckbox = document.getElementById('popup-images-enabled');
|
||||
if (enabledCheckbox) {
|
||||
enabledCheckbox.onchange = () => {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.enabled = enabledCheckbox.checked;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
this.updatePopupImagesInfo();
|
||||
};
|
||||
}
|
||||
|
||||
// Image count mode
|
||||
const countModeSelect = document.getElementById('popup-count-mode');
|
||||
if (countModeSelect) {
|
||||
countModeSelect.onchange = () => {
|
||||
this.updatePopupCountControls(countModeSelect.value);
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.imageCountMode = countModeSelect.value;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
|
||||
// Fixed count slider
|
||||
const countSlider = document.getElementById('popup-image-count');
|
||||
const countValue = document.getElementById('popup-image-count-value');
|
||||
if (countSlider && countValue) {
|
||||
countSlider.oninput = () => {
|
||||
countValue.textContent = countSlider.value;
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.imageCount = parseInt(countSlider.value);
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
|
||||
// Range count inputs
|
||||
const minCountInput = document.getElementById('popup-min-count');
|
||||
const maxCountInput = document.getElementById('popup-max-count');
|
||||
if (minCountInput) {
|
||||
minCountInput.onchange = () => {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.minCount = parseInt(minCountInput.value);
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
if (maxCountInput) {
|
||||
maxCountInput.onchange = () => {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.maxCount = parseInt(maxCountInput.value);
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
|
||||
// Duration mode
|
||||
const durationModeSelect = document.getElementById('popup-duration-mode');
|
||||
if (durationModeSelect) {
|
||||
durationModeSelect.onchange = () => {
|
||||
this.updatePopupDurationControls(durationModeSelect.value);
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.durationMode = durationModeSelect.value;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
|
||||
// Fixed duration slider
|
||||
const durationSlider = document.getElementById('popup-display-duration');
|
||||
const durationValue = document.getElementById('popup-display-duration-value');
|
||||
if (durationSlider && durationValue) {
|
||||
durationSlider.oninput = () => {
|
||||
durationValue.textContent = durationSlider.value + 's';
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.displayDuration = parseInt(durationSlider.value) * 1000;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
|
||||
// Range duration inputs
|
||||
const minDurationInput = document.getElementById('popup-min-duration');
|
||||
const maxDurationInput = document.getElementById('popup-max-duration');
|
||||
if (minDurationInput) {
|
||||
minDurationInput.onchange = () => {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.minDuration = parseInt(minDurationInput.value) * 1000;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
if (maxDurationInput) {
|
||||
maxDurationInput.onchange = () => {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.maxDuration = parseInt(maxDurationInput.value) * 1000;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
|
||||
// Positioning
|
||||
const positioningSelect = document.getElementById('popup-positioning');
|
||||
if (positioningSelect) {
|
||||
positioningSelect.onchange = () => {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config.positioning = positioningSelect.value;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
|
||||
// Visual effect checkboxes
|
||||
const setupCheckbox = (id, configKey) => {
|
||||
const checkbox = document.getElementById(id);
|
||||
if (checkbox) {
|
||||
checkbox.onchange = () => {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config[configKey] = checkbox.checked;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
setupCheckbox('popup-allow-overlap', 'allowOverlap');
|
||||
setupCheckbox('popup-fade-animation', 'fadeAnimation');
|
||||
setupCheckbox('popup-blur-background', 'blurBackground');
|
||||
setupCheckbox('popup-show-timer', 'showTimer');
|
||||
setupCheckbox('popup-prevent-close', 'preventClose');
|
||||
|
||||
// Test buttons
|
||||
const testSingleBtn = document.getElementById('test-popup-single');
|
||||
if (testSingleBtn) {
|
||||
testSingleBtn.onclick = () => {
|
||||
this.popupImageManager.previewPunishmentPopups(1);
|
||||
setTimeout(() => this.updatePopupImagesInfo(), 100);
|
||||
};
|
||||
}
|
||||
|
||||
const testMultipleBtn = document.getElementById('test-popup-multiple');
|
||||
if (testMultipleBtn) {
|
||||
testMultipleBtn.onclick = () => {
|
||||
this.popupImageManager.triggerPunishmentPopups();
|
||||
setTimeout(() => this.updatePopupImagesInfo(), 100);
|
||||
};
|
||||
}
|
||||
|
||||
const clearAllBtn = document.getElementById('clear-all-popups');
|
||||
if (clearAllBtn) {
|
||||
clearAllBtn.onclick = () => {
|
||||
this.popupImageManager.clearAllPopups();
|
||||
setTimeout(() => this.updatePopupImagesInfo(), 100);
|
||||
};
|
||||
}
|
||||
|
||||
// Size control listeners
|
||||
const setupSizeSlider = (elementId, configKey, suffix = '') => {
|
||||
const slider = document.getElementById(elementId);
|
||||
const valueDisplay = document.getElementById(`${elementId}-value`);
|
||||
if (slider && valueDisplay) {
|
||||
slider.oninput = () => {
|
||||
const value = parseInt(slider.value);
|
||||
valueDisplay.textContent = value + suffix;
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config[configKey] = configKey.includes('viewport') ? value / 100 : value;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const setupSizeInput = (elementId, configKey) => {
|
||||
const input = document.getElementById(elementId);
|
||||
if (input) {
|
||||
input.onchange = () => {
|
||||
const value = parseInt(input.value);
|
||||
if (!isNaN(value)) {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
config[configKey] = value;
|
||||
this.popupImageManager.updateConfig(config);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
setupSizeSlider('popup-viewport-width', 'viewportWidthRatio', '%');
|
||||
setupSizeSlider('popup-viewport-height', 'viewportHeightRatio', '%');
|
||||
setupSizeInput('popup-min-width', 'minWidth');
|
||||
setupSizeInput('popup-max-width', 'maxWidth');
|
||||
setupSizeInput('popup-min-height', 'minHeight');
|
||||
setupSizeInput('popup-max-height', 'maxHeight');
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.updatePopupCountControls = function(mode) {
|
||||
const fixedDiv = document.getElementById('popup-fixed-count');
|
||||
const rangeDiv = document.getElementById('popup-range-count');
|
||||
|
||||
if (fixedDiv) fixedDiv.style.display = mode === 'fixed' ? 'block' : 'none';
|
||||
if (rangeDiv) rangeDiv.style.display = mode === 'range' ? 'block' : 'none';
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.updatePopupDurationControls = function(mode) {
|
||||
const fixedDiv = document.getElementById('popup-fixed-duration');
|
||||
const rangeDiv = document.getElementById('popup-range-duration');
|
||||
|
||||
if (fixedDiv) fixedDiv.style.display = mode === 'fixed' ? 'block' : 'none';
|
||||
if (rangeDiv) rangeDiv.style.display = mode === 'range' ? 'block' : 'none';
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.loadPopupImagesSettings = function() {
|
||||
const config = this.popupImageManager.getConfig();
|
||||
|
||||
// Enable/disable
|
||||
const enabledCheckbox = document.getElementById('popup-images-enabled');
|
||||
if (enabledCheckbox) enabledCheckbox.checked = config.enabled;
|
||||
|
||||
// Count settings
|
||||
const countModeSelect = document.getElementById('popup-count-mode');
|
||||
if (countModeSelect) countModeSelect.value = config.imageCountMode;
|
||||
|
||||
const countSlider = document.getElementById('popup-image-count');
|
||||
const countValue = document.getElementById('popup-image-count-value');
|
||||
if (countSlider) countSlider.value = config.imageCount;
|
||||
if (countValue) countValue.textContent = config.imageCount;
|
||||
|
||||
const minCountInput = document.getElementById('popup-min-count');
|
||||
const maxCountInput = document.getElementById('popup-max-count');
|
||||
if (minCountInput) minCountInput.value = config.minCount;
|
||||
if (maxCountInput) maxCountInput.value = config.maxCount;
|
||||
|
||||
// Duration settings
|
||||
const durationModeSelect = document.getElementById('popup-duration-mode');
|
||||
if (durationModeSelect) durationModeSelect.value = config.durationMode;
|
||||
|
||||
const durationSlider = document.getElementById('popup-display-duration');
|
||||
const durationValue = document.getElementById('popup-display-duration-value');
|
||||
if (durationSlider) durationSlider.value = config.displayDuration / 1000;
|
||||
if (durationValue) durationValue.textContent = (config.displayDuration / 1000) + 's';
|
||||
|
||||
const minDurationInput = document.getElementById('popup-min-duration');
|
||||
const maxDurationInput = document.getElementById('popup-max-duration');
|
||||
if (minDurationInput) minDurationInput.value = config.minDuration / 1000;
|
||||
if (maxDurationInput) maxDurationInput.value = config.maxDuration / 1000;
|
||||
|
||||
// Positioning
|
||||
const positioningSelect = document.getElementById('popup-positioning');
|
||||
if (positioningSelect) positioningSelect.value = config.positioning;
|
||||
|
||||
// Visual effects
|
||||
const checkboxes = {
|
||||
'popup-allow-overlap': config.allowOverlap,
|
||||
'popup-fade-animation': config.fadeAnimation,
|
||||
'popup-blur-background': config.blurBackground,
|
||||
'popup-show-timer': config.showTimer,
|
||||
'popup-prevent-close': config.preventClose
|
||||
};
|
||||
|
||||
Object.entries(checkboxes).forEach(([id, value]) => {
|
||||
const checkbox = document.getElementById(id);
|
||||
if (checkbox) checkbox.checked = value;
|
||||
});
|
||||
|
||||
// Size settings
|
||||
const viewportWidthSlider = document.getElementById('popup-viewport-width');
|
||||
const viewportWidthValue = document.getElementById('popup-viewport-width-value');
|
||||
if (viewportWidthSlider) viewportWidthSlider.value = (config.viewportWidthRatio || 0.35) * 100;
|
||||
if (viewportWidthValue) viewportWidthValue.textContent = Math.round((config.viewportWidthRatio || 0.35) * 100) + '%';
|
||||
|
||||
const viewportHeightSlider = document.getElementById('popup-viewport-height');
|
||||
const viewportHeightValue = document.getElementById('popup-viewport-height-value');
|
||||
if (viewportHeightSlider) viewportHeightSlider.value = (config.viewportHeightRatio || 0.4) * 100;
|
||||
if (viewportHeightValue) viewportHeightValue.textContent = Math.round((config.viewportHeightRatio || 0.4) * 100) + '%';
|
||||
|
||||
const sizeInputs = {
|
||||
'popup-min-width': config.minWidth || 200,
|
||||
'popup-max-width': config.maxWidth || 500,
|
||||
'popup-min-height': config.minHeight || 150,
|
||||
'popup-max-height': config.maxHeight || 400
|
||||
};
|
||||
|
||||
Object.entries(sizeInputs).forEach(([id, value]) => {
|
||||
const input = document.getElementById(id);
|
||||
if (input) input.value = value;
|
||||
});
|
||||
|
||||
// Update control visibility
|
||||
this.updatePopupCountControls(config.imageCountMode);
|
||||
this.updatePopupDurationControls(config.durationMode);
|
||||
|
||||
// Update info display
|
||||
this.updatePopupImagesInfo();
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.updatePopupImagesInfo = function() {
|
||||
const availableCountEl = document.getElementById('available-images-count');
|
||||
const activeCountEl = document.getElementById('active-popups-count');
|
||||
|
||||
if (availableCountEl) {
|
||||
const availableImages = this.popupImageManager.getAvailableImages();
|
||||
availableCountEl.textContent = availableImages.length;
|
||||
}
|
||||
|
||||
if (activeCountEl) {
|
||||
const activeCount = this.popupImageManager.getActiveCount();
|
||||
activeCountEl.textContent = activeCount;
|
||||
}
|
||||
};
|
||||
|
||||
// Import/Export Tab Management
|
||||
TaskChallengeGame.prototype.setupImportExportTabListeners = function() {
|
||||
const exportAllBtn = document.getElementById('export-all-messages-btn');
|
||||
|
|
|
|||
28
gameData.js
28
gameData.js
|
|
@ -286,5 +286,33 @@ const gameData = {
|
|||
padding: '20px 30px',
|
||||
maxWidth: '400px',
|
||||
zIndex: 10000
|
||||
},
|
||||
|
||||
// Default Popup Image Configuration (Punishment System)
|
||||
defaultPopupImageConfig: {
|
||||
enabled: true,
|
||||
imageCount: 3, // Number of images to show
|
||||
imageCountMode: 'fixed', // 'fixed', 'random', 'range'
|
||||
minCount: 2, // For range mode
|
||||
maxCount: 5, // For range mode
|
||||
displayDuration: 8000, // 8 seconds default
|
||||
durationMode: 'fixed', // 'fixed', 'random', 'range'
|
||||
minDuration: 5000, // For range mode (5s)
|
||||
maxDuration: 15000, // For range mode (15s)
|
||||
positioning: 'random', // 'random', 'cascade', 'grid', 'center'
|
||||
allowOverlap: true,
|
||||
fadeAnimation: true,
|
||||
blurBackground: true,
|
||||
preventClose: true, // Users cannot close these
|
||||
showTimer: true, // Show countdown timer
|
||||
triggerOnSkip: true, // Trigger when tasks are skipped
|
||||
intensity: 'medium', // 'low', 'medium', 'high' - affects default values
|
||||
// Size constraints for dynamic sizing
|
||||
minWidth: 200,
|
||||
maxWidth: 500,
|
||||
minHeight: 150,
|
||||
maxHeight: 400,
|
||||
viewportWidthRatio: 0.35, // Max 35% of viewport width
|
||||
viewportHeightRatio: 0.4 // Max 40% of viewport height
|
||||
}
|
||||
};
|
||||
212
index.html
212
index.html
|
|
@ -352,6 +352,7 @@
|
|||
<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="popup-images-tab" class="annoyance-tab">🖼️ Popup Images</button>
|
||||
<button id="import-export-tab" class="annoyance-tab">📁 Import/Export</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -539,6 +540,216 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popup Images Tab -->
|
||||
<div id="popup-images-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3>🖼️ Punishment Popups</h3>
|
||||
<p class="help-text">Configure consequence images that appear when tasks are skipped</p>
|
||||
|
||||
<!-- Enable/Disable -->
|
||||
<div class="control-section">
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-images-enabled" />
|
||||
<span class="switch"></span>
|
||||
Enable Punishment Popups
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Count Settings -->
|
||||
<div class="control-section">
|
||||
<h4>📊 Number of Images</h4>
|
||||
<div class="control-group">
|
||||
<label for="popup-count-mode">Count Mode:</label>
|
||||
<select id="popup-count-mode">
|
||||
<option value="fixed">Fixed Amount</option>
|
||||
<option value="random">Random (1-5)</option>
|
||||
<option value="range">Custom Range</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="popup-fixed-count" class="control-group">
|
||||
<label for="popup-image-count">Number of Images:</label>
|
||||
<input type="range" id="popup-image-count" min="1" max="8" value="3" />
|
||||
<span id="popup-image-count-value">3</span>
|
||||
</div>
|
||||
|
||||
<div id="popup-range-count" class="control-group" style="display: none;">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-count">Minimum:</label>
|
||||
<input type="number" id="popup-min-count" min="1" max="5" value="2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-count">Maximum:</label>
|
||||
<input type="number" id="popup-max-count" min="2" max="10" value="5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Display Duration Settings -->
|
||||
<div class="control-section">
|
||||
<h4>⏱️ Display Duration</h4>
|
||||
<div class="control-group">
|
||||
<label for="popup-duration-mode">Duration Mode:</label>
|
||||
<select id="popup-duration-mode">
|
||||
<option value="fixed">Fixed Duration</option>
|
||||
<option value="random">Random (5-15s)</option>
|
||||
<option value="range">Custom Range</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="popup-fixed-duration" class="control-group">
|
||||
<label for="popup-display-duration">Duration (seconds):</label>
|
||||
<input type="range" id="popup-display-duration" min="3" max="30" value="8" />
|
||||
<span id="popup-display-duration-value">8s</span>
|
||||
</div>
|
||||
|
||||
<div id="popup-range-duration" class="control-group" style="display: none;">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-duration">Min (seconds):</label>
|
||||
<input type="number" id="popup-min-duration" min="2" max="20" value="5" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-duration">Max (seconds):</label>
|
||||
<input type="number" id="popup-max-duration" min="5" max="60" value="15" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Positioning & Appearance -->
|
||||
<div class="control-section">
|
||||
<h4>🎯 Positioning</h4>
|
||||
<div class="control-group">
|
||||
<label for="popup-positioning">Layout Style:</label>
|
||||
<select id="popup-positioning">
|
||||
<option value="random">Random Positions</option>
|
||||
<option value="cascade">Cascading</option>
|
||||
<option value="grid">Grid Layout</option>
|
||||
<option value="center">Centered (stacked)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-allow-overlap" />
|
||||
<span class="switch"></span>
|
||||
Allow Overlapping
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Size Settings -->
|
||||
<div class="control-section">
|
||||
<h4>📏 Size Settings</h4>
|
||||
<p class="help-text">Popups automatically size to match image proportions within these limits</p>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="popup-viewport-width">Max Viewport Width:</label>
|
||||
<input type="range" id="popup-viewport-width" min="20" max="60" value="35" />
|
||||
<span id="popup-viewport-width-value">35%</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="popup-viewport-height">Max Viewport Height:</label>
|
||||
<input type="range" id="popup-viewport-height" min="20" max="60" value="40" />
|
||||
<span id="popup-viewport-height-value">40%</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-width">Min Width (px):</label>
|
||||
<input type="number" id="popup-min-width" min="150" max="400" value="200" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-width">Max Width (px):</label>
|
||||
<input type="number" id="popup-max-width" min="300" max="800" value="500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-height">Min Height (px):</label>
|
||||
<input type="number" id="popup-min-height" min="100" max="300" value="150" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-height">Max Height (px):</label>
|
||||
<input type="number" id="popup-max-height" min="200" max="600" value="400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visual Effects -->
|
||||
<div class="control-section">
|
||||
<h4>✨ Visual Effects</h4>
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-fade-animation" />
|
||||
<span class="switch"></span>
|
||||
Fade In/Out Animation
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-blur-background" />
|
||||
<span class="switch"></span>
|
||||
Blur Background
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-show-timer" />
|
||||
<span class="switch"></span>
|
||||
Show Countdown Timer
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-prevent-close" />
|
||||
<span class="switch"></span>
|
||||
Prevent Manual Close
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test & Preview -->
|
||||
<div class="control-section">
|
||||
<h4>🧪 Testing</h4>
|
||||
<div class="test-buttons">
|
||||
<button id="test-popup-single" class="btn btn-info">Test 1 Popup</button>
|
||||
<button id="test-popup-multiple" class="btn btn-primary">Test Multiple</button>
|
||||
<button id="clear-all-popups" class="btn btn-danger">Clear All</button>
|
||||
</div>
|
||||
<p class="help-text">Test your popup settings to see how they look</p>
|
||||
</div>
|
||||
|
||||
<!-- Status & Info -->
|
||||
<div class="control-section">
|
||||
<div class="info-display">
|
||||
<div class="info-item">
|
||||
<span class="info-label">Available Images:</span>
|
||||
<span id="available-images-count">0</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Active Popups:</span>
|
||||
<span id="active-popups-count">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import/Export Tab -->
|
||||
<div id="import-export-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
|
|
@ -667,6 +878,7 @@
|
|||
|
||||
<script src="gameData.js"></script>
|
||||
<script src="flashMessageManager.js"></script>
|
||||
<script src="popupImageManager.js"></script>
|
||||
<script src="desktop-file-manager.js"></script>
|
||||
<script src="game.js"></script>
|
||||
<!-- Statistics Modal -->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,559 @@
|
|||
/**
|
||||
* Popup Image Manager - Handles punishment popups when tasks are skipped
|
||||
* Part of the Annoyance system for consequence enforcement
|
||||
*/
|
||||
class PopupImageManager {
|
||||
constructor(dataManager) {
|
||||
this.dataManager = dataManager;
|
||||
this.activePopups = [];
|
||||
this.config = null;
|
||||
this.isEnabled = true;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.loadConfiguration();
|
||||
console.log('PopupImageManager initialized');
|
||||
}
|
||||
|
||||
loadConfiguration() {
|
||||
// Get saved config or use defaults
|
||||
const savedConfig = this.dataManager.get('popupImageConfig');
|
||||
const defaultConfig = gameData.defaultPopupImageConfig || {
|
||||
enabled: true,
|
||||
imageCount: 3,
|
||||
imageCountMode: 'fixed',
|
||||
minCount: 2,
|
||||
maxCount: 5,
|
||||
displayDuration: 8000,
|
||||
durationMode: 'fixed',
|
||||
minDuration: 5000,
|
||||
maxDuration: 15000,
|
||||
positioning: 'random',
|
||||
allowOverlap: true,
|
||||
fadeAnimation: true,
|
||||
blurBackground: true,
|
||||
preventClose: true,
|
||||
showTimer: true,
|
||||
triggerOnSkip: true,
|
||||
intensity: 'medium'
|
||||
};
|
||||
|
||||
this.config = { ...defaultConfig, ...(savedConfig || {}) };
|
||||
}
|
||||
|
||||
updateConfig(newConfig) {
|
||||
this.config = { ...this.config, ...newConfig };
|
||||
this.dataManager.set('popupImageConfig', this.config);
|
||||
console.log('Popup image config updated:', newConfig);
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
// Main method to trigger punishment popups
|
||||
triggerPunishmentPopups() {
|
||||
if (!this.config.enabled) {
|
||||
console.log('Punishment popups disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get consequence images
|
||||
const images = this.getAvailableImages();
|
||||
if (images.length === 0) {
|
||||
console.log('No consequence images available for punishment popups');
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine how many images to show
|
||||
const imageCount = this.calculateImageCount();
|
||||
|
||||
// Clear any existing popups first
|
||||
this.clearAllPopups();
|
||||
|
||||
// Create background blur if enabled
|
||||
if (this.config.blurBackground) {
|
||||
this.createBackgroundBlur();
|
||||
}
|
||||
|
||||
// Generate popup configurations
|
||||
const popupConfigs = this.generatePopupConfigs(imageCount, images);
|
||||
|
||||
// Create and show popups with slight delays
|
||||
popupConfigs.forEach((config, index) => {
|
||||
setTimeout(() => {
|
||||
this.createPopup(config);
|
||||
}, index * 300); // 300ms delay between each popup to allow for image loading
|
||||
});
|
||||
|
||||
console.log(`Triggered ${imageCount} punishment popups`);
|
||||
}
|
||||
|
||||
getAvailableImages() {
|
||||
// Get consequence images from the game's discovery system
|
||||
const discoveredImages = gameData.discoveredConsequenceImages || [];
|
||||
|
||||
// Get custom consequence images
|
||||
const customImages = this.dataManager.get('customImages') || { task: [], consequence: [] };
|
||||
let customConsequenceImages = [];
|
||||
|
||||
if (!Array.isArray(customImages)) {
|
||||
customConsequenceImages = customImages.consequence || [];
|
||||
}
|
||||
|
||||
// Get disabled images to filter out
|
||||
const disabledImages = this.dataManager.get('disabledImages') || [];
|
||||
|
||||
// Combine and filter images
|
||||
const allImages = [...discoveredImages, ...customConsequenceImages];
|
||||
const availableImages = allImages.filter(img => {
|
||||
const imagePath = typeof img === 'string' ? img : (img.cachedPath || img.originalName);
|
||||
return !disabledImages.includes(imagePath);
|
||||
});
|
||||
|
||||
return availableImages;
|
||||
}
|
||||
|
||||
calculateImageCount() {
|
||||
switch (this.config.imageCountMode) {
|
||||
case 'random':
|
||||
return Math.floor(Math.random() * 5) + 1; // 1-5 images
|
||||
case 'range':
|
||||
const min = this.config.minCount;
|
||||
const max = this.config.maxCount;
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
case 'fixed':
|
||||
default:
|
||||
return this.config.imageCount;
|
||||
}
|
||||
}
|
||||
|
||||
calculateDuration() {
|
||||
switch (this.config.durationMode) {
|
||||
case 'random':
|
||||
return Math.floor(Math.random() * 10000) + 5000; // 5-15 seconds
|
||||
case 'range':
|
||||
const min = this.config.minDuration;
|
||||
const max = this.config.maxDuration;
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
case 'fixed':
|
||||
default:
|
||||
return this.config.displayDuration;
|
||||
}
|
||||
}
|
||||
|
||||
generatePopupConfigs(count, images) {
|
||||
const configs = [];
|
||||
const usedImages = [];
|
||||
const usedPositions = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
// Select a random image (avoid repeats if possible)
|
||||
let selectedImage;
|
||||
let attempts = 0;
|
||||
do {
|
||||
selectedImage = images[Math.floor(Math.random() * images.length)];
|
||||
attempts++;
|
||||
} while (usedImages.includes(selectedImage) && attempts < 10 && images.length > 1);
|
||||
|
||||
usedImages.push(selectedImage);
|
||||
|
||||
// Get image aspect ratio by loading it temporarily
|
||||
const config = {
|
||||
image: selectedImage,
|
||||
position: null, // Will be set after aspect ratio is determined
|
||||
duration: this.calculateDuration(),
|
||||
index: i,
|
||||
id: `punishment-popup-${Date.now()}-${i}`,
|
||||
aspectRatio: 1.33 // Default aspect ratio
|
||||
};
|
||||
|
||||
configs.push(config);
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
generatePosition(index, totalCount, usedPositions, imageAspectRatio = 1.33) {
|
||||
const viewport = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
|
||||
// Calculate popup size based on image aspect ratio and config constraints
|
||||
const maxWidth = Math.min(
|
||||
this.config.maxWidth || 500,
|
||||
viewport.width * (this.config.viewportWidthRatio || 0.35)
|
||||
);
|
||||
const maxHeight = Math.min(
|
||||
this.config.maxHeight || 400,
|
||||
viewport.height * (this.config.viewportHeightRatio || 0.4)
|
||||
);
|
||||
const minWidth = this.config.minWidth || 200;
|
||||
const minHeight = this.config.minHeight || 150;
|
||||
|
||||
let popupSize;
|
||||
if (imageAspectRatio > 1) {
|
||||
// Landscape image - constrain by width
|
||||
const width = Math.max(minWidth, Math.min(maxWidth, maxWidth));
|
||||
const height = Math.max(minHeight, Math.min(width / imageAspectRatio + 40, maxHeight)); // +40 for header
|
||||
popupSize = { width, height };
|
||||
} else {
|
||||
// Portrait or square image - constrain by height
|
||||
const height = Math.max(minHeight, Math.min(maxHeight, maxHeight));
|
||||
const width = Math.max(minWidth, Math.min((height - 40) * imageAspectRatio, maxWidth)); // -40 for header
|
||||
popupSize = { width, height };
|
||||
}
|
||||
|
||||
switch (this.config.positioning) {
|
||||
case 'cascade':
|
||||
return {
|
||||
left: 100 + (index * 30),
|
||||
top: 100 + (index * 30),
|
||||
width: popupSize.width,
|
||||
height: popupSize.height
|
||||
};
|
||||
|
||||
case 'grid':
|
||||
const cols = Math.ceil(Math.sqrt(totalCount));
|
||||
const row = Math.floor(index / cols);
|
||||
const col = index % cols;
|
||||
return {
|
||||
left: (viewport.width / cols) * col + (viewport.width / cols - popupSize.width) / 2,
|
||||
top: (viewport.height / cols) * row + (viewport.height / cols - popupSize.height) / 2,
|
||||
width: popupSize.width,
|
||||
height: popupSize.height
|
||||
};
|
||||
|
||||
case 'center':
|
||||
const offset = (index - Math.floor(totalCount / 2)) * 50;
|
||||
return {
|
||||
left: (viewport.width - popupSize.width) / 2 + offset,
|
||||
top: (viewport.height - popupSize.height) / 2 + offset,
|
||||
width: popupSize.width,
|
||||
height: popupSize.height
|
||||
};
|
||||
|
||||
case 'random':
|
||||
default:
|
||||
let position;
|
||||
let attempts = 0;
|
||||
|
||||
do {
|
||||
position = {
|
||||
left: Math.random() * (viewport.width - popupSize.width),
|
||||
top: Math.random() * (viewport.height - popupSize.height),
|
||||
width: popupSize.width,
|
||||
height: popupSize.height
|
||||
};
|
||||
attempts++;
|
||||
} while (!this.config.allowOverlap && this.overlapsExisting(position, usedPositions) && attempts < 20);
|
||||
|
||||
usedPositions.push(position);
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
overlapsExisting(newPos, existingPositions) {
|
||||
// Add padding to prevent popups from being too close
|
||||
const padding = 20;
|
||||
|
||||
return existingPositions.some(pos => {
|
||||
return !(newPos.left > pos.left + pos.width + padding ||
|
||||
newPos.left + newPos.width + padding < pos.left ||
|
||||
newPos.top > pos.top + pos.height + padding ||
|
||||
newPos.top + newPos.height + padding < pos.top);
|
||||
});
|
||||
}
|
||||
|
||||
createBackgroundBlur() {
|
||||
const blur = document.createElement('div');
|
||||
blur.id = 'punishment-popup-blur';
|
||||
blur.className = 'punishment-popup-blur';
|
||||
blur.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(3px);
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
document.body.appendChild(blur);
|
||||
|
||||
if (this.config.fadeAnimation) {
|
||||
blur.style.opacity = '0';
|
||||
requestAnimationFrame(() => {
|
||||
blur.style.transition = 'opacity 0.3s ease-in-out';
|
||||
blur.style.opacity = '1';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createPopup(config) {
|
||||
// First, load the image to get its aspect ratio
|
||||
const tempImg = new Image();
|
||||
const imageSrc = this.getImageSrc(config.image);
|
||||
|
||||
tempImg.onload = () => {
|
||||
// Calculate aspect ratio
|
||||
const aspectRatio = tempImg.width / tempImg.height;
|
||||
config.aspectRatio = aspectRatio;
|
||||
|
||||
// Generate position with proper aspect ratio
|
||||
const usedPositions = this.activePopups.map(p => p.config.position);
|
||||
config.position = this.generatePosition(config.index, 1, usedPositions, aspectRatio);
|
||||
|
||||
// Now create the actual popup
|
||||
this.createPopupElement(config);
|
||||
};
|
||||
|
||||
tempImg.onerror = () => {
|
||||
// If image fails to load, use default aspect ratio
|
||||
const usedPositions = this.activePopups.map(p => p.config.position);
|
||||
config.position = this.generatePosition(config.index, 1, usedPositions, 1.33);
|
||||
this.createPopupElement(config);
|
||||
};
|
||||
|
||||
tempImg.src = imageSrc;
|
||||
}
|
||||
|
||||
createPopupElement(config) {
|
||||
const popup = document.createElement('div');
|
||||
popup.id = config.id;
|
||||
popup.className = 'punishment-popup';
|
||||
popup.dataset.index = config.index;
|
||||
|
||||
// Style the popup
|
||||
popup.style.cssText = `
|
||||
position: fixed;
|
||||
left: ${config.position.left}px;
|
||||
top: ${config.position.top}px;
|
||||
width: ${config.position.width}px;
|
||||
height: ${config.position.height}px;
|
||||
z-index: 10000;
|
||||
background: white;
|
||||
border: 3px solid #dc3545;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 32px rgba(220, 53, 69, 0.4);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: ${this.config.preventClose ? 'none' : 'auto'};
|
||||
`;
|
||||
|
||||
// Create header with timer
|
||||
if (this.config.showTimer || !this.config.preventClose) {
|
||||
const header = document.createElement('div');
|
||||
header.className = 'punishment-popup-header';
|
||||
header.style.cssText = `
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
if (this.config.showTimer) {
|
||||
const timer = document.createElement('span');
|
||||
timer.className = 'punishment-popup-timer';
|
||||
timer.textContent = `${Math.ceil(config.duration / 1000)}s`;
|
||||
header.appendChild(timer);
|
||||
}
|
||||
|
||||
const title = document.createElement('span');
|
||||
title.textContent = 'Consequence';
|
||||
header.appendChild(title);
|
||||
|
||||
popup.appendChild(header);
|
||||
}
|
||||
|
||||
// Create image container
|
||||
const imageContainer = document.createElement('div');
|
||||
imageContainer.style.cssText = `
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
`;
|
||||
|
||||
// Create image element
|
||||
const img = document.createElement('img');
|
||||
img.style.cssText = `
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
|
||||
// Set image source
|
||||
const imageSrc = this.getImageSrc(config.image);
|
||||
img.src = imageSrc;
|
||||
img.alt = 'Consequence Image';
|
||||
|
||||
imageContainer.appendChild(img);
|
||||
popup.appendChild(imageContainer);
|
||||
|
||||
// Add to DOM
|
||||
document.body.appendChild(popup);
|
||||
|
||||
// Add fade-in animation
|
||||
if (this.config.fadeAnimation) {
|
||||
popup.style.opacity = '0';
|
||||
popup.style.transform = 'scale(0.8)';
|
||||
popup.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
popup.style.opacity = '1';
|
||||
popup.style.transform = 'scale(1)';
|
||||
});
|
||||
}
|
||||
|
||||
// Add to active popups list
|
||||
this.activePopups.push({
|
||||
element: popup,
|
||||
config: config,
|
||||
startTime: Date.now()
|
||||
});
|
||||
|
||||
// Start timer countdown if enabled
|
||||
if (this.config.showTimer) {
|
||||
this.startTimer(popup, config.duration);
|
||||
}
|
||||
|
||||
// Schedule removal
|
||||
setTimeout(() => {
|
||||
this.removePopup(config.id);
|
||||
}, config.duration);
|
||||
}
|
||||
|
||||
startTimer(popup, duration) {
|
||||
const timerElement = popup.querySelector('.punishment-popup-timer');
|
||||
if (!timerElement) return;
|
||||
|
||||
const startTime = Date.now();
|
||||
const updateTimer = () => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const remaining = Math.max(0, duration - elapsed);
|
||||
const seconds = Math.ceil(remaining / 1000);
|
||||
|
||||
timerElement.textContent = `${seconds}s`;
|
||||
|
||||
if (remaining > 0) {
|
||||
requestAnimationFrame(updateTimer);
|
||||
}
|
||||
};
|
||||
|
||||
requestAnimationFrame(updateTimer);
|
||||
}
|
||||
|
||||
removePopup(popupId) {
|
||||
const popupData = this.activePopups.find(p => p.config.id === popupId);
|
||||
if (!popupData) return;
|
||||
|
||||
const popup = popupData.element;
|
||||
|
||||
if (this.config.fadeAnimation) {
|
||||
popup.style.transition = 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out';
|
||||
popup.style.opacity = '0';
|
||||
popup.style.transform = 'scale(0.9)';
|
||||
|
||||
setTimeout(() => {
|
||||
if (popup.parentNode) {
|
||||
popup.parentNode.removeChild(popup);
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
if (popup.parentNode) {
|
||||
popup.parentNode.removeChild(popup);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from active popups
|
||||
this.activePopups = this.activePopups.filter(p => p.config.id !== popupId);
|
||||
|
||||
// Remove background blur if no more popups
|
||||
if (this.activePopups.length === 0) {
|
||||
this.removeBackgroundBlur();
|
||||
}
|
||||
}
|
||||
|
||||
removeBackgroundBlur() {
|
||||
const blur = document.getElementById('punishment-popup-blur');
|
||||
if (blur) {
|
||||
if (this.config.fadeAnimation) {
|
||||
blur.style.transition = 'opacity 0.3s ease-in-out';
|
||||
blur.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
if (blur.parentNode) {
|
||||
blur.parentNode.removeChild(blur);
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
blur.parentNode.removeChild(blur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearAllPopups() {
|
||||
// Remove all active popups immediately
|
||||
this.activePopups.forEach(popupData => {
|
||||
if (popupData.element.parentNode) {
|
||||
popupData.element.parentNode.removeChild(popupData.element);
|
||||
}
|
||||
});
|
||||
|
||||
this.activePopups = [];
|
||||
this.removeBackgroundBlur();
|
||||
}
|
||||
|
||||
getImageSrc(imageData) {
|
||||
// Handle both old path format and new cached metadata format
|
||||
if (typeof imageData === 'string') {
|
||||
return imageData;
|
||||
} else if (imageData.dataUrl) {
|
||||
return imageData.dataUrl;
|
||||
} else {
|
||||
return imageData.cachedPath || imageData.originalName || '';
|
||||
}
|
||||
}
|
||||
|
||||
// Preview functionality for testing
|
||||
previewPunishmentPopups(count = 1) {
|
||||
const oldConfig = { ...this.config };
|
||||
this.config.imageCount = count;
|
||||
this.config.imageCountMode = 'fixed';
|
||||
|
||||
this.triggerPunishmentPopups();
|
||||
|
||||
// Restore config
|
||||
this.config = oldConfig;
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
isActive() {
|
||||
return this.activePopups.length > 0;
|
||||
}
|
||||
|
||||
getActiveCount() {
|
||||
return this.activePopups.length;
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return {
|
||||
active: this.activePopups.length,
|
||||
config: this.getConfig(),
|
||||
availableImages: this.getAvailableImages().length
|
||||
};
|
||||
}
|
||||
}
|
||||
243
styles.css
243
styles.css
|
|
@ -2666,4 +2666,247 @@ body.theme-monochrome {
|
|||
.annoyance-section {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================================
|
||||
Punishment Popup System Styles
|
||||
====================================== */
|
||||
|
||||
/* Background blur for punishment popups */
|
||||
.punishment-popup-blur {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(3px);
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Individual punishment popup */
|
||||
.punishment-popup {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
background: white;
|
||||
border: 3px solid #dc3545;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 32px rgba(220, 53, 69, 0.4);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--font-family);
|
||||
min-width: 200px;
|
||||
min-height: 150px;
|
||||
max-width: 500px;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
/* Popup header with timer and title */
|
||||
.punishment-popup-header {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.punishment-popup-timer {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
min-width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Image container within popup */
|
||||
.punishment-popup img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Popup Images Tab specific styles */
|
||||
.range-inputs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.range-inputs > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.range-inputs input[type="number"] {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.range-inputs input[type="number"]:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.test-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.test-buttons .btn {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.test-buttons .btn-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.test-buttons .btn-info:hover {
|
||||
background: #138496;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.test-buttons .btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.test-buttons .btn-primary:hover {
|
||||
background: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.test-buttons .btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.test-buttons .btn-danger:hover {
|
||||
background: #c82333;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.info-display {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #007bff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.info-item span:last-child {
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
background: rgba(0, 123, 255, 0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
min-width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Responsive styles for popup images tab */
|
||||
@media (max-width: 768px) {
|
||||
.range-inputs {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.test-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.test-buttons .btn {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.punishment-popup {
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.info-item span:last-child {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for popup appearance */
|
||||
@keyframes popupFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popupFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.punishment-popup {
|
||||
animation: popupFadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.punishment-popup.fade-out {
|
||||
animation: popupFadeOut 0.3s ease-in;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
Loading…
Reference in New Issue