diff --git a/js/app.js b/js/app.js index 07293ba..00021f9 100644 --- a/js/app.js +++ b/js/app.js @@ -1,5 +1,5 @@ // Initialize Vue app -new Vue({ +window.app = new Vue({ el: '#app', data: { // Theme @@ -24,13 +24,22 @@ new Vue({ decodeInput: '', decodedMessage: '', selectedCarrier: null, + + // Universal Decoder - works on both tabs + universalDecodeInput: '', + universalDecodeResult: null, + isPasteOperation: false, // Flag to track paste operations activeSteg: null, carriers: window.steganography.carriers, showDecoder: true, // Emoji Library - emojiSearch: '', filteredEmojis: [...window.emojiLibrary.EMOJI_LIST], - selectedEmoji: null + selectedEmoji: null, + + // History of copied content + copyHistory: [], + maxHistoryItems: 10, + showCopyHistory: false }, methods: { // Theme Toggle @@ -38,19 +47,49 @@ new Vue({ 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) { if (this.transformInput) { this.activeTransform = transform; this.transformOutput = transform.func(this.transformInput); - this.copyToClipboard(this.transformOutput); + + // Force copy and log to history + 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'); } }, autoTransform() { - if (this.transformInput && this.activeTransform) { + // Only proceed if we're in the transforms tab and have an active transform + if (this.transformInput && this.activeTransform && this.activeTab === 'transforms') { this.transformOutput = this.activeTransform.func(this.transformInput); - this.copyToClipboard(this.transformOutput); + + // Use forceCopyToClipboard for auto-copy + this.forceCopyToClipboard(this.transformOutput); + + // Add to copy history + this.addToCopyHistory(`Transform: ${this.activeTransform.name}`, this.transformOutput); } }, @@ -67,34 +106,50 @@ new Vue({ } }, setStegMode(mode) { - // Toggle mode selection if clicking the same one again - if (this.activeSteg === mode) { - this.activeSteg = null; - this.encodedMessage = ''; - } else { + // 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; - // When switching to invisible mode, clear the carrier selection - if (mode === 'invisible') { - this.selectedCarrier = null; - } + // 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() { - if (!this.emojiMessage) { + // 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); - // Don't auto-copy to avoid clipboard permission errors + // Auto-copy will be handled in setStegMode method } else if (this.selectedCarrier) { this.encodedMessage = window.steganography.encodeEmoji( this.selectedCarrier.emoji, this.emojiMessage ); - // Don't auto-copy to avoid clipboard permission errors + // Auto-copy for emoji carrier is handled in selectEmoji method } }, autoDecode() { @@ -108,7 +163,21 @@ new Vue({ if (result) { this.decodedMessage = `Decoded (${result.method}): ${result.text}`; - // Don't auto-copy to avoid clipboard permission errors + + // 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'; } @@ -117,27 +186,47 @@ new Vue({ 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 async copyToClipboard(text) { if (!text) return; - // Don't auto-copy in preview/iframe environments to avoid permission errors - // Only copy when explicitly triggered by a button click - const isExplicitUserAction = event && event.isTrusted; - - // Only try to copy if we're not in a restricted environment or it's a direct user action - if (isExplicitUserAction) { - try { - await navigator.clipboard.writeText(text); - - // Show a success notification - this.showNotification(' Copied!', 'success'); - } catch (err) { - console.warn('Clipboard access not available:', err); - - // Try fallback method for copying (textarea method) - this.fallbackCopy(text); - } + // 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); + } catch (err) { + console.warn('Clipboard access not available:', err); + + // Try fallback method for copying (textarea method) + this.fallbackCopy(text); } }, @@ -156,6 +245,23 @@ new Vue({ // Show appropriate notification if (successful) { this.showNotification(' Copied!', 'success'); + + // Add to history when successful + // Try to determine a more specific source based on the context + let source = this.activeTab === 'transforms' ? 'Transform' : 'Steganography'; + + // Add more context if available + 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'); } @@ -168,6 +274,73 @@ new Vue({ } }, + // Force copy to clipboard regardless of event context + forceCopyToClipboard(text) { + if (!text) return; + + // Skip notifications if this was triggered by a paste operation + if (this.isPasteOperation) { + console.log('Skipping clipboard notification for paste operation'); + return; + } + + console.log('Force copying to clipboard:', text); + + try { + // Try to use the Clipboard API first + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text) + .then(() => { + console.log('Force copy successful using Clipboard API'); + // Show clear notification on success + this.showCopiedPopup(); + }) + .catch(err => { + console.warn('Force Clipboard API failed:', err); + this.forceFallbackCopy(text); + // Still show notification, as fallback might work + this.showCopiedPopup(); + }); + } else { + // Fall back to execCommand method + this.forceFallbackCopy(text); + // Show notification for fallback method too + this.showCopiedPopup(); + } + } catch (error) { + console.error('Force copy failed:', error); + // Try one last fallback and still show notification + this.forceFallbackCopy(text); + this.showCopiedPopup(); + } + }, + + // Fallback copy method that doesn't rely on user-initiated events + forceFallbackCopy(text) { + try { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.left = '-9999px'; + textarea.style.top = '0'; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + + try { + document.execCommand('copy'); + console.log('Force fallback copy successful'); + } catch (err) { + console.error('Force fallback copy command failed:', err); + } + + document.body.removeChild(textarea); + } catch (err) { + console.error('Force fallback copy method failed:', err); + } + }, + + // Notification system showNotification(message, type = 'success') { // Create notification element const notification = document.createElement('div'); @@ -186,6 +359,76 @@ new Vue({ }, 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 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 ''; @@ -196,9 +439,13 @@ new Vue({ // - 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'); } } @@ -251,15 +498,127 @@ new Vue({ console.error('Morse decode error:', e); } } + + // - Braille + const braillePattern = /[โ €-โฃฟ]/; + if (braillePattern.test(input)) { + try { + // 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); + } + } - // - Try reverse each transform + // - 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); + } + } + + // - 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]/.test(result)) { + if (result !== input && /[a-zA-Z0-9\s]{3,}/.test(result)) { return { text: result, method: transform.name }; } } catch (e) { @@ -273,86 +632,171 @@ new Vue({ // Emoji Library Methods filterEmojis() { - if (!this.emojiSearch) { - this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; - this.renderEmojiGrid(); - return; - } - - const searchTerm = this.emojiSearch.toLowerCase(); - this.filteredEmojis = window.emojiLibrary.EMOJI_LIST.filter(emoji => { - // Simple search - we could enhance this with emoji names/descriptions later - return emoji.toLowerCase().includes(searchTerm); - }); - + // Always show all emojis - search functionality removed + this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.renderEmojiGrid(); }, selectEmoji(emoji) { this.selectedEmoji = emoji; - // Insert the emoji at cursor position in the textarea - const textarea = document.getElementById('steg-input'); - if (textarea) { - const startPos = textarea.selectionStart; - const endPos = textarea.selectionEnd; - const currentValue = this.emojiMessage; + // 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 (this.emojiMessage) { + this.autoEncode(); - // Insert emoji at cursor position - this.emojiMessage = currentValue.substring(0, startPos) + emoji + currentValue.substring(endPos); - - // Set cursor position after the inserted emoji + // Wait for encoding to complete, then copy to clipboard this.$nextTick(() => { - textarea.focus(); - textarea.selectionStart = startPos + emoji.length; - textarea.selectionEnd = startPos + emoji.length; - - // Trigger encoding - this.autoEncode(); + if (this.encodedMessage) { + // Force clipboard copy regardless of event source + this.forceCopyToClipboard(this.encodedMessage); + this.showNotification(` Copied hidden message with ${emoji}`, 'success'); + + // Add to copy history + this.addToCopyHistory(`Emoji: ${emoji}`, this.encodedMessage); + } }); - - this.showNotification(`Emoji ${emoji} inserted`); + } else { + this.showNotification(`Select an emoji and enter a message first`, 'info'); } }, 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); + + // Add a bold message about copying + const copyNote = document.createElement('div'); + copyNote.style.cssText = 'text-align: center; margin-top: 10px; font-weight: bold; padding: 5px; background-color: #f0f0f0; border-radius: 4px;'; + copyNote.innerHTML = ' Clicking an emoji will automatically copy your hidden message'; + container.appendChild(copyNote); + + // Log success + console.log('Emoji grid rendered successfully'); } }, // Initialize theme and components mounted() { + console.log('Vue app mounted'); // Apply theme if (this.isDarkTheme) { document.body.classList.add('dark-theme'); } - // Initialize emoji grid + // Initialize emoji grid with all emojis shown by default this.$nextTick(() => { - this.renderEmojiGrid(); + 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 = () => { + 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; border: 1px solid #ccc;'); + + // 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: 30px; border: 3px solid var(--accent-color); overflow: visible;'); + } + + // Now render the grid + this.renderEmojiGrid(); + console.log('Emoji grid rendering complete in mounted()'); + } else { + console.error('emoji-grid-container not found, will retry'); + // Try again after a longer delay if not found + setTimeout(initializeEmojiGrid, 500); + } + }; + + // Start with a small delay to ensure DOM is ready + setTimeout(initializeEmojiGrid, 100); + + // Set up paste event handlers for all textareas to prevent unwanted clipboard notifications + this.setupPasteHandlers(); }); }, - // Keyboard shortcuts - created() { - window.addEventListener('keydown', (e) => { - // Theme toggle - if (e.key === 'd' || e.key === 'D') { - this.toggleTheme(); - } - // Tab switching - else if (e.key === 't' || e.key === 'T') { - this.activeTab = 'transforms'; - } - else if (e.key === 'h' || e.key === 'H') { - this.activeTab = 'steganography'; - } - // Transform shortcuts (1-9) - else if (this.activeTab === 'transforms' && e.key >= '1' && e.key <= '9') { - const index = parseInt(e.key) - 1; - if (index < this.transforms.length) { - this.applyTransform(this.transforms[index]); - } - } - + + // 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 emojis stay visible + watch: { + // Make sure emoji list stays loaded when user types in any input + emojiMessage() { + // Reset the filtered emojis to the full list whenever typing occurs + this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; + this.$nextTick(() => { + this.renderEmojiGrid(); + }); + }, + // Also watch the decode input field for typing activity + decodeInput() { + // Always show full emoji list + this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; + this.$nextTick(() => { + this.renderEmojiGrid(); + }); + } } }); diff --git a/js/emojiLibrary.js b/js/emojiLibrary.js index ff9bc41..fe8fa92 100644 --- a/js/emojiLibrary.js +++ b/js/emojiLibrary.js @@ -7,45 +7,137 @@ window.emojiLibrary = {}; window.emojiLibrary.EMOJI_LIST = [ // Faces and People "๐Ÿ˜€", // Grinning Face + "๐Ÿ˜", // Beaming Face with Smiling Eyes "๐Ÿ˜‚", // Face with Tears of Joy - "๐Ÿฅฐ", // Smiling Face with Hearts - "๐Ÿ˜Ž", // Smiling Face with Sunglasses - "๐Ÿค”", // Thinking Face - "๐Ÿ˜…", // Smiling Face with Sweat - "๐Ÿ˜Š", // Smiling Face with Smiling Eyes - "๐Ÿ˜‡", // Smiling Face with Halo - "๐Ÿ™ƒ", // Upside-Down Face + "๐Ÿคฃ", // Rolling on the Floor Laughing + "๐Ÿ˜ƒ", // Grinning Face with Big Eyes + "๐Ÿ˜„", // Grinning Face with Smiling Eyes + "๐Ÿ˜…", // Grinning Face with Sweat + "๐Ÿ˜†", // Grinning Squinting Face "๐Ÿ˜‰", // Winking Face - "๐Ÿฅณ", // Partying Face + "๐Ÿ˜Š", // Smiling Face with Smiling Eyes + "๐Ÿ˜‹", // Face Savoring Food + "๐Ÿ˜Ž", // Smiling Face with Sunglasses + "๐Ÿ˜", // Smiling Face with Heart-Eyes + "๐Ÿ˜˜", // Face Blowing a Kiss + "๐Ÿฅฐ", // Smiling Face with Hearts + "๐Ÿ˜—", // Kissing Face + "๐Ÿ˜™", // Kissing Face with Smiling Eyes + "๐Ÿ˜š", // Kissing Face with Closed Eyes + "๐Ÿ™‚", // Slightly Smiling Face + "๐Ÿค—", // Hugging Face + "๐Ÿคฉ", // Star-Struck + "๐Ÿค”", // Thinking Face + "๐Ÿคจ", // Face with Raised Eyebrow + "๐Ÿ˜", // Neutral Face + "๐Ÿ˜‘", // Expressionless Face + "๐Ÿ˜ถ", // Face Without Mouth + "๐Ÿ™„", // Face with Rolling Eyes + "๐Ÿ˜", // Smirking Face + "๐Ÿ˜ฃ", // Persevering Face + "๐Ÿ˜ฅ", // Sad but Relieved Face + "๐Ÿ˜ฎ", // Face with Open Mouth + "๐Ÿค", // Zipper-Mouth Face + "๐Ÿ˜ฏ", // Hushed Face + "๐Ÿ˜ช", // Sleepy Face + "๐Ÿ˜ซ", // Tired Face "๐Ÿ˜ด", // Sleeping Face - "๐Ÿฅฑ", // Yawning Face + "๐Ÿ˜Œ", // Relieved Face + "๐Ÿ˜›", // Face with Tongue + "๐Ÿ˜œ", // Winking Face with Tongue + "๐Ÿ˜", // Squinting Face with Tongue + "๐Ÿคค", // Drooling Face + "๐Ÿ˜’", // Unamused Face + "๐Ÿ˜“", // Downcast Face with Sweat + "๐Ÿ˜”", // Pensive Face + "๐Ÿ˜•", // Confused Face + "๐Ÿ™ƒ", // Upside-Down Face + "๐Ÿค‘", // Money-Mouth Face + "๐Ÿ˜ฒ", // Astonished Face + "๐Ÿ™", // Slightly Frowning Face + "๐Ÿ˜–", // Confounded Face + "๐Ÿ˜ž", // Disappointed Face + "๐Ÿ˜Ÿ", // Worried Face + "๐Ÿ˜ค", // Face with Steam From Nose + "๐Ÿ˜ข", // Crying Face + "๐Ÿ˜ญ", // Loudly Crying Face + "๐Ÿ˜ง", // Anguished Face + "๐Ÿ˜จ", // Fearful Face + "๐Ÿ˜ฉ", // Weary Face "๐Ÿคฏ", // Exploding Head + "๐Ÿ˜ฑ", // Face Screaming in Fear + "๐Ÿ˜ณ", // Flushed Face + "๐Ÿฅต", // Hot Face + "๐Ÿฅถ", // Cold Face + "๐Ÿ˜ก", // Pouting Face + "๐Ÿ˜ ", // Angry Face + "๐Ÿคฌ", // Face with Symbols on Mouth + "๐Ÿ˜ท", // Face with Medical Mask + "๐Ÿค’", // Face with Thermometer + "๐Ÿค•", // Face with Head-Bandage + "๐Ÿคข", // Nauseated Face + "๐Ÿคฎ", // Face Vomiting + "๐Ÿคง", // Sneezing Face + "๐Ÿ˜‡", // Smiling Face with Halo + "๐Ÿฅณ", // Partying Face + "๐Ÿฅด", // Woozy Face + "๐Ÿฅบ", // Pleading Face + "๐Ÿง", // Face with Monocle + "๐Ÿฅฑ", // Yawning Face "๐Ÿง ", // Brain // Gestures and Body Parts "๐Ÿ‘", // Thumbs Up "๐Ÿ‘Ž", // Thumbs Down "๐Ÿ‘", // Clapping Hands + "๐Ÿ™Œ", // Raising Hands "๐Ÿค", // Handshake "๐Ÿ‘‹", // Waving Hand "โœŒ๏ธ", // Victory Hand "๐ŸคŸ", // Love-You Gesture + "๐Ÿค˜", // Sign of the Horns + "๐Ÿ‘Š", // Oncoming Fist + "โœŠ", // Raised Fist + "๐Ÿ‘†", // Backhand Index Pointing Up + "๐Ÿ‘‡", // Backhand Index Pointing Down + "๐Ÿ‘ˆ", // Backhand Index Pointing Left + "๐Ÿ‘‰", // Backhand Index Pointing Right + "๐Ÿ‘Œ", // OK Hand + "๐ŸคŒ", // Pinched Fingers + "๐Ÿค", // Pinching Hand + "โœ‹", // Raised Hand + "๐Ÿคš", // Raised Back of Hand + "๐Ÿ–๏ธ", // Hand with Fingers Splayed + "๐Ÿ––", // Vulcan Salute "๐Ÿ‘€", // Eyes "๐Ÿ‘๏ธ", // Eye + "๐Ÿ‘„", // Mouth "๐Ÿงฟ", // Nazar Amulet // Celebration & Objects "๐ŸŽ‰", // Party Popper + "๐ŸŽŠ", // Confetti Ball "๐ŸŽ‚", // Birthday Cake "๐ŸŽ", // Wrapped Gift + "๐ŸŽˆ", // Balloon + "๐ŸŽ„", // Christmas Tree + "๐ŸŽƒ", // Jack-O-Lantern "๐Ÿ†", // Trophy "๐Ÿ…", // Sports Medal + "๐Ÿฅ‡", // 1st Place Medal + "๐Ÿฅˆ", // 2nd Place Medal + "๐Ÿฅ‰", // 3rd Place Medal "๐Ÿ’ฐ", // Money Bag "๐Ÿ’ธ", // Money with Wings + "๐Ÿ’ต", // Dollar Banknote + "๐Ÿ’ด", // Yen Banknote + "๐Ÿ’ถ", // Euro Banknote + "๐Ÿ’ท", // Pound Banknote "๐Ÿ’ฏ", // Hundred Points "๐Ÿ“ฑ", // Mobile Phone "๐Ÿ’ป", // Laptop "โŒจ๏ธ", // Keyboard + "๐Ÿ–ฅ๏ธ", // Desktop Computer "๐Ÿ”’", // Locked "๐Ÿ”“", // Unlocked @@ -186,36 +278,84 @@ window.emojiLibrary.EMOJI_LIST = [ // Function to render emoji grid window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filteredList) { + console.log('Rendering emoji grid to:', containerId); + + // Get container by ID const container = document.getElementById(containerId); - if (!container) return; + if (!container) { + console.error('Container not found:', containerId); + return; + } // Clear container container.innerHTML = ''; - // Create emoji grid + // Create emoji grid with enforced styling const gridContainer = document.createElement('div'); gridContainer.className = 'emoji-grid'; - // Set a heading explaining the full library is shown - if (!filteredList || filteredList.length === window.emojiLibrary.EMOJI_LIST.length) { - const fullLibraryNote = document.createElement('div'); - fullLibraryNote.className = 'emoji-grid-note'; - fullLibraryNote.innerHTML = ' Showing all emojis. Use search to filter.'; - container.appendChild(fullLibraryNote); - } + // Force grid styling + gridContainer.style.display = 'grid'; + gridContainer.style.gridTemplateColumns = 'repeat(auto-fill, minmax(50px, 1fr))'; + gridContainer.style.gap = '8px'; + gridContainer.style.padding = '15px'; + gridContainer.style.maxHeight = '300px'; + gridContainer.style.overflowY = 'auto'; + gridContainer.style.border = '1px solid #ccc'; + gridContainer.style.borderRadius = '4px'; + gridContainer.style.margin = '10px 0'; - // Determine which list to use (filtered or full) - const emojisToShow = filteredList || window.emojiLibrary.EMOJI_LIST; + // Add a message showing we're displaying all emojis + const fullLibraryNote = document.createElement('div'); + fullLibraryNote.className = 'emoji-grid-note'; + fullLibraryNote.innerHTML = ' Click an emoji to automatically copy your hidden message'; + fullLibraryNote.style.padding = '10px'; + fullLibraryNote.style.marginBottom = '10px'; + fullLibraryNote.style.backgroundColor = 'rgba(0,0,0,0.05)'; + fullLibraryNote.style.borderRadius = '4px'; + fullLibraryNote.style.textAlign = 'center'; + container.appendChild(fullLibraryNote); - // Add emojis to grid + // Always use full emoji list - search removed + // Use the provided filtered list if available, otherwise default to full list + // This ensures we always show ALL emojis regardless of input state + const emojisToShow = filteredList && filteredList.length > 0 ? filteredList : window.emojiLibrary.EMOJI_LIST; + console.log(`Adding ${emojisToShow.length} emojis to grid`); + + // Add emojis to grid with enforced styling emojisToShow.forEach(emoji => { const emojiButton = document.createElement('button'); emojiButton.className = 'emoji-button'; - emojiButton.innerHTML = emoji; - emojiButton.title = 'Click to select this emoji'; + emojiButton.textContent = emoji; // Use textContent for better emoji handling + emojiButton.title = 'Click to encode with this emoji'; + + // Force button styling + emojiButton.style.fontSize = '24px'; + emojiButton.style.padding = '8px'; + emojiButton.style.border = '1px solid #ddd'; + emojiButton.style.borderRadius = '8px'; + emojiButton.style.cursor = 'pointer'; + emojiButton.style.backgroundColor = '#fff'; + emojiButton.style.transition = 'transform 0.1s'; + + // Add hover effect + emojiButton.onmouseover = function() { + this.style.transform = 'scale(1.1)'; + this.style.boxShadow = '0 0 5px rgba(0,0,0,0.2)'; + }; + emojiButton.onmouseout = function() { + this.style.transform = 'scale(1)'; + this.style.boxShadow = 'none'; + }; + emojiButton.addEventListener('click', () => { if (typeof onEmojiSelect === 'function') { onEmojiSelect(emoji); + // Add visual feedback when clicked + emojiButton.style.backgroundColor = '#e6f7ff'; + setTimeout(() => { + emojiButton.style.backgroundColor = '#fff'; + }, 300); } }); @@ -223,4 +363,15 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte }); container.appendChild(gridContainer); + console.log('Emoji grid rendering complete'); + + // Force container to be visible + container.style.display = 'block !important'; + container.style.visibility = 'visible !important'; + + // Debug info - add count display + const countDisplay = document.createElement('div'); + countDisplay.className = 'emoji-count'; + countDisplay.textContent = `${emojisToShow.length} emojis available`; + container.appendChild(countDisplay); };