--- name: emoji-encoder description: Use when implementing steganographic text encoding using Unicode Variation Selectors - allows encoding any message invisibly into emojis, letters, or any characters using Unicode Variation Selectors (U+FE00-U+FE0F and U+E0100-U+E01EF) when_to_use: When adding steganographic encoding, hiding data in plain text, encoding messages into emojis/characters, or implementing invisible data encoding using Unicode Variation Selectors --- # Emoji Encoder - Steganographic Text Encoding ## Overview Complete implementation guide for encoding any text message invisibly into emojis, letters, or any Unicode characters using Unicode Variation Selectors. The encoded data appears as just the marker character (emoji/letter) followed by invisible variation selector characters, making it perfect for steganographic applications. **Core Capabilities:** - Encode any UTF-8 text into invisible Unicode Variation Selectors - Use any character as a marker (emojis, letters, symbols) - Decode hidden messages from encoded text - Detect if text contains encoded data - React hooks for easy integration - Zero external dependencies - pure TypeScript/JavaScript ## How It Works The encoding uses Unicode Variation Selectors to invisibly encode UTF-8 bytes: - **Bytes 0-15**: Encoded as Variation Selectors (U+FE00-U+FE0F) - **Bytes 16-255**: Encoded as Variation Selectors Supplement (U+E0100-U+E01EF) - **Marker**: **Any Unicode character** (emoji, letter, symbol, number, punctuation, etc.) appears before encoded data - **Result**: Appears as just the marker character, with invisible encoded data following **CRITICAL:** The marker can be **any Unicode character** - there are no restrictions. Use emojis, letters, numbers, symbols, or any other Unicode character that fits your use case. **Example:** ``` Input: "Hello, World!" Marker: "πŸ₯œ" Output: "πŸ₯œ" + [invisible variation selectors] ``` The output looks like just "πŸ₯œ" but contains the full encoded message. ## Prerequisites **IMPORTANT:** Before adding dependencies, review your project's `package.json` to check if any of these packages already exist. If they do, verify the versions are compatible with the requirements below. Only add packages that are missing or need version updates. **Required packages:** ```json { "typescript": "^5.x" } ``` **No external dependencies required** - uses only native JavaScript/TypeScript APIs. ## Implementation Checklist - [ ] Implement core encoding/decoding functions - [ ] Add React hooks for easy integration - [ ] Create UI components for encoding/decoding - [ ] Add emoji/character selector component - [ ] Implement detection function (isEncoded) - [ ] Add error handling and validation - [ ] Create tests for encoding/decoding - [ ] Add copy-to-clipboard functionality ## Part 1: Core Encoding Functions ### Unicode Variation Selector Constants ```typescript // Variation selectors block https://unicode.org/charts/nameslist/n_FE00.html // VS1..=VS16 const VARIATION_SELECTOR_START = 0xfe00; const VARIATION_SELECTOR_END = 0xfe0f; // Variation selectors supplement https://unicode.org/charts/nameslist/n_E0100.html // VS17..=VS256 const VARIATION_SELECTOR_SUPPLEMENT_START = 0xe0100; const VARIATION_SELECTOR_SUPPLEMENT_END = 0xe01ef; ``` ### Byte to Variation Selector ```typescript export function toVariationSelector(byte: number): string | null { if (byte >= 0 && byte < 16) { return String.fromCodePoint(VARIATION_SELECTOR_START + byte); } else if (byte >= 16 && byte < 256) { return String.fromCodePoint(VARIATION_SELECTOR_SUPPLEMENT_START + byte - 16); } else { return null; } } ``` **How it works:** - Bytes 0-15 map to U+FE00-U+FE0F (16 characters) - Bytes 16-255 map to U+E0100-U+E01EF (240 characters) - Total coverage: 256 possible byte values ### Variation Selector to Byte ```typescript export function fromVariationSelector(codePoint: number): number | null { if (codePoint >= VARIATION_SELECTOR_START && codePoint <= VARIATION_SELECTOR_END) { return codePoint - VARIATION_SELECTOR_START; } else if (codePoint >= VARIATION_SELECTOR_SUPPLEMENT_START && codePoint <= VARIATION_SELECTOR_SUPPLEMENT_END) { return codePoint - VARIATION_SELECTOR_SUPPLEMENT_START + 16; } else { return null; } } ``` **Reverse mapping:** - U+FE00-U+FE0F β†’ bytes 0-15 - U+E0100-U+E01EF β†’ bytes 16-255 ### Encode Function ```typescript export function encode(marker: string, text: string): string { // Convert the string to utf-8 bytes const bytes = new TextEncoder().encode(text); let encoded = marker; for (const byte of bytes) { const selector = toVariationSelector(byte); if (selector === null) { throw new Error(`Invalid byte value: ${byte}`); } encoded += selector; } return encoded; } ``` **Key points:** - Uses `TextEncoder` to convert string to UTF-8 bytes - Marker appears first (visible character) - Each byte encoded as variation selector (invisible) - Handles all UTF-8 characters (including Unicode) **Usage:** ```typescript const encoded = encode('πŸ₯œ', 'Hello, World!'); // Result: "πŸ₯œ" + [invisible characters] ``` ### Decode Function ```typescript export function decode(text: string): string { let decoded: number[] = []; const chars = Array.from(text); for (const char of chars) { const codePoint = char.codePointAt(0); if (codePoint === undefined) continue; const byte = fromVariationSelector(codePoint); if (byte === null && decoded.length > 0) { // Stop at first non-variation-selector after decoding started break; } else if (byte === null) { // Skip non-variation-selector characters before decoding starts continue; } decoded.push(byte); } if (decoded.length === 0) { throw new Error('No encoded data found'); } const decodedArray = new Uint8Array(decoded); return new TextDecoder().decode(decodedArray); } ``` **Key points:** - Skips marker character (first non-variation-selector) - Stops at first non-variation-selector after decoding starts - Uses `TextDecoder` to convert UTF-8 bytes back to string - Handles all UTF-8 characters correctly **Usage:** ```typescript const decoded = decode(encodedText); // Result: "Hello, World!" ``` ## Part 2: React Hook Implementation ### useEmojiEncoding Hook ```typescript import { useCallback } from 'react'; export interface EmojiEncodingOptions { /** Marker/prefix before encoded data (default: 'πŸ₯œ'). Can be any string, emoji, or empty. */ marker?: string; } /** * Hook for encoding and decoding any string data using Unicode Variation Selectors. * * Encodes data invisibly using Unicode Variation Selectors (U+FE00-U+FE0F for bytes 0-15, * U+E0100-U+E01EF for bytes 16-255). The marker appears as just an emoji followed by invisible characters. * * @param options - Configuration options * @param options.marker - Marker/prefix before encoded data (default: 'πŸ₯œ') * * @example * ```tsx * const { encode, decode } = useEmojiEncoding({ marker: 'πŸ”' }); * const encoded = encode('secret data'); * const decoded = decode(encoded); * ``` */ export function useEmojiEncoding(options: EmojiEncodingOptions = {}) { const { marker = 'πŸ₯œ' } = options; const byteToVariationSelector = useCallback((byteValue: number): string => { if (byteValue >= 0 && byteValue <= 15) { return String.fromCodePoint(0xfe00 + byteValue); } if (byteValue >= 16 && byteValue <= 255) { return String.fromCodePoint(0xe0100 + (byteValue - 16)); } return ''; }, []); const variationSelectorToByte = useCallback((char: string): number | null => { const codePoint = char.codePointAt(0); if (codePoint === undefined) return null; if (codePoint >= 0xfe00 && codePoint <= 0xfe0f) { return codePoint - 0xfe00; } if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) { return codePoint - 0xe0100 + 16; } return null; }, []); const encode = useCallback((data: string): string => { const bytes = new TextEncoder().encode(data); return ( marker + Array.from(bytes) .map((byte) => byteToVariationSelector(byte)) .join('') ); }, [marker, byteToVariationSelector]); const decode = useCallback((encoded: string): string | undefined => { try { const decoded: number[] = []; for (const char of Array.from(encoded)) { const byteValue = variationSelectorToByte(char); if (byteValue === null && decoded.length > 0) break; if (byteValue === null) continue; decoded.push(byteValue); } if (decoded.length === 0) return undefined; const decodedArray = new Uint8Array(decoded); return new TextDecoder().decode(decodedArray); } catch (error) { console.error('Failed to decode emoji-encoded data:', error); return undefined; } }, [variationSelectorToByte]); const isEncoded = useCallback((text: string): boolean => { if (marker && !text.includes(marker)) return false; const decoded = decode(text); return decoded !== undefined && decoded.length > 0; }, [marker, decode]); return { encode, decode, isEncoded, }; } ``` **Usage:** ```typescript function MyComponent() { const { encode, decode, isEncoded } = useEmojiEncoding({ marker: 'πŸ”' }); const handleEncode = () => { const encoded = encode('secret message'); console.log(encoded); // "πŸ”" + invisible characters }; const handleDecode = () => { const decoded = decode(encodedText); console.log(decoded); // "secret message" }; const checkIfEncoded = () => { if (isEncoded(someText)) { console.log('Text contains encoded data!'); } }; } ``` ## Part 3: UI Components ### Emoji Selector Component ```typescript // components/EmojiSelector.tsx import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; interface EmojiSelectorProps { emojiList: string[]; selectedEmoji: string; onEmojiSelect: (emoji: string) => void; disabled?: boolean; } export function EmojiSelector({ emojiList, selectedEmoji, onEmojiSelect, disabled = false, }: EmojiSelectorProps) { return (
{emojiList.map((emoji) => ( ))}
); } ``` ### Encoder/Decoder Component ```typescript // components/EncoderDecoder.tsx "use client" import { useEffect, useState } from "react"; import { Textarea } from "@/components/ui/textarea"; import { CardContent } from "@/components/ui/card"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Copy, Check } from "lucide-react"; import { encode, decode } from "@/lib/encoding"; import { EmojiSelector } from "@/components/EmojiSelector"; // Optional: Provide suggested markers, but users can use ANY Unicode character import { SUGGESTED_MARKERS } from "@/lib/markers"; export function EncoderDecoder() { const [mode, setMode] = useState<'encode' | 'decode'>('encode'); const [inputText, setInputText] = useState(""); const [selectedMarker, setSelectedMarker] = useState("πŸ₯œ"); const [outputText, setOutputText] = useState(""); const [errorText, setErrorText] = useState(""); const [copied, setCopied] = useState(false); // Convert input whenever it changes useEffect(() => { try { if (mode === 'encode') { const output = encode(selectedMarker, inputText); setOutputText(output); setErrorText(""); } else { const output = decode(inputText); setOutputText(output); setErrorText(""); } } catch (e) { setOutputText(""); setErrorText(`Error ${mode === "encode" ? "encoding" : "decoding"}: ${e instanceof Error ? e.message : 'Invalid input'}`); } }, [mode, selectedMarker, inputText]); const handleCopy = async () => { try { await navigator.clipboard.writeText(outputText); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error('Failed to copy:', err); } }; return (

This tool allows you to encode a hidden message into any Unicode character (emoji, letter, symbol, etc.). You can copy and paste text with a hidden message in it to decode the message.

{ setMode(checked ? 'encode' : 'decode'); setInputText(""); }} />