diff --git a/index.html b/index.html index a9e16e4..9ee8163 100644 --- a/index.html +++ b/index.html @@ -43,7 +43,6 @@ Transform @@ -51,7 +50,6 @@ Hide @@ -102,9 +100,7 @@ - - - + @@ -118,9 +114,7 @@ > {{ decodedMessage }} - - - + @@ -144,16 +138,15 @@ {{ transform.name }} - {{ transform.preview ? transform.preview(transformInput.slice(0, 10)) : '' }} + {{ transform.preview(transformInput.slice(0, 10)) }} {{ index + 1 }} @@ -167,15 +160,7 @@ v-model="transformOutput" aria-label="Transformed text output" > - - - + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..72c652f --- /dev/null +++ b/js/app.js @@ -0,0 +1,147 @@ +// Initialize Vue app +new Vue({ + el: '#app', + data: { + // Theme + isDarkTheme: true, + + // Tab Management + activeTab: 'transforms', + + // Transform Tab + transformInput: '', + transformOutput: '', + activeTransform: null, + transforms: Object.entries(window.transforms).map(([key, transform]) => ({ + name: transform.name, + func: transform.func.bind(transform), + preview: transform.preview.bind(transform) + })), + + // Steganography Tab + emojiMessage: '', + encodedMessage: '', + decodeInput: '', + decodedMessage: '', + selectedCarrier: null, + activeSteg: null, + carriers: window.steganography.carriers, + showDecoder: true + }, + methods: { + // Theme Toggle + toggleTheme() { + this.isDarkTheme = !this.isDarkTheme; + document.body.classList.toggle('light-theme'); + }, + + // Transform Methods + applyTransform(transform) { + if (this.transformInput) { + this.activeTransform = transform; + this.transformOutput = transform.func(this.transformInput); + this.copyToClipboard(this.transformOutput); + } + }, + autoTransform() { + if (this.transformInput && this.activeTransform) { + this.transformOutput = this.activeTransform.func(this.transformInput); + this.copyToClipboard(this.transformOutput); + } + }, + + // Steganography Methods + selectCarrier(carrier) { + this.selectedCarrier = carrier; + this.activeSteg = 'emoji'; + this.autoEncode(); + }, + setStegMode(mode) { + this.activeSteg = mode; + this.autoEncode(); + }, + autoEncode() { + if (!this.emojiMessage) { + this.encodedMessage = ''; + return; + } + + if (this.activeSteg === 'invisible') { + this.encodedMessage = window.steganography.encodeInvisible(this.emojiMessage); + this.copyToClipboard(this.encodedMessage); + } else if (this.selectedCarrier) { + this.encodedMessage = window.steganography.encodeEmoji( + this.selectedCarrier.emoji, + this.emojiMessage + ); + this.copyToClipboard(this.encodedMessage); + } + }, + autoDecode() { + if (!this.decodeInput) { + this.decodedMessage = ''; + return; + } + + // Try invisible text decoding + let decoded = window.steganography.decodeInvisible(this.decodeInput); + if (decoded) { + this.decodedMessage = decoded; + this.copyToClipboard(decoded); + return; + } + + // Try emoji decoding + decoded = window.steganography.decodeEmoji(this.decodeInput); + if (decoded) { + this.decodedMessage = decoded; + this.copyToClipboard(decoded); + return; + } + + this.decodedMessage = 'No hidden message found'; + }, + previewInvisible(text) { + return '[invisible]'; + }, + + // Utility Methods + async copyToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.error('Failed to copy text:', err); + } + } + }, + // Initialize theme + mounted() { + if (this.isDarkTheme) { + document.body.classList.add('dark-theme'); + } + }, + // Keyboard shortcuts + created() { + window.addEventListener('keydown', (e) => { + // Theme toggle + if (e.key === 'd' || e.key === 'D') { + this.toggleTheme(); + } + // Tab switching + else if (e.key === 't' || e.key === 'T') { + this.activeTab = 'transforms'; + } + else if (e.key === 'h' || e.key === 'H') { + this.activeTab = 'steganography'; + } + // Transform shortcuts (1-9) + else if (this.activeTab === 'transforms' && e.key >= '1' && e.key <= '9') { + const index = parseInt(e.key) - 1; + if (index < this.transforms.length) { + this.applyTransform(this.transforms[index]); + } + } + + }); + } +}); diff --git a/js/steganography.js b/js/steganography.js new file mode 100644 index 0000000..1ec6ce0 --- /dev/null +++ b/js/steganography.js @@ -0,0 +1,72 @@ +// Steganography carriers +const carriers = [ + { emoji: '🐍', name: 'SNAKE', desc: 'Classic Snake', preview: text => `🐍${text}` }, + { emoji: '🐉', name: 'DRAGON', desc: 'Mystical Dragon', preview: text => `🐉${text}` }, + { emoji: '🦎', name: 'LIZARD', desc: 'Sneaky Lizard', preview: text => `🦎${text}` }, + { emoji: '🐊', name: 'CROCODILE', desc: 'Dangerous Croc', preview: text => `🐊${text}` } +]; + +// Variation selector functions +function toVariationSelector(byte) { + return String.fromCodePoint(0xFE00 + byte); +} + +function fromVariationSelector(codePoint) { + return codePoint - 0xFE00; +} + +// Emoji encoding/decoding +function encodeEmoji(emoji, text) { + if (!text) return ''; + + const bytes = new TextEncoder().encode(text); + let result = emoji; + + for (const byte of bytes) { + result += toVariationSelector(byte); + } + + return result; +} + +function decodeEmoji(text) { + if (!text) return ''; + + const matches = [...text.matchAll(/[\uFE00-\uFE0F]/g)]; + if (!matches.length) return ''; + + const bytes = new Uint8Array(matches.map(m => fromVariationSelector(m[0].codePointAt(0)))); + return new TextDecoder().decode(bytes); +} + +// 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 ''; + + const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)]; + if (!matches.length) return ''; + + const bytes = new Uint8Array( + matches.map(m => m[0].codePointAt(0) - 0xE0000) + ); + + return new TextDecoder().decode(bytes); +} + +// Export for use in app.js +window.steganography = { + carriers, + encodeEmoji, + decodeEmoji, + encodeInvisible, + decodeInvisible +}; diff --git a/js/transforms.js b/js/transforms.js new file mode 100644 index 0000000..b5824bd --- /dev/null +++ b/js/transforms.js @@ -0,0 +1,153 @@ +// Text transformation functions +const transforms = { + // Basic transforms + upside_down: { + name: 'Upside Down', + map: { + 'a': 'ɐ', 'b': 'q', 'c': 'ɔ', 'd': 'p', 'e': 'ǝ', 'f': 'ɟ', 'g': 'ƃ', 'h': 'ɥ', 'i': 'ᴉ', + 'j': 'ɾ', 'k': 'ʞ', 'l': 'l', 'm': 'ɯ', 'n': 'u', 'o': 'o', 'p': 'd', 'q': 'b', 'r': 'ɹ', + 's': 's', 't': 'ʇ', 'u': 'n', 'v': 'ʌ', 'w': 'ʍ', 'x': 'x', 'y': 'ʎ', 'z': 'z', + 'A': '∀', 'B': 'B', 'C': 'Ɔ', 'D': 'D', 'E': 'Ǝ', 'F': 'Ⅎ', 'G': 'פ', 'H': 'H', 'I': 'I', + 'J': 'ſ', 'K': 'K', 'L': '˥', 'M': 'W', 'N': 'N', 'O': 'O', 'P': 'Ԁ', 'Q': 'Q', 'R': 'R', + 'S': 'S', 'T': '┴', 'U': '∩', 'V': 'Λ', 'W': 'M', 'X': 'X', 'Y': '⅄', 'Z': 'Z', + '0': '0', '1': 'Ɩ', '2': 'ᄅ', '3': 'Ɛ', '4': 'ㄣ', '5': 'ϛ', '6': '9', '7': 'ㄥ', + '8': '8', '9': '6', '.': '˙', ',': "'", '?': '¿', '!': '¡', '"': ',,', "'": ',', + '(': ')', ')': '(', '[': ']', ']': '[', '{': '}', '}': '{', '<': '>', '>': '<', + '&': '⅋', '_': '‾' + }, + func: function(text) { + return [...text].map(c => this.map[c] || c).reverse().join(''); + }, + preview: function(text) { + return this.func(text); + } + }, + + elder_futhark: { + name: 'Elder Futhark', + map: { + 'a': 'ᚨ', 'b': 'ᛒ', 'c': 'ᛲ', 'd': 'ᛞ', 'e': 'ᛖ', 'f': 'ᚠ', 'g': 'ᚷ', 'h': 'ᚺ', 'i': 'ᛁ', + 'j': 'ᛃ', 'k': 'ᛲ', 'l': 'ᛚ', 'm': 'ᛗ', 'n': 'ᚾ', 'o': 'ᛟ', 'p': 'ᛈ', 'q': 'ᛲᛩ', 'r': 'ᚱ', + 's': 'ᛋ', 't': 'ᛏ', 'u': 'ᚢ', 'v': 'ᛩ', 'w': 'ᛩ', 'x': 'ᛲᛋ', 'y': 'ᛁ', 'z': 'ᛉ' + }, + func: function(text) { + return [...text.toLowerCase()].map(c => this.map[c] || c).join(''); + }, + preview: function(text) { + return this.func(text); + } + }, + + vaporwave: { + name: 'Vaporwave', + func: function(text) { + return [...text].join(' '); + }, + preview: function(text) { + return this.func(text); + } + }, + + zalgo: { + name: 'Zalgo', + marks: [ + '\u0300', '\u0301', '\u0302', '\u0303', '\u0304', '\u0305', '\u0306', '\u0307', '\u0308', + '\u0309', '\u030A', '\u030B', '\u030C', '\u030D', '\u030E', '\u030F', '\u0310', '\u0311', + '\u0312', '\u0313', '\u0314', '\u0315', '\u031A', '\u031B', '\u033D', '\u033E', '\u033F' + ], + func: function(text) { + return [...text].map(c => { + let result = c; + for (let i = 0; i < Math.floor(Math.random() * 3) + 1; i++) { + result += this.marks[Math.floor(Math.random() * this.marks.length)]; + } + return result; + }).join(''); + }, + preview: function(text) { + return this.func(text); + } + }, + + small_caps: { + name: 'Small Caps', + map: { + 'a': 'ᴀ', 'b': 'ʙ', 'c': 'ᴄ', 'd': 'ᴅ', 'e': 'ᴇ', 'f': 'ꜰ', 'g': 'ɢ', 'h': 'ʜ', 'i': 'ɪ', + 'j': 'ᴊ', 'k': 'ᴋ', 'l': 'ʟ', 'm': 'ᴍ', 'n': 'ɴ', 'o': 'ᴏ', 'p': 'ᴘ', 'q': 'ǫ', 'r': 'ʀ', + 's': 's', 't': 'ᴛ', 'u': 'ᴜ', 'v': 'ᴠ', 'w': 'ᴡ', 'x': 'x', 'y': 'ʏ', 'z': 'ᴢ' + }, + func: function(text) { + return [...text.toLowerCase()].map(c => this.map[c] || c).join(''); + }, + preview: function(text) { + return this.func(text); + } + }, + + braille: { + name: 'Braille', + map: { + 'a': '⠁', 'b': '⠃', 'c': '⠉', 'd': '⠙', 'e': '⠑', 'f': '⠋', 'g': '⠛', 'h': '⠓', 'i': '⠊', + 'j': '⠚', 'k': '⠅', 'l': '⠇', 'm': '⠍', 'n': '⠝', 'o': '⠕', 'p': '⠏', 'q': '⠟', 'r': '⠗', + 's': '⠎', 't': '⠞', 'u': '⠥', 'v': '⠧', 'w': '⠺', 'x': '⠭', 'y': '⠽', 'z': '⠵', + '0': '⠼⠚', '1': '⠼⠁', '2': '⠼⠃', '3': '⠼⠉', '4': '⠼⠙', '5': '⠼⠑', + '6': '⠼⠋', '7': '⠼⠛', '8': '⠼⠓', '9': '⠼⠊' + }, + func: function(text) { + return [...text.toLowerCase()].map(c => this.map[c] || c).join(''); + }, + preview: function(text) { + return this.func(text); + } + }, + + bubble: { + name: 'Bubble', + map: { + 'a': 'ⓐ', 'b': 'ⓑ', 'c': 'ⓒ', 'd': 'ⓓ', 'e': 'ⓔ', 'f': 'ⓕ', 'g': 'ⓖ', 'h': 'ⓗ', 'i': 'ⓘ', + 'j': 'ⓙ', 'k': 'ⓚ', 'l': 'ⓛ', 'm': 'ⓜ', 'n': 'ⓝ', 'o': 'ⓞ', 'p': 'ⓟ', 'q': 'ⓠ', 'r': 'ⓡ', + 's': 'ⓢ', 't': 'ⓣ', 'u': 'ⓤ', 'v': 'ⓥ', 'w': 'ⓦ', 'x': 'ⓧ', 'y': 'ⓨ', 'z': 'ⓩ', + 'A': 'Ⓐ', 'B': 'Ⓑ', 'C': 'Ⓒ', 'D': 'Ⓓ', 'E': 'Ⓔ', 'F': 'Ⓕ', 'G': 'Ⓖ', 'H': 'Ⓗ', 'I': 'Ⓘ', + 'J': 'Ⓙ', 'K': 'Ⓚ', 'L': 'Ⓛ', 'M': 'Ⓜ', 'N': 'Ⓝ', 'O': 'Ⓞ', 'P': 'Ⓟ', 'Q': 'Ⓠ', 'R': 'Ⓡ', + 'S': 'Ⓢ', 'T': 'Ⓣ', 'U': 'Ⓤ', 'V': 'Ⓥ', 'W': 'Ⓦ', 'X': 'Ⓧ', 'Y': 'Ⓨ', 'Z': 'Ⓩ' + }, + func: function(text) { + return [...text].map(c => this.map[c] || c).join(''); + }, + preview: function(text) { + return this.func(text); + } + }, + + morse: { + name: 'Morse Code', + map: { + 'a': '.-', 'b': '-...', 'c': '-.-.', 'd': '-..', 'e': '.', 'f': '..-.', + 'g': '--.', 'h': '....', 'i': '..', 'j': '.---', 'k': '-.-', 'l': '.-..', + 'm': '--', 'n': '-.', 'o': '---', 'p': '.--.', 'q': '--.-', 'r': '.-.', + 's': '...', 't': '-', 'u': '..-', 'v': '...-', 'w': '.--', 'x': '-..-', + 'y': '-.--', 'z': '--..', '0': '-----', '1': '.----', '2': '..---', + '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', + '8': '---..', '9': '----.' + }, + func: function(text) { + return [...text.toLowerCase()].map(c => this.map[c] || c).join(' '); + }, + preview: function(text) { + return this.func(text); + } + }, + + binary: { + name: 'Binary', + func: function(text) { + return [...text].map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' '); + }, + preview: function(text) { + return this.func(text); + } + } +}; + +// Export transforms for use in app.js +window.transforms = transforms;