From 5af01454ab8dab7421137885b947caf70cf9a7c1 Mon Sep 17 00:00:00 2001 From: KarthiDreamr Date: Wed, 13 Aug 2025 22:06:30 +0530 Subject: [PATCH] feat: added toggle for save to pc --- .kiro/specs/save-to-pc-toggle/tasks.md | 18 +-- content.js | 134 +++++++++++++------ popup.html | 56 +++++++- popup.js | 177 +++++++++++++++++++------ 4 files changed, 296 insertions(+), 89 deletions(-) diff --git a/.kiro/specs/save-to-pc-toggle/tasks.md b/.kiro/specs/save-to-pc-toggle/tasks.md index ebf1a72..f5d16d3 100644 --- a/.kiro/specs/save-to-pc-toggle/tasks.md +++ b/.kiro/specs/save-to-pc-toggle/tasks.md @@ -1,61 +1,61 @@ # Implementation Plan -- [ ] 1. Add save to PC toggle UI to popup +- [x] 1. Add save to PC toggle UI to popup - Add HTML toggle control above the clipboard toggle in popup.html - Style the toggle control to match existing UI design patterns - Include proper ARIA labels and accessibility attributes for the toggle - Position the save to PC toggle logically above clipboard toggle - _Requirements: 1.1, 2.1, 2.4_ -- [ ] 2. Implement save to PC preference storage in popup +- [x] 2. Implement save to PC preference storage in popup - Extend existing storage logic in popup.js to handle saveToPc preference - Set default value to true to maintain existing behavior for current users - Load saved save to PC preference on popup initialization - Save save to PC preference changes to chrome.storage.sync - _Requirements: 1.2, 1.3, 4.1, 4.2_ -- [ ] 3. Add validation logic for output method selection +- [x] 3. Add validation logic for output method selection - Create validation function to ensure at least one output method is enabled - Display warning message in popup when both save to PC and clipboard are disabled - Prevent screenshot capture when no output method is selected - Update start button state based on validation results - _Requirements: 3.4_ -- [ ] 4. Update message passing to include save to PC setting +- [x] 4. Update message passing to include save to PC setting - Modify startSelector message in popup.js to include saveToPc property - Update content script message handler to receive and store save to PC preference - Ensure backward compatibility with existing message structure - _Requirements: 1.1, 1.4_ -- [ ] 5. Implement conditional file download in content script +- [x] 5. Implement conditional file download in content script - Modify captureElement method in ScreenshotSelector class to conditionally download files - Wrap existing download logic in conditional check for saveToPc preference - Ensure canvas generation still occurs regardless of download preference (needed for clipboard) - Maintain existing download filename generation and format - _Requirements: 1.4, 1.5, 3.1, 3.2_ -- [ ] 6. Update user feedback messages for different operation combinations +- [x] 6. Update user feedback messages for different operation combinations - Modify success messages to reflect which operations were performed - Create specific messages for save-only, clipboard-only, and both operations - Update loading messages to reflect enabled operations - Ensure error messages are appropriate for each operation combination - _Requirements: 2.2, 2.3, 3.1, 3.2, 3.3_ -- [ ] 7. Add tooltip and help text for save to PC toggle +- [x] 7. Add tooltip and help text for save to PC toggle - Implement tooltip showing save to PC feature explanation - Add help text explaining the relationship between save to PC and clipboard features - Ensure tooltip is accessible and keyboard navigable - Include information about default behavior and workflow flexibility - _Requirements: 2.4_ -- [ ] 8. Implement warning system for disabled output methods +- [x] 8. Implement warning system for disabled output methods - Create visual warning when both save to PC and clipboard toggles are disabled - Display clear guidance on enabling at least one output method - Make warning accessible to screen readers - Update warning state dynamically as toggles change - _Requirements: 3.4_ -- [ ] 9. Test all combinations of save to PC and clipboard settings +- [x] 9. Test all combinations of save to PC and clipboard settings - Write test cases for save-only mode (saveToPc: true, clipboard: false) - Write test cases for clipboard-only mode (saveToPc: false, clipboard: true) - Write test cases for both enabled (saveToPc: true, clipboard: true) diff --git a/content.js b/content.js index 2b6e2cf..98b61c8 100644 --- a/content.js +++ b/content.js @@ -7,9 +7,10 @@ class ScreenshotSelector { this.style = null; this.background = 'black'; this.copyToClipboard = true; // Default to true + this.saveToPc = true; // Default to true for backward compatibility } - async init(background = 'black', copyToClipboard = true) { + async init(background = 'black', copyToClipboard = true, saveToPc = true) { if (this.isActive) { console.log('Screenshot selector already active'); return; @@ -17,6 +18,7 @@ class ScreenshotSelector { this.background = background; this.copyToClipboard = copyToClipboard; + this.saveToPc = saveToPc; this.isActive = true; this.createUI(); @@ -365,12 +367,25 @@ class ScreenshotSelector { async captureElement(element) { try { - // Determine loading message based on clipboard settings + // Determine loading message based on enabled operations let loadingMessage = 'Capturing screenshot...'; - if (this.copyToClipboard) { + + if (this.saveToPc && this.copyToClipboard) { + // Both operations enabled const availability = this.checkClipboardAPIAvailability(); if (availability.available) { loadingMessage = 'Capturing screenshot and preparing for clipboard...'; + } else { + loadingMessage = 'Capturing screenshot for download... (Clipboard not available)'; + } + } else if (this.saveToPc && !this.copyToClipboard) { + // Only save to PC enabled + loadingMessage = 'Capturing screenshot for download...'; + } else if (!this.saveToPc && this.copyToClipboard) { + // Only clipboard enabled + const availability = this.checkClipboardAPIAvailability(); + if (availability.available) { + loadingMessage = 'Capturing screenshot for clipboard...'; } else { loadingMessage = 'Capturing screenshot... (Clipboard not available)'; } @@ -480,11 +495,13 @@ class ScreenshotSelector { // Restore original styles Object.assign(element.style, originalStyles); - // Always download the image first (core functionality) - const link = document.createElement('a'); - link.href = canvas.toDataURL('image/png'); - link.download = `screenshot-${new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-')}.png`; - link.click(); + // Conditionally download the image based on saveToPc preference + if (this.saveToPc) { + const link = document.createElement('a'); + link.href = canvas.toDataURL('image/png'); + link.download = `screenshot-${new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-')}.png`; + link.click(); + } // Handle clipboard operations if enabled (after successful canvas generation and download) let clipboardResult = null; @@ -513,26 +530,46 @@ class ScreenshotSelector { } } - // Generate user feedback message based on clipboard results - let successMessage = 'Screenshot downloaded successfully!'; + // Generate user feedback message based on operation combinations + let successMessage = 'Screenshot captured!'; let messageColor = 'rgba(39, 174, 96, 0.95)'; // Green for success + let messageIcon = '✅'; - if (this.copyToClipboard) { + // Determine success message based on enabled operations + if (this.saveToPc && this.copyToClipboard) { + // Both operations enabled if (clipboardResult && clipboardResult.success) { - successMessage = 'Screenshot downloaded and copied to clipboard!'; - // Keep green color for full success + successMessage = 'Screenshot saved and copied to clipboard!'; + messageIcon = '✅'; } else { - // Download succeeded but clipboard failed - provide specific error feedback + // Save succeeded but clipboard failed const errorMessage = this.getClipboardErrorMessage(clipboardResult); - successMessage = `Screenshot downloaded successfully! ${errorMessage}`; + successMessage = `Screenshot saved successfully! However, ${errorMessage.toLowerCase()}`; messageColor = 'rgba(243, 156, 18, 0.95)'; // Orange for partial success + messageIcon = '⚠️'; + } + } else if (this.saveToPc && !this.copyToClipboard) { + // Only save to PC enabled + successMessage = 'Screenshot saved to downloads folder!'; + messageIcon = '💾'; + } else if (!this.saveToPc && this.copyToClipboard) { + // Only clipboard enabled + if (clipboardResult && clipboardResult.success) { + successMessage = 'Screenshot copied to clipboard!'; + messageIcon = '📋'; + } else { + // Clipboard failed - this is a complete failure since no other output method is enabled + const errorMessage = this.getClipboardErrorMessage(clipboardResult); + successMessage = `Screenshot capture failed: ${errorMessage}`; + messageColor = 'rgba(231, 76, 60, 0.95)'; // Red for failure + messageIcon = '❌'; } } this.overlay.innerHTML = `
- ✅ + ${messageIcon} ${successMessage}
@@ -546,21 +583,35 @@ class ScreenshotSelector { } catch (error) { console.error('Screenshot failed:', error); - // Provide specific error messages for screenshot failures + // Provide specific error messages for screenshot failures based on enabled operations let errorMessage = 'Screenshot capture failed. Please try again.'; + let operationContext = ''; + + // Add context about what operations were attempted + if (this.saveToPc && this.copyToClipboard) { + operationContext = ' Neither file download nor clipboard copy could be completed.'; + } else if (this.saveToPc && !this.copyToClipboard) { + operationContext = ' File download could not be completed.'; + } else if (!this.saveToPc && this.copyToClipboard) { + operationContext = ' Clipboard copy could not be completed.'; + } if (error.message) { if (error.message.includes('html2canvas')) { - errorMessage = 'Screenshot rendering failed. Try selecting a different element or refresh the page.'; + errorMessage = `Screenshot rendering failed. Try selecting a different element or refresh the page.${operationContext}`; } else if (error.message.includes('timeout')) { - errorMessage = 'Screenshot capture timed out. Try selecting a smaller area or simpler element.'; + errorMessage = `Screenshot capture timed out. Try selecting a smaller area or simpler element.${operationContext}`; } else if (error.message.includes('network') || error.message.includes('CORS')) { - errorMessage = 'Screenshot failed: Some content is blocked by security restrictions.'; + errorMessage = `Screenshot failed: Some content is blocked by security restrictions.${operationContext}`; } else if (error.message.includes('memory') || error.message.includes('quota')) { - errorMessage = 'Screenshot failed: Not enough memory. Try capturing a smaller area.'; + errorMessage = `Screenshot failed: Not enough memory. Try capturing a smaller area.${operationContext}`; } else if (error.message.includes('canvas')) { - errorMessage = 'Screenshot failed: Unable to create image. Try a different element.'; + errorMessage = `Screenshot failed: Unable to create image. Try a different element.${operationContext}`; + } else { + errorMessage = `Screenshot capture failed: ${error.message}${operationContext}`; } + } else { + errorMessage = `Screenshot capture failed. Please try again.${operationContext}`; } this.overlay.innerHTML = ` @@ -661,61 +712,62 @@ class ScreenshotSelector { */ getClipboardErrorMessage(clipboardResult) { if (!clipboardResult || !clipboardResult.error) { - return 'Clipboard copy failed due to unknown error.'; + return 'clipboard copy failed due to unknown error'; } const errorType = clipboardResult.errorType; const errorMessage = clipboardResult.error; // Provide contextual error messages based on error type + // Note: These messages are designed to work in the context of "Screenshot saved successfully! However, [message]" switch (errorType) { case 'permission_denied': - return 'Clipboard copy failed: Permission denied. Please allow clipboard access in your browser settings.'; + return 'clipboard copy failed: permission denied. Please allow clipboard access in your browser settings'; case 'not_supported': case 'api_unavailable': - return 'Clipboard copy not available in this browser. Screenshot was still downloaded.'; + return 'clipboard copy is not available in this browser'; case 'security_error': - return 'Clipboard copy blocked by browser security. Try using HTTPS or check site permissions.'; + return 'clipboard copy was blocked by browser security. Try using HTTPS or check site permissions'; case 'size_limit': case 'quota_exceeded': - return 'Clipboard copy failed: Image too large. Try capturing a smaller area.'; + return 'clipboard copy failed: image too large. Try capturing a smaller area'; case 'timeout': - return 'Clipboard copy timed out. The image may be too large or complex.'; + return 'clipboard copy timed out. The image may be too large or complex'; case 'network_error': - return 'Clipboard copy failed due to network error. Please try again.'; + return 'clipboard copy failed due to network error. Please try again'; case 'canvas_conversion': - return 'Clipboard copy failed: Unable to prepare image. Try capturing a different element.'; + return 'clipboard copy failed: unable to prepare image. Try capturing a different element'; case 'clipboard_item_creation': - return 'Clipboard copy not supported: Your browser doesn\'t support image clipboard operations.'; + return 'clipboard copy is not supported: your browser doesn\'t support image clipboard operations'; case 'invalid_state': - return 'Clipboard copy failed: Browser clipboard is busy. Please try again.'; + return 'clipboard copy failed: browser clipboard is busy. Please try again'; case 'data_error': - return 'Clipboard copy failed: Invalid image data. Please try capturing again.'; + return 'clipboard copy failed: invalid image data. Please try capturing again'; case 'unexpected': - return 'Clipboard copy failed due to unexpected error. Screenshot was still downloaded.'; + return 'clipboard copy failed due to unexpected error'; default: // For unknown error types, try to extract meaningful info from the error message if (errorMessage.includes('permission') || errorMessage.includes('denied')) { - return 'Clipboard copy failed: Access denied. Check browser permissions.'; + return 'clipboard copy failed: access denied. Check browser permissions'; } else if (errorMessage.includes('not supported') || errorMessage.includes('unavailable')) { - return 'Clipboard copy not supported in this browser.'; + return 'clipboard copy is not supported in this browser'; } else if (errorMessage.includes('timeout') || errorMessage.includes('timed out')) { - return 'Clipboard copy timed out. Try capturing a smaller area.'; + return 'clipboard copy timed out. Try capturing a smaller area'; } else if (errorMessage.includes('large') || errorMessage.includes('size')) { - return 'Clipboard copy failed: Image too large for clipboard.'; + return 'clipboard copy failed: image too large for clipboard'; } else { - return `Clipboard copy failed: ${errorMessage}`; + return `clipboard copy failed: ${errorMessage.toLowerCase()}`; } } } @@ -893,7 +945,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { case 'startSelector': // Extract clipboard preference from message, default to true for backward compatibility const copyToClipboard = message.copyToClipboard !== undefined ? message.copyToClipboard : true; - screenshotSelector.init(message.background, copyToClipboard); + // Extract save to PC preference from message, default to true for backward compatibility + const saveToPc = message.saveToPc !== undefined ? message.saveToPc : true; + screenshotSelector.init(message.background, copyToClipboard, saveToPc); sendResponse({ success: true }); break; diff --git a/popup.html b/popup.html index 89d5448..88317d4 100644 --- a/popup.html +++ b/popup.html @@ -241,6 +241,23 @@ background: #f8d7da; color: #721c24; } + + .status.warning { + background: #fff3cd; + color: #856404; + border: 1px solid #ffeaa7; + } + + .btn:disabled { + background: #6c757d; + color: #fff; + cursor: not-allowed; + opacity: 0.6; + } + + .btn:disabled:hover { + background: #6c757d; + } @@ -258,6 +275,39 @@ +
+
+ +
+ + +
+
+
+ Automatically download screenshots as PNG files to your computer. Hover over the "?" for more details. +
+
+
@@ -291,6 +341,10 @@ + + diff --git a/popup.js b/popup.js index edebae4..5c6a712 100644 --- a/popup.js +++ b/popup.js @@ -2,18 +2,43 @@ document.addEventListener('DOMContentLoaded', async () => { const backgroundSelect = document.getElementById('background-select'); const clipboardToggle = document.getElementById('clipboard-toggle'); + const saveToPcToggle = document.getElementById('save-to-pc-toggle'); const startBtn = document.getElementById('start-selector'); const stopBtn = document.getElementById('stop-selector'); const status = document.getElementById('status'); + const validationWarning = document.getElementById('validation-warning'); // Load saved preferences - const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard']); + const saved = await chrome.storage.sync.get(['backgroundPreference', 'copyToClipboard', 'saveToPc']); if (saved.backgroundPreference) { backgroundSelect.value = saved.backgroundPreference; } // Set clipboard preference (default to true if not set) clipboardToggle.checked = saved.copyToClipboard !== undefined ? saved.copyToClipboard : true; + + // Set save to PC preference (default to true if not set to maintain existing behavior) + saveToPcToggle.checked = saved.saveToPc !== undefined ? saved.saveToPc : true; + + // Validation function to ensure at least one output method is enabled + function validateOutputMethods() { + const hasValidOutput = clipboardToggle.checked || saveToPcToggle.checked; + + if (hasValidOutput) { + validationWarning.style.display = 'none'; + startBtn.disabled = false; + startBtn.setAttribute('aria-describedby', ''); + } else { + validationWarning.style.display = 'block'; + startBtn.disabled = true; + startBtn.setAttribute('aria-describedby', 'validation-warning'); + } + + return hasValidOutput; + } + + // Initial validation check + validateOutputMethods(); // Save background preference when changed backgroundSelect.addEventListener('change', () => { @@ -23,10 +48,23 @@ document.addEventListener('DOMContentLoaded', async () => { // Save clipboard preference when changed clipboardToggle.addEventListener('change', () => { chrome.storage.sync.set({ copyToClipboard: clipboardToggle.checked }); + validateOutputMethods(); + }); + + // Save save to PC preference when changed + saveToPcToggle.addEventListener('change', () => { + chrome.storage.sync.set({ saveToPc: saveToPcToggle.checked }); + validateOutputMethods(); }); // Start selector startBtn.addEventListener('click', async () => { + // Validate output methods before proceeding + if (!validateOutputMethods()) { + showStatus('Error: Please enable at least one output method (Save to PC or Copy to Clipboard) to capture screenshots.', 'error'); + return; + } + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); // Check if page is compatible @@ -44,10 +82,22 @@ document.addEventListener('DOMContentLoaded', async () => { await chrome.tabs.sendMessage(tab.id, { action: 'startSelector', background: backgroundSelect.value, - copyToClipboard: clipboardToggle.checked + copyToClipboard: clipboardToggle.checked, + saveToPc: saveToPcToggle.checked }); - showStatus('Selector activated! Hover over elements and click to capture.', 'success'); + // Generate activation message based on enabled operations + let activationMessage = 'Selector activated! Hover over elements and click to capture.'; + + if (saveToPcToggle.checked && clipboardToggle.checked) { + activationMessage = 'Selector activated! Screenshots will be saved and copied to clipboard.'; + } else if (saveToPcToggle.checked && !clipboardToggle.checked) { + activationMessage = 'Selector activated! Screenshots will be saved to downloads folder.'; + } else if (!saveToPcToggle.checked && clipboardToggle.checked) { + activationMessage = 'Selector activated! Screenshots will be copied to clipboard only.'; + } + + showStatus(activationMessage, 'success'); toggleButtons(true); // Auto-close popup after starting @@ -55,11 +105,27 @@ document.addEventListener('DOMContentLoaded', async () => { } catch (error) { console.error('Full error details:', error); + + let errorMessage = 'Error: Could not activate on this page.'; + if (error.message.includes('Could not establish connection')) { - showStatus('Error: Content script not loaded. Try refreshing the page.', 'error'); - } else { - showStatus('Error: Could not activate on this page.', 'error'); + errorMessage = 'Error: Content script not loaded. Try refreshing the page and try again.'; + } else if (error.message.includes('permission')) { + errorMessage = 'Error: Permission denied. Check if the page allows extensions.'; + } else if (error.message.includes('protocol')) { + errorMessage = 'Error: Cannot capture screenshots on this page type (chrome://, file://, etc.).'; } + + // Add context about what operations were attempted + if (saveToPcToggle.checked && clipboardToggle.checked) { + errorMessage += ' Neither file download nor clipboard copy could be set up.'; + } else if (saveToPcToggle.checked && !clipboardToggle.checked) { + errorMessage += ' File download could not be set up.'; + } else if (!saveToPcToggle.checked && clipboardToggle.checked) { + errorMessage += ' Clipboard copy could not be set up.'; + } + + showStatus(errorMessage, 'error'); } }); @@ -126,43 +192,47 @@ document.addEventListener('DOMContentLoaded', async () => { } function setupTooltipAccessibility() { - const tooltipIcon = document.querySelector('.tooltip-icon'); - const tooltipContent = document.querySelector('.tooltip-content'); + const tooltips = document.querySelectorAll('.tooltip'); - if (!tooltipIcon || !tooltipContent) return; + tooltips.forEach(tooltip => { + const tooltipIcon = tooltip.querySelector('.tooltip-icon'); + const tooltipContent = tooltip.querySelector('.tooltip-content'); + + if (!tooltipIcon || !tooltipContent) return; - // Handle keyboard events for tooltip - tooltipIcon.addEventListener('keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - // Toggle tooltip visibility on Enter/Space - const isVisible = tooltipContent.style.visibility === 'visible'; - tooltipContent.style.visibility = isVisible ? 'hidden' : 'visible'; - tooltipContent.style.opacity = isVisible ? '0' : '1'; - } else if (e.key === 'Escape') { - // Hide tooltip on Escape - tooltipContent.style.visibility = 'hidden'; - tooltipContent.style.opacity = '0'; - } - }); - - // Hide tooltip when clicking outside - document.addEventListener('click', (e) => { - if (!tooltipIcon.contains(e.target)) { - tooltipContent.style.visibility = 'hidden'; - tooltipContent.style.opacity = '0'; - } - }); - - // Handle focus loss - tooltipIcon.addEventListener('blur', (e) => { - // Small delay to allow for focus to move to tooltip content if needed - setTimeout(() => { - if (!tooltipIcon.matches(':focus-within')) { + // Handle keyboard events for tooltip + tooltipIcon.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + // Toggle tooltip visibility on Enter/Space + const isVisible = tooltipContent.style.visibility === 'visible'; + tooltipContent.style.visibility = isVisible ? 'hidden' : 'visible'; + tooltipContent.style.opacity = isVisible ? '0' : '1'; + } else if (e.key === 'Escape') { + // Hide tooltip on Escape tooltipContent.style.visibility = 'hidden'; tooltipContent.style.opacity = '0'; } - }, 100); + }); + + // Hide tooltip when clicking outside + document.addEventListener('click', (e) => { + if (!tooltip.contains(e.target)) { + tooltipContent.style.visibility = 'hidden'; + tooltipContent.style.opacity = '0'; + } + }); + + // Handle focus loss + tooltipIcon.addEventListener('blur', (e) => { + // Small delay to allow for focus to move to tooltip content if needed + setTimeout(() => { + if (!tooltipIcon.matches(':focus-within')) { + tooltipContent.style.visibility = 'hidden'; + tooltipContent.style.opacity = '0'; + } + }, 100); + }); }); } }); @@ -173,15 +243,44 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { const startBtn = document.getElementById('start-selector'); const stopBtn = document.getElementById('stop-selector'); const status = document.getElementById('status'); + const saveToPcToggle = document.getElementById('save-to-pc-toggle'); + const clipboardToggle = document.getElementById('clipboard-toggle'); startBtn.style.display = 'block'; stopBtn.style.display = 'none'; if (message.reason === 'screenshot-taken') { - status.textContent = 'Screenshot captured!'; + // Generate success message based on current toggle states + let successMessage = 'Screenshot captured!'; + + if (saveToPcToggle.checked && clipboardToggle.checked) { + successMessage = 'Screenshot saved and copied to clipboard!'; + } else if (saveToPcToggle.checked && !clipboardToggle.checked) { + successMessage = 'Screenshot saved to downloads folder!'; + } else if (!saveToPcToggle.checked && clipboardToggle.checked) { + successMessage = 'Screenshot copied to clipboard!'; + } + + status.textContent = successMessage; status.className = 'status success'; status.style.display = 'block'; setTimeout(() => status.style.display = 'none', 2000); + } else if (message.reason === 'screenshot-failed') { + // Handle screenshot failure + let failureMessage = 'Screenshot capture failed. Please try again.'; + + if (saveToPcToggle.checked && clipboardToggle.checked) { + failureMessage = 'Screenshot capture failed. Neither file download nor clipboard copy could be completed.'; + } else if (saveToPcToggle.checked && !clipboardToggle.checked) { + failureMessage = 'Screenshot capture failed. File download could not be completed.'; + } else if (!saveToPcToggle.checked && clipboardToggle.checked) { + failureMessage = 'Screenshot capture failed. Clipboard copy could not be completed.'; + } + + status.textContent = failureMessage; + status.className = 'status error'; + status.style.display = 'block'; + setTimeout(() => status.style.display = 'none', 3000); } } }); \ No newline at end of file