From 93e35af1d93747de6414b3e2cb69bd8bfbe55428 Mon Sep 17 00:00:00 2001 From: EP Date: Tue, 11 Mar 2025 19:20:08 -0400 Subject: [PATCH] Fix input focus issues and improve clipboard handling - Fixed input box focus after clicking transform buttons - Improved clipboard handling to prevent unwanted notifications - Removed aggressive focus management in transformInput watcher - Added event object to transform button clicks - Fixed syntax errors in fallback copy method --- css/style.css | 242 ++++++++++++++++++++++++++++++---- index.html | 76 +++++++---- js/app.js | 318 ++++++++++++++++++++++++++++++++++++++------- js/emojiLibrary.js | 279 ++++++++++----------------------------- 4 files changed, 615 insertions(+), 300 deletions(-) diff --git a/css/style.css b/css/style.css index f70a62a..65453b3 100644 --- a/css/style.css +++ b/css/style.css @@ -3,7 +3,9 @@ --main-bg-color: #1a1a1a; --secondary-bg: #242424; --text-color: #e0e0e0; + --text-muted: #a0a0a0; --accent-color: #64b5f6; + --accent-color-rgb: 100, 181, 246; --accent-hover: #90caf9; --success-color: #81c784; --button-bg: #2d2d2d; @@ -59,6 +61,77 @@ header { text-align: left; } +.logo h1 { + font-family: 'Courier New', monospace; + font-weight: bold; + letter-spacing: 2px; + text-transform: uppercase; + color: #00ff00; + text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8); + background: linear-gradient(90deg, transparent 0%, rgba(0, 255, 0, 0.2) 50%, transparent 100%); + padding: 8px; + border: 1px solid rgba(0, 255, 0, 0.3); + position: relative; + overflow: hidden; + animation: glitch 3s infinite; +} + +/* Glitch animation */ +@keyframes glitch { + 0% { + text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8); + transform: translateX(0); + } + 5% { + text-shadow: -2px 0 #ff00ff, 2px 0 #00ffff; + transform: translateX(2px); + } + 10% { + text-shadow: 2px 0 #ff00ff, -2px 0 #00ffff; + transform: translateX(-2px); + } + 15% { + text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8); + transform: translateX(0); + } + 85% { + text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8); + transform: translateX(0); + } + 90% { + text-shadow: 1px 0 #ff00ff, -1px 0 #00ffff; + transform: translateX(1px); + } + 95% { + text-shadow: -1px 0 #ff00ff, 1px 0 #00ffff; + transform: translateX(-1px); + } + 100% { + text-shadow: 0 0 5px #00ff00, 0 0 10px rgba(0, 255, 0, 0.8); + transform: translateX(0); + } +} + +/* Adding scanline effect */ +.logo h1::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + transparent 0%, + transparent 50%, + rgba(0, 255, 0, 0.2) 50%, + rgba(0, 255, 0, 0.2) 100% + ); + background-size: 100% 4px; + z-index: 1; + pointer-events: none; + opacity: 0.3; +} + .theme-toggle { margin-left: 20px; } @@ -163,15 +236,93 @@ h1, h2, h3, h4, h5 { position: relative; } +.section-header { + margin-bottom: 15px; +} + +.section-header h3 { + display: flex; + align-items: center; + gap: 8px; + color: var(--accent-color); +} + +.section-header h3 small { + font-size: 0.7em; + font-weight: normal; + color: var(--text-muted); + background-color: rgba(100, 181, 246, 0.1); + padding: 2px 8px; + border-radius: 12px; +} + +.section-header p { + margin: 0; + color: var(--text-muted); + font-size: 0.9em; +} + .decoded-message { margin-top: 8px; - padding: 8px; + padding: 12px; background: var(--button-bg); border-radius: 4px; font-family: 'Fira Code', monospace; position: relative; } +.decode-method { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + color: var(--text-muted); + font-size: 0.9em; +} + +.decode-method strong { + color: var(--accent-color); +} + +.decode-priority { + font-size: 0.8em; + background-color: rgba(var(--accent-color-rgb), 0.1); + padding: 2px 8px; + border-radius: 12px; + color: var(--accent-color); +} + +.decode-result { + margin-bottom: 12px; + word-break: break-all; + white-space: pre-wrap; + line-height: 1.5; +} + +.decode-actions { + display: flex; + gap: 10px; + margin-top: 10px; +} + +.use-as-input-button { + background-color: rgba(var(--accent-color-rgb), 0.1); + color: var(--accent-color); + border: none; + border-radius: 4px; + padding: 5px 10px; + font-size: 0.8em; + cursor: pointer; + display: flex; + align-items: center; + gap: 5px; + transition: all 0.2s ease; +} + +.use-as-input-button:hover { + background-color: rgba(var(--accent-color-rgb), 0.2); +} + textarea { width: 100%; padding: 12px; @@ -345,26 +496,65 @@ textarea { .emoji-grid-note { background: linear-gradient(to right, rgba(102, 187, 106, 0.05), rgba(126, 87, 194, 0.05)); - padding: 8px 12px; - border-radius: 4px; - margin-bottom: 8px; - font-size: 0.85rem; + padding: 10px 15px; + border-radius: 6px; + margin-bottom: 12px; + font-size: 0.9rem; color: var(--text-color); display: flex; align-items: center; - gap: 6px; + justify-content: center; + gap: 8px; border-left: 2px solid var(--special-color); box-shadow: none !important; - max-width: 450px; + max-width: 500px; width: 100%; border-bottom: none !important; border-top: none !important; border-right: none !important; + margin-left: auto; + margin-right: auto; + text-align: center; } -.emoji-grid-note i { +.emoji-header { + padding: 15px 20px; + margin-bottom: 15px; + text-align: center; + border-bottom: 1px solid var(--input-border); + background: linear-gradient(to right, rgba(66, 165, 245, 0.02), rgba(126, 87, 194, 0.02)); +} + +.emoji-header h3 { + font-size: 1.3rem; + margin: 0 0 8px 0; + color: var(--text-color); + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.emoji-header h3 i { color: var(--special-color); - font-size: 1rem; + font-size: 1.2rem; +} + +.emoji-subtitle { + font-size: 0.85rem; + color: var(--text-color); + opacity: 0.85; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; +} + +.emoji-grid-note i, .emoji-subtitle i { + color: var(--special-color); + font-size: 0.9rem; opacity: 0.9; } @@ -387,16 +577,16 @@ textarea { .emoji-grid { display: grid !important; - grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)) !important; - grid-auto-rows: 40px !important; - gap: 4px !important; - padding: 10px !important; - border-radius: 4px !important; + grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)) !important; + grid-auto-rows: 42px !important; + gap: 6px !important; + padding: 12px !important; + border-radius: 6px !important; border: 1px solid var(--input-border) !important; background-color: var(--secondary-bg) !important; box-shadow: none !important; transition: all 0.2s ease !important; - margin-bottom: 8px !important; + margin-bottom: 10px !important; width: 100% !important; max-height: none !important; overflow: visible !important; @@ -406,19 +596,26 @@ textarea { .emoji-category-tabs { display: flex; flex-wrap: wrap; - gap: 4px; - margin-bottom: 8px; + gap: 6px; + margin-bottom: 10px; width: 100%; + justify-content: center; } .emoji-category-tab { - padding: 5px 10px; + padding: 6px 12px; background: var(--button-bg); border: 1px solid var(--input-border); - border-radius: 4px; + border-radius: 6px; cursor: pointer; - font-size: 1rem; + font-size: 0.9rem; transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + min-width: 120px; + white-space: nowrap; } .emoji-category-tab.active, @@ -451,15 +648,16 @@ textarea { justify-content: center; width: 100%; height: 100%; - font-size: 1.3rem; + font-size: 1.5rem; background-color: var(--button-bg); border: 1px solid var(--input-border); - border-radius: 3px; + border-radius: 4px; cursor: pointer; transition: all 0.2s ease; padding: 0; position: relative; overflow: hidden; + line-height: 1; } .emoji-button:before { diff --git a/index.html b/index.html index ae32000..ca443aa 100644 --- a/index.html +++ b/index.html @@ -51,14 +51,14 @@
-
+
-
-
-

