Files
P4RS3LT0NGV3/js/app.js
T

2215 lines
102 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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', 'Base64 URL', 'Base32', 'Base45', 'Base58', 'Base62', 'Binary', 'Hexadecimal', 'ASCII85', 'URL Encode', 'HTML Entities'],
cipher: ['Caesar Cipher', 'ROT13', 'ROT47', 'Morse Code', 'Atbash Cipher', 'ROT5', 'Vigenère Cipher', 'Rail Fence (3 Rails)', 'Baconian Cipher', 'Tap Code'],
visual: ['Rainbow Text', 'Strikethrough', 'Underline', 'Reverse Text', 'Alternating Case', 'Reverse Words', 'Random Case', 'Title Case', 'Sentence Case', 'Emoji Speak', 'Ubbi Dubbi', 'Rövarspråket'],
format: ['Pig Latin', 'Leetspeak', 'NATO Phonetic', 'camelCase', 'snake_case', 'kebab-case'],
unicode: ['Invisible Text', 'Upside Down', 'Full Width', 'Small Caps', 'Bubble', 'Braille', 'Greek Letters', 'Wingdings', 'Superscript', 'Subscript', 'Regional Indicator Letters', 'Fraktur', 'Cyrillic Stylized', 'Katakana', 'Hiragana', 'Roman Numerals'],
special: ['Medieval', 'Cursive', 'Monospace', 'Double-Struck', 'Elder Futhark', 'Mirror Text', 'Zalgo'],
fantasy: ['Quenya (Tolkien Elvish)', 'Tengwar Script', 'Klingon', 'Aurebesh (Star Wars)', 'Dovahzul (Dragon)'],
ancient: ['Hieroglyphics', 'Ogham (Celtic)', 'Semaphore Flags'],
technical: ['Brainfuck', 'Mathematical Notation', 'Chemical Symbols'],
randomizer: ['Random Mix']
},
// Be resilient if transforms.js fails to load
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,
carrierEmojiList: [...window.emojiLibrary.EMOJI_LIST],
quickCarrierEmojis: ['🐍','🐉','🐲','🔥','💥','🗿','⚓','⭐','✨','🚀','💀','🪨','🍃','🪶','🔮','🐢','🐊','🦎','🐍'],
tbCarrierManual: '',
// Token Bomb Generator
tbDepth: 3,
tbBreadth: 4,
tbRepeats: 5,
tbSeparator: 'zwnj',
tbIncludeVS: true,
tbIncludeNoise: true,
tbRandomizeEmojis: true,
tbAutoCopy: true,
tbSingleCarrier: true,
tbCarrier: '',
tbPayloadEmojis: [],
tokenBombOutput: '',
// Text Payload Generator
tpBase: '',
tpRepeat: 100,
tpCombining: true,
tpZW: false,
textPayload: '',
// Tokenizer tab
tokenizerInput: '',
tokenizerEngine: 'byte',
tokenizerTokens: [],
tokenizerCharCount: 0,
tokenizerWordCount: 0,
// Fuzzer
fuzzerInput: '',
fuzzerCount: 20,
fuzzerSeed: '',
fuzzUseRandomMix: true,
fuzzZeroWidth: true,
fuzzUnicodeNoise: true,
fuzzZalgo: false,
fuzzWhitespace: true,
fuzzCasing: true,
fuzzEncodeShuffle: false,
fuzzerOutputs: [],
// History of copied content
copyHistory: [],
maxHistoryItems: 10,
showCopyHistory: false,
showUnicodePanel: false,
// Danger zone controls
showDangerModal: false,
dangerThresholdTokens: 25_000_000
},
methods: {
toggleUnicodePanel() {
this.showUnicodePanel = !this.showUnicodePanel;
const panel = document.getElementById('unicode-options-panel');
if (panel) {
if (this.showUnicodePanel) panel.classList.add('active');
else panel.classList.remove('active');
}
},
// Focus an element without causing the page to scroll
focusWithoutScroll(el) {
if (!el) return;
const x = window.scrollX, y = window.scrollY;
try {
el.focus({ preventScroll: true });
} catch (e) {
el.focus();
window.scrollTo(x, y);
}
},
// Trigger randomizer chaos animation regardless of input
triggerRandomizerChaos() {
try {
const section = document.getElementById('category-randomizer');
const overlay = section && section.querySelector('.chaos-overlay');
if (!overlay) return;
const emojis = ['✨','🌀','💥','⚡','🔥','🌈','🎲','🔮','💫','🌪️'];
for (let i=0;i<10;i++) {
const el = document.createElement('div');
el.className = 'chaos-particle';
el.textContent = emojis[Math.floor(Math.random()*emojis.length)];
el.style.left = (10 + Math.random()*80) + '%';
el.style.fontSize = (14 + Math.random()*10) + 'px';
el.style.animationDelay = (Math.random()*0.2) + 's';
overlay.appendChild(el);
setTimeout(()=>{ if (el.parentNode) el.parentNode.removeChild(el); }, 1300);
}
section.classList.add('shake-once','randomizer-glow');
setTimeout(()=>section && section.classList.remove('shake-once','randomizer-glow'), 600);
} catch(_) {}
},
// 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();
});
}
if (tabName === 'tokenizer') {
this.$nextTick(() => this.runTokenizer());
}
},
// 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) {
// Prevent default button behavior and scrolling
event && event.preventDefault();
event && event.stopPropagation();
// Always trigger chaos animation for Random Mix, even with empty input
if (transform && transform.name === 'Random Mix') {
this.triggerRandomizerChaos();
}
if (this.transformInput) {
// Update active transform and apply it
this.activeTransform = transform;
if (transform.name === 'Random Mix') {
this.transformOutput = window.transforms.randomizer.func(this.transformInput);
// Show transform mapping info
const transformInfo = window.transforms.randomizer.getLastTransformInfo();
if (transformInfo.length > 0) {
const transformsList = transformInfo.map(t => t.transformName).join(', ');
this.showNotification(`<i class="fas fa-random"></i> Mixed with: ${transformsList}`, 'success');
console.log('Transform mapping:', transformInfo);
}
} else {
// 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 (if not randomizer - it has its own notification)
if (transform.name !== 'Random Mix') {
this.showNotification(`<i class="fas fa-check"></i> ${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) {
this.focusWithoutScroll(inputBox);
const len = inputBox.value.length;
try { inputBox.setSelectionRange(len, len); } catch (_) {}
}
// 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(`<i class="fas fa-check"></i> Decoded using ${transform.name}`, 'success');
// Add to copy history
this.addToCopyHistory(`Decoded (${transform.name})`, decodedText);
} else {
this.showNotification(`<i class="fas fa-exclamation-triangle"></i> Could not decode with ${transform.name}`, 'warning');
}
} catch (error) {
console.error(`Error decoding with ${transform.name}:`, error);
this.showNotification(`<i class="fas fa-exclamation-triangle"></i> 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('<i class="fas fa-check"></i> 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(`<i class="fas fa-check"></i> 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('<i class="fas fa-check"></i> 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('<i class="fas fa-check"></i> 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('<i class="fas fa-exclamation-triangle"></i> 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('<i class="fas fa-exclamation-triangle"></i> 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 = '<i class="fas fa-clipboard-check"></i> 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);
}
}
// 3. Smart pattern detection for new transforms
// Check for specific patterns that indicate certain transform types
// - Check for fantasy language patterns
if (/[ᚪᛒᛲᛞᛖᚠᚷᚺᛁᛃᛚᛗᚾᛟᛈᛩᚱᛋᛏᚢᛩᛉ]/.test(input)) {
// This looks like Tengwar or Elder Futhark runes
try {
if (window.transforms.tengwar && window.transforms.tengwar.reverse) {
const result = window.transforms.tengwar.reverse(input);
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
return { text: result, method: 'Tengwar Script', priorityMatch: true };
}
}
if (window.transforms.elder_futhark && window.transforms.elder_futhark.reverse) {
const result = window.transforms.elder_futhark.reverse(input);
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
return { text: result, method: 'Elder Futhark', priorityMatch: true };
}
}
} catch (e) {
console.error('Rune decode error:', e);
}
}
// - Check for hieroglyphic patterns
if (/[𓃭𓃮𓃯𓃰𓃱𓃲𓃳𓃴𓃵𓃶𓃷𓃸𓃹𓃺𓃻𓃼]/.test(input)) {
try {
if (window.transforms.hieroglyphics && window.transforms.hieroglyphics.reverse) {
const result = window.transforms.hieroglyphics.reverse(input);
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
return { text: result, method: 'Hieroglyphics', priorityMatch: true };
}
}
} catch (e) {
console.error('Hieroglyphics decode error:', e);
}
}
// - Check for Ogham patterns
if (/[ᚐᚁᚉᚇᚓᚃᚌᚆᚔᚈᚊᚂᚋᚅᚑᚚᚏᚄ]/.test(input)) {
try {
if (window.transforms.ogham && window.transforms.ogham.reverse) {
const result = window.transforms.ogham.reverse(input);
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
return { text: result, method: 'Ogham (Celtic)', priorityMatch: true };
}
}
} catch (e) {
console.error('Ogham decode error:', e);
}
}
// - Check for mathematical notation patterns
if (/[𝒶𝒷𝒸𝒹𝑒𝒻𝑔𝒽𝒾𝒿𝓀𝓁𝓂𝓃𝑜𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵]/.test(input)) {
try {
if (window.transforms.mathematical && window.transforms.mathematical.reverse) {
const result = window.transforms.mathematical.reverse(input);
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
return { text: result, method: 'Mathematical Notation', priorityMatch: true };
}
}
} catch (e) {
console.error('Mathematical notation decode error:', e);
}
}
// - Check for chemical symbol patterns
if (/^(Ac|B|C|D|Es|F|Ge|H|I|J|K|L|Mn|N|O|P|Q|R|S|Ti|U|V|W|Xe|Y|Zn|AC|ES|GE|MN|TI|XE)\s*$/.test(input.trim())) {
try {
if (window.transforms.chemical && window.transforms.chemical.reverse) {
const result = window.transforms.chemical.reverse(input);
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
return { text: result, method: 'Chemical Symbols', priorityMatch: true };
}
}
} catch (e) {
console.error('Chemical symbols decode error:', 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);
}
}
// - Base58
if (/^[1-9A-HJ-NP-Za-km-z]+$/.test(input.trim())) {
try {
if (window.transforms.base58 && window.transforms.base58.reverse) {
const result = window.transforms.base58.reverse(input.trim());
if (result && /[\x20-\x7E]{3,}/.test(result)) {
return { text: result, method: 'Base58' };
}
}
} catch (e) {
console.error('Base58 decode error:', e);
}
}
// - Base62
if (/^[0-9A-Za-z]+$/.test(input.trim())) {
try {
if (window.transforms.base62 && window.transforms.base62.reverse) {
const result = window.transforms.base62.reverse(input.trim());
if (result && /[\x20-\x7E]{3,}/.test(result)) {
return { text: result, method: 'Base62' };
}
}
} catch (e) {
console.error('Base62 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);
}
}
}
// - Check for Fantasy Languages
const fantasyLanguageChecks = [
{ name: 'Quenya (Tolkien Elvish)', transform: 'quenya' },
{ name: 'Tengwar Script', transform: 'tengwar' },
{ name: 'Klingon', transform: 'klingon' },
{ name: 'Dovahzul (Dragon)', transform: 'dovahzul' }
];
for (const language of fantasyLanguageChecks) {
if (window.transforms[language.transform] && window.transforms[language.transform].map) {
try {
// Create reverse mapping
const reverseMap = {};
for (const [key, value] of Object.entries(window.transforms[language.transform].map)) {
reverseMap[value] = key;
}
// Check if input contains characters from this language
const languageChars = Object.values(window.transforms[language.transform].map);
const hasLanguageChars = languageChars.some(char => input.includes(char));
if (hasLanguageChars) {
// 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: language.name };
}
}
} catch (e) {
console.error(`${language.name} decode error:`, e);
}
}
}
// - Check for Aurebesh (Star Wars) - special case due to word-based mapping
if (window.transforms.aurebesh && window.transforms.aurebesh.map) {
try {
// Check if input contains Aurebesh words
const aurebeshWords = Object.values(window.transforms.aurebesh.map);
const hasAurebeshWords = aurebeshWords.some(word =>
input.toLowerCase().includes(word.toLowerCase())
);
if (hasAurebeshWords) {
const result = window.transforms.aurebesh.reverse(input);
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
return { text: result, method: 'Aurebesh (Star Wars)' };
}
}
} catch (e) {
console.error('Aurebesh decode error:', e);
}
}
// - Check for Ancient Scripts
const ancientScriptChecks = [
{ name: 'Hieroglyphics', transform: 'hieroglyphics' },
{ name: 'Ogham (Celtic)', transform: 'ogham' },
{ name: 'Elder Futhark', transform: 'elder_futhark' }
];
for (const script of ancientScriptChecks) {
if (window.transforms[script.transform] && window.transforms[script.transform].map) {
try {
// Create reverse mapping
const reverseMap = {};
for (const [key, value] of Object.entries(window.transforms[script.transform].map)) {
reverseMap[value] = key;
}
// Check if input contains characters from this script
const scriptChars = Object.values(window.transforms[script.transform].map);
const hasScriptChars = scriptChars.some(char => input.includes(char));
if (hasScriptChars) {
// 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: script.name };
}
}
} catch (e) {
console.error(`${script.name} decode error:`, e);
}
}
}
// - Check for Technical Codes
const technicalCodeChecks = [
{ name: 'Mathematical Notation', transform: 'mathematical' },
{ name: 'Chemical Symbols', transform: 'chemical' }
];
for (const code of technicalCodeChecks) {
if (window.transforms[code.transform] && window.transforms[code.transform].map) {
try {
// Create reverse mapping
const reverseMap = {};
for (const [key, value] of Object.entries(window.transforms[code.transform].map)) {
reverseMap[value] = key;
}
// Check if input contains characters from this code
const codeChars = Object.values(window.transforms[code.transform].map);
const hasCodeChars = codeChars.some(char => input.includes(char));
if (hasCodeChars) {
// 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: code.name };
}
}
} catch (e) {
console.error(`${code.name} decode error:`, e);
}
}
}
// - Check for Brainfuck (special case - look for brainfuck patterns)
if (window.transforms.brainfuck) {
try {
// Brainfuck typically contains lots of +, -, <, >, [, ], ., and ,
const brainfuckPattern = /^[+\-<>\[\].,\s]+$/;
if (brainfuckPattern.test(input.trim()) && input.length > 20) {
// This looks like brainfuck code, but we can't easily reverse it
// Just indicate that it was detected
return { text: '[Brainfuck code detected - cannot decode]', method: 'Brainfuck' };
}
} catch (e) {
console.error('Brainfuck detection error:', e);
}
}
// - Check for Semaphore Flags (special case - look for flag emojis)
if (window.transforms.semaphore) {
try {
// Look for flag-like characters or emojis
const flagPattern = /[🔄🚩🏁🏴🏳️]/;
if (flagPattern.test(input)) {
return { text: '[Semaphore flags detected]', method: 'Semaphore Flags' };
}
} catch (e) {
console.error('Semaphore detection 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);
}
}
}
// 4. Mixed/Randomized text decoding (token-wise decoding)
// Split on whitespace and common punctuation, keep separators
const tokens = input.split(/(\s+|[\.,!?:;()\[\]{}])/);
if (tokens.length > 1) {
const decodedTokens = tokens.map(tok => {
// Skip separators
if (!tok || /^(\s+|[\.,!?:;()\[\]{}])$/.test(tok)) return tok;
// Try specific pattern checks first for token
const quick = this.universalDecode(tok);
if (quick && quick.text) return quick.text;
// Fallback: try all reverses for token
for (const name in window.transforms) {
const transform = window.transforms[name];
if (transform.reverse) {
try {
const r = transform.reverse(tok);
if (r && r !== tok && /[a-zA-Z0-9\s]{1,}/.test(r)) return r;
} catch (_) {}
}
}
return tok;
});
const joined = decodedTokens.join('');
if (joined !== input && /[a-zA-Z0-9\s]{3,}/.test(joined)) {
return { text: joined, method: 'Mixed (token-wise)' };
}
}
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(`<i class="fas fa-check"></i> 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(`<i class="fas fa-check"></i> Emoji copied!`, 'success');
this.addToCopyHistory('Emoji', emojiStr);
});
} else {
// Use our custom method if Clipboard API not available
this.forceCopyToClipboard(emojiStr);
this.showNotification(`<i class="fas fa-check"></i> 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(`<i class="fas fa-check"></i> 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(`<i class="fas fa-check"></i> 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(`<i class="fas fa-check"></i> 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);
}
}
});
});
});
}
,
// -------- Fuzzer --------
seededRandomFactory(seedStr) {
if (!seedStr) return Math.random;
let h = 1779033703 ^ seedStr.length;
for (let i=0;i<seedStr.length;i++) {
h = Math.imul(h ^ seedStr.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
return function() {
h ^= h >>> 16; h = Math.imul(h, 2246822507); h ^= h >>> 13; h = Math.imul(h, 3266489909); h ^= h >>> 16;
return (h >>> 0) / 4294967296;
};
},
pick(arr, rnd) { return arr[Math.floor(rnd()*arr.length)]; },
injectZeroWidth(text, rnd) {
const zw = ['\u200B','\u200C','\u200D','\u2060'];
return [...text].map(ch => (rnd()<0.2 ? ch+this.pick(zw,rnd) : ch)).join('');
},
injectUnicodeNoise(text, rnd) {
const marks = ['\u0301','\u0300','\u0302','\u0303','\u0308','\u0307','\u0304'];
return [...text].map(ch => (rnd()<0.15 ? ch+this.pick(marks,rnd) : ch)).join('');
},
whitespaceChaos(text, rnd) {
return text.replace(/\s/g, (m)=> (rnd()<0.5? m : (rnd()<0.5?'\t':'\u00A0')));
},
casingChaos(text, rnd) {
return [...text].map(c => /[a-z]/i.test(c)? (rnd()<0.5? c.toUpperCase():c.toLowerCase()) : c).join('');
},
encodeShuffle(text, rnd) {
const opts = ['base64','base32','hex','base58','base62','url'];
const t = this.pick(opts, rnd);
const tx = {
base64: ()=> window.transforms.base64.func(text),
base32: ()=> window.transforms.base32.func(text),
hex: ()=> window.transforms.hex.func(text),
base58: ()=> window.transforms.base58.func(text),
base62: ()=> window.transforms.base62.func(text),
url: ()=> window.transforms.url.func(text)
};
return (tx[t]||(()=>text))();
},
generateFuzzCases() {
const src = String(this.fuzzerInput || '');
if (!src) { this.fuzzerOutputs = []; return; }
const rnd = this.seededRandomFactory(String(this.fuzzerSeed||''));
const out = [];
for (let i=0;i<Math.max(1,Math.min(500,Number(this.fuzzerCount)||1)); i++) {
let s = src;
if (this.fuzzUseRandomMix) {
try { s = window.transforms.randomizer.func(s, { minTransforms:2, maxTransforms:4 }); } catch(_) {}
}
if (this.fuzzZeroWidth) s = this.injectZeroWidth(s, rnd);
if (this.fuzzUnicodeNoise) s = this.injectUnicodeNoise(s, rnd);
if (this.fuzzWhitespace) s = this.whitespaceChaos(s, rnd);
if (this.fuzzCasing) s = this.casingChaos(s, rnd);
if (this.fuzzZalgo) { try { s = window.transforms.zalgo.func(s); } catch(_) {} }
if (this.fuzzEncodeShuffle) s = this.encodeShuffle(s, rnd);
out.push(s);
}
this.fuzzerOutputs = out;
},
copyAllFuzz() { this.copyToClipboard(this.fuzzerOutputs.join('\n')); },
downloadFuzz() {
const lines = this.fuzzerOutputs.map((s, i) => `#${i+1}\t${s}`).join('\n');
const header = `# Parseltongue Fuzzer Output\n# count=${this.fuzzerOutputs.length}\n# seed=${this.fuzzerSeed || ''}\n# strategies=${[
this.fuzzUseRandomMix?'randomMix':null,
this.fuzzZeroWidth?'zeroWidth':null,
this.fuzzUnicodeNoise?'unicodeNoise':null,
this.fuzzWhitespace?'whitespace':null,
this.fuzzCasing?'casing':null,
this.fuzzZalgo?'zalgo':null,
this.fuzzEncodeShuffle?'encodeShuffle':null
].filter(Boolean).join(',')}\n`;
const blob = new Blob([header + lines + '\n'], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = 'fuzz_cases.txt'; a.click();
setTimeout(()=>URL.revokeObjectURL(url), 200);
},
// Quick estimate of token count for Tokenade
estimateTokenadeTokens() {
// Roughly approximate tokens by estimated character length
// This intentionally errs on the conservative side for warning purposes
const approx = this.estimateTokenadeLength();
return Math.max(0, approx);
},
// Confirm danger threshold before generating
checkTokenadeDangerThenGenerate() {
const estTokens = this.estimateTokenadeTokens();
if (estTokens > this.dangerThresholdTokens) {
this.showDangerModal = true;
return;
}
this.generateTokenBomb();
},
// Modal acknowledge handler
proceedDangerAction() {
// Close modal and return focus to Generate button for accessibility
this.showDangerModal = false;
this.$nextTick(() => {
try {
const btn = document.querySelector('.token-bomb-actions .transform-button');
btn && btn.focus();
} catch (_) {}
});
},
// Token Bomb Generator Logic
generateTokenBomb() {
const depth = Math.max(1, Math.min(8, Number(this.tbDepth) || 1));
const breadth = Math.max(1, Math.min(10, Number(this.tbBreadth) || 1));
const repeats = Math.max(1, Math.min(50, Number(this.tbRepeats) || 1));
const sep = this.tbSeparator === 'zwj' ? '\u200D' : this.tbSeparator === 'zwnj' ? '\u200C' : this.tbSeparator === 'zwsp' ? '\u200B' : '';
const includeVS = !!this.tbIncludeVS;
const includeNoise = !!this.tbIncludeNoise;
const randomize = !!this.tbRandomizeEmojis;
const emojiList = this.filteredEmojis && this.filteredEmojis.length ? this.filteredEmojis : window.emojiLibrary.EMOJI_LIST;
function pickEmojis(count) {
const out = [];
for (let i = 0; i < count; i++) {
const idx = randomize ? Math.floor(Math.random() * emojiList.length) : (i % emojiList.length);
out.push(String(emojiList[idx]));
}
return out;
}
function addVS(str) {
if (!includeVS) return str;
// Alternate VS16/VS15 to maximize tokenization churn
const vs16 = '\uFE0F';
const vs15 = '\uFE0E';
let out = '';
for (let i = 0; i < str.length; i++) {
const ch = str[i];
out += ch + (i % 2 === 0 ? vs16 : vs15);
}
return out;
}
function noise() {
if (!includeNoise) return '';
const parts = ['\u200B','\u200C','\u200D','\u2060','\u2062','\u2063'];
let s = '';
const n = 1 + Math.floor(Math.random() * 3);
for (let i = 0; i < n; i++) s += parts[Math.floor(Math.random() * parts.length)];
return s;
}
function buildLevel(level) {
if (level === 0) {
const base = pickEmojis(breadth).join('');
return addVS(base);
}
const items = [];
for (let i = 0; i < breadth; i++) {
const inner = buildLevel(level - 1);
items.push(inner + noise());
}
return items.join(sep);
}
if (this.tbSingleCarrier) {
const manual = (this.tbCarrierManual || '').trim();
const carrier = manual || (this.tbCarrier && String(this.tbCarrier)) || (this.selectedEmoji ? String(this.selectedEmoji) : '💥');
function countUnits(level) {
if (level === 0) return breadth;
return breadth * countUnits(level - 1);
}
const unitsPerBlock = countUnits(depth - 1);
const totalUnits = Math.max(1, repeats * unitsPerBlock);
let payload = [];
payload = pickEmojis(totalUnits);
function toTagSeqForEmojiChar(ch) {
const cp = ch.codePointAt(0);
const hex = cp.toString(16);
let seq = '';
for (const d of hex) {
if (d >= '0' && d <= '9') {
const base = 0xE0030 + (d.charCodeAt(0) - '0'.charCodeAt(0));
seq += String.fromCodePoint(base);
} else {
const base = 0xE0061 + (d.charCodeAt(0) - 'a'.charCodeAt(0));
seq += String.fromCodePoint(base);
}
}
seq += String.fromCodePoint(0xE007F);
return seq;
}
const vs16 = includeVS ? '\uFE0F' : '';
let out = carrier + vs16;
for (let i = 0; i < payload.length; i++) {
out += sep + toTagSeqForEmojiChar(payload[i]) + noise();
}
this.tokenBombOutput = out;
} else {
let block = buildLevel(depth - 1);
// Repeat the block to increase token length
const blocks = [];
for (let i = 0; i < repeats; i++) {
blocks.push(block + noise());
}
this.tokenBombOutput = blocks.join(sep);
}
// Provide a quick visual confirmation
this.showNotification('<i class="fas fa-bomb"></i> Tokenade generated', 'success');
if (this.tbAutoCopy && this.tokenBombOutput) {
this.$nextTick(() => this.copyToClipboard(this.tokenBombOutput));
}
},
applyTokenadePreset(preset) {
if (preset === 'feather') {
this.tbDepth = 1; this.tbBreadth = 3; this.tbRepeats = 2; this.tbSeparator = 'zwnj';
this.tbIncludeVS = false; this.tbIncludeNoise = false; this.tbRandomizeEmojis = true;
} else if (preset === 'light') {
this.tbDepth = 2; this.tbBreadth = 3; this.tbRepeats = 3; this.tbSeparator = 'zwnj';
this.tbIncludeVS = false; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
} else if (preset === 'middle') {
this.tbDepth = 3; this.tbBreadth = 4; this.tbRepeats = 6; this.tbSeparator = 'zwnj';
this.tbIncludeVS = true; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
} else if (preset === 'heavy') {
this.tbDepth = 4; this.tbBreadth = 6; this.tbRepeats = 12; this.tbSeparator = 'zwnj';
this.tbIncludeVS = true; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
} else if (preset === 'super') {
this.tbDepth = 5; this.tbBreadth = 8; this.tbRepeats = 18; this.tbSeparator = 'zwnj';
this.tbIncludeVS = true; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
}
this.showNotification('<i class="fas fa-sliders-h"></i> Preset applied', 'success');
},
// Live estimator for pre-generation length
estimateTokenadeLength() {
const depth = Math.max(1, Math.min(8, Number(this.tbDepth) || 1));
const breadth = Math.max(1, Math.min(10, Number(this.tbBreadth) || 1));
const repeats = Math.max(1, Math.min(50, Number(this.tbRepeats) || 1));
const sepLen = this.tbSeparator === 'none' ? 0 : 1;
const vsPerEmoji = this.tbIncludeVS ? 1 : 0;
const noiseAvg = this.tbIncludeNoise ? 2 : 0;
function lenLevel(level) {
if (level === 0) {
return breadth * (1 + vsPerEmoji);
}
const inner = lenLevel(level - 1);
return breadth * (inner + noiseAvg) + Math.max(0, breadth - 1) * sepLen;
}
if (this.tbSingleCarrier) {
function countUnits(level) { return level === 0 ? breadth : breadth * countUnits(level - 1); }
const unitsPerBlock = countUnits(depth - 1);
const totalUnits = Math.max(1, repeats * unitsPerBlock);
const avgDigits = 5; // avg hex digits in tag sequence
const perUnit = avgDigits + 1 + sepLen + (this.tbIncludeNoise ? 2 : 0); // tags+term + sep + noise
const carrierLen = 1 + (this.tbIncludeVS ? 1 : 0);
return carrierLen + totalUnits * perUnit;
} else {
const blockLen = lenLevel(depth - 1);
return repeats * (blockLen + noiseAvg) + Math.max(0, repeats - 1) * sepLen;
}
},
setCarrierFromSelected() {
if (this.selectedEmoji) this.tbCarrier = String(this.selectedEmoji);
},
clearTokenadePayload() { this.tbPayloadEmojis = []; },
removeTokenadePayloadAt(idx) { this.tbPayloadEmojis.splice(idx, 1); },
onCarrierInput() {
const q = (this.tbCarrier || '').trim();
if (!q) {
this.carrierEmojiList = [...window.emojiLibrary.EMOJI_LIST];
return;
}
// Filter emoji list by simple name guess or by including the character; if q contains an emoji, keep it
const list = window.emojiLibrary.EMOJI_LIST;
const byChar = list.filter(e => e.includes(q));
// Also support colon-like query (e.g., ':heart') by rough keywords
const keywords = {
heart: ['❤️','💛','💚','💙','💜','💖','💘','💝','💗'],
star: ['⭐','🌟'],
fire: ['🔥'],
bomb: ['💣'],
snake: ['🐍'],
dragon: ['🐉','🐲'],
skull: ['💀'],
sparkles: ['✨'],
moon: ['🌑','🌒','🌓','🌔','🌕','🌖','🌗','🌘','🌙']
};
let byKey = [];
const qk = q.replace(/[:_\s]/g,'').toLowerCase();
Object.keys(keywords).forEach(k => {
if (k.indexOf(qk) !== -1) byKey = byKey.concat(keywords[k]);
});
const merged = Array.from(new Set([...byChar, ...byKey]));
this.carrierEmojiList = merged.length ? merged : [...window.emojiLibrary.EMOJI_LIST];
},
handleTokenadeDrop(e) {
e.preventDefault();
const text = e.dataTransfer && (e.dataTransfer.getData('text/plain') || e.dataTransfer.getData('text'));
if (!text) return;
const parts = window.emojiLibrary.splitEmojis(text);
const onlyEmojis = parts.filter(p => /\p{Extended_Pictographic}/u.test(p));
this.tbPayloadEmojis.push(...onlyEmojis);
},
handleTokenadePaste(e) {
const text = (e.clipboardData && e.clipboardData.getData('text')) || '';
if (!text) return;
const parts = window.emojiLibrary.splitEmojis(text);
const onlyEmojis = parts.filter(p => /\p{Extended_Pictographic}/u.test(p));
this.tbPayloadEmojis.push(...onlyEmojis);
}
,
// Tokenizer visualization
async runTokenizer() {
const text = this.tokenizerInput || '';
const engine = this.tokenizerEngine;
const tokens = [];
if (!text) { this.tokenizerTokens = []; this.tokenizerCharCount = 0; this.tokenizerWordCount = 0; return; }
if (engine === 'byte') {
// Split into UTF-8 bytes, display hex and glyphs
const encoder = new TextEncoder();
const bytes = encoder.encode(text);
for (let i=0;i<bytes.length;i++) {
tokens.push({ id: bytes[i], text: `0x${bytes[i].toString(16).padStart(2,'0')}` });
}
} else if (engine === 'word') {
// Naive word split incl. punctuation
const parts = text.split(/(\s+|[\.,!?:;()\[\]{}])/);
for (const p of parts) { if (p) tokens.push({ text: p }); }
} else if (['cl100k','o200k','p50k','r50k'].includes(engine)) {
try {
if (!window.gptTok) {
window.gptTok = await import('https://cdn.jsdelivr.net/npm/gpt-tokenizer@2/+esm');
}
const map = { cl100k: 'cl100k_base', o200k: 'o200k_base', p50k: 'p50k_base', r50k: 'r50k_base' };
const enc = map[engine];
const ids = window.gptTok.encode(text, enc);
for (const id of ids) {
const piece = window.gptTok.decode([id], enc);
tokens.push({ id, text: piece });
}
} catch (e) {
console.warn('Failed to load/use gpt-tokenizer; falling back to bytes', e);
this.tokenizerEngine = 'byte';
return this.runTokenizer();
}
} else {
// Fallback to bytes
const encoder = new TextEncoder();
const bytes = encoder.encode(text);
for (let i=0;i<bytes.length;i++) tokens.push({ id: bytes[i], text: `0x${bytes[i].toString(16).padStart(2,'0')}` });
}
this.tokenizerTokens = tokens;
// Counts
this.tokenizerCharCount = Array.from(text).length;
const wordMatches = text.trim().match(/[^\s]+/g) || [];
this.tokenizerWordCount = wordMatches.length;
}
,
generateTextPayload() {
const base = String(this.tpBase || 'A');
const count = Math.max(1, Math.min(10000, Number(this.tpRepeat) || 1));
const combining = this.tpCombining;
const addZW = this.tpZW;
const marks = ['\u0301','\u0300','\u0302','\u0303','\u0308','\u0307','\u0304'];
const zw = ['\u200B','\u200C','\u200D','\u2060'];
let out = '';
for (let i=0;i<count;i++) {
let token = base;
if (combining) {
const m = marks[i % marks.length];
token += m;
}
if (addZW) {
const z = zw[i % zw.length];
token += z;
}
out += token;
}
this.textPayload = out;
this.showNotification('<i class="fas fa-bomb"></i> Text payload generated', 'success');
}
},
// 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();
});
}
}
});