// Initialize Vue app window.app = new Vue({ el: '#app', data: { // Theme isDarkTheme: true, // Tab Management activeTab: 'transforms', // Transform Tab transformInput: '', transformOutput: '', activeTransform: null, // Transform categories for styling transformCategories: { encoding: ['Base64', 'Base32', 'Binary', 'Hexadecimal', 'ASCII85', 'URL Encode', 'HTML Entities'], cipher: ['Caesar Cipher', 'ROT13', 'ROT47', 'Morse Code'], visual: ['Rainbow Text', 'Strikethrough', 'Underline', 'Reverse Text'], format: ['Pig Latin', 'Leetspeak', 'NATO Phonetic'], unicode: ['Invisible Text', 'Upside Down', 'Full Width', 'Small Caps', 'Bubble', 'Braille', 'Greek Letters', 'Wingdings'], special: ['Medieval', 'Cursive', 'Monospace', 'Double-Struck', 'Elder Futhark', 'Mirror Text', 'Zalgo'] }, transforms: Object.entries(window.transforms).map(([key, transform]) => ({ name: transform.name, func: transform.func.bind(transform), preview: transform.preview.bind(transform) })), // Steganography Tab emojiMessage: '', encodedMessage: '', decodeInput: '', decodedMessage: '', selectedCarrier: null, // Universal Decoder - works on both tabs universalDecodeInput: '', universalDecodeResult: null, isPasteOperation: false, // Flag to track paste operations lastCopyTime: 0, // Timestamp of last copy operation for debounce ignoreKeyboardEvents: false, // Flag to prevent keyboard events from triggering copies isTransformCopy: false, // Flag to mark transform-initiated copy operations keyboardEventsTimeout: null, // Timeout for resetting keyboard event flag activeSteg: null, carriers: window.steganography.carriers, showDecoder: true, // Emoji Library filteredEmojis: [...window.emojiLibrary.EMOJI_LIST], selectedEmoji: null, // History of copied content copyHistory: [], maxHistoryItems: 10, showCopyHistory: false }, methods: { // Switch between tabs with proper initialization switchToTab(tabName) { this.activeTab = tabName; console.log('Switched to tab:', tabName); // Reset universal decoder input when switching tabs this.universalDecodeInput = ''; this.universalDecodeResult = null; // Initialize emoji grid when switching to steganography tab if (tabName === 'steganography') { this.$nextTick(() => { console.log('Tab switch: Initializing emoji grid'); const emojiGridContainer = document.getElementById('emoji-grid-container'); if (emojiGridContainer) { console.log('Found emoji grid container after tab switch'); // Make sure the container is visible emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;'); // Render the emoji grid this.renderEmojiGrid(); } else { console.log('Emoji grid container not found after tab switch'); } }); } // Initialize category navigation when switching to transforms tab if (tabName === 'transforms') { this.$nextTick(() => { this.initializeCategoryNavigation(); }); } }, // Get transforms grouped by category getTransformsByCategory(category) { return this.transforms.filter(transform => this.transformCategories[category].includes(transform.name) ); }, // Theme Toggle toggleTheme() { this.isDarkTheme = !this.isDarkTheme; document.body.classList.toggle('light-theme'); }, // Copy History Toggle toggleCopyHistory() { this.showCopyHistory = !this.showCopyHistory; console.log('Copy history toggled:', this.showCopyHistory); // If showing history panel, focus the first copy-again button if available if (this.showCopyHistory && this.copyHistory.length > 0) { this.$nextTick(() => { const firstCopyButton = document.querySelector('.copy-again-button'); if (firstCopyButton) { firstCopyButton.focus(); } }); } }, // Transform Methods applyTransform(transform, event) { if (this.transformInput) { // Prevent default button behavior event && event.preventDefault(); // Update active transform and apply it this.activeTransform = transform; // Handle text with proper Unicode segmentation const segments = window.emojiLibrary.splitEmojis(this.transformInput); const transformedSegments = segments.map(segment => { // Skip transformation for emojis and complex Unicode characters if (segment.length > 1 || /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}]/u.test(segment)) { return segment; } return transform.func(segment); }); this.transformOutput = window.emojiLibrary.joinEmojis(transformedSegments); // Set flag to mark this as a transform-initiated copy this.isTransformCopy = true; // Force copy the transform output to clipboard this.forceCopyToClipboard(this.transformOutput); // Add to copy history this.addToCopyHistory(`Transform: ${transform.name}`, this.transformOutput); // Enhanced notification for transform and copy this.showNotification(` ${transform.name} applied and copied!`, 'success'); // Remove active state from transform buttons document.querySelectorAll('.transform-button').forEach(button => { button.classList.remove('active'); }); // Keep focus on input and move cursor to end const inputBox = document.querySelector('#transform-input'); if (inputBox) { inputBox.focus(); const len = inputBox.value.length; inputBox.setSelectionRange(len, len); } // Reset flags immediately this.isTransformCopy = false; this.ignoreKeyboardEvents = false; } }, autoTransform() { // Only proceed if we're in the transforms tab and have an active transform if (this.transformInput && this.activeTransform && this.activeTab === 'transforms') { // Handle text with proper Unicode segmentation const segments = window.emojiLibrary.splitEmojis(this.transformInput); const transformedSegments = segments.map(segment => { // Skip transformation for emojis and complex Unicode characters if (segment.length > 1 || /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}]/u.test(segment)) { return segment; } return this.activeTransform.func(segment); }); this.transformOutput = window.emojiLibrary.joinEmojis(transformedSegments); } }, // Check if a transform has a reverse function transformHasReverse(transform) { return transform && typeof transform.reverse === 'function'; }, // Decode text using the specific transform's reverse function decodeWithTransform(transform) { if (!this.transformInput || !transform || !this.transformHasReverse(transform)) { return; } try { // Handle text with proper Unicode segmentation const segments = window.emojiLibrary.splitEmojis(this.transformInput); const decodedSegments = segments.map(segment => { // Skip decoding for emojis and complex Unicode characters if (segment.length > 1 || /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}]/u.test(segment)) { return segment; } return transform.reverse(segment); }); const decodedText = window.emojiLibrary.joinEmojis(decodedSegments); if (decodedText !== this.transformInput) { // Update the input with the decoded text this.transformInput = decodedText; // Show a notification this.showNotification(` Decoded using ${transform.name}`, 'success'); // Add to copy history this.addToCopyHistory(`Decoded (${transform.name})`, decodedText); } else { this.showNotification(` Could not decode with ${transform.name}`, 'warning'); } } catch (error) { console.error(`Error decoding with ${transform.name}:`, error); this.showNotification(` Error decoding with ${transform.name}`, 'error'); } }, // Steganography Methods selectCarrier(carrier) { // Toggle carrier selection if clicking the same one again if (this.selectedCarrier === carrier) { this.selectedCarrier = null; this.encodedMessage = ''; } else { this.selectedCarrier = carrier; this.activeSteg = 'emoji'; this.autoEncode(); } }, setStegMode(mode) { // For invisible text, make it a direct action (not a toggle) if (mode === 'invisible') { // Set the mode temporarily to generate the encoded message this.activeSteg = mode; // Clear any carrier selection this.selectedCarrier = null; // Generate the encoded message this.autoEncode(); // Auto-copy the encoded message if (this.encodedMessage) { this.$nextTick(() => { this.forceCopyToClipboard(this.encodedMessage); this.showNotification(' Invisible text created and copied!', 'success'); this.addToCopyHistory('Invisible Text', this.encodedMessage); }); } } else { // For other modes (like emoji), keep the toggle behavior if (this.activeSteg === mode) { this.activeSteg = null; this.encodedMessage = ''; } else { this.activeSteg = mode; this.autoEncode(); } } }, autoEncode() { // Only proceed if we're in the steganography tab if (!this.emojiMessage || this.activeTab !== 'steganography') { this.encodedMessage = ''; return; } if (this.activeSteg === 'invisible') { this.encodedMessage = window.steganography.encodeInvisible(this.emojiMessage); // Auto-copy will be handled in setStegMode method } else if (this.selectedCarrier) { this.encodedMessage = window.steganography.encodeEmoji( this.selectedCarrier.emoji, this.emojiMessage ); // Auto-copy for emoji carrier is handled in selectEmoji method } }, autoDecode() { if (!this.decodeInput) { this.decodedMessage = ''; return; } // Use the universal decoder const result = this.universalDecode(this.decodeInput); if (result) { this.decodedMessage = `Decoded (${result.method}): ${result.text}`; // Auto-copy decoded message to clipboard this.$nextTick(() => { // Only copy the actual decoded text, not the formatted message const decodedText = result.text; if (decodedText) { // Force clipboard copy regardless of event source this.forceCopyToClipboard(decodedText); this.showNotification(` Decoded message copied!`, 'success'); // Add to copy history this.addToCopyHistory(`Decoded (${result.method})`, decodedText); } }); } else { this.decodedMessage = 'No encoded message detected'; } }, previewInvisible(text) { return '[invisible]'; }, // Add to copy history functionality addToCopyHistory(source, content) { // Create history item with timestamp const historyItem = { source: source, content: content, timestamp: new Date().toLocaleTimeString(), date: new Date().toLocaleDateString() }; // Add to beginning of array (most recent first) this.copyHistory.unshift(historyItem); // Limit history to maxHistoryItems if (this.copyHistory.length > this.maxHistoryItems) { this.copyHistory.pop(); } // Log history item for debugging console.log('Added to copy history:', historyItem); }, // Utility Methods // Track last copy operation to prevent rapid repeated copies lastCopyTime: 0, async copyToClipboard(text) { if (!text) return; // Check clipboard lock - don't proceed if locked if (this.clipboardLocked) { console.log('Copy operation prevented by clipboard lock'); return; } // Prevent rapid successive copy operations (debounce) const now = Date.now(); if (now - this.lastCopyTime < 500) { console.log('Copy operation debounced'); return; } this.lastCopyTime = now; // Set clipboard lock immediately this.clipboardLocked = true; console.log('Setting clipboard lock during regular copy'); // Always try to copy, regardless of event source try { await navigator.clipboard.writeText(text); // Show a success notification this.showNotification(' Copied!', 'success'); // Add to history - determine source from active tab or context const source = this.activeTab === 'transforms' ? 'Transform' : 'Steganography'; this.addToCopyHistory(source, text); // Aggressively clear focus and selections if (document.activeElement && document.activeElement.blur) { document.activeElement.blur(); } // Clear any text selection if (window.getSelection) { window.getSelection().removeAllRanges(); } // Focus body to avoid any specific interactive elements document.body.focus(); // Release clipboard lock after a longer delay setTimeout(() => { this.clipboardLocked = false; console.log('Clipboard lock released after regular copy'); }, 500); } catch (err) { console.warn('Clipboard access not available:', err); // Try fallback method for copying (textarea method) this.fallbackCopy(text); } }, fallbackCopy(text) { try { // Check if keyboard events should be ignored if (this.ignoreKeyboardEvents && !this.isTransformCopy) { console.log('Ignoring fallback copy due to keyboard event flag'); return; } // Reset the transform flag if it was set if (this.isTransformCopy) { this.isTransformCopy = false; } // Debounce check const now = Date.now(); if (now - this.lastCopyTime < 300) { console.log('Fallback copy operation debounced'); return; } this.lastCopyTime = now; // Create temporary textarea const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; // Avoid scrolling to bottom textarea.style.left = '-9999px'; // Move offscreen textarea.style.top = '0'; document.body.appendChild(textarea); textarea.select(); // Try the copy command const successful = document.execCommand('copy'); // Show appropriate notification if (successful) { this.showNotification(' Copied!', 'success'); // Add to history with context let source = this.activeTab === 'transforms' ? 'Transform' : 'Steganography'; if (this.activeTab === 'transforms' && this.activeTransform) { source = `Transform: ${this.activeTransform.name}`; } else if (this.activeTab === 'steganography') { if (this.activeSteg === 'invisible') { source = 'Invisible Text'; } else if (this.selectedEmoji) { source = `Emoji: ${this.selectedEmoji}`; } } this.addToCopyHistory(source, text); } else { this.showNotification(' Copy not supported', 'error'); } // Clean up document.body.removeChild(textarea); // Aggressively clear focus and selection if (document.activeElement && document.activeElement.blur) { document.activeElement.blur(); } // Clear any text selection if (window.getSelection) { window.getSelection().removeAllRanges(); } // Focus on body element document.body.focus(); } catch (err) { console.warn('Fallback copy method failed:', err); this.showNotification(' Copy not supported', 'error'); } }, // Force copy to clipboard regardless of event context forceCopyToClipboard(text) { if (!text) return; // Skip copy operations during paste if (this.isPasteOperation) { this.isPasteOperation = false; return; } // Block keyboard-triggered copies unless it's a transform if (!this.isTransformCopy && this.ignoreKeyboardEvents) { return; } try { // Use Clipboard API if (navigator.clipboard && navigator.clipboard.writeText) { // For emojis and complex characters, use a more robust approach const processedText = typeof text === 'string' ? text : String(text); // Try to use the newer clipboard API methods if available if (navigator.clipboard.write && processedText.match(/[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}]/u)) { const blob = new Blob([processedText], { type: 'text/plain;charset=utf-8' }); const clipboardItem = new ClipboardItem({ 'text/plain': blob }); navigator.clipboard.write([clipboardItem]) .then(() => { if (this.isTransformCopy) { this.showCopiedPopup(); this.ignoreKeyboardEvents = true; clearTimeout(this.keyboardEventsTimeout); this.keyboardEventsTimeout = setTimeout(() => { this.ignoreKeyboardEvents = false; }, 1000); } this.isTransformCopy = false; const inputBox = document.querySelector('#transform-input'); if (inputBox) { inputBox.focus(); const len = inputBox.value.length; inputBox.setSelectionRange(len, len); } }) .catch(err => { console.warn('Advanced Clipboard API failed:', err); // Fall back to basic writeText navigator.clipboard.writeText(processedText) .then(() => { if (this.isTransformCopy) { this.showCopiedPopup(); } this.isTransformCopy = false; const inputBox = document.querySelector('#transform-input'); if (inputBox) { inputBox.focus(); const len = inputBox.value.length; inputBox.setSelectionRange(len, len); } }) .catch(err => { console.warn('Basic Clipboard API failed:', err); this.forceFallbackCopy(processedText); }); }); } else { navigator.clipboard.writeText(processedText) .then(() => { if (this.isTransformCopy) { this.showCopiedPopup(); } this.isTransformCopy = false; const inputBox = document.querySelector('#transform-input'); if (inputBox) { inputBox.focus(); const len = inputBox.value.length; inputBox.setSelectionRange(len, len); } }) .catch(err => { console.warn('Basic Clipboard API failed:', err); this.forceFallbackCopy(processedText); }); } } else { this.forceFallbackCopy(text); } } catch (error) { console.error('Force copy failed:', error); this.forceFallbackCopy(text); } }, // Fallback copy method that doesn't rely on user-initiated events forceFallbackCopy(text) { try { // If clipboard is locked, don't proceed if (this.clipboardLocked) { console.log('Fallback copy prevented by clipboard lock'); return; } // Set clipboard lock immediately this.clipboardLocked = true; // Create temporary textarea for copying const textarea = document.createElement('textarea'); textarea.value = text; // Ensure proper emoji rendering textarea.style.fontFamily = "'Segoe UI Emoji', 'Apple Color Emoji', sans-serif"; textarea.style.fontSize = '16px'; // Position offscreen but with proper dimensions textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '0'; textarea.style.width = '100px'; textarea.style.height = '100px'; document.body.appendChild(textarea); // Focus and select the text textarea.focus(); textarea.select(); try { document.execCommand('copy'); console.log('Force fallback copy successful'); } catch (err) { console.error('Force fallback copy command failed:', err); } // Remove the temporary element document.body.removeChild(textarea); // Keep focus on input const inputBox = document.querySelector('#transform-input'); if (inputBox) { inputBox.focus(); const len = inputBox.value.length; inputBox.setSelectionRange(len, len); } // Reset flags immediately this.clipboardLocked = false; this.isTransformCopy = false; this.ignoreKeyboardEvents = false; console.log('Clipboard lock released after fallback copy'); } catch (err) { console.error('Force fallback copy method failed:', err); this.clipboardLocked = false; // Make sure we don't leave it locked in case of error } }, // Notification system showNotification(message, type = 'success') { // Create notification element const notification = document.createElement('div'); notification.className = `copy-notification ${type}`; notification.innerHTML = message; document.body.appendChild(notification); // Remove after animation setTimeout(() => { notification.classList.add('fade-out'); setTimeout(() => { if (notification.parentNode) { document.body.removeChild(notification); } }, 300); }, 1000); }, // Special prominent copy notification showCopiedPopup() { // Create a more visible popup just for copy operations const popup = document.createElement('div'); popup.className = 'copy-popup'; popup.innerHTML = ' Copied to clipboard!'; // Add to body document.body.appendChild(popup); // Force it to be visible and centered popup.style.position = 'fixed'; popup.style.top = '50%'; popup.style.left = '50%'; popup.style.transform = 'translate(-50%, -50%)'; popup.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; popup.style.color = 'white'; popup.style.padding = '15px 25px'; popup.style.borderRadius = '5px'; popup.style.fontSize = '18px'; popup.style.fontWeight = 'bold'; popup.style.zIndex = '10000'; popup.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)'; popup.style.textAlign = 'center'; // Add fade-in animation popup.style.opacity = '0'; popup.style.transition = 'opacity 0.3s ease-in-out'; // Force reflow to make animation work void popup.offsetWidth; // Fade in popup.style.opacity = '1'; // Remove after a short delay setTimeout(() => { popup.style.opacity = '0'; setTimeout(() => { if (popup.parentNode) { document.body.removeChild(popup); } }, 300); }, 1500); }, // Run the universal decoder when input changes runUniversalDecode() { console.log('Running universal decoder with input:', this.universalDecodeInput); // Clear result if input is empty if (!this.universalDecodeInput) { this.universalDecodeResult = null; return; } // Try to decode using the currently selected transform first, if any if (this.activeTransform && this.transformHasReverse(this.activeTransform)) { try { console.log(`Trying to decode with currently selected transform: ${this.activeTransform.name}`); const decodedText = this.activeTransform.reverse(this.universalDecodeInput); // If the decoded text is different from the input and looks like readable text if (decodedText !== this.universalDecodeInput && /[a-zA-Z0-9\s]{3,}/.test(decodedText)) { this.universalDecodeResult = { text: decodedText, method: this.activeTransform.name }; console.log(`Successfully decoded with ${this.activeTransform.name}`); return; } } catch (e) { console.error(`Error decoding with selected transform ${this.activeTransform.name}:`, e); } } // If the selected transform didn't work or there isn't one selected, // fall back to trying all available methods const result = this.universalDecode(this.universalDecodeInput); // Update the result this.universalDecodeResult = result; // Log the result if (result) { console.log(`Universal decoder found a match: ${result.method}`); } else { console.log('Universal decoder could not decode the input'); } }, // Universal Decoder - tries all decoding methods universalDecode(input) { if (!input) return ''; // Try all decoders in order // 1. Try steganography decoders // - Check for emoji steganography first // The emoji encoding uses variation selectors which are hard to see if (/[\u{1F300}-\u{1F6FF}\u{2600}-\u{26FF}]/u.test(input)) { console.log('Detected emoji, attempting to decode...'); const decoded = window.steganography.decodeEmoji(input); if (decoded) { console.log('Successfully decoded emoji:', decoded); return { text: decoded, method: 'Emoji Steganography' }; } else { console.log('Emoji detected but no hidden message found'); } } // - Invisible text (only check if the input actually contains invisible characters) if (/[\uE0000-\uE007F]/.test(input)) { let decoded = window.steganography.decodeInvisible(input); if (decoded && decoded.length > 0) { return { text: decoded, method: 'Invisible Text' }; } } // 2. Try transform reversals // Try to decode using active transform first if (this.activeTab === 'transforms' && this.activeTransform) { try { const transformKey = Object.keys(window.transforms).find( key => window.transforms[key].name === this.activeTransform.name ); if (transformKey && window.transforms[transformKey].reverse) { const result = window.transforms[transformKey].reverse(input); if (result && result !== input) { return { text: result, method: this.activeTransform.name, priorityMatch: true }; } } } catch (e) { console.error('Error decoding with active transform:', e); } } // - Binary (improved with more patterns) if (/^[01\s]+$/.test(input.trim())) { try { // Use binary transform's reverse function if available if (window.transforms.binary && window.transforms.binary.reverse) { const result = window.transforms.binary.reverse(input); if (result && /[\x20-\x7E]{3,}/.test(result)) { // Make sure it's readable ASCII return { text: result, method: 'Binary' }; } } // Try different binary formats (with and without spaces) const variations = [ input.trim(), // Original input input.replace(/\s+/g, ''), // No spaces input.replace(/([01]{8})/g, '$1 ') // Force 8-bit spacing ]; for (const binVariation of variations) { // Fallback implementation const binText = binVariation.replace(/\s+/g, ''); let result = ''; // Try standard 8-bit ASCII for (let i = 0; i < binText.length; i += 8) { const byte = binText.substr(i, 8); if (byte.length === 8) { result += String.fromCharCode(parseInt(byte, 2)); } } if (result && /[\x20-\x7E]{3,}/.test(result)) { // Make sure it's readable ASCII return { text: result, method: 'Binary' }; } } } catch (e) { console.error('Binary decode error:', e); } } // - Morse code if (/^[.\-\s\/]+$/.test(input.trim())) { try { // Use morse transform's reverse function if available if (window.transforms.morse && window.transforms.morse.reverse) { const result = window.transforms.morse.reverse(input); if (result !== input && /[a-zA-Z0-9]/.test(result)) { return { text: result, method: 'Morse Code' }; } } } catch (e) { console.error('Morse decode error:', e); } } // - Braille const braillePattern = /[⠀-⣿]/; if (braillePattern.test(input)) { try { // Count how many braille characters are in the input const brailleMatches = [...input.matchAll(/[⠀-⣿]/g)]; // Only proceed if there are enough braille characters (to avoid false positives) if (brailleMatches.length > 2) { // Create a reverse mapping for braille const brailleReverseMap = {}; if (window.transforms.braille && window.transforms.braille.map) { for (const [key, value] of Object.entries(window.transforms.braille.map)) { brailleReverseMap[value] = key; } // Decode the braille let result = ''; for (const char of input) { result += brailleReverseMap[char] || char; } if (result !== input && /[a-zA-Z0-9]/.test(result)) { return { text: result, method: 'Braille' }; } } } } catch (e) { console.error('Braille decode error:', e); } } // - Base64 if (/^[A-Za-z0-9+/=]+$/.test(input.trim())) { try { // Attempt to decode as base64 const result = atob(input.trim()); // Check if result is readable text if (/[\x20-\x7E]{3,}/.test(result)) { // At least 3 readable ASCII chars return { text: result, method: 'Base64' }; } } catch (e) { // Not valid base64, continue to next decoder console.error('Base64 decode error:', e); } } // - Upside Down text if (window.transforms.upside_down && window.transforms.upside_down.reverse) { try { const result = window.transforms.upside_down.reverse(input); // Check if the result is significantly different if (result !== input && result.length > 3 && /[a-zA-Z0-9\s]{3,}/.test(result)) { return { text: result, method: 'Upside Down' }; } } catch (e) { console.error('Upside Down decode error:', e); } } // - Small Caps (create reverse mapping since there's no built-in decoder) if (window.transforms.small_caps && window.transforms.small_caps.map) { try { // Create reverse mapping const smallCapsReverseMap = {}; for (const [key, value] of Object.entries(window.transforms.small_caps.map)) { smallCapsReverseMap[value] = key; } // Check if input contains small caps characters const smallCapsChars = Object.values(window.transforms.small_caps.map); const hasSmallCaps = smallCapsChars.some(char => input.includes(char)); if (hasSmallCaps) { // Decode text let result = ''; for (const char of input) { result += smallCapsReverseMap[char] || char; } if (result !== input && /[a-zA-Z]/.test(result)) { return { text: result, method: 'Small Caps' }; } } } catch (e) { console.error('Small Caps decode error:', e); } } // - Bubble text (create reverse mapping) if (window.transforms.bubble && window.transforms.bubble.map) { try { // Create reverse mapping const bubbleReverseMap = {}; for (const [key, value] of Object.entries(window.transforms.bubble.map)) { bubbleReverseMap[value] = key; } // Check if input contains bubble characters const bubbleChars = Object.values(window.transforms.bubble.map); const hasBubbleChars = bubbleChars.some(char => input.includes(char)); if (hasBubbleChars) { // Decode text let result = ''; for (const char of input) { result += bubbleReverseMap[char] || char; } if (result !== input && /[a-zA-Z]/.test(result)) { return { text: result, method: 'Bubble' }; } } } catch (e) { console.error('Bubble decode error:', e); } } // Check for specific new transforms before trying the generic approach // - Hexadecimal if (/^[0-9A-Fa-f\s]+$/.test(input.trim())) { try { if (window.transforms.hex && window.transforms.hex.reverse) { const result = window.transforms.hex.reverse(input); if (result && /[\x20-\x7E]{3,}/.test(result)) { return { text: result, method: 'Hexadecimal' }; } } } catch (e) { console.error('Hex decode error:', e); } } // - URL Encoded if (/%[0-9A-Fa-f]{2}/.test(input)) { try { if (window.transforms.url && window.transforms.url.reverse) { const result = window.transforms.url.reverse(input); if (result !== input && /[\x20-\x7E]{3,}/.test(result)) { return { text: result, method: 'URL Encoded' }; } } else { // Fallback implementation try { const result = decodeURIComponent(input); if (result !== input && /[\x20-\x7E]{3,}/.test(result)) { return { text: result, method: 'URL Encoded' }; } } catch (e) { console.error('URL decode fallback error:', e); } } } catch (e) { console.error('URL decode error:', e); } } // - HTML Entities if (/&[#a-zA-Z0-9]+;/.test(input)) { try { if (window.transforms.html && window.transforms.html.reverse) { const result = window.transforms.html.reverse(input); if (result !== input && /[\x20-\x7E]{3,}/.test(result)) { return { text: result, method: 'HTML Entities' }; } } } catch (e) { console.error('HTML entities decode error:', e); } } // - ROT13/Caesar Cipher (check if decoding produces more common English words) if (/^[a-zA-Z\s.,!?]+$/.test(input)) { try { // Try ROT13 first as it's more common if (window.transforms.rot13 && window.transforms.rot13.reverse) { const result = window.transforms.rot13.reverse(input); if (result !== input) { return { text: result, method: 'ROT13' }; } } // Then try Caesar cipher if (window.transforms.caesar && window.transforms.caesar.reverse) { const result = window.transforms.caesar.reverse(input); if (result !== input) { return { text: result, method: 'Caesar Cipher' }; } } } catch (e) { console.error('Cipher decode error:', e); } } // - Base32 if (/^[A-Z2-7=]+$/.test(input.trim())) { try { if (window.transforms.base32 && window.transforms.base32.reverse) { const result = window.transforms.base32.reverse(input); if (result && /[\x20-\x7E]{3,}/.test(result)) { return { text: result, method: 'Base32' }; } } } catch (e) { console.error('Base32 decode error:', e); } } // - ASCII85 if (/^<~.*~>$/.test(input.trim())) { try { if (window.transforms.ascii85 && window.transforms.ascii85.reverse) { const result = window.transforms.ascii85.reverse(input); if (result && /[\x20-\x7E]{3,}/.test(result)) { return { text: result, method: 'ASCII85' }; } } } catch (e) { console.error('ASCII85 decode error:', e); } } // - Check for Zalgo text (text with combining marks) const combiningMarksRegex = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/; if (combiningMarksRegex.test(input)) { try { // Count the number of combining marks to ensure it's actually Zalgo text // and not just text with a few accents const matches = input.match(combiningMarksRegex) || []; if (matches.length > 3) { // Threshold to distinguish Zalgo from normal accented text // Fallback implementation to remove combining marks const result = input.replace(/[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/g, ''); if (result !== input && result.length > 0) { return { text: result, method: 'Zalgo' }; } } } catch (e) { console.error('Zalgo decode error:', e); } } // - Check for various Unicode text styles (medieval, cursive, monospace, double-struck) const unicodeStyleChecks = [ { name: 'Medieval', transform: 'medieval' }, { name: 'Cursive', transform: 'cursive' }, { name: 'Monospace', transform: 'monospace' }, { name: 'Double-Struck', transform: 'doubleStruck' } ]; for (const style of unicodeStyleChecks) { if (window.transforms[style.transform] && window.transforms[style.transform].map) { try { // Create reverse mapping const reverseMap = {}; for (const [key, value] of Object.entries(window.transforms[style.transform].map)) { reverseMap[value] = key; } // Check if input contains characters from this style const styleChars = Object.values(window.transforms[style.transform].map); const hasStyleChars = styleChars.some(char => input.includes(char)); if (hasStyleChars) { // Decode text let result = ''; for (const char of input) { result += reverseMap[char] || char; } if (result !== input && /[a-zA-Z0-9]/.test(result)) { return { text: result, method: style.name }; } } } catch (e) { console.error(`${style.name} decode error:`, e); } } } // - Try reverse each transform that has a built-in reverse function for (const name in window.transforms) { const transform = window.transforms[name]; if (transform.reverse) { try { const result = transform.reverse(input); // Only return if the result is different and contains readable characters if (result !== input && /[a-zA-Z0-9\s]{3,}/.test(result)) { return { text: result, method: transform.name }; } } catch (e) { console.error(`Error decoding with ${name}:`, e); } } } return null; }, // Emoji Library Methods filterEmojis() { // Always show all emojis - search functionality removed this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.renderEmojiGrid(); }, selectEmoji(emoji) { // Directly copy the emoji to clipboard - ensure it's a string const emojiStr = String(emoji); // Special handling for emoji characters if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(emojiStr) .then(() => { this.showNotification(` Emoji copied!`, 'success'); this.addToCopyHistory('Emoji', emojiStr); }) .catch(err => { console.warn('Emoji clipboard API failed:', err); // Fallback to our custom method this.forceCopyToClipboard(emojiStr); this.showNotification(` Emoji copied!`, 'success'); this.addToCopyHistory('Emoji', emojiStr); }); } else { // Use our custom method if Clipboard API not available this.forceCopyToClipboard(emojiStr); this.showNotification(` Emoji copied!`, 'success'); this.addToCopyHistory('Emoji', emojiStr); } // Also set up carrier if we're in steganography mode if (this.activeTab === 'steganography') { this.selectedEmoji = emoji; // Create a temporary carrier for this emoji const tempCarrier = { name: `${emoji} Carrier`, emoji: emoji, encode: (text) => this.steganography.encode(text, emoji), decode: (text) => this.steganography.decode(text), preview: (text) => `${emoji}${text}${emoji}` }; // Use this emoji as carrier this.selectedCarrier = tempCarrier; this.activeSteg = 'emoji'; // Encode the message with this emoji if we have one if (this.emojiMessage) { this.autoEncode(); // Wait for encoding to complete, then copy to clipboard this.$nextTick(() => { if (this.encodedMessage) { const encodedStr = String(this.encodedMessage); // Use native clipboard API first for better emoji support if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(encodedStr) .then(() => { this.showNotification(` Hidden message copied with ${emoji}`, 'success'); this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr); }) .catch(err => { console.warn('Encoded emoji clipboard API failed:', err); // Fall back to our custom method this.forceCopyToClipboard(encodedStr); this.showNotification(` Hidden message copied with ${emoji}`, 'success'); this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr); }); } else { // Use our custom method if Clipboard API not available this.forceCopyToClipboard(encodedStr); this.showNotification(` Hidden message copied with ${emoji}`, 'success'); this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr); } } }); } } }, renderEmojiGrid() { console.log('renderEmojiGrid called with', this.filteredEmojis.length, 'emojis'); // Make sure container exists const container = document.getElementById('emoji-grid-container'); if (!container) { console.error('emoji-grid-container not found!'); return; } // Force container to be completely visible container.style.cssText = 'display: block !important; visibility: visible !important; min-height: 300px;'; // Make sure parent containers are visible too const emojiLibrary = document.querySelector('.emoji-library'); if (emojiLibrary) { emojiLibrary.style.cssText = 'display: block !important; visibility: visible !important;'; } // Clear any existing content to avoid duplication container.innerHTML = ''; // Render the emoji grid window.emojiLibrary.renderEmojiGrid('emoji-grid-container', this.selectEmoji.bind(this), this.filteredEmojis); // Message about copying has been removed as requested // Log success console.log('Emoji grid rendered successfully'); }, // Initialize category navigation for transform sections initializeCategoryNavigation() { this.$nextTick(() => { console.log('Initializing category navigation'); const legendItems = document.querySelectorAll('.transform-category-legend .legend-item'); // First, remove any existing event listeners to prevent duplicates legendItems.forEach(item => { const newItem = item.cloneNode(true); item.parentNode.replaceChild(newItem, item); }); // Now add event listeners to the fresh elements document.querySelectorAll('.transform-category-legend .legend-item').forEach(item => { item.addEventListener('click', () => { const targetId = item.getAttribute('data-target'); if (targetId) { const targetElement = document.getElementById(targetId); if (targetElement) { // Add active class to the clicked legend item document.querySelectorAll('.transform-category-legend .legend-item').forEach(li => { li.classList.remove('active-category'); }); item.classList.add('active-category'); // Jump directly to the target element targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Highlight the section briefly to draw attention targetElement.classList.add('highlight-section'); setTimeout(() => { targetElement.classList.remove('highlight-section'); }, 1000); } } }); }); }); } }, // Initialize theme and components mounted() { console.log('Vue app mounted'); // Apply theme if (this.isDarkTheme) { document.body.classList.add('dark-theme'); } // Initialize category navigation this.initializeCategoryNavigation(); // Initialize emoji grid with all emojis shown by default this.$nextTick(() => { console.log('nextTick: Initializing emoji grid'); // Make sure filtered emojis is populated this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; // Define a function to properly initialize the emoji grid const initializeEmojiGrid = () => { // Only try to initialize when steganography tab is active if (this.activeTab !== 'steganography') { return; } const emojiGridContainer = document.getElementById('emoji-grid-container'); if (emojiGridContainer) { console.log('Found emoji-grid-container, rendering grid'); // Set inline styles to ensure visibility emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;'); // Also make sure the parent container is visible const emojiLibrary = document.querySelector('.emoji-library'); if (emojiLibrary) { emojiLibrary.setAttribute('style', 'display: block !important; visibility: visible !important; margin-top: 20px; overflow: visible;'); } // Now render the grid this.renderEmojiGrid(); console.log('Emoji grid rendering complete in mounted()'); // Stop retrying once we've successfully found and rendered the grid clearInterval(emojiGridInitializer); } else { console.log('emoji-grid-container not found, will retry when steganography tab is active'); } }; // Use an interval instead of recursive setTimeout for more reliable initialization // This will try every 500ms until it succeeds or the page is navigated away from const emojiGridInitializer = setInterval(initializeEmojiGrid, 500); // Set up paste event handlers for all textareas to prevent unwanted clipboard notifications this.setupPasteHandlers(); }); }, // Set up paste event handlers for all textareas setupPasteHandlers() { // Get all textareas in the app const textareas = document.querySelectorAll('textarea'); // Add paste event listener to each textarea textareas.forEach(textarea => { textarea.addEventListener('paste', (e) => { // Mark this as an explicit paste event this.isPasteOperation = true; // Reset the flag after a short delay setTimeout(() => { this.isPasteOperation = false; }, 100); }); }); }, // No keyboard shortcuts - they were removed as requested created() { // Initialize any required functionality // But no keyboard shortcuts/hotkeys for now }, // Watch for input events and ensure proper focus handling watch: { // Watch transform input to update transforms transformInput() { // Only auto-transform if we have an active transform if (this.activeTransform && this.activeTab === 'transforms') { this.transformOutput = this.activeTransform.func(this.transformInput); } }, // Make sure emoji list stays loaded when user types in any input emojiMessage() { this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.$nextTick(() => { this.renderEmojiGrid(); }); }, // Also watch the decode input field for typing activity decodeInput() { this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.$nextTick(() => { this.renderEmojiGrid(); }); } } });