Fixed emoji copying and category navigation, added Greek transform

This commit is contained in:
EP
2025-03-12 00:27:44 -04:00
parent e65a21f5fc
commit 2bdf4f202e
4 changed files with 204 additions and 106 deletions
+20 -9
View File
@@ -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 {
+10
View File
@@ -368,6 +368,13 @@
</div>
<div class="output-section" v-if="transformOutput">
<div class="output-heading">
<h4>
<i class="fas fa-check-circle"></i>
Transformed Message
<small v-if="activeTransform">({{ activeTransform.name }})</small>
</h4>
</div>
<div class="output-container">
<textarea
readonly
@@ -378,6 +385,9 @@
<i class="fas fa-copy"></i>
</button>
</div>
<div class="output-instructions">
<small><i class="fas fa-info-circle"></i> Copy this text and share it. The transformation can be reversed using the Universal Decoder below.</small>
</div>
</div>
<!-- Universal Decoder Section for Transforms Tab -->
+102 -49
View File
@@ -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(`<i class="fas fa-check"></i> 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(`<i class="fas fa-check"></i> Emoji copied!`, 'success');
this.addToCopyHistory('Emoji', emojiStr);
})
.catch(err => {
console.warn('Emoji clipboard API failed:', err);
// Fallback to our custom method
this.forceCopyToClipboard(emojiStr);
this.showNotification(`<i class="fas fa-check"></i> Emoji copied!`, 'success');
this.addToCopyHistory('Emoji', emojiStr);
});
} else {
// Use our custom method if Clipboard API not available
this.forceCopyToClipboard(emojiStr);
this.showNotification(`<i class="fas fa-check"></i> Emoji copied!`, 'success');
this.addToCopyHistory('Emoji', emojiStr);
}
// Also set up carrier if we're in steganography mode
if (this.activeTab === 'steganography') {
@@ -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(`<i class="fas fa-check"></i> 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(`<i class="fas fa-check"></i> Hidden message copied with ${emoji}`, 'success');
this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
})
.catch(err => {
console.warn('Encoded emoji clipboard API failed:', err);
// Fall back to our custom method
this.forceCopyToClipboard(encodedStr);
this.showNotification(`<i class="fas fa-check"></i> Hidden message copied with ${emoji}`, 'success');
this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
});
} else {
// Use our custom method if Clipboard API not available
this.forceCopyToClipboard(encodedStr);
this.showNotification(`<i class="fas fa-check"></i> Hidden message copied with ${emoji}`, 'success');
this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
}
}
});
}
@@ -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(() => {
+72 -48
View File
@@ -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('');
}
}
};