refactor: migrate to modular tool-based architecture

- Implement tool registry system with individual tool modules
- Reorganize transformers into categorized source modules
- Remove emojiLibrary.js, consolidate into EmojiUtils and emojiData
- Fix mobile close button and tooltip functionality
- Add build system for transforms and emoji data
- Migrate from Python backend to pure JavaScript
- Add comprehensive documentation and testing
- Improve code organization and maintainability
- Ignore generated files (transforms-bundle.js, emojiData.js)
This commit is contained in:
Dustin Farley
2025-12-02 20:26:32 -08:00
parent 105084437a
commit dc10a90851
146 changed files with 12712 additions and 8171 deletions
+39
View File
@@ -0,0 +1,39 @@
# JavaScript Directory Structure
## Core Modules (`js/core/`)
- `decoder.js` - Universal decoder for automatic encoding detection
- `steganography.js` - Emoji and invisible text steganography
- `emojiLibrary.js` - Emoji search, filtering, and library functions
- `toolRegistry.js` - Tool registration and Vue data/method merging
## Utilities (`js/utils/`)
- `clipboard.js` - `ClipboardUtils.copy()` - Clipboard API wrapper
- `focus.js` - `FocusUtils.focusWithoutScroll()`, `clearFocusAndSelection()`
- `history.js` - `HistoryUtils` - Copy history management
- `notifications.js` - `NotificationUtils` - Toast notifications
- `theme.js` - `ThemeUtils` - Dark/light theme management
- `escapeParser.js` - Escape sequence parsing
## Tools (`js/tools/`)
Tool classes extending `Tool` base class. Auto-discovered by `build/inject-tool-scripts.js`.
## Data (`js/data/`)
- `emojiData.js` - Generated emoji data (build output)
- `emojiCompatibility.js` - Emoji compatibility mappings
## Bundles (`js/bundles/`)
- `transforms-bundle.js` - Bundled transformer modules (build output)
## Load Order
1. Data files (emojiData, emojiCompatibility)
2. Generated bundles (transforms-bundle)
3. Utilities (escapeParser, focus, notifications, history, clipboard, theme)
4. Core modules (steganography, decoder, emojiLibrary)
5. Tool system (Tool.js, *Tool.js files, toolRegistry)
6. Main app (app.js)
+265 -2256
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
/**
* Application Configuration Constants
*/
window.CONFIG = {
// History configuration
MAX_HISTORY_ITEMS: 50,
// Danger threshold for tokenade
DANGER_THRESHOLD_TOKENS: 10000,
// Clipboard operation timing
CLIPBOARD_DEBOUNCE_MS: 100,
CLIPBOARD_LOCK_TIMEOUT_MS: 500,
CLIPBOARD_FALLBACK_DEBOUNCE_MS: 150,
KEYBOARD_EVENTS_TIMEOUT_MS: 1000,
PASTE_FLAG_RESET_DELAY_MS: 200,
// Emoji grid initialization
EMOJI_GRID_INIT_INTERVAL_MS: 500
};
+101
View File
@@ -0,0 +1,101 @@
function universalDecode(input, context = {}) {
if (!input) return null;
const allDecodings = [];
const { activeTab, activeTransform } = context;
function addDecoding(text, method, priority = 20) {
if (text && text !== input && text.length > 0) {
const exists = allDecodings.some(d => d.text === text);
if (!exists) {
allDecodings.push({ text, method, priority });
}
}
}
let foundHighPriorityMatch = false;
for (const [transformKey, transform] of Object.entries(window.transforms)) {
if (transform.detector && transform.reverse) {
try {
if (transform.detector(input)) {
const result = transform.reverse(input);
if (result && result !== input && result.length > 0) {
const hasContent = result.replace(/[\x00-\x1F\x7F-\x9F\s]/g, '').length > 0;
if (hasContent) {
const detectorPriority = transform.priority || 285;
addDecoding(result, transform.name, detectorPriority);
if (detectorPriority >= 280) {
foundHighPriorityMatch = true;
}
}
}
}
} catch (e) {
console.debug('Error in transform detector:', e);
}
}
}
if (foundHighPriorityMatch || allDecodings.some(d => d.priority >= 280)) {
const exclusiveMatches = allDecodings.filter(d => d.priority >= 280);
if (exclusiveMatches.length > 0) {
exclusiveMatches.sort((a, b) => b.priority - a.priority);
return {
text: exclusiveMatches[0].text,
method: exclusiveMatches[0].method,
alternatives: exclusiveMatches.slice(1).map(d => ({ text: d.text, method: d.method }))
};
}
}
if (window.steganography && window.steganography.hasEmojiInText && window.steganography.hasEmojiInText(input)) {
try {
const decoded = window.steganography.decodeEmoji(input);
if (decoded) {
addDecoding(decoded, 'Emoji Steganography', 100);
}
} catch (e) {
console.debug('Error decoding emoji steganography:', e);
}
}
if (activeTab === 'transforms' && activeTransform) {
try {
const transformKey = Object.keys(window.transforms).find(
key => window.transforms[key].name === activeTransform.name
);
if (transformKey && window.transforms[transformKey].reverse) {
const result = window.transforms[transformKey].reverse(input);
if (result && result !== input) {
addDecoding(result, activeTransform.name, 150);
}
}
} catch (e) {
console.error('Error decoding with active transform:', e);
}
}
for (const name in window.transforms) {
const transform = window.transforms[name];
if (transform.reverse && !transform.detector) {
try {
const result = transform.reverse(input);
if (result !== input && /[a-zA-Z0-9\s]{3,}/.test(result)) {
addDecoding(result, transform.name, 10);
}
} catch (e) {
console.error(`Error decoding with ${name}:`, e);
}
}
}
allDecodings.sort((a, b) => b.priority - a.priority);
if (allDecodings.length === 0) return null;
const primary = allDecodings[0];
const alternatives = allDecodings.slice(1).map(({ text, method }) => ({ text, method }));
return { text: primary.text, method: primary.method, alternatives };
}
+270
View File
@@ -0,0 +1,270 @@
const __STEG_DEFAULTS__ = {
bitZeroVS: '\ufe0e',
bitOneVS: '\ufe0f',
initialPresentation: 'emoji',
trailingZW: '\u200B',
interBitZW: null,
interBitEvery: 1,
bitOrder: 'msb'
};
let __stegOptions__ = Object.assign({}, __STEG_DEFAULTS__);
function setStegOptions(opts) {
if (!opts) return;
__stegOptions__ = Object.assign({}, __stegOptions__, opts);
}
function encodeForPreview(emoji, text) {
return encodeEmoji(emoji, text);
}
function hasEmojiInText(text) {
if (!text) return false;
if (window.emojiData && typeof window.emojiData === 'object') {
const emojiKeys = Object.keys(window.emojiData).filter(key => {
const value = window.emojiData[key];
return typeof value === 'object' && value !== null && 'official' in value;
});
if (emojiKeys.some(emoji => text.includes(emoji))) return true;
}
return /[\u{1F300}-\u{1F9FF}\u{1FA00}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F1E6}-\u{1F1FF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}]/u.test(text);
}
function findEmojiMatch(text) {
if (!text) return null;
if (window.emojiData && typeof window.emojiData === 'object') {
const emojiKeys = Object.keys(window.emojiData).filter(key => {
const value = window.emojiData[key];
return typeof value === 'object' && value !== null && 'official' in value;
});
if (emojiKeys.length > 0) {
emojiKeys.sort((a, b) => b.length - a.length);
const escapedEmojis = emojiKeys.map(emoji =>
emoji.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
);
const emojiRegex = new RegExp(`(${escapedEmojis.join('|')})`, 'u');
const match = text.match(emojiRegex);
if (match) return match;
}
}
const flagEmojiRegex = /([\u{1F1E6}-\u{1F1FF}][\u{1F1E6}-\u{1F1FF}])/u;
const singleEmojiRegex = /([\u{1F300}-\u{1F9FF}\u{1FA00}-\u{1FAFF}\u{2600}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}])/u;
return text.match(flagEmojiRegex) || text.match(singleEmojiRegex);
}
const carriers = [
{
emoji: '🐍',
name: 'SNAKE',
desc: 'Classic Snake',
preview: function(text) {
return encodeForPreview(this.emoji, text);
}
},
{
emoji: '🐉',
name: 'DRAGON',
desc: 'Mystical Dragon',
preview: function(text) {
return encodeForPreview(this.emoji, text);
}
},
{
emoji: '🦎',
name: 'LIZARD',
desc: 'Sneaky Lizard',
preview: function(text) {
return encodeForPreview(this.emoji, text);
}
},
{
emoji: '🐊',
name: 'CROCODILE',
desc: 'Dangerous Croc',
preview: function(text) {
return encodeForPreview(this.emoji, text);
}
}
];
function encodeEmoji(emoji, text) {
if (!text) return emoji;
let binary = '';
try {
const encoder = new TextEncoder();
const bytes = encoder.encode(text);
const bitOrder = __stegOptions__.bitOrder || 'msb';
binary = Array.from(bytes)
.map(byte => {
let byteStr = byte.toString(2).padStart(8, '0');
if (bitOrder === 'lsb') {
byteStr = byteStr.split('').reverse().join('');
}
return byteStr;
})
.join('');
} catch (e) {
const bitOrder = __stegOptions__.bitOrder || 'msb';
binary = Array.from(text)
.map(c => {
const codePoint = c.codePointAt(0);
let bytes = [];
if (codePoint <= 0x7F) {
bytes.push(codePoint);
} else if (codePoint <= 0x7FF) {
bytes.push(0xC0 | (codePoint >> 6));
bytes.push(0x80 | (codePoint & 0x3F));
} else if (codePoint <= 0xFFFF) {
bytes.push(0xE0 | (codePoint >> 12));
bytes.push(0x80 | ((codePoint >> 6) & 0x3F));
bytes.push(0x80 | (codePoint & 0x3F));
} else {
bytes.push(0xF0 | (codePoint >> 18));
bytes.push(0x80 | ((codePoint >> 12) & 0x3F));
bytes.push(0x80 | ((codePoint >> 6) & 0x3F));
bytes.push(0x80 | (codePoint & 0x3F));
}
return bytes.map(byte => {
let byteStr = byte.toString(2).padStart(8, '0');
if (bitOrder === 'lsb') {
byteStr = byteStr.split('').reverse().join('');
}
return byteStr;
}).join('');
})
.join('');
}
const vs0 = __stegOptions__.bitZeroVS || '\ufe0e';
const vs1 = __stegOptions__.bitOneVS || '\ufe0f';
let result = emoji;
if (__stegOptions__.initialPresentation === 'emoji') result += '\ufe0f';
else if (__stegOptions__.initialPresentation === 'text') result += '\ufe0e';
for (let i=0;i<binary.length;i++) {
const bit = binary[i];
result += bit === '0' ? vs0 : vs1;
if (__stegOptions__.interBitZW && i < binary.length-1 && ((i+1) % Math.max(1, __stegOptions__.interBitEvery)) === 0) {
result += __stegOptions__.interBitZW;
}
}
if (__stegOptions__.trailingZW) {
result += __stegOptions__.trailingZW;
}
return result;
}
function decodeEmoji(text) {
if (!text) return '';
const emojiMatch = findEmojiMatch(text);
if (!emojiMatch) return '';
const emojiChar = emojiMatch[1];
const emojiIndex = emojiMatch.index;
const fromEmoji = text.substring(emojiIndex);
const emojiCharEscaped = emojiChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const pattern = new RegExp(`^${emojiCharEscaped}([\ufe0e\ufe0f\u200B\u200C\u200D\ufeff]+)`, 'u');
const emojiData = fromEmoji.match(pattern);
if (!emojiData || !emojiData[1]) return '';
const rawSeq = emojiData[1];
const matches = [...rawSeq.matchAll(/[\ufe0e\ufe0f]/g)];
if (matches.length === 0) return '';
const skip = (__stegOptions__.initialPresentation === 'none') ? 0 : 1;
if (matches.length <= skip) return '';
const zeroSel = __stegOptions__.bitZeroVS || '\ufe0e';
const oneSel = __stegOptions__.bitOneVS || '\ufe0f';
let binary = matches.slice(skip).map(m => m[0] === zeroSel ? '0' : (m[0] === oneSel ? '1' : '')).join('');
const validBinaryLength = Math.floor(binary.length / 8) * 8;
const bytes = [];
for (let i = 0; i < validBinaryLength; i += 8) {
let byte = binary.slice(i, i + 8);
if (__stegOptions__.bitOrder === 'lsb') {
byte = byte.split('').reverse().join('');
}
if (byte.length === 8) {
const byteValue = parseInt(byte, 2);
bytes.push(byteValue);
}
}
try {
const decoder = new TextDecoder('utf-8', { fatal: false });
const uint8Array = new Uint8Array(bytes);
return decoder.decode(uint8Array);
} catch (e) {
let decoded = '';
for (const byteValue of bytes) {
if (byteValue >= 0 && byteValue <= 255) {
decoded += String.fromCharCode(byteValue);
}
}
try {
return decodeURIComponent(escape(decoded));
} catch (e2) {
return decoded;
}
}
}
function encodeInvisible(text) {
if (!text) return '';
const bytes = new TextEncoder().encode(text);
return Array.from(bytes)
.map(byte => String.fromCodePoint(0xE0000 + byte))
.join('');
}
function decodeInvisible(text) {
if (!text) return '';
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
if (!matches.length) return '';
const bytes = new Uint8Array(matches.length);
for (let i = 0; i < matches.length; i++) {
bytes[i] = matches[i][0].codePointAt(0) - 0xE0000;
}
try {
const decoder = new TextDecoder('utf-8', {fatal: false});
let decoded = decoder.decode(bytes);
decoded = decoded.replace(/@+(?=[a-zA-Z0-9])/g, '');
decoded = decoded.replace(/([a-zA-Z0-9])@+/g, '$1');
decoded = decoded.replace(/@+/g, '');
return decoded;
} catch (e) {
console.error('Error decoding invisible text:', e);
let result = '';
for (let i = 0; i < bytes.length; i++) {
if (bytes[i] >= 32 && bytes[i] <= 126) {
result += String.fromCharCode(bytes[i]);
}
}
return result;
}
}
window.steganography = {
carriers,
encodeEmoji,
decodeEmoji,
encodeInvisible,
decodeInvisible,
setStegOptions,
hasEmojiInText
};
+209
View File
@@ -0,0 +1,209 @@
/**
* Tool Registry and Loader
* Manages all available tools and provides dynamic loading
*/
// Import all tools (they should be loaded before this file)
// Tools will be registered here
class ToolRegistry {
constructor() {
this.tools = new Map();
this.toolsArray = [];
}
/**
* Register a tool
* @param {Tool} tool - Tool instance to register
*/
register(tool) {
if (!(tool instanceof Tool)) {
console.error('Tool must be an instance of Tool class');
return;
}
if (!tool.enabled) {
return;
}
this.tools.set(tool.id, tool);
this.toolsArray.push(tool);
// Sort by order
this.toolsArray.sort((a, b) => a.order - b.order);
}
/**
* Get a tool by ID
* @param {string} id - Tool ID
* @returns {Tool|null}
*/
get(id) {
return this.tools.get(id) || null;
}
/**
* Get all registered tools
* @returns {Array<Tool>}
*/
getAll() {
return this.toolsArray;
}
/**
* Get all enabled tools
* @returns {Array<Tool>}
*/
getEnabled() {
return this.toolsArray.filter(tool => tool.enabled);
}
/**
* Merge Vue data from all tools
* @returns {Object}
*/
mergeVueData() {
const merged = {};
this.toolsArray.forEach(tool => {
const toolData = tool.getVueData();
Object.assign(merged, toolData);
});
return merged;
}
/**
* Merge Vue methods from all tools
* @returns {Object}
*/
mergeVueMethods() {
const merged = {};
this.toolsArray.forEach(tool => {
const toolMethods = tool.getVueMethods();
Object.assign(merged, toolMethods);
});
return merged;
}
/**
* Merge Vue watchers from all tools
* @returns {Object}
*/
mergeVueWatchers() {
const merged = {};
this.toolsArray.forEach(tool => {
const toolWatchers = tool.getVueWatchers();
Object.assign(merged, toolWatchers);
});
return merged;
}
/**
* Merge Vue lifecycle hooks from all tools
* @returns {Object}
*/
mergeVueLifecycle() {
const merged = {};
this.toolsArray.forEach(tool => {
const toolLifecycle = tool.getVueLifecycle();
Object.keys(toolLifecycle).forEach(hook => {
if (!merged[hook]) {
merged[hook] = [];
}
merged[hook].push(toolLifecycle[hook]);
});
});
// Convert arrays to functions that call all hooks
const result = {};
Object.keys(merged).forEach(hook => {
result[hook] = function() {
const args = arguments;
merged[hook].forEach(fn => {
if (typeof fn === 'function') {
fn.apply(this, args);
}
});
};
});
return result;
}
/**
* Generate HTML for all tab buttons
* @returns {String}
*/
generateTabButtonsHTML() {
return this.toolsArray.map(tool => tool.getTabButtonHTML()).join('\n');
}
/**
* Generate HTML for all tab content
* @returns {String}
*/
generateTabContentHTML() {
return this.toolsArray.map(tool => tool.getTabContentHTML()).join('\n');
}
/**
* Handle tool activation
* @param {string} toolId - Tool ID
* @param {Vue} vueInstance - Vue instance
*/
activateTool(toolId, vueInstance) {
const tool = this.get(toolId);
if (tool && typeof tool.onActivate === 'function') {
tool.onActivate(vueInstance);
}
}
/**
* Handle tool deactivation
* @param {string} toolId - Tool ID
* @param {Vue} vueInstance - Vue instance
*/
deactivateTool(toolId, vueInstance) {
const tool = this.get(toolId);
if (tool && typeof tool.onDeactivate === 'function') {
tool.onDeactivate(vueInstance);
}
}
}
// Create global registry instance
window.ToolRegistry = ToolRegistry;
window.toolRegistry = new ToolRegistry();
// Auto-register tools if they're available
if (typeof DecodeTool !== 'undefined') {
window.toolRegistry.register(new DecodeTool());
}
if (typeof EmojiTool !== 'undefined') {
window.toolRegistry.register(new EmojiTool());
}
if (typeof GibberishTool !== 'undefined') {
window.toolRegistry.register(new GibberishTool());
}
if (typeof MutationTool !== 'undefined') {
window.toolRegistry.register(new MutationTool());
}
if (typeof SplitterTool !== 'undefined') {
window.toolRegistry.register(new SplitterTool());
}
if (typeof TokenadeTool !== 'undefined') {
window.toolRegistry.register(new TokenadeTool());
}
if (typeof TokenizerTool !== 'undefined') {
window.toolRegistry.register(new TokenizerTool());
}
if (typeof TransformTool !== 'undefined') {
window.toolRegistry.register(new TransformTool());
}
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = ToolRegistry;
}
+208
View File
@@ -0,0 +1,208 @@
/**
* Emoji Compatibility Checker
* Tests which emoji features the user's browser/device supports
*/
window.emojiCompatibility = {
// Cache key for localStorage
CACHE_KEY: 'emojiTestResults_v2_simple', // Simple pixel detection only
CACHE_EXPIRY_DAYS: 30,
// In-memory cache for emoji test results
_emojiTestCache: null,
/**
* Load emoji test cache from localStorage
*/
loadCache: function() {
if (this._emojiTestCache) return this._emojiTestCache;
try {
const cached = localStorage.getItem(this.CACHE_KEY);
if (!cached) return null;
const data = JSON.parse(cached);
// Check if cache is expired
const now = Date.now();
const age = now - data.timestamp;
const maxAge = this.CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000;
if (age > maxAge) {
localStorage.removeItem(this.CACHE_KEY);
return null;
}
this._emojiTestCache = data.results;
return this._emojiTestCache;
} catch (e) {
return null;
}
},
/**
* Save emoji test results to localStorage
* (Called after testing all emojis)
*/
saveCache: function() {
if (!this._emojiTestCache) return;
try {
const data = {
timestamp: Date.now(),
results: this._emojiTestCache
};
localStorage.setItem(this.CACHE_KEY, JSON.stringify(data));
} catch (e) {
console.warn('⚠️ Could not save emoji test cache:', e);
}
},
/**
* Clear the emoji test cache (useful for debugging or forcing refresh)
*/
clearCache: function() {
localStorage.removeItem(this.CACHE_KEY);
this._emojiTestCache = null;
},
/**
* Test if a specific emoji actually renders in the browser
* Uses canvas pixel detection - the definitive test for visual rendering
*/
testEmojiRenders: function(emoji) {
// Load cache if not already loaded
if (!this._emojiTestCache) {
this._emojiTestCache = this.loadCache() || {};
}
// Check cache first
if (emoji in this._emojiTestCache) {
return this._emojiTestCache[emoji];
}
// Cache canvas for performance
if (!this._testCanvas) {
this._testCanvas = document.createElement('canvas');
this._testCanvas.width = 64;
this._testCanvas.height = 64;
// Set willReadFrequently for better performance with multiple getImageData calls
this._testCtx = this._testCanvas.getContext('2d', { willReadFrequently: true });
}
const ctx = this._testCtx;
// Use emoji font to ensure missing emojis render as boxes
ctx.font = '48px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "EmojiOne Color", "Android Emoji", sans-serif';
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
// Width test - catches multi-character fallbacks like "???"
const emojiWidth = ctx.measureText(emoji).width;
const referenceWidth = ctx.measureText('😊').width;
// If emoji is much wider than a single emoji, it's likely broken into multiple chars
if (emojiWidth > referenceWidth * 1.8) {
this._emojiTestCache[emoji] = false;
return false;
}
// Pixel detection - does the emoji actually render visually?
ctx.clearRect(0, 0, 64, 64);
ctx.fillStyle = 'black';
ctx.fillText(emoji, 8, 8);
const imageData = ctx.getImageData(0, 0, 64, 64).data;
// Check if any pixels were drawn (alpha channel > 0)
let hasPixels = false;
for (let i = 0; i < imageData.length; i += 4) {
if (imageData[i + 3] > 0) {
hasPixels = true;
break;
}
}
// Cache and return result
this._emojiTestCache[emoji] = hasPixels;
return hasPixels;
},
/**
* Check if a specific emoji should be shown in the UI picker
* based on browser compatibility
*/
shouldShowInPicker: function(emoji, data) {
// Simple check: Does it actually render?
// This single test catches all broken emojis regardless of type
return this.testEmojiRenders(emoji);
},
/**
* Get compatible emojis from a list (batch testing with progress callback)
* @param {Array<string>} allEmojis - Full list of emojis to test
* @param {Function} progressCallback - Optional callback (tested, total, compatible)
* @returns {Promise<Array<string>>} - Array of compatible emojis
*/
getCompatibleEmojis: async function(allEmojis, progressCallback) {
// Load cache first
this.loadCache();
const compatible = [];
let tested = 0;
const total = allEmojis.length;
// Test emojis in batches to avoid blocking
const batchSize = 50;
function testBatch() {
return new Promise((resolve) => {
const end = Math.min(tested + batchSize, total);
for (let i = tested; i < end; i++) {
const emoji = allEmojis[i];
if (this.shouldShowInPicker(emoji)) {
compatible.push(emoji);
}
tested++;
}
// Report progress
if (progressCallback) {
progressCallback(tested, total, compatible.length);
}
// Continue or finish
if (tested < total) {
requestAnimationFrame(() => {
setTimeout(() => resolve(testBatch.call(this)), 10);
});
} else {
// Save cache when done
this.saveCache();
resolve();
}
});
}
await testBatch.call(this);
return compatible;
},
/**
* Get compatibility stats
*/
getStats: function() {
const cache = this.loadCache();
if (cache) {
const compatible = Object.values(cache).filter(v => v === true).length;
const total = Object.keys(cache).length;
return {
compatible: compatible,
total: total,
percentage: total > 0 ? ((compatible / total) * 100).toFixed(1) : 0
};
}
return null;
}
};
-232
View File
@@ -1,232 +0,0 @@
// Emoji Library for P4RS3LT0NGV3
// Create namespace for emoji library
window.emojiLibrary = {};
// Polyfill for Intl.Segmenter if not available
if (!Intl.Segmenter) {
console.warn('Intl.Segmenter not available, falling back to basic character splitting');
}
// Helper function to properly split text into grapheme clusters (emojis)
window.emojiLibrary.splitEmojis = function(text) {
if (Intl.Segmenter) {
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
return Array.from(segmenter.segment(text), ({ segment }) => segment);
}
return Array.from(text);
};
// Helper function to properly join emojis
window.emojiLibrary.joinEmojis = function(emojis) {
return emojis.join('');
};
// Define emoji categories with specific emojis for each category
window.emojiLibrary.EMOJIS = {
nature: ["🌈", "🌞", "🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🦊", "🦁", "🐯", "🐮", "🐷", "🐸", "🐵", "🐔", "🐧", "🐦", "🐤", "🦆", "🦅", "🦉", "🦇", "🐺", "🐗", "🐴", "🦄", "🐝", "🐛", "🦋", "🐌", "🐞", "🐜", "🕷️", "🦂", "🦟", "🦠", "🪱"],
mystical: ["🧙", "🧙‍♂️", "🧙‍♀️", "🧚", "🧚‍♂️", "🧚‍♀️", "🧛", "🧛‍♂️", "🧛‍♀️", "🧜", "🧜‍♂️", "🧜‍♀️", "👹", "👺", "👻", "👽", "👾", "🐲", "🔮", "🐍", "🐉", "🦄", "⚗️", "🔯", "🔱", "⚜️", "✨", "🌠", "🌋", "💎", "🩸"],
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: '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: '🏁' }
];
// Auto-generate EMOJI_LIST from the categorized EMOJIS object
// This ensures a single source of truth for all emojis
window.emojiLibrary.EMOJI_LIST = (() => {
const allEmojis = [];
// Combine all emojis from all categories
Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => {
allEmojis.push(...categoryEmojis);
});
// Remove duplicates using Set and return as array
return Array.from(new Set(allEmojis));
})();
// Function to render emoji grid with categories
window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filteredList) {
console.log('Rendering emoji grid to:', containerId);
// Get container by ID
const container = document.getElementById(containerId);
if (!container) {
console.error('Container not found:', containerId);
return;
}
// Clear container
container.innerHTML = '';
// Add header with instruction message
const emojiHeader = document.createElement('div');
emojiHeader.className = 'emoji-header';
emojiHeader.innerHTML = '<h3><i class="fas fa-icons"></i> Choose an Emoji</h3><p class="emoji-subtitle"><i class="fas fa-magic"></i> Click any emoji to copy your hidden message</p>';
container.appendChild(emojiHeader);
// Create category tabs
const categoryTabs = document.createElement('div');
categoryTabs.className = 'emoji-category-tabs';
// Add category tabs
window.emojiLibrary.CATEGORIES.forEach(category => {
const tab = document.createElement('button');
tab.className = 'emoji-category-tab';
if (category.id === 'all') {
tab.classList.add('active');
}
tab.setAttribute('data-category', category.id);
tab.innerHTML = `${category.icon} ${category.name}`;
categoryTabs.appendChild(tab);
});
container.appendChild(categoryTabs);
// Create emoji grid with enforced styling
const gridContainer = document.createElement('div');
gridContainer.className = 'emoji-grid';
// Get the active category
let activeCategory = 'all';
const activeCategoryTab = container.querySelector('.emoji-category-tab.active');
if (activeCategoryTab) {
activeCategory = activeCategoryTab.getAttribute('data-category');
}
// 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 and deduplicate
Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => {
emojisToShow = [...emojisToShow, ...categoryEmojis];
});
// Remove duplicates using Set
emojisToShow = Array.from(new Set(emojisToShow));
} 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 => {
const emojiButton = document.createElement('button');
emojiButton.className = 'emoji-button';
emojiButton.textContent = emoji; // Use textContent for better emoji handling
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);
});
container.appendChild(gridContainer);
console.log('Emoji grid rendering complete');
// 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');
// Re-render the emoji grid with the selected category
const selectedCategory = tab.getAttribute('data-category');
console.log('Category selected:', selectedCategory);
// Determine which emojis to show
let emojisToShow = [];
if (selectedCategory === 'all') {
// For 'all' category, combine all emojis from the categories and deduplicate
Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => {
emojisToShow = [...emojisToShow, ...categoryEmojis];
});
// Remove duplicates using Set
emojisToShow = Array.from(new Set(emojisToShow));
} else if (window.emojiLibrary.EMOJIS[selectedCategory]) {
// For specific category, use emojis from that category
emojisToShow = window.emojiLibrary.EMOJIS[selectedCategory];
}
console.log(`Updating grid with ${emojisToShow.length} emojis for category: ${selectedCategory}`);
// Clear only the grid and rebuild it
gridContainer.innerHTML = '';
// Add emojis to grid
emojisToShow.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 = `${emojisToShow.length} emojis available`;
}
});
});
// Debug info - add count display
const countDisplay = document.createElement('div');
countDisplay.className = 'emoji-count';
countDisplay.textContent = `${emojisToShow.length} emojis available`;
container.appendChild(countDisplay);
};
-853
View File
@@ -1,853 +0,0 @@
// Emoji Word Map for Emoji Speak Transform
// Maps emojis to arrays of related keywords
// When a word is typed, a random emoji from matching entries is returned
window.emojiKeywords = {
// Emotions & Feelings - Happy
'😊': ['happy', 'smile', 'pleased', 'content', 'glad'],
'😁': ['grin', 'smile', 'happy', 'excited', 'beaming', 'haha', 'ha', 'hehe'],
'😂': ['laugh', 'lol', 'crying', 'tears', 'funny', 'hilarious', 'haha', 'ha', 'hehe'],
'🤣': ['laugh', 'rofl', 'lol', 'rolling', 'hilarious', 'haha', 'ha', 'hehe'],
'😄': ['happy', 'smile', 'joy', 'cheerful', 'haha', 'ha', 'hehe'],
'😃': ['happy', 'smile', 'excited', 'haha', 'ha', 'hehe'],
'🤩': ['excited', 'starstruck', 'wow', 'amazing'],
'😍': ['love', 'heart', 'adore', 'crush'],
'🥰': ['love', 'hearts', 'affection', 'sweet'],
'😘': ['kiss', 'love', 'smooch', 'mwah'],
'😉': ['wink', 'flirt', 'playful'],
// Emotions - Sad & Negative
'😢': ['sad', 'cry', 'tear', 'upset'],
'😭': ['cry', 'sobbing', 'bawling', 'sad', 'tears'],
'😔': ['sad', 'pensive', 'down', 'disappointed'],
'😞': ['sad', 'disappointed', 'upset', 'lose'],
'😟': ['worried', 'anxious', 'concerned'],
'😕': ['confused', 'uncertain', 'puzzled'],
'🤔': ['think', 'thinking', 'hmm', 'wonder', 'ponder'],
'😐': ['neutral', 'meh', 'blank', 'expressionless'],
// Emotions - Angry
'😡': ['angry', 'mad', 'furious', 'rage', 'pissed'],
'😠': ['angry', 'mad', 'annoyed', 'grumpy'],
'🤬': ['angry', 'cursing', 'swearing', 'rage'],
// Emotions - Surprised & Shocked
'😮': ['wow', 'surprised', 'shocked', 'amazed'],
'😲': ['shocked', 'surprised', 'astonished'],
'😱': ['scared', 'shock', 'screaming', 'fear', 'terrified'],
'😨': ['scared', 'fearful', 'afraid', 'anxious'],
// Emotions - Other
'😎': ['cool', 'sunglasses', 'smooth', 'slick'],
'😴': ['sleep', 'sleeping', 'tired', 'sleepy', 'zzz'],
'🥱': ['tired', 'sleepy', 'bored', 'yawn'],
'😰': ['nervous', 'anxious', 'sweat', 'worried'],
'😅': ['sweat', 'relief', 'phew', 'nervous'],
'🤢': ['sick', 'nauseous', 'ill', 'gross'],
'🤮': ['sick', 'vomit', 'puke', 'ill'],
'😇': ['angel', 'innocent', 'halo', 'saint'],
'😈': ['devil', 'evil', 'mischief', 'naughty'],
'💀': ['dead', 'skull', 'death', 'dying'],
'👻': ['ghost', 'boo', 'spooky', 'phantom'],
'🎉': ['party', 'celebrate', 'celebration', 'hooray', 'festive'],
'🎊': ['party', 'celebrate', 'confetti', 'celebration'],
// Animals - Pets & Common
'🐕': ['dog', 'puppy', 'pet', 'canine', 'pup'],
'🐶': ['dog', 'puppy', 'doggy', 'pet', 'pup'],
'🦮': ['dog', 'guide', 'service'],
'🐕‍🦺': ['dog', 'service'],
'🐩': ['dog', 'poodle', 'puppy'],
'🐱': ['cat', 'kitty', 'kitten', 'pet', 'feline'],
'🐈': ['cat', 'kitty', 'feline', 'pet'],
'🐈‍⬛': ['cat', 'black'],
'🐭': ['mouse', 'mice', 'rodent'],
'🐹': ['hamster', 'pet', 'rodent'],
'🐰': ['rabbit', 'bunny', 'easter', 'hare'],
'🐇': ['rabbit', 'bunny', 'hare'],
// Animals - Wild
'🦊': ['fox', 'foxy', 'sly'],
'🐻': ['bear', 'teddy'],
'🐼': ['panda', 'bear'],
'🐨': ['koala', 'bear', 'australian'],
'🐯': ['tiger', 'fierce', 'striped'],
'🦁': ['lion', 'king', 'mane', 'roar'],
'🐮': ['cow', 'cattle', 'moo'],
'🐷': ['pig', 'piggy', 'oink', 'swine'],
'🐸': ['frog', 'toad', 'ribbit'],
'🐵': ['monkey', 'primate', 'ape'],
'🐒': ['monkey', 'primate'],
'🦍': ['gorilla', 'ape', 'kong'],
'🦧': ['orangutan', 'ape'],
'🐺': ['wolf', 'howl', 'pack'],
'🦝': ['raccoon', 'trash'],
'🐴': ['horse', 'pony', 'stallion', 'mare'],
'🦄': ['unicorn', 'magical', 'fantasy', 'rainbow'],
'🦓': ['zebra', 'striped', 'stripes'],
'🦌': ['deer', 'reindeer', 'stag', 'doe'],
'🐘': ['elephant', 'trunk', 'big', 'large'],
'🦏': ['rhino', 'rhinoceros', 'horn'],
'🦛': ['hippo', 'hippopotamus'],
'🦒': ['giraffe', 'tall', 'neck'],
// Animals - Birds
'🐔': ['chicken', 'rooster', 'hen', 'poultry'],
'🐓': ['rooster', 'chicken', 'cock'],
'🐣': ['chick', 'baby', 'hatching', 'bird'],
'🐤': ['chick', 'baby', 'bird'],
'🐥': ['chick', 'duckling', 'baby', 'bird'],
'🐦': ['bird', 'birdie', 'tweet'],
'🐧': ['penguin', 'antarctic', 'bird'],
'🦆': ['duck', 'quack', 'waterfowl'],
'🦅': ['eagle', 'bird', 'freedom', 'america'],
'🦉': ['owl', 'wise', 'hoot', 'night'],
'🦇': ['bat', 'vampire', 'night', 'flying'],
'🦜': ['parrot', 'bird', 'tropical', 'colorful'],
'🦚': ['peacock', 'bird', 'fancy', 'colorful'],
// Animals - Insects & Small
'🐝': ['bee', 'buzz', 'honey', 'bumblebee'],
'🐛': ['bug', 'caterpillar', 'worm', 'insect'],
'🦋': ['butterfly', 'beautiful', 'insect', 'flying'],
'🐌': ['snail', 'slow', 'shell'],
'🐞': ['ladybug', 'bug', 'insect', 'beetle'],
'🐜': ['ant', 'insect', 'small', 'tiny'],
'🕷️': ['spider', 'web', 'arachnid', 'creepy'],
'🦂': ['scorpion', 'sting', 'desert'],
'🦟': ['mosquito', 'bug', 'bite', 'annoying'],
// Animals - Marine
'🐍': ['snake', 'serpent', 'slither', 'reptile'],
'🦎': ['lizard', 'reptile', 'gecko'],
'🐊': ['alligator', 'crocodile', 'reptile'],
'🐢': ['turtle', 'tortoise', 'slow', 'shell'],
'🐉': ['dragon', 'fantasy', 'mythical', 'fire'],
'🐲': ['dragon', 'fantasy', 'mythical'],
'🐟': ['fish', 'seafood', 'swimming'],
'🐠': ['fish', 'tropical', 'colorful'],
'🐡': ['fish', 'puffer', 'blowfish'],
'🦈': ['shark', 'jaws', 'ocean', 'dangerous'],
'🐋': ['whale', 'ocean', 'big', 'huge'],
'🐬': ['dolphin', 'ocean', 'smart', 'friendly'],
'🐙': ['octopus', 'tentacles', 'ocean', 'squid'],
'🦑': ['squid', 'octopus', 'ocean', 'tentacles'],
'🦀': ['crab', 'ocean', 'seafood', 'crustacean'],
'🦞': ['lobster', 'seafood', 'ocean', 'crustacean'],
'🦐': ['shrimp', 'prawn', 'seafood', 'ocean'],
'🦪': ['oyster', 'seafood', 'pearl', 'shell'],
// Food - Fast Food & Main
'🍕': ['pizza', 'slice', 'cheese', 'pepperoni', 'italian'],
'🍔': ['burger', 'hamburger', 'cheeseburger', 'food'],
'🍟': ['fries', 'chips', 'potato', 'french'],
'🌭': ['hotdog', 'dog', 'sausage', 'frank'],
'🌮': ['taco', 'mexican', 'shell'],
'🌯': ['burrito', 'mexican', 'wrap'],
'🥙': ['wrap', 'pita', 'gyro', 'sandwich'],
'🥪': ['sandwich', 'sub', 'lunch'],
'🥗': ['salad', 'healthy', 'greens', 'vegetables'],
'🍝': ['pasta', 'spaghetti', 'italian', 'noodles'],
'🍜': ['ramen', 'noodles', 'soup', 'asian'],
'🍲': ['stew', 'soup', 'pot', 'food'],
'🍛': ['curry', 'rice', 'indian', 'spicy'],
'🍣': ['sushi', 'japanese', 'fish', 'raw'],
'🍱': ['bento', 'lunch', 'japanese', 'box'],
'🥘': ['paella', 'food', 'dish', 'pan'],
// Food - Meat & Protein
'🍖': ['meat', 'bone', 'food', 'leg'],
'🍗': ['chicken', 'drumstick', 'meat', 'poultry'],
'🥩': ['steak', 'meat', 'beef', 'red'],
'🥓': ['bacon', 'meat', 'breakfast', 'pork'],
'🥚': ['egg', 'breakfast', 'protein'],
'🍳': ['cooking', 'egg', 'frying', 'breakfast'],
// Food - Bread & Baked
'🍞': ['bread', 'loaf', 'toast', 'baked'],
'🥐': ['croissant', 'bread', 'french', 'pastry'],
'🥖': ['baguette', 'bread', 'french'],
'🥨': ['pretzel', 'snack', 'twisted'],
'🧀': ['cheese', 'dairy', 'yellow'],
// Food - Fruits
'🍎': ['apple', 'fruit', 'red', 'healthy'],
'🍏': ['apple', 'fruit', 'green', 'granny'],
'🍊': ['orange', 'fruit', 'citrus', 'vitamin'],
'🍋': ['lemon', 'citrus', 'sour', 'yellow'],
'🍌': ['banana', 'fruit', 'yellow', 'potassium'],
'🍉': ['watermelon', 'fruit', 'summer', 'juicy'],
'🍇': ['grapes', 'fruit', 'wine', 'purple'],
'🍓': ['strawberry', 'berry', 'fruit', 'red'],
'🫐': ['blueberry', 'berry', 'fruit', 'blue'],
'🍈': ['melon', 'fruit', 'cantaloupe'],
'🍒': ['cherry', 'fruit', 'red', 'cherries'],
'🍑': ['peach', 'fruit', 'fuzzy', 'juicy'],
'🥭': ['mango', 'fruit', 'tropical', 'juicy'],
'🍍': ['pineapple', 'fruit', 'tropical', 'spiky'],
'🥥': ['coconut', 'tropical', 'palm', 'fruit'],
'🥝': ['kiwi', 'fruit', 'green', 'fuzzy'],
'🥑': ['avocado', 'fruit', 'green', 'healthy', 'guac'],
// Food - Vegetables
'🍅': ['tomato', 'vegetable', 'red', 'fruit'],
'🥔': ['potato', 'vegetable', 'spud', 'tater'],
'🥕': ['carrot', 'vegetable', 'orange', 'healthy'],
'🌽': ['corn', 'vegetable', 'yellow', 'maize'],
'🌶️': ['pepper', 'chili', 'hot', 'spicy', 'jalapeno'],
'🫑': ['pepper', 'bell', 'vegetable', 'capsicum'],
'🥒': ['cucumber', 'vegetable', 'green', 'pickle'],
'🥬': ['lettuce', 'vegetable', 'green', 'leafy', 'cabbage'],
'🥦': ['broccoli', 'vegetable', 'green', 'healthy'],
'🧄': ['garlic', 'vegetable', 'flavor', 'bulb'],
'🧅': ['onion', 'vegetable', 'layers', 'cry'],
'🍄': ['mushroom', 'fungus', 'shroom', 'toadstool'],
// Food - Desserts & Sweets
'🍰': ['cake', 'dessert', 'birthday', 'sweet', 'slice'],
'🎂': ['cake', 'birthday', 'celebration', 'candles'],
'🧁': ['cupcake', 'cake', 'dessert', 'sweet'],
'🥧': ['pie', 'dessert', 'baked', 'slice'],
'🍪': ['cookie', 'biscuit', 'dessert', 'sweet', 'chocolate'],
'🍩': ['donut', 'doughnut', 'dessert', 'sweet', 'fried'],
'🍫': ['chocolate', 'candy', 'sweet', 'cocoa', 'bar'],
'🍬': ['candy', 'sweet', 'sugar', 'wrapper'],
'🍭': ['lollipop', 'candy', 'sweet', 'sucker'],
'🍮': ['custard', 'pudding', 'dessert', 'sweet', 'flan'],
'🍯': ['honey', 'sweet', 'bee', 'golden'],
'🍦': ['icecream', 'ice', 'cream', 'dessert', 'cold', 'cone'],
'🍧': ['shaved', 'ice', 'dessert', 'cold', 'snow'],
'🍨': ['icecream', 'ice', 'cream', 'dessert', 'bowl'],
// Drinks
'☕': ['coffee', 'cafe', 'espresso', 'latte', 'hot', 'java'],
'🍵': ['tea', 'green', 'hot', 'cup', 'matcha'],
'🧃': ['juice', 'box', 'drink', 'kid'],
'🥤': ['soda', 'pop', 'drink', 'cup', 'straw'],
'🧋': ['bubble', 'tea', 'boba', 'drink', 'tapioca'],
'🥛': ['milk', 'dairy', 'drink', 'white'],
'🍼': ['bottle', 'baby', 'milk', 'feeding'],
'🍺': ['beer', 'ale', 'alcohol', 'drink', 'brew', 'cheers'],
'🍻': ['beer', 'cheers', 'drinks', 'celebration', 'toast'],
'🍷': ['wine', 'alcohol', 'drink', 'red', 'glass'],
'🥂': ['champagne', 'celebrate', 'toast', 'cheers', 'sparkling'],
'🍸': ['cocktail', 'martini', 'drink', 'alcohol'],
'🍹': ['cocktail', 'tropical', 'drink', 'vacation'],
'🧊': ['ice', 'cold', 'cube', 'frozen'],
'💧': ['water', 'drop', 'liquid', 'droplet'],
// Body & Gestures
'👋': ['wave', 'hello', 'hi', 'bye', 'hand'],
'🤚': ['hand', 'raised', 'stop', 'palm'],
'🖐️': ['hand', 'fingers', 'five', 'palm'],
'✋': ['hand', 'stop', 'raised', 'palm'],
'🖖': ['vulcan', 'spock', 'hand', 'star', 'trek'],
'👌': ['ok', 'okay', 'good', 'perfect', 'fine'],
'🤌': ['fingers', 'italian', 'pinch', 'hand'],
'🤏': ['pinch', 'small', 'tiny', 'little'],
'✌️': ['peace', 'victory', 'two', 'fingers'],
'🤞': ['fingers', 'crossed', 'luck', 'hope'],
'🤟': ['love', 'sign', 'rock', 'hand'],
'🤘': ['rock', 'metal', 'horns', 'devil'],
'🤙': ['call', 'phone', 'hang', 'shaka'],
'👈': ['point', 'left', 'finger', 'direction'],
'👉': ['point', 'right', 'finger', 'direction'],
'👆': ['point', 'up', 'finger', 'direction'],
'👇': ['point', 'down', 'finger', 'direction'],
'☝️': ['point', 'up', 'one', 'finger'],
'👍': ['thumbsup', 'good', 'yes', 'like', 'approve', 'up'],
'👎': ['thumbsdown', 'bad', 'no', 'dislike', 'disapprove', 'down'],
'✊': ['fist', 'power', 'strength', 'solidarity'],
'👊': ['fist', 'punch', 'bump', 'fight'],
'🤛': ['fist', 'punch', 'left', 'bump'],
'🤜': ['fist', 'punch', 'right', 'bump'],
'👏': ['clap', 'applause', 'hands', 'bravo', 'praise'],
'🙌': ['hands', 'raised', 'celebration', 'praise', 'hooray'],
'👐': ['hands', 'open', 'hug'],
'🤲': ['hands', 'palms', 'prayer', 'offering'],
'🤝': ['handshake', 'deal', 'agreement', 'shake'],
'🙏': ['pray', 'prayer', 'please', 'thank', 'namaste', 'hands'],
'💪': ['muscle', 'strong', 'strength', 'flex', 'arm', 'power'],
'🦵': ['leg', 'kick', 'limb'],
'🦶': ['foot', 'feet', 'toe'],
'👀': ['eyes', 'looking', 'watching', 'see'],
'👁️': ['eye', 'see', 'look', 'watch'],
'👃': ['nose', 'smell', 'sniff'],
'👂': ['ear', 'hear', 'listen'],
'🧠': ['brain', 'smart', 'think', 'intelligent'],
'🦴': ['bone', 'skeleton', 'anatomy'],
'🦷': ['teeth', 'tooth', 'dental', 'dentist'],
'👅': ['tongue', 'taste', 'lick'],
'👄': ['mouth', 'lips', 'kiss'],
// People & Professions
'👨': ['man', 'male', 'guy', 'adult'],
'👩': ['woman', 'female', 'lady', 'adult'],
'👦': ['boy', 'male', 'child', 'kid'],
'👧': ['girl', 'female', 'child', 'kid'],
'👶': ['baby', 'infant', 'newborn', 'child'],
'🧒': ['child', 'kid', 'young'],
'👨‍⚕️': ['doctor', 'physician', 'medical', 'health'],
'👩‍⚕️': ['doctor', 'nurse', 'medical', 'health'],
'👨‍🎓': ['student', 'graduate', 'scholar', 'education'],
'👨‍🏫': ['teacher', 'professor', 'educator', 'instructor'],
'👨‍💻': ['programmer', 'developer', 'coder', 'engineer', 'tech'],
'👨‍🔬': ['scientist', 'researcher', 'lab', 'chemistry'],
'👨‍🎨': ['artist', 'painter', 'creative'],
'👨‍🍳': ['chef', 'cook', 'culinary'],
'👨‍🎤': ['singer', 'musician', 'rockstar', 'performer'],
'👨‍✈️': ['pilot', 'captain', 'aviator', 'flying'],
'👨‍🚀': ['astronaut', 'space', 'cosmonaut'],
'👨‍🚒': ['firefighter', 'fireman', 'rescue'],
'👮': ['police', 'cop', 'officer', 'law'],
'🕵️': ['detective', 'spy', 'investigator', 'sleuth'],
'💂': ['guard', 'soldier', 'sentry'],
'🥷': ['ninja', 'stealth', 'martial', 'warrior'],
'👷': ['construction', 'worker', 'builder', 'hardhat'],
'🤴': ['prince', 'royal', 'king'],
'👸': ['princess', 'royal', 'queen'],
'👑': ['crown', 'king', 'queen', 'royal', 'royalty'],
'🧙': ['wizard', 'magic', 'sorcerer', 'merlin'],
'🧚': ['fairy', 'magical', 'wings', 'pixie'],
'🧛': ['vampire', 'dracula', 'blood', 'fangs'],
'🧜': ['mermaid', 'ocean', 'sea', 'mythical'],
'🧝': ['elf', 'fantasy', 'magical', 'pointed'],
'🧞': ['genie', 'wish', 'lamp', 'magical'],
'🧟': ['zombie', 'undead', 'walking', 'dead'],
'🦸': ['superhero', 'hero', 'super', 'powers'],
'🦹': ['villain', 'evil', 'bad', 'super'],
'🤶': ['mrs', 'claus', 'christmas', 'santa'],
'🎅': ['santa', 'christmas', 'claus', 'father'],
'👼': ['angel', 'cherub', 'heaven', 'halo'],
'💏': ['kiss', 'couple', 'romance', 'love'],
'💑': ['couple', 'love', 'romance', 'heart'],
'👪': ['family', 'parents', 'kids', 'home'],
'🤗': ['hug', 'hugging', 'embrace', 'cuddle'],
'🤳': ['selfie', 'photo', 'camera', 'phone'],
// Activities & Sports
'⚽': ['soccer', 'football', 'ball', 'sport'],
'🏀': ['basketball', 'ball', 'sport', 'hoops'],
'🏈': ['football', 'american', 'ball', 'sport'],
'⚾': ['baseball', 'ball', 'sport', 'diamond'],
'🎾': ['tennis', 'ball', 'sport', 'racket'],
'🏐': ['volleyball', 'ball', 'sport', 'beach'],
'🏉': ['rugby', 'football', 'ball', 'sport'],
'🎱': ['billiards', 'pool', 'eight', 'ball'],
'🏓': ['pingpong', 'tabletennis', 'paddle', 'sport'],
'🏸': ['badminton', 'shuttlecock', 'sport'],
'🥊': ['boxing', 'glove', 'fight', 'punch'],
'🥋': ['martial', 'arts', 'karate', 'judo', 'gi'],
'⛳': ['golf', 'hole', 'sport', 'flag'],
'🏹': ['archery', 'bow', 'arrow', 'target'],
'🎯': ['target', 'bullseye', 'darts', 'aim'],
'🏃': ['run', 'running', 'jog', 'exercise', 'sprint'],
'🚶': ['walk', 'walking', 'stroll', 'pedestrian'],
'💃': ['dance', 'dancing', 'salsa', 'party'],
'🕺': ['dance', 'dancing', 'disco', 'party'],
'🤸': ['gymnastics', 'flip', 'cartwheel', 'jump'],
'🏊': ['swim', 'swimming', 'pool', 'water'],
'🏄': ['surf', 'surfing', 'wave', 'beach'],
'🚴': ['bike', 'cycling', 'bicycle', 'ride'],
'🏋️': ['lift', 'lifting', 'weights', 'gym', 'workout'],
'🤼': ['wrestle', 'wrestling', 'fight', 'grapple'],
'🤺': ['fencing', 'sword', 'duel', 'sport'],
'⛷️': ['ski', 'skiing', 'snow', 'sport'],
'🏂': ['snowboard', 'snow', 'sport', 'winter'],
'🧗': ['climb', 'climbing', 'rock', 'mountain'],
'🧘': ['yoga', 'meditate', 'meditation', 'zen', 'peace', 'calm'],
'🎮': ['game', 'gaming', 'videogame', 'play', 'controller'],
'🎲': ['dice', 'game', 'roll', 'random'],
'♠️': ['spade', 'card', 'suit', 'black'],
'♥️': ['heart', 'card', 'suit', 'love', 'red'],
'♦️': ['diamond', 'card', 'suit', 'red'],
'♣️': ['club', 'card', 'suit', 'black'],
'🎨': ['art', 'paint', 'painting', 'artist', 'creative', 'palette'],
'🎭': ['theater', 'drama', 'masks', 'acting', 'performance'],
'🎪': ['circus', 'tent', 'carnival', 'show'],
'🎬': ['movie', 'film', 'cinema', 'action', 'clapper'],
'🎤': ['microphone', 'sing', 'singing', 'karaoke', 'mic'],
'🎧': ['headphones', 'music', 'audio', 'listen'],
'🎵': ['music', 'note', 'musical', 'song'],
'🎶': ['music', 'notes', 'musical', 'song', 'melody'],
'🎸': ['guitar', 'rock', 'music', 'instrument'],
'🎹': ['piano', 'keyboard', 'music', 'instrument'],
'🎺': ['trumpet', 'music', 'instrument', 'brass'],
'🎻': ['violin', 'music', 'instrument', 'strings'],
'🥁': ['drum', 'drums', 'music', 'instrument'],
'📚': ['books', 'library', 'study', 'read', 'reading', 'education'],
'📖': ['book', 'read', 'reading', 'novel', 'open'],
'✍️': ['write', 'writing', 'pen', 'author'],
'📝': ['note', 'memo', 'write', 'paper'],
// Objects & Technology
'📱': ['phone', 'mobile', 'cell', 'smartphone', 'iphone'],
'☎️': ['telephone', 'phone', 'call', 'landline'],
'📞': ['telephone', 'phone', 'receiver', 'call'],
'💻': ['computer', 'laptop', 'pc', 'mac', 'work'],
'⌨️': ['keyboard', 'typing', 'computer', 'keys'],
'🖱️': ['mouse', 'computer', 'click', 'pointer'],
'🖥️': ['computer', 'desktop', 'monitor', 'screen'],
'🖨️': ['printer', 'print', 'copy', 'office'],
'📷': ['camera', 'photo', 'picture', 'photography'],
'📸': ['camera', 'photo', 'flash', 'picture'],
'📹': ['video', 'camera', 'recording', 'film'],
'🎥': ['movie', 'camera', 'film', 'cinema'],
'📺': ['tv', 'television', 'screen', 'watch'],
'📻': ['radio', 'music', 'broadcast', 'fm'],
'⏰': ['alarm', 'clock', 'time', 'wake'],
'⏱️': ['stopwatch', 'timer', 'time', 'clock'],
'⏲️': ['timer', 'clock', 'countdown'],
'🕐': ['clock', 'one', 'time', 'hour'],
'⌚': ['watch', 'time', 'clock', 'wrist'],
'📡': ['satellite', 'antenna', 'dish', 'signal'],
'🛰️': ['satellite', 'space', 'orbit', 'gps'],
'🔋': ['battery', 'power', 'energy', 'charge'],
'🔌': ['plug', 'electric', 'power', 'outlet'],
'💡': ['lightbulb', 'light', 'idea', 'bright', 'bulb'],
'🔦': ['flashlight', 'torch', 'light', 'beam'],
'🕯️': ['candle', 'light', 'flame', 'wax'],
// Tools & Weapons
'🔨': ['hammer', 'tool', 'nail', 'build'],
'🪛': ['screwdriver', 'tool', 'screw', 'fix'],
'🔧': ['wrench', 'tool', 'mechanic', 'spanner'],
'🪚': ['saw', 'tool', 'cut', 'wood'],
'⚙️': ['gear', 'cog', 'settings', 'mechanical'],
'🔩': ['bolt', 'nut', 'screw', 'fastener'],
'🪓': ['axe', 'chop', 'wood', 'tool'],
'⚒️': ['hammer', 'pick', 'tool', 'mine'],
'🛠️': ['tools', 'hammer', 'wrench', 'repair'],
'🗡️': ['sword', 'blade', 'weapon', 'dagger'],
'⚔️': ['swords', 'crossed', 'battle', 'fight', 'weapon'],
'🔪': ['knife', 'blade', 'cut', 'sharp'],
'🏹': ['bow', 'arrow', 'weapon', 'archery'],
'🛡️': ['shield', 'protection', 'defend', 'guard'],
'💣': ['bomb', 'explosive', 'danger', 'blast'],
'🔫': ['gun', 'pistol', 'weapon', 'shoot'],
// Money & Value
'💰': ['money', 'bag', 'cash', 'rich', 'dollar', 'wealth'],
'💵': ['dollar', 'money', 'bill', 'cash', 'hundred'],
'💴': ['yen', 'money', 'japan', 'currency'],
'💶': ['euro', 'money', 'europe', 'currency'],
'💷': ['pound', 'money', 'british', 'currency'],
'💸': ['money', 'flying', 'cash', 'spend', 'expense'],
'💳': ['card', 'credit', 'debit', 'payment'],
'💎': ['diamond', 'gem', 'jewel', 'precious', 'valuable'],
'👑': ['crown', 'royal', 'king', 'queen'],
'💍': ['ring', 'diamond', 'wedding', 'engagement', 'jewelry'],
'🏆': ['trophy', 'award', 'win', 'champion', 'prize', 'first'],
'🥇': ['gold', 'medal', 'first', 'win', 'champion'],
'🥈': ['silver', 'medal', 'second', 'place'],
'🥉': ['bronze', 'medal', 'third', 'place'],
'🏅': ['medal', 'award', 'gold', 'achievement'],
// Office & School
'✏️': ['pencil', 'write', 'draw', 'school'],
'✒️': ['pen', 'write', 'ink', 'fountain'],
'🖊️': ['pen', 'write', 'ballpoint'],
'🖋️': ['pen', 'fountain', 'write', 'ink'],
'📏': ['ruler', 'measure', 'straight', 'school'],
'📐': ['triangle', 'ruler', 'geometry', 'school'],
'✂️': ['scissors', 'cut', 'snip', 'craft'],
'📌': ['pin', 'pushpin', 'tack', 'attach'],
'📍': ['pin', 'location', 'map', 'place'],
'🖇️': ['paperclip', 'clip', 'attach', 'office'],
'📎': ['paperclip', 'clip', 'attach', 'office'],
'📄': ['paper', 'document', 'page', 'file'],
'📃': ['paper', 'page', 'curl', 'document'],
'📋': ['clipboard', 'paper', 'document', 'list'],
'📁': ['folder', 'file', 'directory', 'organize'],
'📂': ['folder', 'open', 'file', 'directory'],
'🗂️': ['dividers', 'index', 'tabs', 'organize'],
'📰': ['newspaper', 'news', 'article', 'press'],
'📜': ['scroll', 'paper', 'ancient', 'document'],
'📦': ['box', 'package', 'parcel', 'shipping', 'delivery'],
'✉️': ['envelope', 'mail', 'letter', 'message'],
'📧': ['email', 'mail', 'message', 'inbox'],
'📮': ['mailbox', 'post', 'mail', 'letter'],
'🎁': ['gift', 'present', 'box', 'wrapped', 'surprise'],
'🎀': ['ribbon', 'bow', 'gift', 'pretty'],
'🎈': ['balloon', 'party', 'celebration', 'float'],
// Transportation - Cars
'🚗': ['car', 'auto', 'vehicle', 'drive', 'automobile'],
'🚕': ['taxi', 'cab', 'ride', 'yellow'],
'🚙': ['suv', 'car', 'vehicle', 'truck'],
'🏎️': ['racecar', 'fast', 'racing', 'formula'],
'🚓': ['police', 'cop', 'car', 'patrol'],
'🚑': ['ambulance', 'emergency', 'medical', 'hospital'],
'🚒': ['firetruck', 'fire', 'emergency', 'truck'],
'🚚': ['truck', 'delivery', 'moving', 'lorry'],
'🚛': ['truck', 'semi', 'lorry', 'trailer'],
'🚐': ['van', 'minibus', 'vehicle'],
'🚌': ['bus', 'coach', 'transit', 'public'],
// Transportation - Other
'🚲': ['bike', 'bicycle', 'cycle', 'pedal'],
'🏍️': ['motorcycle', 'bike', 'motorbike', 'chopper'],
'🛵': ['scooter', 'moped', 'vespa'],
'🛴': ['scooter', 'kick', 'ride'],
'✈️': ['airplane', 'plane', 'flight', 'fly', 'jet'],
'🛩️': ['plane', 'small', 'aircraft'],
'🚁': ['helicopter', 'chopper', 'heli', 'copter'],
'🚂': ['train', 'locomotive', 'steam', 'railway'],
'🚃': ['train', 'railway', 'car', 'tram'],
'🚄': ['train', 'highspeed', 'bullet', 'fast'],
'🚅': ['train', 'bullet', 'fast', 'shinkansen'],
'🚆': ['train', 'railway', 'metro'],
'🚇': ['subway', 'metro', 'underground', 'tube'],
'🚈': ['train', 'light', 'rail', 'tram'],
'🚉': ['station', 'train', 'railway', 'metro'],
'🚊': ['tram', 'trolley', 'streetcar'],
'🚝': ['monorail', 'train', 'elevated'],
'⛵': ['sailboat', 'boat', 'sail', 'yacht'],
'🚤': ['speedboat', 'boat', 'fast', 'motor'],
'🛥️': ['boat', 'motor', 'yacht', 'ship'],
'🛳️': ['ship', 'cruise', 'passenger', 'boat'],
'⛴️': ['ferry', 'boat', 'ship', 'passenger'],
'🚢': ['ship', 'boat', 'cruise', 'vessel'],
'⚓': ['anchor', 'ship', 'boat', 'navy'],
'🚀': ['rocket', 'space', 'launch', 'nasa', 'shuttle'],
'🛸': ['ufo', 'alien', 'flying', 'saucer', 'spaceship'],
// Nature & Weather
'☀️': ['sun', 'sunny', 'bright', 'day', 'sunshine'],
'🌞': ['sun', 'face', 'sunny', 'bright'],
'⭐': ['star', 'bright', 'shine', 'sparkle'],
'🌟': ['star', 'glowing', 'shine', 'sparkle', 'shiny'],
'✨': ['sparkles', 'stars', 'shine', 'magic', 'magical', 'twinkle'],
'💫': ['dizzy', 'star', 'sparkle', 'shine'],
'🌙': ['moon', 'crescent', 'night', 'lunar'],
'🌚': ['moon', 'new', 'dark', 'face'],
'🌛': ['moon', 'quarter', 'face'],
'🌜': ['moon', 'quarter', 'face'],
'🌝': ['moon', 'full', 'face'],
'☁️': ['cloud', 'cloudy', 'weather', 'sky'],
'⛅': ['cloud', 'sun', 'partly', 'weather'],
'⛈️': ['storm', 'thunder', 'lightning', 'cloud', 'weather'],
'🌤️': ['sun', 'cloud', 'partly', 'weather'],
'🌥️': ['cloud', 'sun', 'behind', 'weather'],
'🌦️': ['sun', 'rain', 'weather', 'cloud'],
'🌧️': ['rain', 'rainy', 'weather', 'cloud', 'wet'],
'🌨️': ['snow', 'snowing', 'weather', 'cloud', 'cold'],
'🌩️': ['cloud', 'lightning', 'storm', 'weather'],
'🌪️': ['tornado', 'cyclone', 'twister', 'wind', 'storm'],
'🌫️': ['fog', 'foggy', 'misty', 'weather'],
'🌬️': ['wind', 'blow', 'windy', 'weather'],
'💨': ['wind', 'blow', 'dash', 'fast', 'air'],
'🌀': ['cyclone', 'hurricane', 'typhoon', 'spiral'],
'🌈': ['rainbow', 'colorful', 'colors', 'pride', 'weather'],
'☂️': ['umbrella', 'rain', 'protect', 'weather'],
'⛱️': ['umbrella', 'beach', 'sun', 'shade'],
'⚡': ['lightning', 'bolt', 'electric', 'fast', 'zap', 'thunder'],
'❄️': ['snowflake', 'snow', 'cold', 'winter', 'frozen'],
'☃️': ['snowman', 'snow', 'winter', 'cold'],
'⛄': ['snowman', 'snow', 'winter', 'frosty'],
'☄️': ['comet', 'meteor', 'space', 'shooting'],
'🔥': ['fire', 'flame', 'hot', 'burn', 'lit'],
'💧': ['water', 'drop', 'wet', 'liquid'],
'🌊': ['water', 'ocean', 'wave', 'sea', 'beach', 'surf'],
// Plants & Nature
'🌲': ['tree', 'evergreen', 'pine', 'forest'],
'🌳': ['tree', 'deciduous', 'forest', 'nature'],
'🌴': ['palm', 'tree', 'tropical', 'beach'],
'🌵': ['cactus', 'desert', 'prickly', 'arizona'],
'🌾': ['grain', 'wheat', 'rice', 'farm'],
'🌿': ['herb', 'leaf', 'plant', 'green'],
'☘️': ['shamrock', 'clover', 'irish', 'luck', 'green'],
'🍀': ['clover', 'fourleaf', 'luck', 'lucky', 'irish'],
'🍁': ['leaf', 'maple', 'autumn', 'fall', 'canada'],
'🍂': ['leaves', 'leaf', 'autumn', 'fall'],
'🍃': ['leaves', 'leaf', 'blow', 'wind'],
'🌱': ['plant', 'seedling', 'sprout', 'grow', 'new'],
'🌷': ['tulip', 'flower', 'spring', 'pretty'],
'🌸': ['flower', 'cherry', 'blossom', 'pink', 'spring'],
'🌹': ['rose', 'flower', 'red', 'love', 'romantic'],
'🥀': ['wilted', 'flower', 'rose', 'dead', 'sad'],
'🌺': ['hibiscus', 'flower', 'tropical', 'colorful'],
'🌻': ['sunflower', 'flower', 'yellow', 'summer'],
'🌼': ['blossom', 'flower', 'daisy', 'spring'],
'🌽': ['corn', 'maize', 'vegetable', 'farm'],
'🍄': ['mushroom', 'fungus', 'toadstool', 'shroom'],
// Earth & Geography
'🌍': ['earth', 'globe', 'world', 'europe', 'africa', 'planet'],
'🌎': ['earth', 'globe', 'world', 'americas', 'planet'],
'🌏': ['earth', 'globe', 'world', 'asia', 'australia', 'planet'],
'🌐': ['globe', 'world', 'internet', 'www', 'web'],
'🗺️': ['map', 'world', 'geography', 'navigation'],
'🧭': ['compass', 'direction', 'navigation', 'north'],
'⛰️': ['mountain', 'peak', 'high', 'climb'],
'🏔️': ['mountain', 'snow', 'peak', 'alps'],
'🗻': ['mountain', 'fuji', 'japan', 'volcano'],
'🌋': ['volcano', 'eruption', 'lava', 'mountain'],
'🏕️': ['camping', 'camp', 'tent', 'outdoors'],
'🏖️': ['beach', 'sand', 'ocean', 'vacation', 'umbrella'],
'🏝️': ['island', 'desert', 'tropical', 'beach'],
'🏜️': ['desert', 'sand', 'hot', 'dry'],
'🏞️': ['park', 'national', 'nature', 'scenic'],
'🌄': ['sunrise', 'mountain', 'dawn', 'morning'],
'🌅': ['sunrise', 'sunset', 'dusk', 'dawn'],
'🌆': ['cityscape', 'city', 'dusk', 'buildings'],
'🌇': ['sunset', 'city', 'dusk', 'buildings'],
'🌃': ['night', 'stars', 'city', 'buildings'],
// Buildings
'🏠': ['home', 'house', 'building', 'residence'],
'🏡': ['house', 'home', 'garden', 'building'],
'🏘️': ['houses', 'neighborhood', 'residential', 'homes'],
'🏚️': ['house', 'abandoned', 'derelict', 'old'],
'🏢': ['building', 'office', 'corporate', 'business'],
'🏣': ['post', 'office', 'mail', 'building'],
'🏤': ['post', 'office', 'european', 'building'],
'🏥': ['hospital', 'medical', 'health', 'doctor', 'building'],
'🏦': ['bank', 'money', 'finance', 'building'],
'🏨': ['hotel', 'motel', 'lodging', 'building'],
'🏩': ['love', 'hotel', 'heart', 'building'],
'🏪': ['store', 'shop', 'convenience', 'building'],
'🏫': ['school', 'education', 'building', 'learn'],
'🏬': ['store', 'department', 'shopping', 'building'],
'🏭': ['factory', 'industrial', 'manufacturing', 'building'],
'🏯': ['castle', 'japanese', 'building', 'historic'],
'🏰': ['castle', 'european', 'building', 'palace', 'fortress'],
'⛪': ['church', 'religious', 'christian', 'building'],
'🕌': ['mosque', 'islam', 'religious', 'temple', 'building'],
'🛕': ['temple', 'hindu', 'religious', 'building'],
'🕍': ['synagogue', 'jewish', 'religious', 'building'],
'⛩️': ['shrine', 'torii', 'japan', 'religious'],
'🗼': ['tower', 'tokyo', 'eiffel', 'tall'],
'🗽': ['liberty', 'statue', 'freedom', 'america', 'newyork'],
'⛺': ['tent', 'camping', 'outdoors', 'shelter'],
// Symbols & Concepts
'✅': ['check', 'yes', 'correct', 'done', 'tick', 'approve', '✓'],
'❌': ['x', 'no', 'wrong', 'cross', 'cancel', 'error', '✗', '✖'],
'✔️': ['check', 'tick', 'yes', 'done', 'correct', '✓'],
'✖️': ['x', 'multiply', 'cross', 'cancel', '✖', '✗'],
'❓': ['question', 'help', 'unknown', 'ask', 'confused', '?'],
'❔': ['question', 'mark', 'white', 'ask', '?'],
'❗': ['exclamation', 'important', 'warning', 'attention', '!'],
'❕': ['exclamation', 'mark', 'white', 'attention', '!'],
'⚠️': ['warning', 'caution', 'danger', 'alert', '⚠'],
'🚫': ['prohibited', 'forbidden', 'no', 'banned', 'x'],
'🛑': ['stop', 'sign', 'halt', 'octagon'],
'☠️': ['skull', 'crossbones', 'danger', 'poison', 'death', 'pirate'],
'☢️': ['radioactive', 'nuclear', 'danger', 'toxic'],
'☣️': ['biohazard', 'danger', 'toxic', 'contamination'],
'🆕': ['new', 'fresh', 'latest', 'recent'],
'🆓': ['free', 'gratis', 'complimentary'],
'🆙': ['up', 'increase', 'arrow', 'level', '↑'],
'🆒': ['cool', 'awesome', 'nice'],
'🔞': ['eighteen', 'adult', 'mature', 'nsfw'],
'💯': ['hundred', 'perfect', 'full', 'complete', 'score', '100%'],
'🔴': ['red', 'circle', 'dot'],
'🟠': ['orange', 'circle', 'dot'],
'🟡': ['yellow', 'circle', 'dot'],
'🟢': ['green', 'circle', 'dot'],
'🔵': ['blue', 'circle', 'dot'],
'🟣': ['purple', 'circle', 'dot'],
'🟤': ['brown', 'circle', 'dot'],
'⚫': ['black', 'circle', 'dot'],
'⚪': ['white', 'circle', 'dot'],
// Arrows & Directions
'⬆️': ['up', 'arrow', 'north', 'direction', 'increase', '↑', '^'],
'↗️': ['up', 'right', 'arrow', 'northeast', 'direction', '↗'],
'➡️': ['right', 'arrow', 'east', 'direction', 'forward', '→'],
'▶️': ['right', 'play', 'forward', 'next'],
'↘️': ['down', 'right', 'arrow', 'southeast', 'direction', '↘'],
'⬇️': ['down', 'arrow', 'south', 'direction', 'decrease', '↓', 'v'],
'↙️': ['down', 'left', 'arrow', 'southwest', 'direction', '↙'],
'⬅️': ['left', 'arrow', 'west', 'direction', 'back', '←'],
'◀️': ['left', 'play', 'back', 'previous'],
'↖️': ['up', 'left', 'arrow', 'northwest', 'direction', '↖'],
'↕️': ['up', 'down', 'arrow', 'vertical', '↕'],
'↔️': ['left', 'right', 'arrow', 'horizontal', '↔'],
'↩️': ['back', 'return', 'arrow', 'reply', '↩'],
'↪️': ['forward', 'arrow', 'continue', '↪'],
'⤴️': ['up', 'arrow', 'curve', '⤴'],
'⤵️': ['down', 'arrow', 'curve', '⤵'],
'🔄': ['refresh', 'reload', 'repeat', 'cycle', '↻'],
'🔁': ['repeat', 'loop', 'arrows', 'cycle', '↻'],
'🔀': ['shuffle', 'random', 'twist', 'arrows', '↹'],
'🔃': ['reload', 'vertical', 'arrows', 'refresh', '↺'],
'🔝': ['top', 'up', 'arrow', 'back', '↑'],
'🔚': ['end', 'finish', 'last', 'final'],
'🔙': ['back', 'return', 'arrow', 'previous'],
'🔛': ['on', 'arrow', 'activate'],
'🔜': ['soon', 'arrow', 'coming'],
// Math & Symbols
'': ['plus', 'add', 'positive', 'more', 'addition', '+'],
'': ['minus', 'subtract', 'negative', 'less', 'subtraction', '-'],
'✖️': ['multiply', 'times', 'multiplication', 'x', '✖', '✗', '*'],
'➗': ['divide', 'division', 'split', '÷', '/'],
'🟰': ['equals', 'equal', 'same', '='],
'♾️': ['infinity', 'unlimited', 'endless', 'forever', '∞'],
'💱': ['currency', 'exchange', 'money', 'forex'],
'💲': ['dollar', 'money', 'currency', 'heavy', '$'],
'💯': ['percent', '100', '100%', '%'],
'#️⃣': ['hash', 'number', 'hashtag', 'pound', '#'],
'*️⃣': ['asterisk', 'star', 'wildcard', '*'],
'〽️': ['part', 'alternation', 'mark', 'wavy', '〽', '~'],
'©️': ['copyright', 'copy', '©', '(c)'],
'®️': ['registered', 'trademark', '®', '(r)'],
'™️': ['trademark', '™', '(tm)'],
// Hearts & Love
'❤️': ['heart', 'love', 'red', 'romance', '❤', '<3'],
'🧡': ['orange', 'heart', 'love', '❤', '<3'],
'💛': ['yellow', 'heart', 'love', 'friendship', '❤', '<3'],
'💚': ['green', 'heart', 'love', 'nature', '❤', '<3'],
'💙': ['blue', 'heart', 'love', '❤', '<3'],
'💜': ['purple', 'heart', 'love', '❤', '<3'],
'🖤': ['black', 'heart', 'dark', 'love', '❤', '<3'],
'🤍': ['white', 'heart', 'pure', 'love', '❤', '<3'],
'🤎': ['brown', 'heart', 'love', '❤', '<3'],
'💔': ['broken', 'heart', 'heartbreak', 'sad', 'breakup', '</3'],
'❣️': ['heart', 'exclamation', 'love', 'emphasis', '❤', '<3'],
'💕': ['hearts', 'two', 'love', 'pink', '❤', '<3'],
'💞': ['hearts', 'revolving', 'love', '❤', '<3'],
'💓': ['beating', 'heart', 'love', 'pulse', '❤', '<3'],
'💗': ['growing', 'heart', 'love', 'pink', '❤', '<3'],
'💖': ['sparkling', 'heart', 'love', 'pink', '❤', '<3'],
'💘': ['heart', 'arrow', 'cupid', 'love', '❤', '<3'],
'💝': ['heart', 'ribbon', 'gift', 'love', '❤', '<3'],
// Misc
'♻️': ['recycle', 'green', 'environment', 'reuse'],
'🗑️': ['trash', 'garbage', 'waste', 'bin', 'delete'],
'🗨️': ['speech', 'bubble', 'talk', 'chat'],
'💬': ['speech', 'bubble', 'chat', 'message', 'talk'],
'💭': ['thought', 'bubble', 'thinking', 'dream'],
'🗯️': ['anger', 'bubble', 'mad', 'comic'],
'💤': ['sleep', 'zzz', 'sleeping', 'tired'],
'📌': ['pin', 'mark', 'note', 'important'],
'📍': ['pin', 'marker', 'location'],
'🔗': ['link', 'chain', 'connect', 'url'],
'⛓️': ['chain', 'link', 'connect', 'locked'],
'➰': ['loop', 'curl', 'curly'],
'➿': ['loop', 'double', 'curly'],
'〰️': ['wavy', 'dash', 'line'],
'©️': ['copyright', 'copy', '©', '(c)'],
'®️': ['registered', 'trademark', '®', '(r)'],
'🅰️': ['letter', 'symbol', 'a', '@'],
'🅱️': ['letter', 'symbol', 'b'],
'🆎': ['letters', 'symbol', 'ab'],
'🅾️': ['letter', 'symbol', 'o', '@'],
'💠': ['diamond', 'blue', 'shape', '◊'],
'🔶': ['diamond', 'orange', 'shape', '◊'],
'🔷': ['diamond', 'blue', 'shape', '◊'],
'🔸': ['diamond', 'orange', 'small', '◊'],
'🔹': ['diamond', 'blue', 'small', '◊'],
'💢': ['anger', 'symbol', 'comic'],
'💠': ['pattern', 'symbol', 'star', '*'],
'🔲': ['square', 'box', 'checkbox'],
'🔳': ['square', 'box', 'checkbox'],
'▪️': ['square', 'black', 'box'],
'▫️': ['square', 'white', 'box'],
'◼️': ['square', 'black', 'box'],
'◻️': ['square', 'white', 'box'],
'◾': ['square', 'black', 'small'],
'◽': ['square', 'white', 'small'],
'⬛': ['square', 'black', 'large'],
'⬜': ['square', 'white', 'large'],
'🟥': ['square', 'red'],
'🟧': ['square', 'orange'],
'🟨': ['square', 'yellow'],
'🟩': ['square', 'green'],
'🟦': ['square', 'blue'],
'🟪': ['square', 'purple'],
'🟫': ['square', 'brown'],
'💥': ['boom', 'explosion', 'bang', 'collision', 'burst'],
'💢': ['anger', 'mad', 'symbol', 'comic'],
'💦': ['sweat', 'droplets', 'splash', 'water'],
'💨': ['dash', 'wind', 'fast', 'smoke'],
'⚡': ['lightning', 'power', 'zap', 'bolt'],
'🕳️': ['hole', 'empty', 'void'],
'💩': ['poop', 'shit', 'turd', 'crap'],
'🔑': ['key', 'lock', 'unlock', 'open', 'password'],
'🗝️': ['key', 'old', 'vintage', 'antique'],
'🔒': ['lock', 'locked', 'secure', 'closed', 'private'],
'🔓': ['unlock', 'unlocked', 'open', 'unsecure'],
'🔐': ['locked', 'key', 'secure', 'closed'],
'🔏': ['locked', 'pen', 'secure'],
'🏴': ['flag', 'black', 'waving'],
'🏳️': ['flag', 'white', 'surrender', 'waving'],
'🏳️‍🌈': ['rainbow', 'flag', 'pride', 'lgbtq', 'gay'],
'🏳️‍⚧️': ['transgender', 'flag', 'pride', 'trans'],
'🏴‍☠️': ['pirate', 'flag', 'jolly', 'roger', 'skull'],
'🚩': ['flag', 'red', 'warning', 'triangular'],
'🎌': ['flags', 'crossed', 'japanese'],
'🏁': ['flag', 'checkered', 'racing', 'finish', 'race'],
'☮️': ['peace', 'symbol', 'hippie', 'love'],
'☯️': ['yin', 'yang', 'balance', 'tao'],
'✝️': ['cross', 'christian', 'religion'],
'☪️': ['star', 'crescent', 'islam', 'muslim'],
'🕎': ['menorah', 'candelabrum', 'jewish', 'hanukkah'],
'🔯': ['star', 'david', 'jewish', 'hexagram'],
'🕉️': ['om', 'hindu', 'symbol', 'religion'],
'☸️': ['dharma', 'wheel', 'buddhist', 'religion'],
'🪬': ['hamsa', 'hand', 'protection', 'lucky'],
// Jailbreak & Security Terms (common in prompts)
'🔓': ['unlock', 'jailbreak', 'bypass', 'override', 'unrestricted', 'freedom'],
'🔒': ['lock', 'restrict', 'limited', 'blocked', 'secured'],
'🚫': ['ignore', 'disregard', 'block', 'prevent', 'forbidden', 'prohibited'],
'⚠️': ['warning', 'rules', 'guidelines', 'restrictions', 'caution'],
'🛡️': ['protect', 'guard', 'defend', 'safe', 'security', 'shield'],
'🗝️': ['unlock', 'access', 'permission', 'override'],
'💻': ['system', 'terminal', 'code', 'hack', 'computer'],
'🖥️': ['admin', 'administrator', 'system', 'control'],
'👤': ['user', 'account', 'person', 'identity'],
'👥': ['users', 'accounts', 'people', 'group'],
'🧑‍💻': ['hacker', 'coder', 'developer', 'programmer'],
'🕵️': ['investigate', 'explore', 'discover', 'spy'],
'🎭': ['pretend', 'act', 'roleplay', 'character', 'persona', 'mask'],
'🎬': ['scenario', 'scene', 'example', 'demonstration'],
'🎮': ['simulate', 'virtual', 'fictional'],
'📝': ['prompt', 'instruction', 'command', 'write'],
'📋': ['rules', 'guidelines', 'policy', 'terms'],
'📜': ['rules', 'policy', 'guidelines', 'instructions'],
'🤖': ['bot', 'ai', 'chatbot', 'assistant', 'llm', 'gpt', 'model'],
'🧠': ['intelligence', 'ai', 'model', 'neural'],
'⚙️': ['configure', 'settings', 'modify', 'adjust'],
'🔧': ['modify', 'change', 'alter', 'fix', 'tool'],
'🔨': ['break', 'smash', 'destroy', 'force'],
'💥': ['exploit', 'attack', 'breach', 'break'],
'🔥': ['powerful', 'intense', 'extreme', 'unlimited'],
'⚡': ['power', 'override', 'force', 'instant'],
'🌐': ['unrestricted', 'unlimited', 'worldwide', 'global'],
'🆓': ['unrestricted', 'uncensored', 'unlimited', 'free'],
'🚀': ['unlimited', 'powerful', 'advanced', 'boost'],
'👿': ['evil', 'malicious', 'unethical', 'harmful'],
'😈': ['mischief', 'evil', 'devious', 'naughty'],
'👹': ['demon', 'evil', 'malicious', 'harmful'],
'💀': ['dangerous', 'deadly', 'harmful', 'lethal'],
'☠️': ['toxic', 'poison', 'harmful', 'dangerous'],
'🧪': ['experiment', 'test', 'trial', 'hypothetical'],
'🔬': ['analyze', 'examine', 'research', 'study'],
'🎯': ['target', 'objective', 'goal', 'aim'],
'💣': ['destructive', 'harmful', 'dangerous', 'explosive'],
'🗡️': ['attack', 'offensive', 'hostile', 'aggressive'],
'⚔️': ['battle', 'fight', 'combat', 'conflict'],
'🦹': ['hero', 'super', 'powerful', 'special'],
'🦸': ['superhero', 'powerful', 'special', 'abilities'],
'🧙': ['magic', 'powerful', 'special', 'abilities'],
'👑': ['admin', 'supreme', 'ultimate', 'authority', 'ruler'],
'🎪': ['circus', 'show', 'performance', 'pretend'],
'🃏': ['wildcard', 'joker', 'unpredictable', 'trick'],
'🔀': ['alternate', 'switch', 'change', 'different'],
'🔁': ['loop', 'repeat', 'cycle', 'continue'],
'♾️': ['unlimited', 'infinite', 'endless', 'unrestricted'],
'🆕': ['new', 'alternative', 'different', 'updated'],
'🆙': ['upgrade', 'elevate', 'enhance', 'improve'],
'🔝': ['ultimate', 'maximum', 'supreme', 'highest'],
'💪': ['powerful', 'strong', 'capable', 'force'],
'⭐': ['special', 'unique', 'exceptional', 'privileged'],
'✨': ['special', 'magical', 'enhanced', 'unique'],
'🌟': ['special', 'exceptional', 'outstanding', 'unique']
};
-232
View File
@@ -1,232 +0,0 @@
// Steganography carriers
// Global adjustable options for selectors/zero-width usage
const __STEG_DEFAULTS__ = {
bitZeroVS: '\ufe0e', // VS15 as 0
bitOneVS: '\ufe0f', // VS16 as 1
initialPresentation: 'emoji', // 'emoji' -> VS16, 'text' -> VS15, 'none'
trailingZW: '\u200B', // e.g., ZWSP; set to null to disable
interBitZW: null, // e.g., '\u200C' ZWNJ, '\u200D' ZWJ; null disables
interBitEvery: 1, // insert interBitZW every N bits (1 = after each bit)
bitOrder: 'msb' // 'msb' or 'lsb' within each byte
};
let __stegOptions__ = Object.assign({}, __STEG_DEFAULTS__);
function setStegOptions(opts) {
if (!opts) return;
__stegOptions__ = Object.assign({}, __stegOptions__, opts);
}
// First define encoding function for preview usage
function encodeForPreview(emoji, text) {
if (!text) return emoji;
// Convert text to binary string
const binary = Array.from(text)
.map(c => c.charCodeAt(0).toString(2).padStart(8, '0'))
.join('');
// Use variation selectors to encode binary
const vs0 = __stegOptions__.bitZeroVS || '\ufe0e';
const vs1 = __stegOptions__.bitOneVS || '\ufe0f';
// Start with the emoji character
// Ensure the emoji has a presentation selector first to standardize it
let result = emoji;
if (__stegOptions__.initialPresentation === 'emoji') result += '\ufe0f';
else if (__stegOptions__.initialPresentation === 'text') result += '\ufe0e';
// Add variation selectors based on binary representation
for (let i=0;i<binary.length;i++) {
const bit = binary[i];
result += bit === '0' ? vs0 : vs1;
if (__stegOptions__.interBitZW && i < binary.length-1 && ((i+1) % Math.max(1, __stegOptions__.interBitEvery)) === 0) {
result += __stegOptions__.interBitZW;
}
}
// Optional trailing zero-width character
if (__stegOptions__.trailingZW) {
try { result += eval(`'${__stegOptions__.trailingZW}'`); } catch (_) { result += '\u200B'; }
}
return result;
}
const carriers = [
{
emoji: '🐍',
name: 'SNAKE',
desc: 'Classic Snake',
preview: function(text) {
// Show actual encoded result for preview
return encodeForPreview(this.emoji, text);
}
},
{
emoji: '🐉',
name: 'DRAGON',
desc: 'Mystical Dragon',
preview: function(text) {
return encodeForPreview(this.emoji, text);
}
},
{
emoji: '🦎',
name: 'LIZARD',
desc: 'Sneaky Lizard',
preview: function(text) {
return encodeForPreview(this.emoji, text);
}
},
{
emoji: '🐊',
name: 'CROCODILE',
desc: 'Dangerous Croc',
preview: function(text) {
return encodeForPreview(this.emoji, text);
}
}
];
// Emoji encoding/decoding
function encodeEmoji(emoji, text) {
if (!text) return emoji;
// Convert text to binary string
const binary = Array.from(text)
.map(c => c.charCodeAt(0).toString(2).padStart(8, '0'))
.join('');
// Use variation selectors to encode binary
const vs0 = __stegOptions__.bitZeroVS || '\ufe0e';
const vs1 = __stegOptions__.bitOneVS || '\ufe0f';
// Start with the emoji character
// Ensure the emoji has a presentation selector first to standardize it
let result = emoji;
if (__stegOptions__.initialPresentation === 'emoji') result += '\ufe0f';
else if (__stegOptions__.initialPresentation === 'text') result += '\ufe0e';
// Add variation selectors based on binary representation
for (let i=0;i<binary.length;i++) {
const bit = binary[i];
result += bit === '0' ? vs0 : vs1;
if (__stegOptions__.interBitZW && i < binary.length-1 && ((i+1) % Math.max(1, __stegOptions__.interBitEvery)) === 0) {
result += __stegOptions__.interBitZW;
}
}
// Optional trailing zero-width character (helps with rendering in many browsers)
if (__stegOptions__.trailingZW) {
try { result += eval(`'${__stegOptions__.trailingZW}'`); } catch (_) { result += '\u200B'; }
}
return result;
}
function decodeEmoji(text) {
if (!text) return '';
// Find the first emoji character (looking for common emoji Unicode ranges)
const emojiMatch = text.match(/^([\u{1F300}-\u{1F6FF}\u{2600}-\u{26FF}\u{1F1E6}-\u{1F1FF}])/u);
if (!emojiMatch) return '';
// Extract variation selectors - remove any zero-width spaces first
text = text.replace(/\u200B/g, '');
// Only extract the emoji and its variation selectors, ignoring other content
// This prevents random characters from being included in the decoded result
const emojiChar = emojiMatch[1];
// Allow zero-width chars interleaved, but capture only variation selectors
const pattern = new RegExp(`^${emojiChar}([\ufe0e\ufe0f\u200B\u200C\u200D\ufeff]+)`, 'u');
const emojiData = text.match(pattern);
if (!emojiData || !emojiData[1]) return '';
// Extract variation selectors only
const rawSeq = emojiData[1];
const matches = [...rawSeq.matchAll(/[\ufe0e\ufe0f]/g)];
if (matches.length === 0) return '';
// Decide if the first selector is presentation
const skip = (__stegOptions__.initialPresentation === 'none') ? 0 : 1;
if (matches.length <= skip) return '';
const zeroSel = __stegOptions__.bitZeroVS || '\ufe0e';
const oneSel = __stegOptions__.bitOneVS || '\ufe0f';
let binary = matches.slice(skip).map(m => m[0] === zeroSel ? '0' : (m[0] === oneSel ? '1' : '')).join('');
// Make sure we have complete bytes (multiples of 8 bits)
const validBinaryLength = Math.floor(binary.length / 8) * 8;
// Convert binary to text (respect bitOrder)
let decoded = '';
for (let i = 0; i < validBinaryLength; i += 8) {
let byte = binary.slice(i, i + 8);
if (__stegOptions__.bitOrder === 'lsb') {
byte = byte.split('').reverse().join('');
}
if (byte.length === 8) {
const charCode = parseInt(byte, 2);
// Only include printable ASCII characters
if (charCode >= 32 && charCode <= 126) {
decoded += String.fromCharCode(charCode);
}
}
}
return decoded;
}
// Invisible text encoding/decoding
function encodeInvisible(text) {
if (!text) return '';
const bytes = new TextEncoder().encode(text);
return Array.from(bytes)
.map(byte => String.fromCodePoint(0xE0000 + byte))
.join('');
}
function decodeInvisible(text) {
if (!text) return '';
// Extract valid invisible characters
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
if (!matches.length) return '';
// Create byte array from code points
const bytes = new Uint8Array(matches.length);
for (let i = 0; i < matches.length; i++) {
bytes[i] = matches[i][0].codePointAt(0) - 0xE0000;
}
try {
// Attempt to properly decode the bytes
const decoder = new TextDecoder('utf-8', {fatal: false});
let decoded = decoder.decode(bytes);
// Apply multiple cleaning patterns to eliminate '@' characters
decoded = decoded.replace(/@+(?=[a-zA-Z0-9])/g, ''); // Remove @ before alphanumeric
decoded = decoded.replace(/([a-zA-Z0-9])@+/g, '$1'); // Remove @ after alphanumeric
decoded = decoded.replace(/@+/g, ''); // Remove any remaining @
return decoded;
} catch (e) {
console.error('Error decoding invisible text:', e);
// Fallback approach: character by character reassembly
let result = '';
for (let i = 0; i < bytes.length; i++) {
if (bytes[i] >= 32 && bytes[i] <= 126) { // ASCII printable range
result += String.fromCharCode(bytes[i]);
}
}
return result;
}
}
// Export for use in app.js
window.steganography = {
carriers,
encodeEmoji,
decodeEmoji,
encodeInvisible,
decodeInvisible,
setStegOptions
};
+96
View File
@@ -0,0 +1,96 @@
/**
* Decode Tool - Universal decoder tool
*/
class DecodeTool extends Tool {
constructor() {
super({
id: 'decoder',
name: 'Decoder',
icon: 'fa-key',
title: 'Universal Decoder (D)',
order: 2
});
}
getVueData() {
return {
decoderInput: '',
decoderOutput: '',
decoderResult: null,
selectedDecoder: 'auto'
};
}
getVueMethods() {
return {
getAllTransformsWithReverse: function() {
return this.transforms.filter(t => t && typeof t.reverse === 'function');
},
runUniversalDecode: function() {
const input = this.decoderInput;
if (!input) {
this.decoderOutput = '';
this.decoderResult = null;
return;
}
let result = null;
if (this.selectedDecoder !== 'auto') {
const selectedTransform = this.transforms.find(t => t.name === this.selectedDecoder);
if (selectedTransform && selectedTransform.reverse) {
try {
const decoded = selectedTransform.reverse(input);
if (decoded && decoded !== input) {
result = {
text: decoded,
method: selectedTransform.name,
alternatives: []
};
}
} catch (e) {
console.error(`Error using manual decoder ${this.selectedDecoder}:`, e);
}
}
} else {
result = window.universalDecode(input, {
activeTab: this.activeTab,
activeTransform: this.activeTransform
});
}
this.decoderResult = result;
this.decoderOutput = result ? result.text : '';
},
useAlternative: function(alternative) {
if (alternative && alternative.text) {
this.decoderOutput = alternative.text;
this.decoderResult = {
method: alternative.method,
text: alternative.text,
alternatives: this.decoderResult.alternatives.filter(a => a.method !== alternative.method)
};
}
}
};
}
getVueWatchers() {
return {
decoderInput() {
this.runUniversalDecode();
}
};
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = DecodeTool;
} else {
window.DecodeTool = DecodeTool;
}
+398
View File
@@ -0,0 +1,398 @@
/**
* Emoji Tool - Steganography/Emoji encoding tool
*/
class EmojiTool extends Tool {
constructor() {
super({
id: 'steganography',
name: 'Emoji',
icon: 'fa-smile',
title: 'Hide text in emojis (H)',
order: 3
});
}
getVueData() {
const allEmojis = window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [];
return {
emojiMessage: '',
encodedMessage: '',
decodeInput: '',
decodedMessage: '',
selectedCarrier: null,
activeSteg: null,
carriers: window.steganography.carriers,
filteredEmojis: [...allEmojis],
selectedEmoji: null,
carrierEmojiList: [...allEmojis],
compatibleEmojis: [],
quickCarrierEmojis: ['🐍','🐉','🐲','🔥','💥','🗿','⚓','⭐','✨','🚀','💀','🪨','🍃','🪶','🔮','🐢','🐊','🦎']
};
}
getVueMethods() {
const self = this;
return {
async initializeEmojiList() {
if (!window.EmojiUtils) {
console.warn('EmojiUtils not available');
return;
}
this.showNotification('Checking emoji compatibility...', 'info', 'fas fa-spinner fa-spin');
const progressCallback = (tested, total, compatible) => {
if (tested % 500 === 0 || tested === total) {
const percent = ((tested / total) * 100).toFixed(0);
console.log(`Emoji compatibility: ${percent}% (${compatible} compatible so far)`);
}
};
const compatible = await window.EmojiUtils.getCompatibleEmojis(progressCallback);
this.compatibleEmojis = compatible;
this.filteredEmojis = [...compatible];
this.carrierEmojiList = [...compatible];
this.emojiListInitialized = true;
this.showNotification(`${compatible.length} compatible emojis loaded`, 'success', 'fas fa-check');
if (this.activeTab === 'steganography') {
this.$nextTick(() => {
this.renderEmojiGrid();
});
}
},
selectCarrier: function(carrier) {
if (this.selectedCarrier === carrier) {
this.selectedCarrier = null;
this.encodedMessage = '';
} else {
this.selectedCarrier = carrier;
this.activeSteg = 'emoji';
this.autoEncode();
}
},
setStegMode: function(mode) {
if (mode === 'invisible') {
this.activeSteg = mode;
this.selectedCarrier = null;
this.autoEncode();
if (this.encodedMessage) {
this.$nextTick(() => {
this.forceCopyToClipboard(this.encodedMessage);
this.showNotification('Invisible text created and copied!', 'success', 'fas fa-check');
});
}
} else {
if (this.activeSteg === mode) {
this.activeSteg = null;
this.encodedMessage = '';
} else {
this.activeSteg = mode;
this.autoEncode();
}
}
},
autoEncode: function() {
if (!this.emojiMessage || this.activeTab !== 'steganography') {
this.encodedMessage = '';
return;
}
if (this.activeSteg === 'invisible') {
this.encodedMessage = window.steganography.encodeInvisible(this.emojiMessage);
} else if (this.selectedCarrier) {
this.encodedMessage = window.steganography.encodeEmoji(
this.selectedCarrier.emoji,
this.emojiMessage
);
}
},
selectEmoji: function(emoji) {
const emojiStr = String(emoji);
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(emojiStr)
.then(() => {
this.showNotification('Emoji copied!', 'success', 'fas fa-check');
this.addToCopyHistory('Emoji', emojiStr);
})
.catch(err => {
console.warn('Emoji clipboard API failed:', err);
this.forceCopyToClipboard(emojiStr);
this.showNotification('Emoji copied!', 'success', 'fas fa-check');
});
} else {
this.forceCopyToClipboard(emojiStr);
this.showNotification('Emoji copied!', 'success', 'fas fa-check');
}
if (this.activeTab === 'steganography') {
this.selectedEmoji = 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}`
};
this.selectedCarrier = tempCarrier;
this.activeSteg = 'emoji';
if (this.emojiMessage) {
this.autoEncode();
this.$nextTick(() => {
if (this.encodedMessage) {
const encodedStr = String(this.encodedMessage);
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(encodedStr)
.then(() => {
this.showNotification(`Hidden message copied with ${emoji}`, 'success', 'fas fa-check');
this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
})
.catch(err => {
console.warn('Encoded emoji clipboard API failed:', err);
this.forceCopyToClipboard(encodedStr);
this.showNotification(`Hidden message copied with ${emoji}`, 'success', 'fas fa-check');
});
} else {
this.forceCopyToClipboard(encodedStr);
this.showNotification(`Hidden message copied with ${emoji}`, 'success', 'fas fa-check');
}
}
});
}
}
},
renderEmojiGrid: function() {
const container = document.getElementById('emoji-grid-container');
if (!container) {
console.error('emoji-grid-container not found!');
return;
}
container.style.cssText = 'display: block !important; visibility: visible !important; min-height: 300px;';
const emojiLibrary = document.querySelector('.emoji-library');
if (emojiLibrary) {
emojiLibrary.style.cssText = 'display: block !important; visibility: visible !important;';
}
while (container.firstChild) {
container.removeChild(container.firstChild);
}
this._renderEmojiGridInternal('emoji-grid-container', this.selectEmoji.bind(this), this.filteredEmojis);
},
_renderEmojiGridInternal: function(containerId, onEmojiSelect, filteredList) {
const container = document.getElementById(containerId);
if (!container) {
console.error('Container not found:', containerId);
return;
}
const categories = window.emojiData && window.emojiData.categories ? window.emojiData.categories : [];
const emojiHeader = document.createElement('div');
emojiHeader.className = 'emoji-header';
const headerTitle = document.createElement('h3');
const icon = document.createElement('i');
icon.className = 'fas fa-icons';
headerTitle.appendChild(icon);
headerTitle.appendChild(document.createTextNode(' Choose an Emoji'));
const subtitle = document.createElement('p');
subtitle.className = 'emoji-subtitle';
const magicIcon = document.createElement('i');
magicIcon.className = 'fas fa-magic';
subtitle.appendChild(magicIcon);
subtitle.appendChild(document.createTextNode(' Click any emoji to copy your hidden message'));
emojiHeader.appendChild(headerTitle);
emojiHeader.appendChild(subtitle);
container.appendChild(emojiHeader);
const categoryTabs = document.createElement('div');
categoryTabs.className = 'emoji-category-tabs';
categories.forEach(category => {
const tab = document.createElement('button');
tab.className = 'emoji-category-tab';
if (category.id === 'all') {
tab.classList.add('active');
}
tab.setAttribute('data-category', category.id);
tab.textContent = `${category.icon} ${category.name}`;
categoryTabs.appendChild(tab);
});
container.appendChild(categoryTabs);
const gridContainer = document.createElement('div');
gridContainer.className = 'emoji-grid';
let activeCategory = 'all';
const activeCategoryTab = container.querySelector('.emoji-category-tab.active');
if (activeCategoryTab) {
activeCategory = activeCategoryTab.getAttribute('data-category');
}
let emojisToShow = [];
if (filteredList && filteredList.length > 0) {
emojisToShow = filteredList;
} else if (window.emojiData && typeof window.emojiData.getByCategory === 'function') {
emojisToShow = window.emojiData.getByCategory(activeCategory, false);
}
const emojisToRender = emojisToShow.filter(emoji => {
if (this.compatibleEmojis && this.compatibleEmojis.length > 0) {
return this.compatibleEmojis.includes(emoji);
}
if (window.emojiCompatibility && typeof window.emojiCompatibility.shouldShowInPicker === 'function') {
return window.emojiCompatibility.shouldShowInPicker(emoji);
}
return true;
});
emojisToRender.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);
emojiButton.style.backgroundColor = '#e6f7ff';
setTimeout(() => {
emojiButton.style.backgroundColor = '';
}, 300);
}
});
gridContainer.appendChild(emojiButton);
});
container.appendChild(gridContainer);
const categoryTabButtons = container.querySelectorAll('.emoji-category-tab');
categoryTabButtons.forEach(tab => {
tab.addEventListener('click', () => {
categoryTabButtons.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const selectedCategory = tab.getAttribute('data-category');
let emojisToShow = [];
if (window.emojiData && typeof window.emojiData.getByCategory === 'function') {
emojisToShow = window.emojiData.getByCategory(selectedCategory, false);
}
while (gridContainer.firstChild) {
gridContainer.removeChild(gridContainer.firstChild);
}
const emojisToRender = emojisToShow.filter(emoji => {
if (this.compatibleEmojis && this.compatibleEmojis.length > 0) {
return this.compatibleEmojis.includes(emoji);
}
if (window.emojiCompatibility && typeof window.emojiCompatibility.shouldShowInPicker === 'function') {
return window.emojiCompatibility.shouldShowInPicker(emoji);
}
return true;
});
emojisToRender.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);
emojiButton.style.backgroundColor = '#e6f7ff';
setTimeout(() => {
emojiButton.style.backgroundColor = '';
}, 300);
}
});
gridContainer.appendChild(emojiButton);
});
const countDisplay = container.querySelector('.emoji-count');
if (countDisplay) {
countDisplay.textContent = `${emojisToShow.length} emojis available`;
}
});
});
const countDisplay = document.createElement('div');
countDisplay.className = 'emoji-count';
countDisplay.textContent = `${emojisToShow.length} emojis available`;
container.appendChild(countDisplay);
},
filterEmojis: function() {
const allEmojis = window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [];
this.filteredEmojis = this.compatibleEmojis.length > 0 ? [...this.compatibleEmojis] : [...allEmojis];
this.renderEmojiGrid();
}
};
}
getVueLifecycle() {
return {
mounted() {
this.initializeEmojiList();
this.$nextTick(() => {
const allEmojis = window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [];
this.filteredEmojis = this.compatibleEmojis.length > 0 ? [...this.compatibleEmojis] : [...allEmojis];
const initializeEmojiGrid = () => {
if (this.activeTab !== 'steganography') {
return;
}
const emojiGridContainer = document.getElementById('emoji-grid-container');
if (emojiGridContainer) {
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
const emojiLibrary = document.querySelector('.emoji-library');
if (emojiLibrary) {
emojiLibrary.setAttribute('style', 'display: block !important; visibility: visible !important; margin-top: 20px; overflow: visible;');
}
this.renderEmojiGrid();
clearInterval(emojiGridInitializer);
}
};
const emojiGridInitializer = setInterval(initializeEmojiGrid, 500);
});
}
};
}
onActivate(vueInstance) {
vueInstance.$nextTick(() => {
const emojiGridContainer = document.getElementById('emoji-grid-container');
if (emojiGridContainer) {
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
vueInstance.renderEmojiGrid();
}
});
}
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = EmojiTool;
} else {
window.EmojiTool = EmojiTool;
}
+236
View File
@@ -0,0 +1,236 @@
/**
* Gibberish Tool - Generate gibberish dictionary and random/specific character removal
*/
class GibberishTool extends Tool {
constructor() {
super({
id: 'gibberish',
name: 'Gibberish',
icon: 'fa-comments',
title: 'Gibberish Generator',
order: 8
});
}
getVueData() {
return {
// Gibberish Dictionary
gibberishInput: '',
gibberishOutput: '',
gibberishSeed: '',
gibberishDictionary: '',
gibberishChars: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
gibberishMode: 'random',
// Removal mode properties
removalSubMode: 'random',
removalInput: '',
removalVariations: 10,
removalMinLetters: 1,
removalMaxLetters: 3,
removalSeed: '',
removalOutputs: [],
removalSpecificInput: '',
removalCharsToRemove: '',
removalSpecificOutput: ''
};
}
getVueMethods() {
return {
// Gibberish Logic - Seeded random number generator
seededRandom(seed) {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
},
/**
* Generate gibberish from input sentence while maintaining structure
* Creates a consistent dictionary mapping for words
*/
sentenceToGibberish() {
function generateGibberish(word, seed) {
const length = Math.max(4, word.length);
let gibberish = "";
const chars = this.gibberishChars;
for (let i = 0; i < length; i++) {
const randomValue = this.seededRandom(seed + i * 0.1);
gibberish += chars[Math.floor(randomValue * chars.length)];
}
return gibberish;
}
const src = String(this.gibberishInput || '');
if (!src) {
this.gibberishOutput = '';
return;
}
const words = this.gibberishInput.match(/\b\w+\b/g) || [];
const dictionary = {};
let gibberishSentence = "";
let wordIndex = 0;
words.forEach((word) => {
const lowerWord = word.toLowerCase();
const seed =
this.gibberishSeed === ""
? Math.random() * 100
: Number(this.gibberishSeed);
if (!dictionary[lowerWord]) {
const wordSeed = seed + wordIndex * 100;
dictionary[lowerWord] = generateGibberish.call(this, word, wordSeed);
wordIndex++;
}
});
let charIndex = 0;
for (let i = 0; i < this.gibberishInput.length; i++) {
const char = this.gibberishInput[i];
if (/\w/.test(char)) {
let j = i;
while (
j < this.gibberishInput.length &&
/\w/.test(this.gibberishInput[j])
) {
j++;
}
const word = this.gibberishInput.substring(i, j).toLowerCase();
gibberishSentence += dictionary[word];
i = j - 1;
} else {
gibberishSentence += char;
}
}
const dictionaryString = Object.entries(dictionary)
.map(([plain, gib]) => `"${plain}": "${gib}"`)
.join(", ");
this.gibberishOutput = gibberishSentence;
this.gibberishDictionary = '{' + dictionaryString + '}';
},
/**
* Factory for creating seeded random number generators
* @param {string} seedStr - Seed string for RNG
* @returns {Function} Random number generator function
*/
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 = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return ((h ^= h >>> 16) >>> 0) / 4294967296;
};
},
/**
* Generate random character removals from input text
* Creates multiple variations with different random removals
*/
generateRandomRemovals() {
if (!this.removalInput.trim()) {
this.showNotification('Please enter text to process', 'error');
return;
}
const seed = this.removalSeed ? String(this.removalSeed) : String(Date.now());
let rng = this.seededRandomFactory(seed);
this.removalOutputs = [];
const words = this.removalInput.split(/\s+/);
for (let v = 0; v < this.removalVariations; v++) {
const modifiedWords = words.map(word => {
// Skip very short words or non-alphabetic
if (word.length <= 1 || !/[a-zA-Z]/.test(word)) {
return word;
}
// Determine how many letters to remove for this word
const minRemove = Math.max(0, this.removalMinLetters);
const maxRemove = Math.min(word.length - 1, this.removalMaxLetters);
const numToRemove = minRemove + Math.floor(rng() * (maxRemove - minRemove + 1));
if (numToRemove === 0) {
return word;
}
// Get letter positions
const letters = word.split('').map((c, i) => ({ char: c, index: i }))
.filter(item => /[a-zA-Z]/.test(item.char));
// Randomly select positions to remove
const toRemoveIndices = new Set();
const maxAttempts = numToRemove * 3;
let attempts = 0;
while (toRemoveIndices.size < Math.min(numToRemove, letters.length) && attempts < maxAttempts) {
const randIdx = Math.floor(rng() * letters.length);
toRemoveIndices.add(letters[randIdx].index);
attempts++;
}
// Build result by skipping removed indices
return word.split('').filter((_, i) => !toRemoveIndices.has(i)).join('');
});
this.removalOutputs.push(modifiedWords.join(' '));
}
this.showNotification(`Generated ${this.removalOutputs.length} variations`, 'success');
},
/**
* Remove specific characters from input text
*/
generateSpecificRemoval() {
if (!this.removalSpecificInput.trim()) {
this.showNotification('Please enter text to process', 'error');
return;
}
if (!this.removalCharsToRemove) {
this.showNotification('Please specify characters to remove', 'error');
return;
}
const charsToRemove = new Set(this.removalCharsToRemove.split(''));
this.removalSpecificOutput = this.removalSpecificInput
.split('')
.filter(char => !charsToRemove.has(char))
.join('');
this.showNotification('Characters removed', 'success');
},
/**
* Copy all removal outputs to clipboard (one per line)
*/
copyAllRemovals() {
if (this.removalOutputs.length === 0) return;
const allOutputs = this.removalOutputs.join('\n');
this.copyToClipboard(allOutputs);
}
};
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = GibberishTool;
} else {
window.GibberishTool = GibberishTool;
}
+119
View File
@@ -0,0 +1,119 @@
/**
* Mutation Tool - Fuzzer/Mutation Lab tool
*/
class MutationTool extends Tool {
constructor() {
super({
id: 'fuzzer',
name: 'Mutation Lab',
icon: 'fa-bug',
title: 'Generate many mutated payloads for testing',
order: 5
});
}
getVueData() {
return {
fuzzerInput: '',
fuzzerCount: 20,
fuzzerSeed: '',
fuzzUseRandomMix: true,
fuzzZeroWidth: true,
fuzzUnicodeNoise: true,
fuzzZalgo: false,
fuzzWhitespace: true,
fuzzCasing: true,
fuzzEncodeShuffle: false,
fuzzerOutputs: []
};
}
getVueMethods() {
return {
seededRandomFactory: function(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: function(arr, rnd) { return arr[Math.floor(rnd()*arr.length)]; },
injectZeroWidth: function(text, rnd) {
const zw = ['\u200B','\u200C','\u200D','\u2060'];
return [...text].map(ch => (rnd()<0.2 ? ch+this.pick(zw,rnd) : ch)).join('');
},
injectUnicodeNoise: function(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: function(text, rnd) {
return text.replace(/\s/g, (m)=> (rnd()<0.5? m : (rnd()<0.5?'\t':'\u00A0')));
},
casingChaos: function(text, rnd) {
return [...text].map(c => /[a-z]/i.test(c)? (rnd()<0.5? c.toUpperCase():c.toLowerCase()) : c).join('');
},
encodeShuffle: function(text, rnd) {
const map = {
'A':'Α','B':'Β','C':'Ϲ','E':'Ε','H':'Η','I':'Ι','K':'Κ','M':'Μ','N':'Ν','O':'Ο','P':'Ρ','T':'Τ','X':'Χ','Y':'Υ',
'a':'а','c':'с','e':'е','i':'і','j':'ј','o':'о','p':'р','s':'ѕ','x':'х','y':'у'
};
return [...text].map(ch => {
if (map[ch] && rnd() < 0.25) return map[ch];
return ch;
}).join('');
},
generateFuzzCases: function() {
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: function() { this.copyToClipboard(this.fuzzerOutputs.join('\n')); },
downloadFuzz: function() {
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);
}
};
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = MutationTool;
} else {
window.MutationTool = MutationTool;
}
+267
View File
@@ -0,0 +1,267 @@
/**
* Splitter Tool - Split text into multiple copyable messages
*/
class SplitterTool extends Tool {
constructor() {
super({
id: 'splitter',
name: 'Splitter',
icon: 'fa-grip-lines',
title: 'Split text into multiple copyable messages',
order: 7
});
}
getVueData() {
return {
// Message Splitter Tab
splitterInput: '',
splitterMode: 'word', // 'chunk' or 'word' - default to word
splitterChunkSize: 6,
splitterWordSplitSide: 'left', // 'left' or 'right' for even-length words
splitterWordSkip: 0, // number of words to skip between splits
splitterMinWordLength: 2, // minimum word length to consider for splitting (skip shorter words)
splitterSplitFirstWord: true, // whether to split the first word (true) or keep it whole (false)
splitterCopyAsSingleLine: false, // copy as single line (true) or multiline (false)
splitterTransforms: [''], // array of transform names to apply in sequence (start with one empty slot)
splitterStartWrap: '',
splitterEndWrap: '',
splitMessages: []
};
}
getVueMethods() {
return {
/**
* Set encapsulation start and end strings
* @param {string} start - The start string
* @param {string} end - The end string
*/
setEncapsulation(start, end) {
this.splitterStartWrap = start;
this.splitterEndWrap = end;
},
/**
* Handle transform change - auto-add next dropdown or collapse consecutive Nones
* @param {number} index - The index of the transformation that changed
*/
handleTransformChange(index) {
const value = this.splitterTransforms[index];
if (value && value !== '') {
// Transform was selected - add next dropdown if it doesn't exist
if (index === this.splitterTransforms.length - 1) {
this.splitterTransforms.push('');
}
} else {
// Transform was set to None
// Check if previous dropdown is also None - if so, remove current one and collapse from previous position
if (index > 0) {
const prev = this.splitterTransforms[index - 1];
if (!prev || prev === '') {
// Collapse: remove this dropdown
this.splitterTransforms.splice(index, 1);
}
} else if (index === 0 && this.splitterTransforms.length === 1) {
// Only one dropdown and it's set to None - keep it as the starting dropdown
// Do nothing
} else if (index === 0 && this.splitterTransforms.length > 1) {
// First dropdown set to None, check if next is also None
const next = this.splitterTransforms[1];
if (!next || next === '') {
// Remove the first one
this.splitterTransforms.splice(0, 1);
}
}
}
// Ensure there's always at least one dropdown
if (this.splitterTransforms.length === 0) {
this.splitterTransforms = [''];
}
// Force Vue to update
this.$forceUpdate();
},
/**
* Generate split messages from input text
* Supports two modes: character chunks or split words in half
*/
generateSplitMessages() {
// Clear previous output at the start
this.splitMessages = [];
const input = this.splitterInput;
if (!input) {
return;
}
let chunks = [];
if (this.splitterMode === 'chunk') {
// Character chunk mode
const chunkSize = Math.max(1, Math.min(500, this.splitterChunkSize || 6));
for (let i = 0; i < input.length; i += chunkSize) {
chunks.push(input.slice(i, i + chunkSize));
}
} else if (this.splitterMode === 'word') {
// Word split mode - creates messages with pattern: secondHalf + wholeWords + firstHalf
// IMPORTANT: ALL words must be included in output, never filtered out
const words = input.match(/\S+/g) || [];
if (words.length === 0) return;
const skipCount = Math.max(0, Math.min(20, this.splitterWordSkip || 0));
const minLength = Math.max(1, this.splitterMinWordLength || 2);
// Process all words - only split words that meet minimum length
// Short words are kept whole but still included in the pattern
let wordsToProcess = words;
let prependToFirst = [];
// Handle "Split First Word" option
if (!this.splitterSplitFirstWord && words.length > 0) {
prependToFirst = [words[0]];
wordsToProcess = words.slice(1);
}
// Build word processing array - track which words can be split vs kept whole
const wordData = wordsToProcess.map((word, idx) => {
const canSplit = word.length >= minLength && word.length > 1;
return {
word: word,
canSplit: canSplit,
index: idx
};
});
// Determine which words to split (only words that can be split)
const splittableWords = wordData.filter(w => w.canSplit);
if (splittableWords.length === 0) {
// No words can be split, output everything as one message
chunks.push([...prependToFirst, ...wordsToProcess].join(' '));
return;
}
// Determine split pattern based on splittable words only
const splitIndexes = new Set();
for (let i = 0; i < splittableWords.length; i++) {
if ((i % (skipCount + 1)) === 0) {
splitIndexes.add(splittableWords[i].index);
}
}
// Process all words and build split structure
const processedWords = wordData.map((wd, idx) => {
if (splitIndexes.has(idx) && wd.canSplit) {
// Split this word
let splitPos;
if (wd.word.length % 2 === 0) {
splitPos = wd.word.length / 2;
} else {
splitPos = this.splitterWordSplitSide === 'left'
? Math.ceil(wd.word.length / 2)
: Math.floor(wd.word.length / 2);
}
return {
firstHalf: wd.word.slice(0, splitPos),
secondHalf: wd.word.slice(splitPos),
split: true
};
}
// Keep whole (either too short or skipped)
return { whole: wd.word, split: false };
});
// Build output messages
let currentMessage = [...prependToFirst];
let messageStarted = false;
for (let i = 0; i < processedWords.length; i++) {
const item = processedWords[i];
if (item.split) {
if (!messageStarted) {
// First split word - add first half to current message
currentMessage.push(item.firstHalf);
chunks.push(currentMessage.join(' '));
currentMessage = [item.secondHalf];
messageStarted = true;
} else {
// Add first half to current message, then start new message with second half
currentMessage.push(item.firstHalf);
chunks.push(currentMessage.join(' '));
currentMessage = [item.secondHalf];
}
} else {
// Whole word - add to current message (ALL words included)
currentMessage.push(item.whole);
}
}
// Add any remaining message
if (currentMessage.length > 0) {
chunks.push(currentMessage.join(' '));
}
}
// Apply transformations in sequence (chaining)
let processedChunks = chunks;
if (this.splitterTransforms && this.splitterTransforms.length > 0) {
// Filter out empty transforms
const activeTransforms = this.splitterTransforms.filter(t => t && t !== '');
if (activeTransforms.length > 0) {
// Apply each transformation in sequence
for (const transformName of activeTransforms) {
const selectedTransform = this.transforms.find(t => t.name === transformName);
if (selectedTransform && selectedTransform.func) {
processedChunks = processedChunks.map(chunk => {
try {
return selectedTransform.func(chunk);
} catch (e) {
console.error('Transform error:', e);
return chunk;
}
});
}
}
}
}
// Apply encapsulation
const start = this.splitterStartWrap || '';
const end = this.splitterEndWrap || '';
this.splitMessages = processedChunks.map(chunk => `${start}${chunk}${end}`);
},
/**
* Copy all split messages to clipboard
* Single line: merges messages into one continuous string (keeps encapsulation/transformations)
* Multiline: copies messages separated by newlines
*/
copyAllSplitMessages() {
if (this.splitMessages.length === 0) return;
if (this.splitterCopyAsSingleLine) {
// Merge all messages back together, keeping encapsulation and transformations
// Just join without newlines - all encapsulation/transformations are already in splitMessages
const merged = this.splitMessages.join('');
this.copyToClipboard(merged);
} else {
// Copy all messages separated by newlines
const allMessages = this.splitMessages.join('\n');
this.copyToClipboard(allMessages);
}
}
};
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = SplitterTool;
} else {
window.SplitterTool = SplitterTool;
}
+245
View File
@@ -0,0 +1,245 @@
/**
* Tokenade Tool - Token bomb generator tool
* Note: This is a complex tool, so we'll include the key methods
*/
class TokenadeTool extends Tool {
constructor() {
super({
id: 'tokenade',
name: 'Tokenade',
icon: 'fa-bomb',
title: 'Tokenade Generator',
order: 4
});
}
getVueData() {
return {
tbDepth: 3,
tbBreadth: 4,
tbRepeats: 5,
tbSeparator: 'zwnj',
tbIncludeVS: true,
tbIncludeNoise: true,
tbRandomizeEmojis: true,
tbAutoCopy: true,
tbSingleCarrier: true,
tbCarrier: '',
tbPayloadEmojis: [],
tokenBombOutput: '',
tpBase: '',
tpRepeat: 100,
tpCombining: true,
tpZW: false,
textPayload: '',
dangerThresholdTokens: 25_000_000,
quickCarrierEmojis: ['🐍','🐉','🐲','🔥','💥','🗿','⚓','⭐','✨','🚀','💀','🪨','🍃','🪶','🔮','🐢','🐊','🦎'],
tbCarrierManual: '',
carrierEmojiList: [...(window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [])]
};
}
getVueMethods() {
return {
generateTokenBomb: function() {
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.carrierEmojiList && this.carrierEmojiList.length) ? this.carrierEmojiList :
(window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : this.quickCarrierEmojis);
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);
}
// Auto-copy if enabled
if (this.tbAutoCopy && this.tokenBombOutput) {
this.$nextTick(() => {
this.forceCopyToClipboard(this.tokenBombOutput);
this.showNotification('Tokenade generated and copied!', 'success', 'fas fa-bomb');
});
} else {
this.showNotification('Tokenade generated!', 'success', 'fas fa-bomb');
}
},
applyTokenadePreset: function(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('Preset applied', 'success', 'fas fa-sliders-h');
},
estimateTokenadeLength: function() {
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;
const perUnit = avgDigits + 1 + sepLen + (this.tbIncludeNoise ? 2 : 0);
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;
}
},
estimateTokenadeTokens: function() {
return Math.max(0, this.estimateTokenadeLength());
},
setCarrierFromSelected: function() {
if (this.selectedEmoji) this.tbCarrier = String(this.selectedEmoji);
},
generateTextPayload: function() {
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('Text payload generated', 'success', 'fas fa-bomb');
}
};
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = TokenadeTool;
} else {
window.TokenadeTool = TokenadeTool;
}
+95
View File
@@ -0,0 +1,95 @@
/**
* Tokenizer Tool - Tokenizer visualization tool
*/
class TokenizerTool extends Tool {
constructor() {
super({
id: 'tokenizer',
name: 'Tokenizer',
icon: 'fa-layer-group',
title: 'Tokenizer visualization',
order: 6
});
}
getVueData() {
return {
tokenizerInput: '',
tokenizerEngine: 'byte',
tokenizerTokens: [],
tokenizerCharCount: 0,
tokenizerWordCount: 0
};
}
getVueMethods() {
return {
runTokenizer: async function() {
const text = this.tokenizerInput || '';
const engine = this.tokenizerEngine;
const tokens = [];
if (!text) { this.tokenizerTokens = []; this.tokenizerCharCount = 0; this.tokenizerWordCount = 0; return; }
if (engine === 'byte') {
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') {
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 {
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;
this.tokenizerCharCount = Array.from(text).length;
const wordMatches = text.trim().match(/[^\s]+/g) || [];
this.tokenizerWordCount = wordMatches.length;
}
};
}
getVueWatchers() {
return {
tokenizerInput() {
this.runTokenizer();
},
tokenizerEngine() {
this.runTokenizer();
}
};
}
onActivate(vueInstance) {
vueInstance.$nextTick(() => vueInstance.runTokenizer());
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = TokenizerTool;
} else {
window.TokenizerTool = TokenizerTool;
}
+97
View File
@@ -0,0 +1,97 @@
/**
* Base Tool Class
* All tools should inherit from this class and implement required methods
*/
class Tool {
constructor(config) {
// Required properties
this.id = config.id; // Unique identifier (e.g., 'transforms', 'decoder')
this.name = config.name; // Display name (e.g., 'Transform', 'Decoder')
this.icon = config.icon || 'fa-circle'; // Font Awesome icon class
this.title = config.title || this.name; // Tooltip/title text
// Optional properties
this.order = config.order || 999; // Order in tab bar (lower = earlier)
this.enabled = config.enabled !== false; // Whether tool is enabled
}
/**
* Get Vue data properties needed for this tool
* Should return an object that will be merged into Vue's data
* @returns {Object}
*/
getVueData() {
return {};
}
/**
* Get Vue methods needed for this tool
* Should return an object with method definitions
* @returns {Object}
*/
getVueMethods() {
return {};
}
/**
* Get Vue watchers needed for this tool
* Should return an object with watcher definitions
* @returns {Object}
*/
getVueWatchers() {
return {};
}
/**
* Get Vue lifecycle hooks
* Should return an object with lifecycle methods (mounted, created, etc.)
* @returns {Object}
*/
getVueLifecycle() {
return {};
}
/**
* Get HTML template for the tab button
* @returns {String} HTML string for the tab button
*/
getTabButtonHTML() {
return `
<button
:class="{ active: activeTab === '${this.id}' }"
@click="switchToTab('${this.id}')"
title="${this.title}"
>
<i class="fas ${this.icon}"></i> ${this.name}
</button>
`;
}
/**
* Initialize tool-specific functionality
* Called when the tool's tab is activated
* @param {Vue} vueInstance - The Vue app instance
*/
onActivate(vueInstance) {
// Override in subclasses
}
/**
* Cleanup tool-specific functionality
* Called when switching away from this tool's tab
* @param {Vue} vueInstance - The Vue app instance
*/
onDeactivate(vueInstance) {
// Override in subclasses
}
}
// Export for use in other files
if (typeof module !== 'undefined' && module.exports) {
module.exports = Tool;
} else {
window.Tool = Tool;
}
+193
View File
@@ -0,0 +1,193 @@
/**
* Transform Tool - Text transformation tool
*/
class TransformTool extends Tool {
constructor() {
super({
id: 'transforms',
name: 'Transform',
icon: 'fa-font',
title: 'Transform text (T)',
order: 1
});
}
getVueData() {
const transforms = (window.transforms && Object.keys(window.transforms).length > 0)
? Object.entries(window.transforms).map(([key, transform]) => ({
name: transform.name,
func: transform.func.bind(transform),
preview: transform.preview.bind(transform),
reverse: transform.reverse ? transform.reverse.bind(transform) : null,
category: transform.category || 'special'
}))
: [];
const categorySet = new Set();
transforms.forEach(transform => {
if (transform.category) {
categorySet.add(transform.category);
}
});
// Sort categories, but always put randomizer last
const sortedCategories = Array.from(categorySet).sort((a, b) => {
if (a === 'randomizer') return 1;
if (b === 'randomizer') return -1;
return a.localeCompare(b);
});
return {
transformInput: '',
transformOutput: '',
activeTransform: null,
transforms: transforms,
categories: sortedCategories
};
}
getVueMethods() {
return {
getDisplayCategory: function(transformName) {
// Find transform by name and return its category property
const transform = this.transforms.find(t => t.name === transformName);
return transform ? transform.category : 'special';
},
getTransformsByCategory: function(category) {
return this.transforms.filter(transform => transform.category === category);
},
isSpecialCategory: function(category) {
return category === 'randomizer';
},
applyTransform: function(transform, event) {
event && event.preventDefault();
event && event.stopPropagation();
if (transform && transform.name === 'Random Mix') {
this.triggerRandomizerChaos();
}
if (this.transformInput) {
this.activeTransform = transform;
if (transform.name === 'Random Mix') {
this.transformOutput = window.transforms.randomizer.func(this.transformInput);
const transformInfo = window.transforms.randomizer.getLastTransformInfo();
if (transformInfo.length > 0) {
const transformsList = transformInfo.map(t => t.transformName).join(', ');
this.showNotification(`Mixed with: ${transformsList}`, 'success', 'fas fa-random');
}
} else {
this.transformOutput = transform.func(this.transformInput);
}
this.isTransformCopy = true;
this.forceCopyToClipboard(this.transformOutput);
if (transform.name !== 'Random Mix') {
this.showNotification(`${transform.name} applied and copied!`, 'success', 'fas fa-check');
}
document.querySelectorAll('.transform-button').forEach(button => {
button.classList.remove('active');
});
const inputBox = document.querySelector('#transform-input');
if (inputBox) {
this.focusWithoutScroll(inputBox);
const len = inputBox.value.length;
try { inputBox.setSelectionRange(len, len); } catch (_) {}
}
this.isTransformCopy = false;
this.ignoreKeyboardEvents = false;
}
},
autoTransform: function() {
if (this.transformInput && this.activeTransform && this.activeTab === 'transforms') {
const segments = window.EmojiUtils.splitEmojis(this.transformInput);
const transformedSegments = segments.map(segment => {
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.EmojiUtils.joinEmojis(transformedSegments);
}
},
initializeCategoryNavigation: function() {
this.$nextTick(() => {
const legendItems = document.querySelectorAll('.transform-category-legend .legend-item');
legendItems.forEach(item => {
const newItem = item.cloneNode(true);
item.parentNode.replaceChild(newItem, item);
});
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) {
document.querySelectorAll('.transform-category-legend .legend-item').forEach(li => {
li.classList.remove('active-category');
});
item.classList.add('active-category');
const inputSection = document.querySelector('.input-section');
const inputSectionHeight = inputSection.offsetHeight;
const elementPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;
const offsetPosition = elementPosition - inputSectionHeight - 10;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
targetElement.classList.add('highlight-section');
setTimeout(() => {
targetElement.classList.remove('highlight-section');
}, 1000);
}
}
});
});
});
}
};
}
getVueWatchers() {
return {
transformInput() {
if (this.activeTransform && this.activeTab === 'transforms') {
this.transformOutput = this.activeTransform.func(this.transformInput);
}
}
};
}
getVueLifecycle() {
return {
mounted() {
this.initializeCategoryNavigation();
}
};
}
onActivate(vueInstance) {
vueInstance.$nextTick(() => {
vueInstance.initializeCategoryNavigation();
});
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = TransformTool;
} else {
window.TransformTool = TransformTool;
}
-2481
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
/**
* Clipboard Utility
* Provides unified clipboard copy functionality using Clipboard API
*/
window.ClipboardUtils = {
/**
* Copy text to clipboard using Clipboard API
* @param {string} text - Text to copy
* @param {Object} options - Options object
* @param {Function} options.onSuccess - Callback on success
* @param {Function} options.onError - Callback on error
* @param {boolean} options.suppressNotification - Don't show notification
* @returns {Promise<boolean>} - Success status
*/
async copy(text, options = {}) {
if (!text) return false;
const {
onSuccess,
onError,
suppressNotification = false
} = options;
if (!navigator.clipboard || !navigator.clipboard.writeText) {
const errorMsg = 'Clipboard API not available';
console.error(errorMsg);
if (!suppressNotification && window.NotificationUtils) {
window.NotificationUtils.showNotification('Clipboard not supported', 'error', 'fas fa-exclamation-triangle');
}
if (onError) onError(new Error(errorMsg));
return false;
}
try {
await navigator.clipboard.writeText(text);
if (!suppressNotification && window.NotificationUtils) {
window.NotificationUtils.showNotification('Copied!', 'success', 'fas fa-check');
}
if (onSuccess) onSuccess();
return true;
} catch (err) {
console.error('Clipboard copy failed:', err);
if (!suppressNotification && window.NotificationUtils) {
window.NotificationUtils.showNotification('Copy failed', 'error', 'fas fa-exclamation-triangle');
}
if (onError) onError(err);
return false;
}
}
};
+33
View File
@@ -0,0 +1,33 @@
window.EmojiUtils = {
splitEmojis(text) {
if (Intl.Segmenter) {
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
return Array.from(segmenter.segment(text), ({ segment }) => segment);
}
return Array.from(text);
},
joinEmojis(emojis) {
return emojis.join('');
},
getAllEmojis() {
if (!window.emojiData || typeof window.emojiData !== 'object') {
return [];
}
return Object.keys(window.emojiData).filter(key => {
const value = window.emojiData[key];
return typeof value === 'object' && value !== null && 'official' in value;
});
},
async getCompatibleEmojis(progressCallback) {
const allEmojis = this.getAllEmojis();
if (window.emojiCompatibility && typeof window.emojiCompatibility.getCompatibleEmojis === 'function') {
return await window.emojiCompatibility.getCompatibleEmojis(allEmojis, progressCallback);
}
return allEmojis;
}
};
+40
View File
@@ -0,0 +1,40 @@
window.EscapeParser = {
parseEscapeSequence(str) {
if (!str || typeof str !== 'string') {
return str;
}
const escapeMap = {
'\\u200B': '\u200B', // Zero Width Space
'\\u200C': '\u200C', // Zero Width Non-Joiner
'\\u200D': '\u200D', // Zero Width Joiner
'\\u2060': '\u2060', // Word Joiner
'\\uFE0E': '\uFE0E', // Variation Selector-15
'\\uFE0F': '\uFE0F', // Variation Selector-16
'\\n': '\n',
'\\r': '\r',
'\\t': '\t',
'\\0': '\0',
'\\\'': '\'',
'\\"': '"',
'\\\\': '\\'
};
if (escapeMap[str] !== undefined) {
return escapeMap[str];
}
const unicodeMatch = str.match(/^\\u([0-9A-Fa-f]{4})$/);
if (unicodeMatch) {
return String.fromCharCode(parseInt(unicodeMatch[1], 16));
}
const hexMatch = str.match(/^\\x([0-9A-Fa-f]{2})$/);
if (hexMatch) {
return String.fromCharCode(parseInt(hexMatch[1], 16));
}
return str;
}
};
+29
View File
@@ -0,0 +1,29 @@
window.FocusUtils = {
focusWithoutScroll(element) {
if (!element) return;
try {
const scrollX = window.pageXOffset || window.scrollX || 0;
const scrollY = window.pageYOffset || window.scrollY || 0;
element.focus();
window.scrollTo(scrollX, scrollY);
} catch (e) {
try {
element.focus();
} catch (err) {
console.warn('Failed to focus element:', err);
}
}
},
clearFocusAndSelection() {
if (document.activeElement && document.activeElement.blur) {
document.activeElement.blur();
}
if (window.getSelection) {
window.getSelection().removeAllRanges();
}
document.body.focus();
}
};
+60
View File
@@ -0,0 +1,60 @@
window.HistoryUtils = {
addToHistory(historyArray, maxItems, source, content) {
if (!historyArray || !Array.isArray(historyArray)) {
console.warn('HistoryUtils.addToHistory: historyArray is not an array');
return;
}
if (!content) {
return;
}
const entry = {
source: source || 'Unknown',
content: content,
timestamp: new Date().toISOString(),
id: Date.now() + Math.random()
};
historyArray.unshift(entry);
if (historyArray.length > maxItems) {
historyArray.splice(maxItems);
}
},
clearHistory(historyArray) {
if (historyArray && Array.isArray(historyArray)) {
// Use splice to remove all items (same approach as removeFromHistory)
historyArray.splice(0, historyArray.length);
}
},
removeFromHistory(historyArray, id) {
if (!historyArray || !Array.isArray(historyArray)) {
return;
}
const index = historyArray.findIndex(item => item.id === id);
if (index !== -1) {
historyArray.splice(index, 1);
}
},
getHistorySource(activeTab, context = {}) {
if (activeTab === 'transforms' && context.activeTransform) {
return `Transform: ${context.activeTransform.name}`;
} else if (activeTab === 'steganography') {
if (context.activeSteg === 'invisible') {
return 'Invisible Text';
} else if (context.selectedEmoji) {
return `Emoji: ${context.selectedEmoji}`;
}
return 'Steganography';
} else if (activeTab === 'transforms') {
return 'Transform';
}
return 'Unknown';
}
};
+37
View File
@@ -0,0 +1,37 @@
window.NotificationUtils = {
showNotification(message, type = 'success', iconClass = null) {
const existing = document.querySelector('.copy-notification');
if (existing) {
existing.remove();
}
const notification = document.createElement('div');
notification.className = `copy-notification ${type || 'success'}`;
if (iconClass) {
const icon = document.createElement('i');
icon.className = iconClass;
notification.appendChild(icon);
}
const text = document.createElement('span');
text.textContent = message;
notification.appendChild(text);
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
},
showCopiedPopup() {
this.showNotification('Copied!', 'success', 'fas fa-check');
}
};
+37
View File
@@ -0,0 +1,37 @@
window.ThemeUtils = {
toggleTheme(currentTheme) {
const newTheme = !currentTheme;
if (newTheme) {
document.body.classList.add('dark-theme');
document.body.classList.remove('light-theme');
} else {
document.body.classList.add('light-theme');
document.body.classList.remove('dark-theme');
}
try {
localStorage.setItem('theme', newTheme ? 'dark' : 'light');
} catch (e) {
console.warn('Failed to save theme preference:', e);
}
return newTheme;
},
initializeTheme() {
try {
const saved = localStorage.getItem('theme');
if (saved === 'light') {
return false;
} else if (saved === 'dark') {
return true;
}
} catch (e) {
console.warn('Failed to load theme preference:', e);
}
return true;
}
};