Choose an Emoji

-
-
+
@@ -136,8 +132,14 @@
-

Universal Decoder

-

Paste any encoded text to try all decoding methods at once

+

+ Universal Decoder + (Prioritizing {{ activeTransform.name }}) +

+

+ Paste encoded text to decode with {{ activeTransform.name }} or try other methods +

+

Paste any encoded text to try all decoding methods at once

-
Decoded using: {{ universalDecodeResult.method }}
+
+ Decoded using: {{ universalDecodeResult.method }} + (Priority Match) +
{{ universalDecodeResult.text }}
- +
+ + +
@@ -187,7 +197,7 @@
+
+ + +
diff --git a/js/app.js b/js/app.js index 433bf39..5ba8436 100644 --- a/js/app.js +++ b/js/app.js @@ -38,6 +38,10 @@ window.app = new Vue({ 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, @@ -51,6 +55,33 @@ window.app = new Vue({ 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'); + } + }); + } + }, + // Get transforms grouped by category getTransformsByCategory(category) { return this.transforms.filter(transform => @@ -81,12 +112,19 @@ window.app = new Vue({ }, // Transform Methods - applyTransform(transform) { + applyTransform(transform, event) { if (this.transformInput) { + // Prevent default button behavior + event && event.preventDefault(); + + // Update active transform and apply it this.activeTransform = transform; this.transformOutput = transform.func(this.transformInput); - // Force copy and log to history + // 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 @@ -94,18 +132,30 @@ window.app = new Vue({ // 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') { + // Update the output without copying this.transformOutput = this.activeTransform.func(this.transformInput); - - // Use forceCopyToClipboard for auto-copy - this.forceCopyToClipboard(this.transformOutput); - - // Add to copy history - this.addToCopyHistory(`Transform: ${this.activeTransform.name}`, this.transformOutput); } }, @@ -258,9 +308,30 @@ window.app = new Vue({ }, // 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); @@ -271,6 +342,25 @@ window.app = new Vue({ // 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); @@ -281,10 +371,31 @@ window.app = new Vue({ 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(); @@ -295,11 +406,8 @@ window.app = new Vue({ if (successful) { this.showNotification(' Copied!', 'success'); - // Add to history when successful - // Try to determine a more specific source based on the context + // Add to history with 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') { @@ -309,7 +417,6 @@ window.app = new Vue({ source = `Emoji: ${this.selectedEmoji}`; } } - this.addToCopyHistory(source, text); } else { this.showNotification(' Copy not supported', 'error'); @@ -317,6 +424,19 @@ window.app = new Vue({ // 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'); @@ -327,52 +447,79 @@ window.app = new Vue({ forceCopyToClipboard(text) { if (!text) return; - // Skip notifications if this was triggered by a paste operation + // Skip copy operations during paste if (this.isPasteOperation) { - console.log('Skipping clipboard notification for paste operation'); + this.isPasteOperation = false; return; } - console.log('Force copying to clipboard:', text); + // Block keyboard-triggered copies unless it's a transform + if (!this.isTransformCopy && this.ignoreKeyboardEvents) { + return; + } try { - // Try to use the Clipboard API first + // Use Clipboard API 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(); + // Only show notification for transform copies + if (this.isTransformCopy) { + this.showCopiedPopup(); + + // Temporarily disable keyboard events + this.ignoreKeyboardEvents = true; + clearTimeout(this.keyboardEventsTimeout); + this.keyboardEventsTimeout = setTimeout(() => { + this.ignoreKeyboardEvents = false; + }, 1000); + } + + // Reset transform flag + this.isTransformCopy = false; + + // Focus back on input + const inputBox = document.querySelector('#transform-input'); + if (inputBox) { + inputBox.focus(); + const len = inputBox.value.length; + inputBox.setSelectionRange(len, len); + } }) .catch(err => { - console.warn('Force Clipboard API failed:', err); + console.warn('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 { + // 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; textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '0'; document.body.appendChild(textarea); + + // Focus and select the text textarea.focus(); textarea.select(); @@ -383,9 +530,25 @@ window.app = new Vue({ 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 } }, @@ -464,7 +627,28 @@ window.app = new Vue({ return; } - // Try to decode using all available methods + // 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 @@ -507,26 +691,60 @@ window.app = new Vue({ } // 2. Try transform reversals - // - Binary + // 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]/.test(result)) { // Make sure it's readable ASCII + if (result && /[\x20-\x7E]{3,}/.test(result)) { // Make sure it's readable ASCII return { text: result, method: 'Binary' }; } - } else { + } + + // 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 = input.replace(/\s+/g, ''); + 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]/.test(result)) { // Make sure it's readable ASCII + + if (result && /[\x20-\x7E]{3,}/.test(result)) { // Make sure it's readable ASCII return { text: result, method: 'Binary' }; } } @@ -962,32 +1180,39 @@ window.app = new Vue({ // 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; border: 1px solid #ccc;'); + 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: 30px; border: 3px solid var(--accent-color); overflow: visible;'); + 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.error('emoji-grid-container not found, will retry'); - // Try again after a longer delay if not found - setTimeout(initializeEmojiGrid, 500); + console.log('emoji-grid-container not found, will retry when steganography tab is active'); } }; - // Start with a small delay to ensure DOM is ready - setTimeout(initializeEmojiGrid, 100); + // 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(); @@ -1018,11 +1243,17 @@ window.app = new Vue({ // But no keyboard shortcuts/hotkeys for now }, - // Watch for input events and ensure emojis stay visible + // 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() { - // Reset the filtered emojis to the full list whenever typing occurs this.filteredEmojis = [...window.emojiLibrary.EMOJI_LIST]; this.$nextTick(() => { this.renderEmojiGrid(); @@ -1030,7 +1261,6 @@ window.app = new Vue({ }, // 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 def7ab5..cd5e152 100644 --- a/js/emojiLibrary.js +++ b/js/emojiLibrary.js @@ -427,17 +427,38 @@ window.emojiLibrary.EMOJI_LIST = [ "๐Ÿ‡ฎ๐Ÿ‡ช", // Ireland ]; -// Define standard emoji categories using the Unicode CLDR categorization +// Define emoji categories with specific emojis for each category +window.emojiLibrary.EMOJIS = { + faces_people: ["๐Ÿ˜€", "๐Ÿ˜", "๐Ÿ˜‚", "๐Ÿคฃ", "๐Ÿ˜ƒ", "๐Ÿ˜„", "๐Ÿ˜…", "๐Ÿ˜†", "๐Ÿ˜‰", "๐Ÿ˜Š", "๐Ÿ˜‹", "๐Ÿ˜Ž", "๐Ÿ˜", "๐Ÿ˜˜", "๐Ÿฅฐ", "๐Ÿ˜—", "๐Ÿ˜™", "๐Ÿ˜š", "๐Ÿ™‚", "๐Ÿค—", "๐Ÿคฉ", "๐Ÿค”", "๐Ÿคจ", "๐Ÿ˜", "๐Ÿ˜‘", "๐Ÿ˜ถ", "๐Ÿ™„", "๐Ÿ˜", "๐Ÿ˜ฃ", "๐Ÿ˜ฅ", "๐Ÿ˜ฎ", "๐Ÿค", "๐Ÿ˜ฏ", "๐Ÿ˜ช", "๐Ÿ˜ซ", "๐Ÿ˜ด", "๐Ÿ˜Œ", "๐Ÿ˜›", "๐Ÿ˜œ", "๐Ÿ˜", "๐Ÿคค", "๐Ÿ˜’", "๐Ÿ˜“", "๐Ÿ˜”", "๐Ÿ˜•", "๐Ÿ™ƒ", "๐Ÿค‘", "๐Ÿ˜ฒ", "๐Ÿ™", "๐Ÿ˜–", "๐Ÿ˜ž", "๐Ÿ˜Ÿ", "๐Ÿ˜ค", "๐Ÿ˜ข", "๐Ÿ˜ญ", "๐Ÿ˜ง", "๐Ÿ˜จ", "๐Ÿ˜ฉ", "๐Ÿคฏ", "๐Ÿ˜ฑ", "๐Ÿ˜ณ", "๐Ÿฅต", "๐Ÿฅถ", "๐Ÿ˜ก", "๐Ÿ˜ ", "๐Ÿคฌ", "๐Ÿ˜ท", "๐Ÿค’", "๐Ÿค•", "๐Ÿคข", "๐Ÿคฎ", "๐Ÿคง", "๐Ÿ˜‡", "๐Ÿฅณ", "๐Ÿฅด", "๐Ÿฅบ", "๐Ÿง", "๐Ÿฅฑ", "๐Ÿง "], + + gestures: ["๐Ÿ‘", "๐Ÿ‘Ž", "๐Ÿ‘Œ", "โœŒ๏ธ", "๐Ÿคž", "๐ŸคŸ", "๐Ÿค˜", "๐Ÿค™", "๐Ÿ‘ˆ", "๐Ÿ‘‰", "๐Ÿ‘†", "๐Ÿ‘‡", "๐Ÿ–•", "โ˜๏ธ", "โœ‹", "๐Ÿคš", "๐Ÿ–๏ธ", "๐Ÿ––", "๐Ÿ‘‹", "๐Ÿค", "๐Ÿ‘", "๐Ÿ™Œ", "๐Ÿ‘", "๐Ÿค", "๐Ÿ™"], + + animals_nature: ["๐ŸฆŠ", "๐Ÿฆ", "๐Ÿฏ", "๐Ÿฎ", "๐Ÿท", "๐Ÿธ", "๐Ÿต", "๐Ÿ”", "๐Ÿง", "๐Ÿฆ", "๐Ÿค", "๐Ÿฆ†", "๐Ÿฆ…", "๐Ÿฆ‰", "๐Ÿฆ‡", "๐Ÿบ", "๐Ÿ—", "๐Ÿด", "๐Ÿ", "๐Ÿ›", "๐Ÿฆ‹", "๐ŸŒ", "๐Ÿž", "๐Ÿœ", "๐Ÿ•ท๏ธ", "๐Ÿฆ‚", "๐Ÿ", "๐Ÿฆจ", "๐Ÿฆฉ", "๐Ÿฆซ", "๐Ÿฆฌ", "๐Ÿปโ€โ„๏ธ", "๐Ÿผ", "๐Ÿจ", "๐Ÿ•", "๐Ÿถ", "๐Ÿฉ", "๐Ÿˆ", "๐Ÿฑ"], + + activities_sports: ["โšฝ", "๐Ÿ€", "๐Ÿˆ", "๐Ÿ", "๐Ÿ‰", "๐ŸŽพ", "๐ŸŽณ", "๐Ÿ‘", "๐Ÿ’", "๐Ÿ“", "๐Ÿธ", "๐ŸฅŠ", "๐Ÿฅ‹", "๐Ÿฅ…", "๐Ÿคพ", "๐ŸŽฟ", "๐Ÿ„", "๐Ÿ‚", "๐ŸŠ", "๐Ÿ‹๏ธ", "๐Ÿคผ", "๐Ÿคธ", "๐Ÿคบ", "๐Ÿคฝ", "๐Ÿคน", "๐ŸŽฏ", "๐ŸŽฑ", "๐ŸŽฝ", "๐Ÿšด", "๐Ÿšต"], + + technology_objects: ["๐Ÿ’ป", "โŒจ๏ธ", "๐Ÿ–ฅ๏ธ", "๐Ÿ–ฑ๏ธ", "๐Ÿ–จ๏ธ", "๐Ÿ“ฑ", "โ˜Ž๏ธ", "๐Ÿ“ž", "๐Ÿ“Ÿ", "๐Ÿ“ ", "๐Ÿ“บ", "๐Ÿ“ป", "๐ŸŽ™๏ธ", "๐ŸŽš๏ธ", "๐ŸŽ›๏ธ", "๐Ÿงญ", "๐Ÿ“ก", "๐Ÿ”‹", "๐Ÿ”Œ", "๐Ÿ’ก", "๐Ÿ›ข๏ธ", "๐Ÿ’ธ", "๐Ÿ’ต", "๐Ÿ’ณ", "๐Ÿ”‘", "๐Ÿ”“", "๐Ÿ”’"], + + mystical_fantasy: ["๐Ÿง™", "๐Ÿงš", "๐Ÿง›", "๐Ÿงœ", "๐Ÿ‘น", "๐Ÿ‘บ", "๐Ÿ‘ป", "๐Ÿ‘ฝ", "๐Ÿ‘พ", "๐Ÿ”ฎ", "๐Ÿช„", "๐Ÿ‰", "๐Ÿฒ", "๐Ÿฆ„"], + + nature_weather: ["๐ŸŒˆ", "๐ŸŒž", "๐ŸŒ™", "โญ", "๐ŸŒŸ", "โšก", "โ„๏ธ", "๐Ÿ”ฅ", "๐Ÿ’ง", "๐ŸŒŠ", "๐ŸŒช๏ธ", "๐ŸŒ‹"], + + symbols: ["โค๏ธ", "๐Ÿ’›", "๐Ÿ’š", "๐Ÿ’™", "๐Ÿ’œ", "๐Ÿ’”", "๐Ÿ’•", "๐Ÿ’ž", "๐Ÿ’“", "๐Ÿ’—", "๐Ÿ’–", "๐Ÿ’˜", "๐Ÿ’", "๐Ÿ’Ÿ", "๐Ÿ’ข", "๐Ÿ’ฃ", "๐Ÿ’ฅ", "๐Ÿ’ฆ", "๐Ÿ’จ", "๐Ÿ’ฉ", "๐Ÿ’ซ", "๐Ÿ’ฌ", "๐Ÿ’ ", "๐Ÿ’ฎ"], + + flags: ["๐Ÿ", "๐Ÿšฉ", "๐ŸŽŒ", "๐Ÿด", "๐Ÿณ๏ธ", "๐Ÿณ๏ธโ€๐ŸŒˆ", "๐Ÿณ๏ธโ€โšง๏ธ", "๐Ÿดโ€โ˜ ๏ธ", "๐Ÿ‡บ๐Ÿ‡ธ", "๐Ÿ‡จ๐Ÿ‡ฆ", "๐Ÿ‡ฌ๐Ÿ‡ง", "๐Ÿ‡ฉ๐Ÿ‡ช", "๐Ÿ‡ซ๐Ÿ‡ท", "๐Ÿ‡ฎ๐Ÿ‡น", "๐Ÿ‡ฏ๐Ÿ‡ต", "๐Ÿ‡ฐ๐Ÿ‡ท", "๐Ÿ‡ท๐Ÿ‡บ", "๐Ÿ‡จ๐Ÿ‡ณ", "๐Ÿ‡ฎ๐Ÿ‡ณ", "๐Ÿ‡ง๐Ÿ‡ท", "๐Ÿ‡ฆ๐Ÿ‡บ", "๐Ÿ‡ช๐Ÿ‡ธ", "๐Ÿ‡ณ๐Ÿ‡ฑ", "๐Ÿ‡ธ๐Ÿ‡ช"] +}; + +// Define standard emoji categories window.emojiLibrary.CATEGORIES = [ { id: 'all', name: 'All Emojis', icon: '๐Ÿ”' }, - { id: 'smileys', name: 'Smileys & Emotion', icon: '๐Ÿ˜€' }, - { id: 'people', name: 'People & Body', icon: '๐Ÿ‘‹' }, - { id: 'animals', name: 'Animals & Nature', icon: '๐Ÿต' }, - { id: 'food', name: 'Food & Drink', icon: '๐ŸŽ' }, - { id: 'travel', name: 'Travel & Places', icon: '๐Ÿš—' }, - { id: 'activities', name: 'Activities', icon: 'โšฝ' }, - { id: 'objects', name: 'Objects', icon: '๐Ÿ’ก' }, - { id: 'symbols', name: 'Symbols', icon: '๐Ÿ”ฃ' }, + { id: 'faces_people', name: 'Faces & People', icon: '๐Ÿ˜€' }, + { id: 'gestures', name: 'Gestures', icon: '๐Ÿ‘' }, + { id: 'animals_nature', name: 'Animals & Nature', icon: '๐ŸฆŠ' }, + { id: 'activities_sports', name: 'Activities & Sports', icon: 'โšฝ' }, + { id: 'technology_objects', name: 'Tech & Objects', icon: '๐Ÿ’ป' }, + { id: 'mystical_fantasy', name: 'Mystical & Fantasy', icon: '๐Ÿง™' }, + { id: 'nature_weather', name: 'Nature & Weather', icon: '๐ŸŒˆ' }, + { id: 'symbols', name: 'Symbols', icon: 'โค๏ธ' }, { id: 'flags', name: 'Flags', icon: '๐Ÿ' } ]; @@ -455,11 +476,11 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte // Clear container container.innerHTML = ''; - // Create grid note - const gridNote = document.createElement('div'); - gridNote.className = 'emoji-grid-note'; - gridNote.innerHTML = ' Click any emoji to automatically copy your hidden message'; - container.appendChild(gridNote); + // Add header with instruction message + const emojiHeader = document.createElement('div'); + emojiHeader.className = 'emoji-header'; + emojiHeader.innerHTML = '

Choose an Emoji

Click any emoji to copy your hidden message

'; + container.appendChild(emojiHeader); // Create category tabs const categoryTabs = document.createElement('div'); @@ -483,12 +504,30 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte const gridContainer = document.createElement('div'); gridContainer.className = 'emoji-grid'; - // Combine all emojis for a larger selection - const allEmojis = [...window.emojiLibrary.EMOJI_LIST, ...window.emojiLibrary.ADDITIONAL_EMOJIS]; + // Get the active category + let activeCategory = 'all'; + const activeCategoryTab = container.querySelector('.emoji-category-tab.active'); + if (activeCategoryTab) { + activeCategory = activeCategoryTab.getAttribute('data-category'); + } - // Use the provided filtered list if available, otherwise default to full list - const emojisToShow = filteredList && filteredList.length > 0 ? filteredList : allEmojis; - console.log(`Adding ${emojisToShow.length} emojis to grid`); + // Determine which emojis to show based on category and filter + let emojisToShow = []; + + if (filteredList && filteredList.length > 0) { + // If we have a filtered list (from search), use that + emojisToShow = filteredList; + } else if (activeCategory === 'all') { + // For 'all' category, combine all emojis from the categories + Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => { + emojisToShow = [...emojisToShow, ...categoryEmojis]; + }); + } else if (window.emojiLibrary.EMOJIS[activeCategory]) { + // For specific category, use emojis from that category + emojisToShow = window.emojiLibrary.EMOJIS[activeCategory]; + } + + console.log(`Adding ${emojisToShow.length} emojis to grid for category: ${activeCategory}`); // Add emojis to grid with enforced styling emojisToShow.forEach(emoji => { @@ -514,197 +553,21 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte container.appendChild(gridContainer); console.log('Emoji grid rendering complete'); - // Helper function to categorize emojis using standard Unicode ranges - function categorizeEmoji(emoji) { - // Get the code point of the emoji - const code = emoji.codePointAt(0); - - // Smileys & Emotion (faces, emotions, hearts) - if ((code >= 0x1F600 && code <= 0x1F64F) || // Emoticons - (code >= 0x1F910 && code <= 0x1F92F) || // Face-hand - (code >= 0x1F970 && code <= 0x1F97A) || // Faces - (code >= 0x1F9D0 && code <= 0x1F9DF) || // Faces - (code >= 0x2763 && code <= 0x2764) || // Hearts - (code >= 0x1F48B && code <= 0x1F49F) || // Hearts and love - (code >= 0x1F493 && code <= 0x1F49F) || // Hearts - emoji === '๐Ÿ˜€' || emoji === '๐Ÿ˜ƒ' || emoji === '๐Ÿ˜„' || emoji === '๐Ÿ˜' || emoji === '๐Ÿ˜†' || - emoji === '๐Ÿ˜…' || emoji === '๐Ÿ˜‚' || emoji === '๐Ÿคฃ' || emoji === 'โ˜บ๏ธ' || emoji === '๐Ÿ˜Š') { - return 'smileys'; - } - - // People & Body (people, hands, body parts) - if ((code >= 0x1F466 && code <= 0x1F487) || // People - (code >= 0x1F9D1 && code <= 0x1F9DD) || // People - (code >= 0x1F468 && code <= 0x1F469) || // Man/Woman - (code >= 0x1F46E && code <= 0x1F9CF) || // People roles - (code >= 0x1F44B && code <= 0x1F450) || // Hands - (code >= 0x1F918 && code <= 0x1F91F) || // Hand symbols - (code >= 0x1F926 && code <= 0x1F937) || // People gestures - emoji.includes('๐Ÿ‘จ') || emoji.includes('๐Ÿ‘ฉ') || emoji.includes('๐Ÿง‘') || - emoji.includes('๐Ÿ‘ถ') || emoji.includes('๐Ÿ‘ฆ') || emoji.includes('๐Ÿ‘ง') || - emoji.includes('๐Ÿง’') || emoji.includes('๐Ÿ‘ด') || emoji.includes('๐Ÿ‘ต') || - emoji.includes('๐Ÿง“') || emoji.includes('๐Ÿ‘ฎ') || emoji.includes('๐Ÿ‘ท')) { - return 'people'; - } - - // Animals & Nature (animals, plants, weather) - if ((code >= 0x1F400 && code <= 0x1F43F) || // Animals - (code >= 0x1F980 && code <= 0x1F9AF) || // Animals - (code >= 0x1F330 && code <= 0x1F33F) || // Plants - (code >= 0x1F340 && code <= 0x1F37F) || // More plants - (code >= 0x1F300 && code <= 0x1F32C) || // Weather - emoji === '๐Ÿต' || emoji === '๐Ÿ’' || emoji === '๐Ÿฆ' || emoji === '๐Ÿฆง' || - emoji === '๐Ÿถ' || emoji === '๐Ÿ•' || emoji === '๐Ÿฆฎ' || emoji === '๐Ÿฉ' || - emoji === '๐Ÿบ' || emoji === '๐ŸฆŠ' || emoji === '๐Ÿฆ' || emoji === '๐Ÿฑ' || - emoji === '๐ŸŒฑ' || emoji === '๐ŸŒฒ' || emoji === '๐ŸŒณ' || emoji === '๐ŸŒด' || - emoji === '๐ŸŒต' || emoji === '๐ŸŒท' || emoji === '๐ŸŒธ' || emoji === '๐ŸŒน') { - return 'animals'; - } - - // Food & Drink - if ((code >= 0x1F32D && code <= 0x1F37F) || // Food items - (code >= 0x1F95F && code <= 0x1F9AA) || // More food - (code >= 0x1F950 && code <= 0x1F96F) || // More food - emoji === '๐Ÿ‡' || emoji === '๐Ÿˆ' || emoji === '๐Ÿ‰' || emoji === '๐ŸŠ' || - emoji === '๐Ÿ‹' || emoji === '๐ŸŒ' || emoji === '๐Ÿ' || emoji === '๐Ÿฅญ' || - emoji === '๐ŸŽ' || emoji === '๐Ÿ' || emoji === '๐Ÿ' || emoji === '๐Ÿ‘' || - emoji === '๐Ÿ’' || emoji === '๐Ÿ“' || emoji === '๐Ÿฅ' || emoji === '๐Ÿ…' || - emoji === '๐Ÿฅฅ' || emoji === '๐Ÿฅ‘' || emoji === '๐Ÿ†' || emoji === '๐Ÿฅ”') { - return 'food'; - } - - // Travel & Places (transportation, buildings, maps) - if ((code >= 0x1F680 && code <= 0x1F6FF) || // Transport - (code >= 0x1F30D && code <= 0x1F32C) || // Earth/Weather - (code >= 0x1F3D7 && code <= 0x1F3DB) || // Buildings - (code >= 0x1F3E0 && code <= 0x1F3F0) || // Buildings - (code >= 0x26E9 && code <= 0x26F5) || // Buildings/Places - emoji === '๐Ÿš—' || emoji === '๐Ÿš•' || emoji === '๐Ÿš™' || emoji === '๐ŸšŒ' || - emoji === '๐ŸšŽ' || emoji === '๐ŸŽ๏ธ' || emoji === '๐Ÿš“' || emoji === '๐Ÿš‘' || - emoji === '๐Ÿš’' || emoji === '๐Ÿš' || emoji === '๐Ÿ›ป' || emoji === '๐Ÿšš' || - emoji === '๐Ÿš›' || emoji === '๐Ÿšœ' || emoji === '๐Ÿ›ต' || emoji === '๐Ÿ๏ธ' || - emoji === '๐Ÿ›บ' || emoji === '๐Ÿšฒ' || emoji === '๐Ÿ›ด' || emoji === '๐Ÿš') { - return 'travel'; - } - - // Activities (sports, music, arts, hobbies) - if ((code >= 0x1F380 && code <= 0x1F3A0) || // Events - (code >= 0x1F3A3 && code <= 0x1F3BE) || // Sports - (code >= 0x1F3BF && code <= 0x1F3C9) || // Sports - (code >= 0x1F3CF && code <= 0x1F3D6) || // Sports - (code >= 0x1F3F8 && code <= 0x1F3FF) || // Activities - (code >= 0x1F93A && code <= 0x1F94F) || // Sports - emoji === 'โšฝ' || emoji === 'โšพ' || emoji === '๐Ÿ€' || emoji === '๐Ÿ' || - emoji === '๐Ÿˆ' || emoji === '๐Ÿ‰' || emoji === '๐ŸŽพ' || emoji === '๐Ÿฅ' || - emoji === '๐ŸŽณ' || emoji === '๐Ÿ' || emoji === '๐Ÿ‘' || emoji === '๐Ÿ’' || - emoji === '๐Ÿฅ' || emoji === '๐Ÿ“' || emoji === '๐Ÿธ' || emoji === '๐ŸฅŠ') { - return 'activities'; - } - - // Objects (household, office, tools) - if ((code >= 0x1F4A1 && code <= 0x1F4CC) || // Office - (code >= 0x1F4D0 && code <= 0x1F4F7) || // Office/Tools - (code >= 0x1F4FF && code <= 0x1F53D) || // Various objects - (code >= 0x1F56F && code <= 0x1F5A4) || // Objects - (code >= 0x1F5D1 && code <= 0x1F5FF) || // Office objects - (code >= 0x1F6D1 && code <= 0x1F6DF) || // Misc objects - emoji === 'โŒš' || emoji === '๐Ÿ“ฑ' || emoji === '๐Ÿ“ฒ' || emoji === '๐Ÿ’ป' || - emoji === 'โŒจ๏ธ' || emoji === '๐Ÿ–ฅ๏ธ' || emoji === '๐Ÿ–จ๏ธ' || emoji === '๐Ÿ–ฑ๏ธ' || - emoji === '๐Ÿ–ฒ๏ธ' || emoji === '๐Ÿ•น๏ธ' || emoji === '๐Ÿ—œ๏ธ' || emoji === '๐Ÿ’ฝ' || - emoji === '๐Ÿ’พ' || emoji === '๐Ÿ’ฟ' || emoji === '๐Ÿ“€' || emoji === '๐Ÿ“ผ') { - return 'objects'; - } - - // Symbols (punctuation, alphanum, geometric, etc) - if ((code >= 0x1F300 && code <= 0x1F320) || // Various symbols - (code >= 0x1F170 && code <= 0x1F251) || // Enclosed characters - (code >= 0x1F523 && code <= 0x1F5FF) || // Symbols - (code >= 0x2600 && code <= 0x26FF) || // Misc symbols - (code >= 0x2700 && code <= 0x27BF) || // Dingbats - (code >= 0x1F5FB && code <= 0x1F64F) || // Symbols - (code >= 0x1F680 && code <= 0x1F6FF) || // Transport symbols - emoji === '๐Ÿ’ฏ' || emoji === '๐Ÿ“›' || emoji === '๐Ÿ”ฐ' || emoji === 'โญ•' || - emoji === 'โœ…' || emoji === 'โ˜‘๏ธ' || emoji === 'โœ”๏ธ' || emoji === 'โŒ' || - emoji === 'โŽ' || emoji === 'โžฐ' || emoji === 'โžฟ' || emoji === 'ใ€ฝ๏ธ' || - emoji === 'โœณ๏ธ' || emoji === 'โœด๏ธ' || emoji === 'โ‡๏ธ' || emoji === 'ยฉ๏ธ') { - return 'symbols'; - } - - // Flags (country flags, flag symbols) - if ((code >= 0x1F1E6 && code <= 0x1F1FF) || // Regional indicators for flags - emoji === '๐Ÿ' || emoji === '๐Ÿšฉ' || emoji === '๐ŸŽŒ' || emoji === '๐Ÿด' || - emoji.includes('๐Ÿณ๏ธ') || // Flag variants - emoji.includes('๐Ÿด') || // Flag variants - // Check for country flags (pairs of regional indicators) - (emoji.length >= 2 && - emoji.codePointAt(0) >= 0x1F1E6 && emoji.codePointAt(0) <= 0x1F1FF && - emoji.codePointAt(2) >= 0x1F1E6 && emoji.codePointAt(2) <= 0x1F1FF)) { - return 'flags'; - } - - // Default to 'all' if we can't categorize - return 'all'; - } - - // Add event listeners to category tabs with actual filtering - document.querySelectorAll('.emoji-category-tab').forEach(tab => { - tab.addEventListener('click', function() { - // Remove active class from all tabs - document.querySelectorAll('.emoji-category-tab').forEach(t => { - t.classList.remove('active'); - }); - // Add active class to clicked tab - this.classList.add('active'); + // Add event listeners to category tabs + const categoryTabButtons = container.querySelectorAll('.emoji-category-tab'); + categoryTabButtons.forEach(tab => { + tab.addEventListener('click', () => { + // Update active tab + categoryTabButtons.forEach(t => t.classList.remove('active')); + tab.classList.add('active'); - const selectedCategory = this.getAttribute('data-category'); - console.log('Selected category:', selectedCategory); + // Re-render the emoji grid with the selected category + const selectedCategory = tab.getAttribute('data-category'); + console.log('Category selected:', selectedCategory); - // Get all emoji buttons - const allEmojis = [...window.emojiLibrary.EMOJI_LIST, ...window.emojiLibrary.ADDITIONAL_EMOJIS]; - - // Filter emojis based on selected category - let filteredEmojis = allEmojis; - if (selectedCategory !== 'all') { - filteredEmojis = allEmojis.filter(emoji => { - const category = categorizeEmoji(emoji); - console.log(`Emoji: ${emoji}, Category: ${category}`); - return category === selectedCategory; - }); - } - - // Clear and rebuild the grid with filtered emojis - const gridContainer = container.querySelector('.emoji-grid'); - if (gridContainer) { - // Clear existing emojis - gridContainer.innerHTML = ''; - - // Add filtered emojis - filteredEmojis.forEach(emoji => { - const emojiButton = document.createElement('button'); - emojiButton.className = 'emoji-button'; - emojiButton.textContent = emoji; - emojiButton.title = 'Click to encode with this emoji'; - - emojiButton.addEventListener('click', () => { - if (typeof onEmojiSelect === 'function') { - onEmojiSelect(emoji); - // Add visual feedback when clicked - emojiButton.style.backgroundColor = '#e6f7ff'; - setTimeout(() => { - emojiButton.style.backgroundColor = ''; - }, 300); - } - }); - - gridContainer.appendChild(emojiButton); - }); - - // Update the count display - const countDisplay = container.querySelector('.emoji-count'); - if (countDisplay) { - countDisplay.textContent = `${filteredEmojis.length} emojis available`; - } - } + // Clear and recreate the grid + container.removeChild(gridContainer); + window.emojiLibrary.renderEmojiGrid(containerId, onEmojiSelect); }); });