Files
swiper/js/enhanced-main.js
Aodhan Collins 5e893a0c9d Bug fixes
2025-07-31 01:38:07 +01:00

917 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Enhanced Main Application with Physics-Based Swipe Animations
* Integrates SwipePhysics engine and EnhancedSwipeCard component
*/
import { showToast, updateImageInfo } from './utils.js';
import { swipeIntegration, createSwipeSettingsPanel } from './swipe-integration.js';
document.addEventListener('DOMContentLoaded', () => {
// Enhanced application state
const state = {
currentImageInfo: null,
currentOrientation: ['all'],
currentActions: ['Unactioned'],
previousOrientation: ['all'],
allowNsfw: false,
searchKeywords: [],
sortOrder: 'random',
isLoading: false,
// Physics and animation state
animationQuality: getAnimationQuality(),
performanceMode: getPerformanceMode(),
reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
// Statistics for performance monitoring
stats: {
totalSwipes: 0,
averageSwipeTime: 0,
lastSwipeTime: 0,
performanceMetrics: []
}
};
// DOM elements
const lastActionText = document.getElementById('last-action');
const orientationFilters = document.querySelector('.orientation-filters');
const actionFilters = document.querySelector('.action-filters');
const modal = document.getElementById('fullscreen-modal');
const fullscreenImage = document.getElementById('fullscreen-image');
const closeModal = document.querySelector('.close-modal');
const searchInput = document.getElementById('search-input');
const nsfwToggleBtn = document.getElementById('toggle-nsfw');
const searchButton = document.getElementById('search-button');
const keywordPillsContainer = document.getElementById('keyword-pills-container');
const fullscreenToggle = document.getElementById('fullscreen-toggle');
// Initialize swipe system using integration manager
let enhancedSwipeCard = null;
// Initialize the swipe integration system
swipeIntegration.initialize(
document.querySelector('.swipe-container'),
performEnhancedSwipe,
{
threshold: 120,
velocityThreshold: state.reducedMotion ? 1 : 3,
springTension: state.animationQuality === 'high' ? 320 : 280,
springFriction: state.animationQuality === 'high' ? 28 : 35,
mass: 1.1,
momentumDecay: 0.91,
enablePhysics: true,
enableVisualEffects: true,
enableHapticFeedback: true,
animationQuality: state.animationQuality,
performanceMode: state.performanceMode
}
).then(() => {
enhancedSwipeCard = swipeIntegration.getSwipeCard();
console.log('Swipe system initialized successfully');
// Setup modal interactions after swipe card is initialized
if (enhancedSwipeCard && enhancedSwipeCard.card) {
enhancedSwipeCard.card.addEventListener('click', () => {
if (!enhancedSwipeCard.state.hasMoved && state.currentImageInfo) {
openEnhancedModal();
}
});
}
// Load initial image after initialization
loadNewImageWithAnimation();
}).catch(error => {
console.error('Failed to initialize swipe system:', error);
// Fallback to basic functionality
loadNewImageWithAnimation();
});
// Performance monitoring
let performanceObserver;
if ('PerformanceObserver' in window) {
performanceObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (entry.entryType === 'measure' && entry.name.includes('swipe')) {
state.stats.performanceMetrics.push({
name: entry.name,
duration: entry.duration,
timestamp: entry.startTime
});
// Keep only last 50 measurements
if (state.stats.performanceMetrics.length > 50) {
state.stats.performanceMetrics.shift();
}
}
});
});
performanceObserver.observe({ entryTypes: ['measure'] });
}
/**
* Enhanced swipe handler with physics feedback
*/
function performEnhancedSwipe(direction) {
if (!state.currentImageInfo) return;
// Prevent duplicate calls within a short time window
const now = performance.now();
if (state.stats.lastSwipeTime && (now - state.stats.lastSwipeTime) < 500) {
console.log('Duplicate swipe prevented:', direction);
return;
}
const swipeStartTime = now;
performance.mark('swipe-start');
// Update statistics
state.stats.totalSwipes++;
state.stats.lastSwipeTime = swipeStartTime;
// Enhanced action mapping with more descriptive feedback
const actionMap = {
left: 'Discarded',
right: 'Kept',
up: 'Favorited',
down: 'Marked for review'
};
// Backend action mapping (single letters for simplicity and backwards compatibility)
const backendActionMap = {
left: 'D', // Discard
right: 'K', // Keep
up: 'F', // Favourite
down: 'R' // Review
};
const actionName = actionMap[direction] || direction;
const backendAction = backendActionMap[direction] || direction;
lastActionText.textContent = `Last action: ${actionName}`;
// Enhanced toast with physics-based animation
showEnhancedToast(actionName, direction);
// Record selection with performance tracking - use backend action name
recordEnhancedSelection(state.currentImageInfo, backendAction);
// Add haptic feedback for supported devices
if ('vibrate' in navigator && !state.reducedMotion) {
const vibrationPattern = getVibrationPattern(direction);
navigator.vibrate(vibrationPattern);
}
// Performance measurement
performance.mark('swipe-end');
performance.measure('swipe-complete', 'swipe-start', 'swipe-end');
// Update average swipe time
const swipeTime = performance.now() - swipeStartTime;
state.stats.averageSwipeTime = (
(state.stats.averageSwipeTime * (state.stats.totalSwipes - 1) + swipeTime) /
state.stats.totalSwipes
);
// Load new image with staggered animation
setTimeout(() => {
loadNewImageWithAnimation();
}, state.animationQuality === 'high' ? 600 : 400);
}
/**
* Enhanced toast notification with direction-based styling
*/
function showEnhancedToast(message, direction) {
const toastEl = document.getElementById('toast');
if (!toastEl) return;
// Add direction-specific class for styling
toastEl.className = `toast enhanced-toast toast-${direction}`;
// Enhanced message with icon
const icons = {
left: '🗑️',
right: '✅',
up: '⭐',
down: '⏰'
};
toastEl.innerHTML = `
<span class="toast-icon">${icons[direction] || '📱'}</span>
<span class="toast-message">${message}</span>
`;
toastEl.classList.add('show');
// Auto-hide with enhanced timing
setTimeout(() => {
toastEl.classList.remove('show');
setTimeout(() => {
toastEl.className = 'toast';
}, 300);
}, state.reducedMotion ? 2000 : 3000);
}
/**
* Get vibration pattern based on swipe direction
*/
function getVibrationPattern(direction) {
const patterns = {
left: [50, 30, 50], // Double tap for discard
right: [100], // Single strong for keep
up: [30, 20, 30, 20, 30], // Triple tap for favorite
down: [80, 40, 80] // Double long for review
};
return patterns[direction] || [50];
}
/**
* Enhanced selection recording with retry logic
*/
async function recordEnhancedSelection(imageInfo, action) {
const maxRetries = 3;
let retryCount = 0;
const attemptRecord = async () => {
try {
const response = await fetch('/selection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-ID': generateRequestId()
},
body: JSON.stringify({
image_path: imageInfo.path,
action,
timestamp: Date.now(),
swipe_stats: {
total_swipes: state.stats.totalSwipes,
average_time: state.stats.averageSwipeTime
}
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('Enhanced selection recorded:', data);
return data;
} catch (error) {
console.error(`Selection recording attempt ${retryCount + 1} failed:`, error);
if (retryCount < maxRetries) {
retryCount++;
// Exponential backoff
const delay = Math.pow(2, retryCount) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
return attemptRecord();
} else {
// Show user-friendly error
showEnhancedToast('Failed to save selection', 'error');
throw error;
}
}
};
return attemptRecord();
}
/**
* Load new image with enhanced animations
*/
function loadNewImageWithAnimation() {
if (state.isLoading) return;
state.isLoading = true;
performance.mark('image-load-start');
// Show enhanced loading state
if (enhancedSwipeCard && enhancedSwipeCard.showLoading) {
enhancedSwipeCard.showLoading();
}
const params = new URLSearchParams({
orientation: state.currentOrientation.join(','),
t: new Date().getTime(),
allow_nsfw: state.allowNsfw ? '1' : '0',
sort: state.sortOrder
});
if (state.searchKeywords.length > 0) {
params.append('search', state.searchKeywords.join(','));
}
if (state.currentActions.length > 0) {
params.append('actions', state.currentActions.join(','));
}
fetch(`/random-image?${params.toString()}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
state.isLoading = false;
if (enhancedSwipeCard && enhancedSwipeCard.hideLoading) {
enhancedSwipeCard.hideLoading();
}
performance.mark('image-load-end');
performance.measure('image-load-complete', 'image-load-start', 'image-load-end');
if (data && data.path) {
state.currentImageInfo = data;
// Set image with enhanced loading animation
if (enhancedSwipeCard && enhancedSwipeCard.setImage) {
enhancedSwipeCard.setImage(data);
}
updateImageInfo(data);
adjustContainerToImageWithAnimation(data.orientation);
// Preload next image for smoother experience
if (state.performanceMode !== 'low') {
preloadNextImage();
}
} else {
handleNoImagesState(data);
}
})
.catch(error => {
console.error('Enhanced image loading error:', error);
state.isLoading = false;
if (enhancedSwipeCard && enhancedSwipeCard.hideLoading) {
enhancedSwipeCard.hideLoading();
}
// Show enhanced error state
showEnhancedError('Failed to load image. Please try again.');
});
}
/**
* Adjust container with smooth animation
*/
function adjustContainerToImageWithAnimation(orientation) {
const container = document.querySelector('.swipe-container');
if (window.innerWidth < 992) return; // Skip on mobile
container.style.transition = 'all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
if (orientation === 'landscape') {
container.style.flex = '4';
container.style.transform = 'scale(1.02)';
} else {
container.style.flex = '2';
container.style.transform = 'scale(1)';
}
// Reset transform after animation
setTimeout(() => {
container.style.transform = '';
}, 600);
}
/**
* Preload next image for smoother experience
*/
function preloadNextImage() {
const params = new URLSearchParams({
orientation: state.currentOrientation.join(','),
allow_nsfw: state.allowNsfw ? '1' : '0',
sort: state.sortOrder,
preload: '1'
});
fetch(`/random-image?${params.toString()}`)
.then(response => response.json())
.then(data => {
if (data && data.path) {
// Preload the image
const img = new Image();
img.src = data.path;
}
})
.catch(() => {
// Silently fail preloading
});
}
/**
* Handle no images state with enhanced feedback
*/
function handleNoImagesState(data) {
const message = data?.message || 'No more images available.';
if (enhancedSwipeCard && enhancedSwipeCard.card) {
enhancedSwipeCard.card.innerHTML = `
<div class="no-images-message enhanced-message">
<div class="no-images-icon">📷</div>
<div class="no-images-text">${message}</div>
<button class="retry-btn" onclick="location.reload()">Refresh</button>
</div>
`;
} else {
// Fallback for when swipe card isn't initialized
const container = document.querySelector('.swipe-container .image-card');
if (container) {
container.innerHTML = `
<div class="no-images-message enhanced-message">
<div class="no-images-icon">📷</div>
<div class="no-images-text">${message}</div>
<button class="retry-btn" onclick="location.reload()">Refresh</button>
</div>
`;
}
}
updateImageInfo({
filename: 'No image',
creation_date: '',
resolution: '',
prompt_data: ''
});
state.currentImageInfo = null;
}
/**
* Show enhanced error message
*/
function showEnhancedError(message) {
if (enhancedSwipeCard && enhancedSwipeCard.card) {
enhancedSwipeCard.card.innerHTML = `
<div class="error-message enhanced-error">
<div class="error-icon">⚠️</div>
<div class="error-text">${message}</div>
<button class="retry-btn" onclick="location.reload()">Try Again</button>
</div>
`;
} else {
// Fallback for when swipe card isn't initialized
const container = document.querySelector('.swipe-container .image-card');
if (container) {
container.innerHTML = `
<div class="error-message enhanced-error">
<div class="error-icon">⚠️</div>
<div class="error-text">${message}</div>
<button class="retry-btn" onclick="location.reload()">Try Again</button>
</div>
`;
}
}
}
// Enhanced event listeners with improved performance
setupEnhancedEventListeners();
setupKeyboardShortcuts();
setupPerformanceMonitoring();
/**
* Setup enhanced event listeners
*/
function setupEnhancedEventListeners() {
// Orientation filters with enhanced feedback
orientationFilters?.addEventListener('click', (e) => {
const button = e.target.closest('button');
if (!button) return;
// Add ripple effect
createRippleEffect(button, e);
const clickedOrientation = button.dataset.orientation;
if (clickedOrientation === 'all') {
state.currentOrientation = ['all'];
} else {
if (state.currentOrientation.length === 1 && state.currentOrientation[0] === 'all') {
state.currentOrientation = [];
}
const index = state.currentOrientation.indexOf(clickedOrientation);
if (index > -1) {
state.currentOrientation.splice(index, 1);
} else {
state.currentOrientation.push(clickedOrientation);
}
}
if (state.currentOrientation.length === 0) {
state.currentOrientation = ['all'];
}
updateFilterButtonStates(orientationFilters, state.currentOrientation);
loadNewImageWithAnimation();
});
// Action filters with enhanced feedback
actionFilters?.addEventListener('click', (e) => {
const button = e.target.closest('button');
if (!button) return;
createRippleEffect(button, e);
const clickedAction = button.dataset.action;
if (state.currentActions.length === 1 && state.currentActions[0] === 'Unactioned' && clickedAction !== 'Unactioned') {
state.currentActions = [];
}
const index = state.currentActions.indexOf(clickedAction);
if (index > -1) {
state.currentActions.splice(index, 1);
} else {
state.currentActions.push(clickedAction);
}
if (state.currentActions.length === 0) {
state.currentActions = ['Unactioned'];
}
updateFilterButtonStates(actionFilters, state.currentActions);
loadNewImageWithAnimation();
});
// Enhanced search functionality
searchButton?.addEventListener('click', addSearchKeywordWithAnimation);
searchInput?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
addSearchKeywordWithAnimation();
}
});
closeModal?.addEventListener('click', closeEnhancedModal);
modal?.addEventListener('click', (e) => {
if (e.target === modal) {
closeEnhancedModal();
}
});
// NSFW toggle with enhanced feedback
nsfwToggleBtn?.addEventListener('click', () => {
state.allowNsfw = !state.allowNsfw;
nsfwToggleBtn.dataset.allow = state.allowNsfw ? '1' : '0';
nsfwToggleBtn.classList.toggle('active', state.allowNsfw);
// Enhanced visual feedback
createButtonPulse(nsfwToggleBtn);
loadNewImageWithAnimation();
});
// Sort order change with enhanced feedback
document.getElementById('sort-order')?.addEventListener('change', (e) => {
state.sortOrder = e.target.value;
showEnhancedToast(`Sorted by: ${e.target.selectedOptions[0].text}`, 'info');
loadNewImageWithAnimation();
});
// Button click handlers with enhanced feedback
document.getElementById('btn-left')?.addEventListener('click', () => {
createButtonPulse(document.getElementById('btn-left'));
performEnhancedSwipe('left');
});
document.getElementById('btn-right')?.addEventListener('click', () => {
createButtonPulse(document.getElementById('btn-right'));
performEnhancedSwipe('right');
});
document.getElementById('btn-up')?.addEventListener('click', () => {
createButtonPulse(document.getElementById('btn-up'));
performEnhancedSwipe('up');
});
document.getElementById('btn-down')?.addEventListener('click', () => {
createButtonPulse(document.getElementById('btn-down'));
performEnhancedSwipe('down');
});
// Fullscreen toggle functionality
fullscreenToggle?.addEventListener('click', () => {
toggleFullscreenMode();
});
}
/**
* Setup keyboard shortcuts with enhanced feedback
*/
function setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
if (state.isLoading || document.activeElement === searchInput) return;
if (modal?.style.display === 'flex') return;
const keyMap = {
ArrowLeft: 'left',
ArrowRight: 'right',
ArrowUp: 'up',
ArrowDown: 'down',
KeyA: 'left',
KeyD: 'right',
KeyW: 'up',
KeyS: 'down'
};
if (keyMap[e.code]) {
e.preventDefault();
// Visual feedback for keyboard shortcuts
const button = document.getElementById(`btn-${keyMap[e.code]}`);
if (button) {
createButtonPulse(button);
}
performEnhancedSwipe(keyMap[e.code]);
}
// Modal controls
if (modal?.style.display === 'flex' && e.key === 'Escape') {
closeEnhancedModal();
}
// Fullscreen controls
if (e.key === 'Escape' && document.body.classList.contains('fullscreen-mode')) {
e.preventDefault();
toggleFullscreenMode();
}
// F11 alternative for fullscreen (F key)
if (e.key === 'f' || e.key === 'F') {
e.preventDefault();
toggleFullscreenMode();
}
});
}
/**
* Setup performance monitoring
*/
function setupPerformanceMonitoring() {
// Monitor frame rate
let frameCount = 0;
let lastTime = performance.now();
function monitorFPS() {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
const fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
// Adjust animation quality based on performance
if (fps < 30 && state.animationQuality === 'high') {
state.animationQuality = 'medium';
console.log('Reduced animation quality due to low FPS:', fps);
} else if (fps > 50 && state.animationQuality === 'medium') {
state.animationQuality = 'high';
console.log('Increased animation quality due to good FPS:', fps);
}
frameCount = 0;
lastTime = currentTime;
}
requestAnimationFrame(monitorFPS);
}
if (state.performanceMode !== 'low') {
requestAnimationFrame(monitorFPS);
}
}
// Utility functions
function getAnimationQuality() {
const userAgent = navigator.userAgent.toLowerCase();
const isMobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/.test(userAgent);
const isLowEnd = /android.*chrome\/[1-6][0-9]/.test(userAgent);
if (isLowEnd) return 'low';
if (isMobile) return 'medium';
return 'high';
}
function getPerformanceMode() {
if ('connection' in navigator) {
const connection = navigator.connection;
if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
return 'low';
}
}
return 'normal';
}
function generateRequestId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
function createRippleEffect(button, event) {
if (state.reducedMotion) return;
const ripple = document.createElement('span');
ripple.classList.add('ripple-effect');
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = event.clientX - rect.left - size / 2;
const y = event.clientY - rect.top - size / 2;
ripple.style.width = ripple.style.height = `${size}px`;
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
button.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 600);
}
function createButtonPulse(button) {
if (state.reducedMotion) return;
button.style.transform = 'scale(0.95)';
setTimeout(() => {
button.style.transform = '';
}, 150);
}
function updateFilterButtonStates(container, activeStates) {
container.querySelectorAll('button').forEach(btn => {
const isActive = activeStates.includes(btn.dataset.orientation || btn.dataset.action);
btn.classList.toggle('active', isActive);
});
}
function addSearchKeywordWithAnimation() {
const newKeyword = searchInput.value.trim();
if (newKeyword && !state.searchKeywords.includes(newKeyword)) {
state.searchKeywords.push(newKeyword);
renderKeywordPillsWithAnimation();
loadNewImageWithAnimation();
}
searchInput.value = '';
searchInput.focus();
}
function renderKeywordPillsWithAnimation() {
keywordPillsContainer.innerHTML = '';
state.searchKeywords.forEach((keyword, index) => {
const pill = document.createElement('div');
pill.className = 'keyword-pill enhanced-pill';
pill.style.animationDelay = `${index * 100}ms`;
pill.innerHTML = `
<span class="pill-text">${keyword}</span>
<button class="remove-keyword" data-keyword="${keyword}">×</button>
`;
keywordPillsContainer.appendChild(pill);
});
// Add event listeners for removal
keywordPillsContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('remove-keyword')) {
const keywordToRemove = e.target.dataset.keyword;
state.searchKeywords = state.searchKeywords.filter(k => k !== keywordToRemove);
renderKeywordPillsWithAnimation();
loadNewImageWithAnimation();
}
});
}
function openEnhancedModal() {
if (!state.currentImageInfo) return;
fullscreenImage.src = state.currentImageInfo.path;
document.getElementById('modal-resolution').textContent = `Resolution: ${state.currentImageInfo.resolution}`;
document.getElementById('modal-filename').textContent = `Filename: ${state.currentImageInfo.filename || 'N/A'}`;
document.getElementById('modal-creation-date').textContent = `Creation Date: ${state.currentImageInfo.creation_date || 'N/A'}`;
document.getElementById('modal-prompt-data').textContent = `Prompt: ${state.currentImageInfo.prompt_data || 'N/A'}`;
modal.style.display = 'flex';
if (!state.reducedMotion) {
setTimeout(() => {
modal.classList.add('show');
}, 10);
} else {
modal.classList.add('show');
}
}
function closeEnhancedModal() {
modal.classList.remove('show');
setTimeout(() => {
modal.style.display = 'none';
}, state.reducedMotion ? 0 : 400);
}
// Setup settings panel
setupSettingsPanel();
// Expose state for debugging
window.enhancedSwipeState = state;
window.enhancedSwipeCard = enhancedSwipeCard;
window.swipeIntegration = swipeIntegration;
/**
* Setup settings panel for swipe mode selection
*/
function setupSettingsPanel() {
const settingsToggle = document.getElementById('swipe-settings-toggle');
const settingsPanelContainer = document.getElementById('swipe-settings-panel');
let settingsPanel = null;
let isPanelOpen = false;
if (!settingsToggle || !settingsPanelContainer) {
console.warn('Settings panel elements not found');
return;
}
settingsToggle.addEventListener('click', () => {
if (!isPanelOpen) {
// Create and show settings panel
settingsPanel = createSwipeSettingsPanel(swipeIntegration);
settingsPanelContainer.appendChild(settingsPanel);
settingsPanelContainer.style.display = 'block';
// Add show class after a brief delay for animation
setTimeout(() => {
settingsPanel.classList.add('show');
}, 10);
isPanelOpen = true;
// Add click outside to close
document.addEventListener('click', handleOutsideClick);
} else {
closeSettingsPanel();
}
});
function handleOutsideClick(e) {
if (settingsPanel && !settingsPanel.contains(e.target) && !settingsToggle.contains(e.target)) {
closeSettingsPanel();
}
}
function closeSettingsPanel() {
if (settingsPanel) {
settingsPanel.classList.remove('show');
setTimeout(() => {
settingsPanelContainer.removeChild(settingsPanel);
settingsPanelContainer.style.display = 'none';
settingsPanel = null;
}, 300);
}
isPanelOpen = false;
document.removeEventListener('click', handleOutsideClick);
}
// Add keyboard shortcut for settings (Ctrl/Cmd + ,)
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === ',') {
e.preventDefault();
settingsToggle.click();
}
});
}
/**
* Toggle fullscreen mode for immersive image viewing
*/
function toggleFullscreenMode() {
const body = document.body;
const isFullscreen = body.classList.contains('fullscreen-mode');
if (isFullscreen) {
// Exit fullscreen mode
body.classList.remove('fullscreen-mode');
fullscreenToggle.setAttribute('aria-label', 'Enter fullscreen mode');
fullscreenToggle.title = 'Enter fullscreen mode';
// Enhanced feedback
showEnhancedToast('Exited fullscreen mode', 'info');
} else {
// Enter fullscreen mode
body.classList.add('fullscreen-mode');
fullscreenToggle.setAttribute('aria-label', 'Exit fullscreen mode');
fullscreenToggle.title = 'Exit fullscreen mode';
// Enhanced feedback
showEnhancedToast('Entered fullscreen mode', 'info');
}
// Add visual feedback to the button
createButtonPulse(fullscreenToggle);
// Trigger a resize event to help any responsive components adjust
setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 300);
}
});