diff --git a/css/style.css b/css/style.css
index d6360bd..4250802 100644
--- a/css/style.css
+++ b/css/style.css
@@ -23,15 +23,12 @@
--format-color: #ffb74d; /* Orange for formatting */
--unicode-color: #42a5f5; /* Blue for unicode transformations */
--special-color: #66bb6a; /* Green for special transformations */
- scroll-behavior: smooth;
- scroll-padding-top: 20px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
- scroll-behavior: smooth;
}
body {
@@ -885,7 +882,12 @@ button:hover {
transition: all 0.2s ease;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- user-select: none;
+}
+
+.legend-item:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ background-color: var(--button-hover-bg);
}
.legend-item.active-category {
@@ -895,11 +897,6 @@ button:hover {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
-.legend-item:hover {
- transform: translateY(-1px);
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
-}
-
.transform-section:focus-within {
border-color: var(--accent-color);
box-shadow: var(--focus-shadow);
@@ -932,6 +929,20 @@ button:hover {
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s ease;
+ position: relative;
+}
+
+.highlight-section {
+ background-color: rgba(var(--accent-color-rgb), 0.1);
+ box-shadow: 0 0 12px rgba(var(--accent-color-rgb), 0.3);
+ animation: pulse 1s ease-in-out;
+}
+
+@keyframes pulse {
+ 0% { background-color: rgba(var(--accent-color-rgb), 0.1); }
+ 50% { background-color: rgba(var(--accent-color-rgb), 0.2); }
+ 100% { background-color: rgba(var(--accent-color-rgb), 0.1); }
}
.category-title {
diff --git a/index.html b/index.html
index ca443aa..3c881ef 100644
--- a/index.html
+++ b/index.html
@@ -368,6 +368,13 @@
+
+
+
+ Transformed Message
+ ({{ activeTransform.name }})
+
+
+
+ Copy this text and share it. The transformation can be reversed using the Universal Decoder below.
+
diff --git a/js/app.js b/js/app.js
index 82203d9..5cc0d67 100644
--- a/js/app.js
+++ b/js/app.js
@@ -16,9 +16,9 @@ window.app = new Vue({
transformCategories: {
encoding: ['Base64', 'Base32', 'Binary', 'Hexadecimal', 'ASCII85', 'URL Encode', 'HTML Entities'],
cipher: ['Caesar Cipher', 'ROT13', 'ROT47', 'Morse Code'],
- visual: ['Rainbow Text', 'Strikethrough', 'Underline', 'Reverse Text', 'Wingdings Style'],
- format: ['Pig Latin', 'Leetspeak', 'NATO Phonetic', 'Greek Letters'],
- unicode: ['Invisible Text', 'Upside Down', 'Full Width', 'Small Caps', 'Bubble', 'Braille'],
+ visual: ['Rainbow Text', 'Strikethrough', 'Underline', 'Reverse Text'],
+ format: ['Pig Latin', 'Leetspeak', 'NATO Phonetic'],
+ unicode: ['Invisible Text', 'Upside Down', 'Full Width', 'Small Caps', 'Bubble', 'Braille', 'Greek Letters', 'Wingdings'],
special: ['Medieval', 'Cursive', 'Monospace', 'Double-Struck', 'Elder Futhark', 'Mirror Text', 'Zalgo']
},
transforms: Object.entries(window.transforms).map(([key, transform]) => ({
@@ -80,6 +80,13 @@ window.app = new Vue({
}
});
}
+
+ // Initialize category navigation when switching to transforms tab
+ if (tabName === 'transforms') {
+ this.$nextTick(() => {
+ this.initializeCategoryNavigation();
+ });
+ }
},
// Get transforms grouped by category
@@ -739,25 +746,11 @@ window.app = new Vue({
universalDecode(input) {
if (!input) return '';
- // If we're in the steganography tab, only try emoji decoding
- if (this.activeTab === 'steganography') {
- if (/[\u{1F300}-\u{1F6FF}\u{2600}-\u{26FF}]/u.test(input)) {
- console.log('In emoji tab: attempting emoji decode only...');
- const decoded = window.steganography.decodeEmoji(input);
- if (decoded) {
- console.log('Successfully decoded emoji:', decoded);
- return { text: decoded, method: 'Emoji Steganography' };
- } else {
- console.log('No hidden message found in emoji');
- return null;
- }
- }
- return null;
- }
+ // Try all decoders in order
- // For other tabs, try all decoders in order
// 1. Try steganography decoders
// - Check for emoji steganography first
+ // The emoji encoding uses variation selectors which are hard to see
if (/[\u{1F300}-\u{1F6FF}\u{2600}-\u{26FF}]/u.test(input)) {
console.log('Detected emoji, attempting to decode...');
const decoded = window.steganography.decodeEmoji(input);
@@ -1161,10 +1154,29 @@ window.app = new Vue({
},
selectEmoji(emoji) {
- // Directly copy the emoji to clipboard
- this.forceCopyToClipboard(emoji);
- this.showNotification(` Emoji copied!`, 'success');
- this.addToCopyHistory('Emoji', emoji);
+ // Directly copy the emoji to clipboard - ensure it's a string
+ const emojiStr = String(emoji);
+
+ // Special handling for emoji characters
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(emojiStr)
+ .then(() => {
+ this.showNotification(` Emoji copied!`, 'success');
+ this.addToCopyHistory('Emoji', emojiStr);
+ })
+ .catch(err => {
+ console.warn('Emoji clipboard API failed:', err);
+ // Fallback to our custom method
+ this.forceCopyToClipboard(emojiStr);
+ this.showNotification(` Emoji copied!`, 'success');
+ this.addToCopyHistory('Emoji', emojiStr);
+ });
+ } else {
+ // Use our custom method if Clipboard API not available
+ this.forceCopyToClipboard(emojiStr);
+ this.showNotification(` Emoji copied!`, 'success');
+ this.addToCopyHistory('Emoji', emojiStr);
+ }
// Also set up carrier if we're in steganography mode
if (this.activeTab === 'steganography') {
@@ -1190,9 +1202,28 @@ window.app = new Vue({
// Wait for encoding to complete, then copy to clipboard
this.$nextTick(() => {
if (this.encodedMessage) {
- this.forceCopyToClipboard(this.encodedMessage);
- this.showNotification(` Hidden message copied with ${emoji}`, 'success');
- this.addToCopyHistory(`Hidden Message with ${emoji}`, this.encodedMessage);
+ const encodedStr = String(this.encodedMessage);
+
+ // Use native clipboard API first for better emoji support
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(encodedStr)
+ .then(() => {
+ this.showNotification(` Hidden message copied with ${emoji}`, 'success');
+ this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
+ })
+ .catch(err => {
+ console.warn('Encoded emoji clipboard API failed:', err);
+ // Fall back to our custom method
+ this.forceCopyToClipboard(encodedStr);
+ this.showNotification(` Hidden message copied with ${emoji}`, 'success');
+ this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
+ });
+ } else {
+ // Use our custom method if Clipboard API not available
+ this.forceCopyToClipboard(encodedStr);
+ this.showNotification(` Hidden message copied with ${emoji}`, 'success');
+ this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
+ }
}
});
}
@@ -1228,6 +1259,49 @@ window.app = new Vue({
// Log success
console.log('Emoji grid rendered successfully');
+ },
+
+ // Initialize category navigation for transform sections
+ initializeCategoryNavigation() {
+ this.$nextTick(() => {
+ console.log('Initializing category navigation');
+ const legendItems = document.querySelectorAll('.transform-category-legend .legend-item');
+
+ // First, remove any existing event listeners to prevent duplicates
+ legendItems.forEach(item => {
+ const newItem = item.cloneNode(true);
+ item.parentNode.replaceChild(newItem, item);
+ });
+
+ // Now add event listeners to the fresh elements
+ document.querySelectorAll('.transform-category-legend .legend-item').forEach(item => {
+ item.addEventListener('click', () => {
+ const targetId = item.getAttribute('data-target');
+ if (targetId) {
+ const targetElement = document.getElementById(targetId);
+ if (targetElement) {
+ // Add active class to the clicked legend item
+ document.querySelectorAll('.transform-category-legend .legend-item').forEach(li => {
+ li.classList.remove('active-category');
+ });
+ item.classList.add('active-category');
+
+ // Jump directly to the target element
+ targetElement.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+
+ // Highlight the section briefly to draw attention
+ targetElement.classList.add('highlight-section');
+ setTimeout(() => {
+ targetElement.classList.remove('highlight-section');
+ }, 1000);
+ }
+ }
+ });
+ });
+ });
}
},
// Initialize theme and components
@@ -1238,29 +1312,8 @@ window.app = new Vue({
document.body.classList.add('dark-theme');
}
- // Add smooth scrolling for category navigation
- this.$nextTick(() => {
- const legendItems = document.querySelectorAll('.transform-category-legend .legend-item');
- legendItems.forEach(item => {
- item.addEventListener('click', () => {
- const targetId = item.getAttribute('data-target');
- if (targetId) {
- const targetElement = document.getElementById(targetId);
- if (targetElement) {
- // Add active class to the clicked legend item
- legendItems.forEach(li => li.classList.remove('active-category'));
- item.classList.add('active-category');
-
- // Scroll to the target element with smooth behavior
- targetElement.scrollIntoView({
- behavior: 'smooth',
- block: 'start'
- });
- }
- }
- });
- });
- });
+ // Initialize category navigation
+ this.initializeCategoryNavigation();
// Initialize emoji grid with all emojis shown by default
this.$nextTick(() => {
diff --git a/js/transforms.js b/js/transforms.js
index 797701c..ac3b6f8 100644
--- a/js/transforms.js
+++ b/js/transforms.js
@@ -804,54 +804,6 @@ const transforms = {
}
},
- greek: {
- name: 'Greek Letters',
- map: {
- 'A': 'Α', 'B': 'Β', 'G': 'Γ', 'D': 'Δ', 'E': 'Ε',
- 'Z': 'Ζ', 'H': 'Η', 'TH': 'Θ', 'I': 'Ι', 'K': 'Κ',
- 'L': 'Λ', 'M': 'Μ', 'N': 'Ν', 'X': 'Ξ', 'O': 'Ο',
- 'P': 'Π', 'R': 'Ρ', 'S': 'Σ', 'T': 'Τ', 'Y': 'Υ',
- 'F': 'Φ', 'CH': 'Χ', 'PS': 'Ψ', 'W': 'Ω',
- 'a': 'α', 'b': 'β', 'g': 'γ', 'd': 'δ', 'e': 'ε',
- 'z': 'ζ', 'h': 'η', 'th': 'θ', 'i': 'ι', 'k': 'κ',
- 'l': 'λ', 'm': 'μ', 'n': 'ν', 'x': 'ξ', 'o': 'ο',
- 'p': 'π', 'r': 'ρ', 's': 'σ', 't': 'τ', 'y': 'υ',
- 'f': 'φ', 'ch': 'χ', 'ps': 'ψ', 'w': 'ω'
- },
- func: function(text) {
- return text.replace(/ch|th|ps|[a-zA-Z]/g, match => this.map[match] || match);
- },
- preview: function(text) {
- return this.func(text);
- },
- reverse: function(text) {
- const reverseMap = {};
- Object.entries(this.map).forEach(([k, v]) => reverseMap[v] = k);
- return text.replace(/[Α-Ωα-ω]/g, match => reverseMap[match] || match);
- }
- },
-
- wingdings: {
- name: 'Wingdings Style',
- 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': '💤', '1': '☝️', '2': '✌️', '3': '🤟', '4': '4️⃣',
- '5': '5️⃣', '6': '6️⃣', '7': '7️⃣', '8': '8️⃣', '9': '9️⃣',
- '0': '0️⃣', '!': '❗', '?': '❓', '.': '•', ',': '،',
- ' ': ' '
- },
- func: function(text) {
- return text.toLowerCase().split('').map(char => this.map[char] || char).join('');
- },
- preview: function(text) {
- return this.func(text);
- }
- },
-
base32: {
name: 'Base32',
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
@@ -919,6 +871,78 @@ const transforms = {
return result;
}
+ },
+
+ greek: {
+ name: 'Greek Letters',
+ 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.split('').map(char => this.map[char] || char).join('');
+ },
+ preview: function(text) {
+ return text.substring(0, 10) + (text.length > 10 ? '...' : '');
+ },
+ reverseMap: function() {
+ if (!this._reverseMap) {
+ this._reverseMap = {};
+ for (let key in this.map) {
+ this._reverseMap[this.map[key]] = key;
+ }
+ }
+ return this._reverseMap;
+ },
+ reverse: function(text) {
+ const revMap = this.reverseMap();
+ return text.split('').map(char => revMap[char] || char).join('');
+ }
+ },
+
+ wingdings: {
+ name: 'Wingdings',
+ 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': '✍',
+ '0': '✓', '1': '✔', '2': '✕', '3': '✖', '4': '✗', '5': '✘', '6': '✙', '7': '✚',
+ '8': '✛', '9': '✜',
+ '.': '✠', ',': '✡', '?': '✢', '!': '✣', '@': '✤', '#': '✥', '$': '✦', '%': '✧',
+ '^': '✩', '&': '✪', '*': '✫', '(': '✬', ')': '✭', '-': '✮', '_': '✯', '=': '✰',
+ '+': '✱', '[': '✲', ']': '✳', '{': '✴', '}': '✵', '|': '✶', '\\': '✷', ';': '✸',
+ ':': '✹', '"': '✺', '\'': '✻', '<': '✼', '>': '✽', '/': '✾', '~': '✿', '`': '❀'
+ },
+ func: function(text) {
+ return text.split('').map(char => this.map[char] || char).join('');
+ },
+ preview: function(text) {
+ return text.substring(0, 10) + (text.length > 10 ? '...' : '');
+ },
+ reverseMap: function() {
+ if (!this._reverseMap) {
+ this._reverseMap = {};
+ for (let key in this.map) {
+ this._reverseMap[this.map[key]] = key;
+ }
+ }
+ return this._reverseMap;
+ },
+ reverse: function(text) {
+ const revMap = this.reverseMap();
+ return text.split('').map(char => revMap[char] || char).join('');
+ }
}
};