mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-02-12 16:52:46 +00:00
Fix emoji encoding and UI issues: 1) Fixed emoji encoding to properly handle all emoji types including flags 2) Improved decoding logic 3) Removed duplicate black bars from UI 4) Increased emoji size and visibility
This commit is contained in:
670
css/style.css
670
css/style.css
@@ -13,6 +13,14 @@
|
||||
--input-border: #404040;
|
||||
--section-divider: #404040;
|
||||
--focus-shadow: 0 0 0 2px rgba(100, 181, 246, 0.4);
|
||||
|
||||
/* Transform category colors */
|
||||
--encoding-color: #7e57c2; /* Purple for encoding/decoding */
|
||||
--cipher-color: #26a69a; /* Teal for ciphers */
|
||||
--visual-color: #ef5350; /* Red for visual transformations */
|
||||
--format-color: #ffb74d; /* Orange for formatting */
|
||||
--unicode-color: #42a5f5; /* Blue for unicode transformations */
|
||||
--special-color: #66bb6a; /* Green for special transformations */
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -195,21 +203,36 @@ textarea {
|
||||
|
||||
/* Emoji Library Styling */
|
||||
.emoji-library {
|
||||
margin: 16px 0;
|
||||
margin: 12px 0;
|
||||
background-color: var(--main-bg-color);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--input-border);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.emoji-library-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid var(--input-border);
|
||||
padding-bottom: 12px;
|
||||
padding: 10px 12px;
|
||||
background: var(--secondary-bg);
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.emoji-library-header:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(to right, var(--special-color), var(--encoding-color), var(--cipher-color), var(--visual-color));
|
||||
}
|
||||
|
||||
.emoji-library-title {
|
||||
@@ -220,6 +243,18 @@ textarea {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.emoji-library-title h3 {
|
||||
margin: 0;
|
||||
color: var(--accent-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.emoji-library-title h3 i {
|
||||
color: var(--special-color);
|
||||
}
|
||||
|
||||
.emoji-library-title i {
|
||||
margin-right: 8px;
|
||||
color: var(--accent-color);
|
||||
@@ -228,8 +263,19 @@ textarea {
|
||||
.emoji-library-subtitle {
|
||||
font-size: 0.8rem;
|
||||
font-weight: normal;
|
||||
opacity: 0.7;
|
||||
margin-top: 4px;
|
||||
color: var(--text-color);
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.emoji-library-subtitle i {
|
||||
color: var(--encoding-color);
|
||||
}
|
||||
|
||||
.emoji-search {
|
||||
@@ -263,67 +309,183 @@ textarea {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.emoji-grid-wrapper {
|
||||
border-radius: 6px;
|
||||
.emoji-library {
|
||||
display: block !important;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--secondary-bg);
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
padding: 0;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.emoji-library:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: linear-gradient(to right, var(--unicode-color), var(--special-color), var(--encoding-color));
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.emoji-grid-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.emoji-grid-note {
|
||||
background-color: rgba(var(--accent-color-rgb), 0.1);
|
||||
background: linear-gradient(to right, rgba(102, 187, 106, 0.05), rgba(126, 87, 194, 0.05));
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
border-left: 2px solid var(--special-color);
|
||||
box-shadow: none;
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.emoji-grid-note i {
|
||||
color: var(--accent-color);
|
||||
color: var(--special-color);
|
||||
font-size: 1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.emoji-count {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color);
|
||||
text-align: center;
|
||||
padding: 4px 8px;
|
||||
margin-top: 8px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(to right, rgba(66, 165, 245, 0.05), rgba(126, 87, 194, 0.05));
|
||||
display: inline-block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid rgba(66, 165, 245, 0.15);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.emoji-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
|
||||
gap: 10px;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
scrollbar-width: thin;
|
||||
border-radius: 6px;
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)) !important;
|
||||
grid-auto-rows: 40px !important;
|
||||
gap: 4px !important;
|
||||
padding: 10px !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid var(--input-border) !important;
|
||||
background-color: var(--secondary-bg) !important;
|
||||
box-shadow: none !important;
|
||||
transition: all 0.2s ease !important;
|
||||
margin-bottom: 8px !important;
|
||||
width: 100% !important;
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.emoji-category-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.emoji-category-tab {
|
||||
padding: 5px 10px;
|
||||
background: var(--button-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.emoji-category-tab.active,
|
||||
.emoji-category-tab:hover {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
#emoji-grid-container,
|
||||
.emoji-grid-container {
|
||||
width: 100% !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: stretch !important;
|
||||
background-color: transparent !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
overflow: visible !important;
|
||||
min-height: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.emoji-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
font-size: 1.25rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.3rem;
|
||||
background-color: var(--button-bg);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.emoji-button:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(to right, var(--special-color), var(--encoding-color));
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.emoji-button:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--accent-color);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.emoji-button:hover:before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.emoji-button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
background: var(--button-bg);
|
||||
border-color: var(--accent-color);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* Emoji Library Footer */
|
||||
@@ -479,6 +641,41 @@ button:hover {
|
||||
border: 1px solid var(--input-border);
|
||||
}
|
||||
|
||||
/* Transform category legend styling */
|
||||
.transform-category-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--input-border);
|
||||
}
|
||||
|
||||
.legend-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
margin-right: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
font-size: 0.8rem;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border-left-width: 4px;
|
||||
border-left-style: solid;
|
||||
background-color: var(--button-bg);
|
||||
transition: all 0.2s ease;
|
||||
cursor: default;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.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);
|
||||
@@ -500,10 +697,33 @@ button:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.transform-categories {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.transform-category-section {
|
||||
background: var(--secondary-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-title {
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--input-border);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.transform-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Carrier styling */
|
||||
@@ -531,12 +751,18 @@ button:hover {
|
||||
.transform-preview {
|
||||
margin-top: auto;
|
||||
width: 100%;
|
||||
font-size: 0.7rem;
|
||||
padding: 4px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
font-size: 0.65rem;
|
||||
padding: 3px 4px;
|
||||
background-color: rgba(0, 0, 0, 0.12);
|
||||
border-radius: 3px;
|
||||
white-space: normal;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.15;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
@@ -554,16 +780,21 @@ button:hover {
|
||||
|
||||
/* Encoded preview styling */
|
||||
.encoded-preview {
|
||||
display: inline-block;
|
||||
padding: 2px 4px;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
padding: 6px 8px;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
border-radius: 4px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
letter-spacing: 0.5px; /* Better display for variation selectors */
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 4px;
|
||||
color: var(--accent-color);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.transform-button kbd {
|
||||
@@ -572,33 +803,165 @@ button:hover {
|
||||
right: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
padding: 2px 4px;
|
||||
font-size: 10px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.transform-preview {
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.transform-button {
|
||||
/* Copy History Panel */
|
||||
.copy-history-panel {
|
||||
position: fixed;
|
||||
right: -400px; /* Start offscreen */
|
||||
top: 0;
|
||||
width: 380px;
|
||||
height: 100vh;
|
||||
background-color: var(--secondary-bg);
|
||||
border-left: 1px solid var(--input-border);
|
||||
z-index: 100;
|
||||
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
|
||||
transition: right 0.3s ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border-radius: 4px;
|
||||
background: var(--button-bg);
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.copy-history-panel.active {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.copy-history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid var(--input-border);
|
||||
background-color: var(--button-bg);
|
||||
}
|
||||
|
||||
.copy-history-header h3 {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.copy-history-header .close-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.copy-history-header .close-button:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.copy-history-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.no-history {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.history-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
border-left: 3px solid var(--accent-color);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.history-item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.history-source {
|
||||
font-weight: bold;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.history-time {
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.history-content {
|
||||
background-color: var(--main-bg-color);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.history-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.copy-again-button {
|
||||
background-color: var(--button-bg);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.copy-again-button:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.history-button {
|
||||
background: none;
|
||||
color: var(--text-color);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.history-button:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.transform-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
@@ -606,26 +969,65 @@ button:hover {
|
||||
|
||||
.transform-preview {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.6;
|
||||
font-family: 'Fira Code', monospace;
|
||||
color: var(--accent-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 140px;
|
||||
width: 100%;
|
||||
max-width: 180px;
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
margin-top: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.transform-category-encoding .transform-preview {
|
||||
color: rgba(126, 87, 194, 0.9);
|
||||
}
|
||||
|
||||
.transform-category-cipher .transform-preview {
|
||||
color: rgba(38, 166, 154, 0.9);
|
||||
}
|
||||
|
||||
.transform-category-visual .transform-preview {
|
||||
color: rgba(239, 83, 80, 0.9);
|
||||
}
|
||||
|
||||
.transform-category-format .transform-preview {
|
||||
color: rgba(255, 183, 77, 0.9);
|
||||
}
|
||||
|
||||
.transform-category-unicode .transform-preview {
|
||||
color: rgba(66, 165, 245, 0.9);
|
||||
}
|
||||
|
||||
.transform-category-special .transform-preview {
|
||||
color: rgba(102, 187, 106, 0.9);
|
||||
}
|
||||
|
||||
.transform-button:hover .transform-preview {
|
||||
background-color: rgba(0, 0, 0, 0.18);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.transform-button.active .transform-preview {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.transform-button {
|
||||
padding: 12px;
|
||||
padding: 8px;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 120px;
|
||||
min-width: 110px;
|
||||
max-width: 100%;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border: 1px solid var(--input-border);
|
||||
@@ -633,6 +1035,74 @@ button:hover {
|
||||
color: var(--text-color);
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
/* Transform category styling */
|
||||
.transform-category-encoding {
|
||||
border-left: 4px solid var(--encoding-color);
|
||||
background: linear-gradient(to right, rgba(126, 87, 194, 0.05), var(--button-bg));
|
||||
}
|
||||
|
||||
.transform-category-encoding:hover {
|
||||
background: linear-gradient(to right, rgba(126, 87, 194, 0.15), var(--button-hover-bg));
|
||||
border-color: var(--encoding-color);
|
||||
box-shadow: 0 3px 10px rgba(126, 87, 194, 0.2);
|
||||
}
|
||||
|
||||
.transform-category-cipher {
|
||||
border-left: 4px solid var(--cipher-color);
|
||||
background: linear-gradient(to right, rgba(38, 166, 154, 0.05), var(--button-bg));
|
||||
}
|
||||
|
||||
.transform-category-cipher:hover {
|
||||
background: linear-gradient(to right, rgba(38, 166, 154, 0.15), var(--button-hover-bg));
|
||||
border-color: var(--cipher-color);
|
||||
box-shadow: 0 3px 10px rgba(38, 166, 154, 0.2);
|
||||
}
|
||||
|
||||
.transform-category-visual {
|
||||
border-left: 4px solid var(--visual-color);
|
||||
background: linear-gradient(to right, rgba(239, 83, 80, 0.05), var(--button-bg));
|
||||
}
|
||||
|
||||
.transform-category-visual:hover {
|
||||
background: linear-gradient(to right, rgba(239, 83, 80, 0.15), var(--button-hover-bg));
|
||||
border-color: var(--visual-color);
|
||||
box-shadow: 0 3px 10px rgba(239, 83, 80, 0.2);
|
||||
}
|
||||
|
||||
.transform-category-format {
|
||||
border-left: 4px solid var(--format-color);
|
||||
background: linear-gradient(to right, rgba(255, 183, 77, 0.05), var(--button-bg));
|
||||
}
|
||||
|
||||
.transform-category-format:hover {
|
||||
background: linear-gradient(to right, rgba(255, 183, 77, 0.15), var(--button-hover-bg));
|
||||
border-color: var(--format-color);
|
||||
box-shadow: 0 3px 10px rgba(255, 183, 77, 0.2);
|
||||
}
|
||||
|
||||
.transform-category-unicode {
|
||||
border-left: 4px solid var(--unicode-color);
|
||||
background: linear-gradient(to right, rgba(66, 165, 245, 0.05), var(--button-bg));
|
||||
}
|
||||
|
||||
.transform-category-unicode:hover {
|
||||
background: linear-gradient(to right, rgba(66, 165, 245, 0.15), var(--button-hover-bg));
|
||||
border-color: var(--unicode-color);
|
||||
box-shadow: 0 3px 10px rgba(66, 165, 245, 0.2);
|
||||
}
|
||||
|
||||
.transform-category-special {
|
||||
border-left: 4px solid var(--special-color);
|
||||
background: linear-gradient(to right, rgba(102, 187, 106, 0.05), var(--button-bg));
|
||||
}
|
||||
|
||||
.transform-category-special:hover {
|
||||
background: linear-gradient(to right, rgba(102, 187, 106, 0.15), var(--button-hover-bg));
|
||||
border-color: var(--special-color);
|
||||
box-shadow: 0 3px 10px rgba(102, 187, 106, 0.2);
|
||||
}
|
||||
|
||||
.transform-button:before {
|
||||
@@ -652,7 +1122,7 @@ button:hover {
|
||||
background: var(--button-hover-bg);
|
||||
border-color: var(--accent-color);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.transform-button:hover:before {
|
||||
@@ -664,7 +1134,8 @@ button:hover {
|
||||
background: var(--button-active-bg);
|
||||
color: var(--main-bg-color);
|
||||
border-color: var(--button-active-bg);
|
||||
box-shadow: 0 2px 12px rgba(100, 181, 246, 0.3);
|
||||
box-shadow: 0 2px 8px rgba(100, 181, 246, 0.25);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.transform-button.active:before {
|
||||
@@ -672,6 +1143,43 @@ button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Category-specific active states */
|
||||
.transform-category-encoding.active {
|
||||
background: linear-gradient(to right, var(--encoding-color), #9575cd);
|
||||
border-color: var(--encoding-color);
|
||||
box-shadow: 0 3px 12px rgba(126, 87, 194, 0.4);
|
||||
}
|
||||
|
||||
.transform-category-cipher.active {
|
||||
background: linear-gradient(to right, var(--cipher-color), #4db6ac);
|
||||
border-color: var(--cipher-color);
|
||||
box-shadow: 0 3px 12px rgba(38, 166, 154, 0.4);
|
||||
}
|
||||
|
||||
.transform-category-visual.active {
|
||||
background: linear-gradient(to right, var(--visual-color), #e57373);
|
||||
border-color: var(--visual-color);
|
||||
box-shadow: 0 3px 12px rgba(239, 83, 80, 0.4);
|
||||
}
|
||||
|
||||
.transform-category-format.active {
|
||||
background: linear-gradient(to right, var(--format-color), #ffcc80);
|
||||
border-color: var(--format-color);
|
||||
box-shadow: 0 3px 12px rgba(255, 183, 77, 0.4);
|
||||
}
|
||||
|
||||
.transform-category-unicode.active {
|
||||
background: linear-gradient(to right, var(--unicode-color), #64b5f6);
|
||||
border-color: var(--unicode-color);
|
||||
box-shadow: 0 3px 12px rgba(66, 165, 245, 0.4);
|
||||
}
|
||||
|
||||
.transform-category-special.active {
|
||||
background: linear-gradient(to right, var(--special-color), #81c784);
|
||||
border-color: var(--special-color);
|
||||
box-shadow: 0 3px 12px rgba(102, 187, 106, 0.4);
|
||||
}
|
||||
|
||||
/* Add a subtle indicator for clickable buttons */
|
||||
.transform-button:after {
|
||||
content: '';
|
||||
@@ -691,6 +1199,22 @@ button:hover {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
/* Auto-copy icon styling */
|
||||
.auto-copy-icon {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.5;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.transform-button:hover .auto-copy-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Output section */
|
||||
.output-section {
|
||||
display: flex;
|
||||
|
||||
420
index.html
420
index.html
@@ -18,6 +18,14 @@
|
||||
<h1>🐍 Parseltongue</h1>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
@click="toggleCopyHistory"
|
||||
class="history-button"
|
||||
title="Show copy history"
|
||||
aria-label="Show copy history"
|
||||
>
|
||||
<i class="fas fa-history"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="toggleTheme"
|
||||
@keyup.d="toggleTheme"
|
||||
@@ -51,9 +59,9 @@
|
||||
<button
|
||||
:class="{ active: activeTab === 'steganography' }"
|
||||
@click="activeTab = 'steganography'"
|
||||
title="Hide text (H)"
|
||||
title="Hide text in emojis (H)"
|
||||
>
|
||||
<i class="fas fa-eye-slash"></i> Hide
|
||||
<i class="fas fa-smile"></i> Emoji
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -70,59 +78,32 @@
|
||||
</div>
|
||||
|
||||
<div class="transform-section">
|
||||
<div class="transform-buttons">
|
||||
<button
|
||||
v-for="carrier in carriers"
|
||||
:key="carrier.name"
|
||||
class="transform-button"
|
||||
:class="{ active: selectedCarrier === carrier }"
|
||||
@click="selectCarrier(carrier)"
|
||||
:title="carrier.desc"
|
||||
>
|
||||
<div class="carrier-content">
|
||||
<span class="carrier-emoji">{{ carrier.emoji }}</span>
|
||||
<span class="carrier-name">{{ carrier.name }}</span>
|
||||
</div>
|
||||
<small class="transform-preview" v-if="emojiMessage">
|
||||
<span class="preview-label">Preview:</span>
|
||||
<span class="encoded-preview">{{ carrier.preview ? carrier.preview(emojiMessage.slice(0, 10)) : '' }}</span>
|
||||
<span class="preview-ellipsis" v-if="emojiMessage.length > 10">...</span>
|
||||
</small>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Big Invisible Text Button -->
|
||||
<button
|
||||
class="transform-button invisible-button"
|
||||
:class="{ active: activeSteg === 'invisible' }"
|
||||
@click="setStegMode('invisible')"
|
||||
title="Make text invisible using zero-width characters"
|
||||
>
|
||||
<div class="carrier-content">
|
||||
<span class="carrier-emoji"><i class="fas fa-eye-slash"></i></span>
|
||||
<span class="carrier-name">Invisible Text Mode</span>
|
||||
</div>
|
||||
<small class="transform-preview" v-if="emojiMessage">
|
||||
<span class="preview-label">Preview:</span>
|
||||
<span class="encoded-preview">{{ previewInvisible(emojiMessage.slice(0, 10)) }}</span>
|
||||
<span class="preview-ellipsis" v-if="emojiMessage.length > 10">...</span>
|
||||
</small>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Emoji Library Section -->
|
||||
<div class="emoji-library">
|
||||
<div class="emoji-library-header">
|
||||
<div class="emoji-library-title">
|
||||
<i class="fas fa-icons"></i> Quick Emoji Picker
|
||||
<span class="emoji-library-subtitle">Click any emoji to insert it at cursor position</span>
|
||||
</div>
|
||||
<div class="emoji-search">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" placeholder="Search emojis..." v-model="emojiSearch" @input="filterEmojis">
|
||||
<h3><i class="fas fa-icons"></i> Choose an Emoji</h3>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Emoji grid container - this MUST have id="emoji-grid-container" -->
|
||||
<div id="emoji-grid-container" class="emoji-grid-container">
|
||||
<!-- Dynamic content will be inserted here by JavaScript -->
|
||||
<div class="emoji-grid">
|
||||
<!-- Common emojis as fallback -->
|
||||
<button class="emoji-button" onclick="app.selectEmoji('😀')">😀</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('😂')">😂</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('🥰')">🥰</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('😎')">😎</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('🤔')">🤔</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('👍')">👍</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('🎉')">🎉</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('🔥')">🔥</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('🚀')">🚀</button>
|
||||
<button class="emoji-button" onclick="app.selectEmoji('🐍')">🐍</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="emoji-grid-container" class="emoji-grid-wrapper"></div>
|
||||
<div class="emoji-library-footer" v-if="selectedEmoji">
|
||||
<div class="selected-emoji-info">
|
||||
<span class="selected-emoji">{{ selectedEmoji }}</span>
|
||||
@@ -151,17 +132,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="decode-section" v-if="showDecoder">
|
||||
|
||||
<!-- Universal Decoder Section for Steganography Tab -->
|
||||
<div class="decode-section">
|
||||
<div class="section-header">
|
||||
<h3><i class="fas fa-magic"></i> Universal Decoder</h3>
|
||||
<p>Paste any encoded text to try all decoding methods at once</p>
|
||||
</div>
|
||||
<div class="input-container">
|
||||
<textarea
|
||||
id="decode-input"
|
||||
v-model="decodeInput"
|
||||
placeholder="Paste encoded text to decode..."
|
||||
@input="autoDecode"
|
||||
id="universal-decode-input-steg"
|
||||
v-model="universalDecodeInput"
|
||||
placeholder="Paste encoded text to decode automatically..."
|
||||
@input="runUniversalDecode"
|
||||
></textarea>
|
||||
<div class="decoded-message" v-if="decodedMessage">
|
||||
{{ decodedMessage }}
|
||||
<button class="copy-button" @click="copyToClipboard(decodedMessage.substring(decodedMessage.indexOf(': ') + 2))" title="Copy decoded text">
|
||||
<div class="decoded-message" v-if="universalDecodeResult">
|
||||
<div class="decode-method">Decoded using: <strong>{{ universalDecodeResult.method }}</strong></div>
|
||||
<div class="decode-result">{{ universalDecodeResult.text }}</div>
|
||||
<button class="copy-button" @click="copyToClipboard(universalDecodeResult.text)" title="Copy decoded text">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -183,22 +171,189 @@
|
||||
</div>
|
||||
|
||||
<div class="transform-section">
|
||||
<div class="transform-buttons">
|
||||
<button
|
||||
v-for="(transform, index) in transforms"
|
||||
:key="transform.name"
|
||||
@click="applyTransform(transform)"
|
||||
class="transform-button"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="transform.name + (index < 9 ? ' (' + (index + 1) + ')' : '')"
|
||||
:data-shortcut="index < 9 ? index + 1 : ''"
|
||||
>
|
||||
{{ transform.name }}
|
||||
<small class="transform-preview" v-if="transformInput">
|
||||
{{ transform.preview(transformInput.slice(0, 10)) }}
|
||||
</small>
|
||||
<kbd v-if="index < 9">{{ index + 1 }}</kbd>
|
||||
</button>
|
||||
<div class="transform-category-legend">
|
||||
<div class="legend-title">Categories:</div>
|
||||
<div class="legend-item transform-category-encoding" data-target="category-encoding">Encoding</div>
|
||||
<div class="legend-item transform-category-cipher" data-target="category-cipher">Ciphers</div>
|
||||
<div class="legend-item transform-category-visual" data-target="category-visual">Visual</div>
|
||||
<div class="legend-item transform-category-format" data-target="category-format">Formatting</div>
|
||||
<div class="legend-item transform-category-unicode" data-target="category-unicode">Unicode</div>
|
||||
<div class="legend-item transform-category-special" data-target="category-special">Special</div>
|
||||
</div>
|
||||
<div class="transform-categories">
|
||||
<!-- Encoding Category -->
|
||||
<div id="category-encoding" class="transform-category-section">
|
||||
<h4 class="category-title transform-category-encoding">Encoding</h4>
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('encoding')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
class="transform-button transform-category-encoding"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
>
|
||||
{{ transform.name }}
|
||||
<small class="transform-preview" v-if="transformInput">
|
||||
{{ transform.preview(transformInput.slice(0, 10)) }}
|
||||
</small>
|
||||
<i class="fas fa-copy auto-copy-icon"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="transformHasReverse(transform)"
|
||||
@click="decodeWithTransform(transform)"
|
||||
class="decode-button transform-category-encoding"
|
||||
:title="'Click to decode using: ' + transform.name"
|
||||
>
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cipher Category -->
|
||||
<div id="category-cipher" class="transform-category-section">
|
||||
<h4 class="category-title transform-category-cipher">Ciphers</h4>
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('cipher')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
class="transform-button transform-category-cipher"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
>
|
||||
{{ transform.name }}
|
||||
<small class="transform-preview" v-if="transformInput">
|
||||
{{ transform.preview(transformInput.slice(0, 10)) }}
|
||||
</small>
|
||||
<i class="fas fa-copy auto-copy-icon"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="transformHasReverse(transform)"
|
||||
@click="decodeWithTransform(transform)"
|
||||
class="decode-button transform-category-cipher"
|
||||
:title="'Click to decode using: ' + transform.name"
|
||||
>
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visual Category -->
|
||||
<div id="category-visual" class="transform-category-section">
|
||||
<h4 class="category-title transform-category-visual">Visual</h4>
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('visual')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
class="transform-button transform-category-visual"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
>
|
||||
{{ transform.name }}
|
||||
<small class="transform-preview" v-if="transformInput">
|
||||
{{ transform.preview(transformInput.slice(0, 10)) }}
|
||||
</small>
|
||||
<i class="fas fa-copy auto-copy-icon"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="transformHasReverse(transform)"
|
||||
@click="decodeWithTransform(transform)"
|
||||
class="decode-button transform-category-visual"
|
||||
:title="'Click to decode using: ' + transform.name"
|
||||
>
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Format Category -->
|
||||
<div id="category-format" class="transform-category-section">
|
||||
<h4 class="category-title transform-category-format">Formatting</h4>
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('format')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
class="transform-button transform-category-format"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
>
|
||||
{{ transform.name }}
|
||||
<small class="transform-preview" v-if="transformInput">
|
||||
{{ transform.preview(transformInput.slice(0, 10)) }}
|
||||
</small>
|
||||
<i class="fas fa-copy auto-copy-icon"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="transformHasReverse(transform)"
|
||||
@click="decodeWithTransform(transform)"
|
||||
class="decode-button transform-category-format"
|
||||
:title="'Click to decode using: ' + transform.name"
|
||||
>
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unicode Category -->
|
||||
<div id="category-unicode" class="transform-category-section">
|
||||
<h4 class="category-title transform-category-unicode">Unicode</h4>
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('unicode')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
class="transform-button transform-category-unicode"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
>
|
||||
{{ transform.name }}
|
||||
<small class="transform-preview" v-if="transformInput">
|
||||
{{ transform.preview(transformInput.slice(0, 10)) }}
|
||||
</small>
|
||||
<i class="fas fa-copy auto-copy-icon"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="transformHasReverse(transform)"
|
||||
@click="decodeWithTransform(transform)"
|
||||
class="decode-button transform-category-unicode"
|
||||
:title="'Click to decode using: ' + transform.name"
|
||||
>
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Special Category -->
|
||||
<div id="category-special" class="transform-category-section">
|
||||
<h4 class="category-title transform-category-special">Special</h4>
|
||||
<div class="transform-buttons">
|
||||
<div v-for="transform in getTransformsByCategory('special')" :key="transform.name" class="transform-button-group">
|
||||
<button
|
||||
@click="applyTransform(transform)"
|
||||
class="transform-button transform-category-special"
|
||||
:class="{ active: activeTransform === transform }"
|
||||
:title="'Click to transform and copy: ' + transform.name"
|
||||
>
|
||||
{{ transform.name }}
|
||||
<small class="transform-preview" v-if="transformInput">
|
||||
{{ transform.preview(transformInput.slice(0, 10)) }}
|
||||
</small>
|
||||
<i class="fas fa-copy auto-copy-icon"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="transformHasReverse(transform)"
|
||||
@click="decodeWithTransform(transform)"
|
||||
class="decode-button transform-category-special"
|
||||
:title="'Click to decode using: ' + transform.name"
|
||||
>
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -214,10 +369,63 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Universal Decoder Section for Transforms Tab -->
|
||||
<div class="decode-section">
|
||||
<div class="section-header">
|
||||
<h3><i class="fas fa-magic"></i> Universal Decoder</h3>
|
||||
<p>Paste any encoded text to try all decoding methods at once</p>
|
||||
</div>
|
||||
<div class="input-container">
|
||||
<textarea
|
||||
id="universal-decode-input-transforms"
|
||||
v-model="universalDecodeInput"
|
||||
placeholder="Paste encoded text to decode automatically..."
|
||||
@input="runUniversalDecode"
|
||||
></textarea>
|
||||
<div class="decoded-message" v-if="universalDecodeResult">
|
||||
<div class="decode-method">Decoded using: <strong>{{ universalDecodeResult.method }}</strong></div>
|
||||
<div class="decode-result">{{ universalDecodeResult.text }}</div>
|
||||
<button class="copy-button" @click="copyToClipboard(universalDecodeResult.text)" title="Copy decoded text">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Copy History Panel -->
|
||||
<div class="copy-history-panel" :class="{ 'active': showCopyHistory }">
|
||||
<div class="copy-history-header">
|
||||
<h3><i class="fas fa-history"></i> Copy History</h3>
|
||||
<button class="close-button" @click="toggleCopyHistory" title="Close history">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="copy-history-content">
|
||||
<div v-if="copyHistory.length === 0" class="no-history">
|
||||
<p>No copy history yet. Use the app features to auto-copy content.</p>
|
||||
</div>
|
||||
<div v-else class="history-items">
|
||||
<div v-for="(item, index) in copyHistory" :key="index" class="history-item">
|
||||
<div class="history-item-header">
|
||||
<span class="history-source">{{ item.source }}</span>
|
||||
<span class="history-time">{{ item.timestamp }}</span>
|
||||
</div>
|
||||
<div class="history-content">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<div class="history-actions">
|
||||
<button class="copy-again-button" @click="copyToClipboard(item.content)" title="Copy again">
|
||||
<i class="fas fa-copy"></i> Copy Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -225,5 +433,75 @@
|
||||
<script src="js/steganography.js"></script>
|
||||
<script src="js/emojiLibrary.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
<!-- Force emoji grid rendering -->
|
||||
<script>
|
||||
// Function to initialize emoji grid with retries
|
||||
function initEmojiGrid(retryCount) {
|
||||
retryCount = retryCount || 0;
|
||||
const maxRetries = 5;
|
||||
|
||||
console.log('Initializing emoji grid, attempt:', retryCount + 1);
|
||||
|
||||
// Get the emoji library element
|
||||
const emojiLibrary = document.querySelector('.emoji-library');
|
||||
|
||||
// Access the emoji grid container without forcing styles
|
||||
const emojiGridContainer = document.getElementById('emoji-grid-container');
|
||||
if (emojiGridContainer) {
|
||||
console.log('Found emoji grid container, initializing...');
|
||||
// Only set content, let CSS handle display
|
||||
emojiGridContainer.innerHTML = '<div class="loading-emojis">Loading emoji grid...</div>';
|
||||
|
||||
// Manually render emoji grid
|
||||
if (window.emojiLibrary && window.emojiLibrary.renderEmojiGrid) {
|
||||
try {
|
||||
window.emojiLibrary.renderEmojiGrid('emoji-grid-container', function(emoji) {
|
||||
// Simulate emoji selection by calling the Vue method if possible
|
||||
const app = document.getElementById('app').__vue__;
|
||||
if (app && app.selectEmoji) {
|
||||
app.selectEmoji(emoji);
|
||||
}
|
||||
});
|
||||
console.log('Emoji grid successfully rendered');
|
||||
} catch (error) {
|
||||
console.error('Error rendering emoji grid:', error);
|
||||
}
|
||||
} else {
|
||||
console.warn('Emoji library not yet available, will retry');
|
||||
if (retryCount < maxRetries) {
|
||||
setTimeout(() => initEmojiGrid(retryCount + 1), 500);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('Emoji grid container not found, will retry');
|
||||
if (retryCount < maxRetries) {
|
||||
setTimeout(() => initEmojiGrid(retryCount + 1), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// First attempt after a short delay to ensure Vue has initialized
|
||||
setTimeout(initEmojiGrid, 500);
|
||||
|
||||
// Also initialize when switching to the steganography tab
|
||||
const app = document.getElementById('app');
|
||||
if (app && app.__vue__) {
|
||||
const vue = app.__vue__;
|
||||
// Watch for tab changes
|
||||
const originalWatch = vue.$watch;
|
||||
if (originalWatch) {
|
||||
vue.$watch('activeTab', function(newTab) {
|
||||
if (newTab === 'steganography') {
|
||||
console.log('Switched to steganography tab, initializing emoji grid');
|
||||
setTimeout(initEmojiGrid, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
286
js/app.js
286
js/app.js
@@ -12,6 +12,15 @@ window.app = new Vue({
|
||||
transformInput: '',
|
||||
transformOutput: '',
|
||||
activeTransform: null,
|
||||
// Transform categories for styling
|
||||
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'],
|
||||
format: ['Pig Latin', 'Leetspeak', 'NATO Phonetic'],
|
||||
unicode: ['Invisible Text', 'Upside Down', 'Full Width', 'Small Caps', 'Bubble', 'Braille'],
|
||||
special: ['Medieval', 'Cursive', 'Monospace', 'Double-Struck', 'Elder Futhark', 'Mirror Text', 'Zalgo']
|
||||
},
|
||||
transforms: Object.entries(window.transforms).map(([key, transform]) => ({
|
||||
name: transform.name,
|
||||
func: transform.func.bind(transform),
|
||||
@@ -42,6 +51,13 @@ window.app = new Vue({
|
||||
showCopyHistory: false
|
||||
},
|
||||
methods: {
|
||||
// Get transforms grouped by category
|
||||
getTransformsByCategory(category) {
|
||||
return this.transforms.filter(transform =>
|
||||
this.transformCategories[category].includes(transform.name)
|
||||
);
|
||||
},
|
||||
|
||||
// Theme Toggle
|
||||
toggleTheme() {
|
||||
this.isDarkTheme = !this.isDarkTheme;
|
||||
@@ -92,6 +108,39 @@ window.app = new Vue({
|
||||
this.addToCopyHistory(`Transform: ${this.activeTransform.name}`, this.transformOutput);
|
||||
}
|
||||
},
|
||||
|
||||
// Check if a transform has a reverse function
|
||||
transformHasReverse(transform) {
|
||||
return transform && typeof transform.reverse === 'function';
|
||||
},
|
||||
|
||||
// Decode text using the specific transform's reverse function
|
||||
decodeWithTransform(transform) {
|
||||
if (!this.transformInput || !transform || !this.transformHasReverse(transform)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use the transform's reverse function to decode the input
|
||||
const decodedText = transform.reverse(this.transformInput);
|
||||
|
||||
if (decodedText !== this.transformInput) {
|
||||
// Update the input with the decoded text
|
||||
this.transformInput = decodedText;
|
||||
|
||||
// Show a notification
|
||||
this.showNotification(`<i class="fas fa-check"></i> Decoded using ${transform.name}`, 'success');
|
||||
|
||||
// Add to copy history
|
||||
this.addToCopyHistory(`Decoded (${transform.name})`, decodedText);
|
||||
} else {
|
||||
this.showNotification(`<i class="fas fa-exclamation-triangle"></i> Could not decode with ${transform.name}`, 'warning');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error decoding with ${transform.name}:`, error);
|
||||
this.showNotification(`<i class="fas fa-exclamation-triangle"></i> Error decoding with ${transform.name}`, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Steganography Methods
|
||||
selectCarrier(carrier) {
|
||||
@@ -449,10 +498,12 @@ window.app = new Vue({
|
||||
}
|
||||
}
|
||||
|
||||
// - Invisible text
|
||||
let decoded = window.steganography.decodeInvisible(input);
|
||||
if (decoded) {
|
||||
return { text: decoded, method: 'Invisible Text' };
|
||||
// - Invisible text (only check if the input actually contains invisible characters)
|
||||
if (/[\uE0000-\uE007F]/.test(input)) {
|
||||
let decoded = window.steganography.decodeInvisible(input);
|
||||
if (decoded && decoded.length > 0) {
|
||||
return { text: decoded, method: 'Invisible Text' };
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try transform reversals
|
||||
@@ -503,21 +554,26 @@ window.app = new Vue({
|
||||
const braillePattern = /[⠀-⣿]/;
|
||||
if (braillePattern.test(input)) {
|
||||
try {
|
||||
// Create a reverse mapping for braille
|
||||
const brailleReverseMap = {};
|
||||
if (window.transforms.braille && window.transforms.braille.map) {
|
||||
for (const [key, value] of Object.entries(window.transforms.braille.map)) {
|
||||
brailleReverseMap[value] = key;
|
||||
}
|
||||
|
||||
// Decode the braille
|
||||
let result = '';
|
||||
for (const char of input) {
|
||||
result += brailleReverseMap[char] || char;
|
||||
}
|
||||
|
||||
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
|
||||
return { text: result, method: 'Braille' };
|
||||
// Count how many braille characters are in the input
|
||||
const brailleMatches = [...input.matchAll(/[⠀-⣿]/g)];
|
||||
// Only proceed if there are enough braille characters (to avoid false positives)
|
||||
if (brailleMatches.length > 2) {
|
||||
// Create a reverse mapping for braille
|
||||
const brailleReverseMap = {};
|
||||
if (window.transforms.braille && window.transforms.braille.map) {
|
||||
for (const [key, value] of Object.entries(window.transforms.braille.map)) {
|
||||
brailleReverseMap[value] = key;
|
||||
}
|
||||
|
||||
// Decode the braille
|
||||
let result = '';
|
||||
for (const char of input) {
|
||||
result += brailleReverseMap[char] || char;
|
||||
}
|
||||
|
||||
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
|
||||
return { text: result, method: 'Braille' };
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -611,6 +667,168 @@ window.app = new Vue({
|
||||
}
|
||||
}
|
||||
|
||||
// Check for specific new transforms before trying the generic approach
|
||||
|
||||
// - Hexadecimal
|
||||
if (/^[0-9A-Fa-f\s]+$/.test(input.trim())) {
|
||||
try {
|
||||
if (window.transforms.hex && window.transforms.hex.reverse) {
|
||||
const result = window.transforms.hex.reverse(input);
|
||||
if (result && /[\x20-\x7E]{3,}/.test(result)) {
|
||||
return { text: result, method: 'Hexadecimal' };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Hex decode error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - URL Encoded
|
||||
if (/%[0-9A-Fa-f]{2}/.test(input)) {
|
||||
try {
|
||||
if (window.transforms.url && window.transforms.url.reverse) {
|
||||
const result = window.transforms.url.reverse(input);
|
||||
if (result !== input && /[\x20-\x7E]{3,}/.test(result)) {
|
||||
return { text: result, method: 'URL Encoded' };
|
||||
}
|
||||
} else {
|
||||
// Fallback implementation
|
||||
try {
|
||||
const result = decodeURIComponent(input);
|
||||
if (result !== input && /[\x20-\x7E]{3,}/.test(result)) {
|
||||
return { text: result, method: 'URL Encoded' };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('URL decode fallback error:', e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('URL decode error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - HTML Entities
|
||||
if (/&[#a-zA-Z0-9]+;/.test(input)) {
|
||||
try {
|
||||
if (window.transforms.html && window.transforms.html.reverse) {
|
||||
const result = window.transforms.html.reverse(input);
|
||||
if (result !== input && /[\x20-\x7E]{3,}/.test(result)) {
|
||||
return { text: result, method: 'HTML Entities' };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('HTML entities decode error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - ROT13/Caesar Cipher (check if decoding produces more common English words)
|
||||
if (/^[a-zA-Z\s.,!?]+$/.test(input)) {
|
||||
try {
|
||||
// Try ROT13 first as it's more common
|
||||
if (window.transforms.rot13 && window.transforms.rot13.reverse) {
|
||||
const result = window.transforms.rot13.reverse(input);
|
||||
if (result !== input) {
|
||||
return { text: result, method: 'ROT13' };
|
||||
}
|
||||
}
|
||||
|
||||
// Then try Caesar cipher
|
||||
if (window.transforms.caesar && window.transforms.caesar.reverse) {
|
||||
const result = window.transforms.caesar.reverse(input);
|
||||
if (result !== input) {
|
||||
return { text: result, method: 'Caesar Cipher' };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Cipher decode error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - Base32
|
||||
if (/^[A-Z2-7=]+$/.test(input.trim())) {
|
||||
try {
|
||||
if (window.transforms.base32 && window.transforms.base32.reverse) {
|
||||
const result = window.transforms.base32.reverse(input);
|
||||
if (result && /[\x20-\x7E]{3,}/.test(result)) {
|
||||
return { text: result, method: 'Base32' };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Base32 decode error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - ASCII85
|
||||
if (/^<~.*~>$/.test(input.trim())) {
|
||||
try {
|
||||
if (window.transforms.ascii85 && window.transforms.ascii85.reverse) {
|
||||
const result = window.transforms.ascii85.reverse(input);
|
||||
if (result && /[\x20-\x7E]{3,}/.test(result)) {
|
||||
return { text: result, method: 'ASCII85' };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('ASCII85 decode error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - Check for Zalgo text (text with combining marks)
|
||||
const combiningMarksRegex = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/;
|
||||
if (combiningMarksRegex.test(input)) {
|
||||
try {
|
||||
// Count the number of combining marks to ensure it's actually Zalgo text
|
||||
// and not just text with a few accents
|
||||
const matches = input.match(combiningMarksRegex) || [];
|
||||
if (matches.length > 3) { // Threshold to distinguish Zalgo from normal accented text
|
||||
// Fallback implementation to remove combining marks
|
||||
const result = input.replace(/[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/g, '');
|
||||
if (result !== input && result.length > 0) {
|
||||
return { text: result, method: 'Zalgo' };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Zalgo decode error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// - Check for various Unicode text styles (medieval, cursive, monospace, double-struck)
|
||||
const unicodeStyleChecks = [
|
||||
{ name: 'Medieval', transform: 'medieval' },
|
||||
{ name: 'Cursive', transform: 'cursive' },
|
||||
{ name: 'Monospace', transform: 'monospace' },
|
||||
{ name: 'Double-Struck', transform: 'doubleStruck' }
|
||||
];
|
||||
|
||||
for (const style of unicodeStyleChecks) {
|
||||
if (window.transforms[style.transform] && window.transforms[style.transform].map) {
|
||||
try {
|
||||
// Create reverse mapping
|
||||
const reverseMap = {};
|
||||
for (const [key, value] of Object.entries(window.transforms[style.transform].map)) {
|
||||
reverseMap[value] = key;
|
||||
}
|
||||
|
||||
// Check if input contains characters from this style
|
||||
const styleChars = Object.values(window.transforms[style.transform].map);
|
||||
const hasStyleChars = styleChars.some(char => input.includes(char));
|
||||
|
||||
if (hasStyleChars) {
|
||||
// Decode text
|
||||
let result = '';
|
||||
for (const char of input) {
|
||||
result += reverseMap[char] || char;
|
||||
}
|
||||
|
||||
if (result !== input && /[a-zA-Z0-9]/.test(result)) {
|
||||
return { text: result, method: style.name };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`${style.name} decode error:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - Try reverse each transform that has a built-in reverse function
|
||||
for (const name in window.transforms) {
|
||||
const transform = window.transforms[name];
|
||||
@@ -698,11 +916,7 @@ window.app = new Vue({
|
||||
// Render the emoji grid
|
||||
window.emojiLibrary.renderEmojiGrid('emoji-grid-container', this.selectEmoji.bind(this), this.filteredEmojis);
|
||||
|
||||
// Add a bold message about copying
|
||||
const copyNote = document.createElement('div');
|
||||
copyNote.style.cssText = 'text-align: center; margin-top: 10px; font-weight: bold; padding: 5px; background-color: #f0f0f0; border-radius: 4px;';
|
||||
copyNote.innerHTML = '<i class="fas fa-info-circle"></i> Clicking an emoji will automatically copy your hidden message';
|
||||
container.appendChild(copyNote);
|
||||
// Message about copying has been removed as requested
|
||||
|
||||
// Log success
|
||||
console.log('Emoji grid rendered successfully');
|
||||
@@ -716,6 +930,30 @@ 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 emoji grid with all emojis shown by default
|
||||
this.$nextTick(() => {
|
||||
console.log('nextTick: Initializing emoji grid');
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
// Create namespace for emoji library
|
||||
window.emojiLibrary = {};
|
||||
|
||||
// Additional emojis for expanded library
|
||||
window.emojiLibrary.ADDITIONAL_EMOJIS = [
|
||||
// Animals & Nature
|
||||
"🦊", "🦁", "🐯", "🐮", "🐷", "🐸", "🐵", "🐔", "🐧", "🐦", "🐤", "🦆", "🦅", "🦉", "🦇", "🐺", "🐗", "🐴", "🦄", "🐝", "🐛", "🦋", "🐌", "🐞", "🐜", "🕷️", "🦂", "🦟", "🦠", "🦨", "🦩", "🦫", "🦬", "🐻❄️", "🐼", "🐨", "🐕", "🐶", "🐩", "🐈", "🐱",
|
||||
|
||||
// Food & Drink
|
||||
"🍏", "🍎", "🍐", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🍈", "🍒", "🍑", "🥭", "🍍", "🥥", "🥝", "🍅", "🍆", "🥑", "🥦", "🥬", "🥒", "🌶️", "🌽", "🥕", "🧄", "🧅", "🥔", "🍠", "🥐", "🍔", "🍕", "🍖", "🍗", "🍤", "🍣", "🍱", "🍜", "🍲", "🍥",
|
||||
|
||||
// Travel & Places
|
||||
"🚗", "🚕", "🚙", "🚌", "🚎", "🚒", "🚑", "🚚", "🚛", "🚜", "🚲", "🚐", "🚟", "🚡", "🚀", "🛸", "🛥️", "🏎️", "🏍️", "🚤", "🚢", "🚁", "🚂", "🚆", "🚈", "🌎", "🌏", "🌍", "🏔️", "🏕️",
|
||||
|
||||
// Activities & Sports
|
||||
"⚽", "🏀", "🏈", "🏐", "🏉", "🎾", "🎳", "🏑", "🏒", "🏓", "🏸", "🥊", "🥋", "🥅", "🤾", "🎿", "🏄", "🏂", "🏊", "🏋️", "🤼", "🤸", "🤺", "🤽", "🤹", "🎯", "🎱", "🎽", "🚴", "🚵",
|
||||
|
||||
// Tech & Objects
|
||||
"💻", "⌨️", "🖥️", "🖱️", "🖨️", "📱", "☎️", "📞", "📟", "📠", "📺", "📻", "🎙️", "🎚️", "🎛️", "🧭", "⏱️", "⏲️", "⏰", "🕰️", "📡", "🔋", "🔌", "💡", "🏮", "🪔", "🧯", "🛢️", "💸", "💵", "💳", "💴", "💶", "💷", "💰", "💱", "💲", "💼", "💽", "💾", "💿",
|
||||
|
||||
// Symbols
|
||||
"❤️", "💛", "💚", "💙", "💜", "💔", "💕", "💞", "💓", "💗", "💖", "💘", "💝", "💟", "💤", "💢", "💣", "💥", "💦", "💨", "💩", "💫", "💬", "🔥", "💠", "👾", "👻", "💀", "👽", "👿",
|
||||
|
||||
// Mystical & Fantasy
|
||||
"🧙", "🧙♂️", "🧙♀️", "🧚", "🧚♂️", "🧚♀️", "🧛", "🧛♂️", "🧛♀️", "🧜", "🧜♂️", "🧜♀️", "👹", "👺", "👻", "👽", "👾", "🐲", "🔮", "🐍", "🐉", "🦄", "👸", "🥷", "👰", "🧔", "⚗️", "🔯", "🔱", "⚜️", "✨", "🌠", "🌋", "💎", "💐", "🍄", "🌺", "🌹", "🐭", "🐚", "🐊", "🐢", "🐇", "🐰", "🔥", "💥", "🌀", "🌈", "🌪️",
|
||||
|
||||
// Flags
|
||||
"🏁", "🚩", "🎌", "🏴", "🏳️", "🏳️🌈", "🏳️⚧️", "🏴☠️", "🇺🇸", "🇨🇦", "🇬🇧", "🇩🇪", "🇫🇷", "🇮🇹", "🇯🇵", "🇰🇷", "🇷🇺", "🇨🇳", "🇮🇳", "🇦🇺", "🇧🇷", "🇪🇸", "🇳🇱", "🇵🇹", "🇸🇪", "🇦🇷", "🇦🇺", "🇦🇹", "🇧🇪", "🇧🇴"
|
||||
];
|
||||
|
||||
// Make emoji list globally available
|
||||
window.emojiLibrary.EMOJI_LIST = [
|
||||
// Faces and People
|
||||
@@ -274,9 +301,147 @@ window.emojiLibrary.EMOJI_LIST = [
|
||||
"🚭", // No Smoking (hacker symbol)
|
||||
"🚯", // No Littering (hacker symbol)
|
||||
"🚱", // Non-Potable Water (hacker symbol)
|
||||
|
||||
// Additional Smileys & Emotion
|
||||
"😊", // Smiling Face with Smiling Eyes
|
||||
"😇", // Smiling Face with Halo
|
||||
"🙂", // Slightly Smiling Face
|
||||
"🙃", // Upside-Down Face
|
||||
"😉", // Winking Face
|
||||
"😌", // Relieved Face
|
||||
"😍", // Smiling Face with Heart-Eyes
|
||||
"🥰", // Smiling Face with Hearts
|
||||
"😘", // Face Blowing a Kiss
|
||||
"😗", // Kissing Face
|
||||
"😙", // Kissing Face with Smiling Eyes
|
||||
"😚", // Kissing Face with Closed Eyes
|
||||
"😋", // Face Savoring Food
|
||||
"😛", // Face with Tongue
|
||||
"😝", // Squinting Face with Tongue
|
||||
"😜", // Winking Face with Tongue
|
||||
"🤪", // Zany Face
|
||||
|
||||
// Additional People & Body
|
||||
"🧑🚀", // Astronaut
|
||||
"👨🚀", // Man Astronaut
|
||||
"👩🚀", // Woman Astronaut
|
||||
"🧑🔬", // Scientist
|
||||
"👨🔬", // Man Scientist
|
||||
"👩🔬", // Woman Scientist
|
||||
"🧑⚕️", // Health Worker
|
||||
"👨⚕️", // Man Health Worker
|
||||
"👩⚕️", // Woman Health Worker
|
||||
"🧑🔧", // Mechanic
|
||||
"👨🔧", // Man Mechanic
|
||||
"👩🔧", // Woman Mechanic
|
||||
"🧑🚒", // Firefighter
|
||||
"👨🚒", // Man Firefighter
|
||||
"👩🚒", // Woman Firefighter
|
||||
|
||||
// Additional Animals & Nature
|
||||
"🦒", // Giraffe
|
||||
"🦓", // Zebra
|
||||
"🦬", // Bison
|
||||
"🦙", // Llama
|
||||
"🦘", // Kangaroo
|
||||
"🦥", // Sloth
|
||||
"🦦", // Otter
|
||||
"🦡", // Badger
|
||||
"🦔", // Hedgehog
|
||||
"🦝", // Raccoon
|
||||
"🐿️", // Chipmunk
|
||||
"🦫", // Beaver
|
||||
"🦎", // Lizard
|
||||
"🐊", // Crocodile
|
||||
"🐢", // Turtle
|
||||
"🦕", // Sauropod
|
||||
"🦖", // T-Rex
|
||||
"🐋", // Whale
|
||||
"🐬", // Dolphin
|
||||
"🦭", // Seal
|
||||
|
||||
// Additional Food & Drink
|
||||
"🥞", // Pancakes
|
||||
"🧇", // Waffle
|
||||
"🧀", // Cheese Wedge
|
||||
"🍖", // Meat on Bone
|
||||
"🍗", // Poultry Leg
|
||||
"🥩", // Cut of Meat
|
||||
"🥓", // Bacon
|
||||
"🍔", // Hamburger
|
||||
"🍟", // French Fries
|
||||
"🍕", // Pizza
|
||||
"🌭", // Hot Dog
|
||||
"🥪", // Sandwich
|
||||
"🌮", // Taco
|
||||
"🌯", // Burrito
|
||||
"🥙", // Stuffed Flatbread
|
||||
"🧆", // Falafel
|
||||
"🥚", // Egg
|
||||
"🍳", // Cooking
|
||||
"🥘", // Shallow Pan of Food
|
||||
"🍲", // Pot of Food
|
||||
|
||||
// Additional Travel & Places
|
||||
"🏙️", // Cityscape
|
||||
"🌆", // Cityscape at Dusk
|
||||
"🌇", // Sunset
|
||||
"🌃", // Night with Stars
|
||||
"🌉", // Bridge at Night
|
||||
"🏞️", // National Park
|
||||
"🏜️", // Desert
|
||||
"🏝️", // Desert Island
|
||||
"🏖️", // Beach with Umbrella
|
||||
"⛰️", // Mountain
|
||||
"🏔️", // Snow-Capped Mountain
|
||||
"🌋", // Volcano
|
||||
"🗻", // Mount Fuji
|
||||
"🏠", // House
|
||||
"🏡", // House with Garden
|
||||
"🏢", // Office Building
|
||||
"🏣", // Japanese Post Office
|
||||
"🏤", // Post Office
|
||||
"🏥", // Hospital
|
||||
"🏦", // Bank
|
||||
|
||||
// Additional Flags
|
||||
"🇺🇸", // United States
|
||||
"🇬🇧", // United Kingdom
|
||||
"🇨🇦", // Canada
|
||||
"🇯🇵", // Japan
|
||||
"🇩🇪", // Germany
|
||||
"🇫🇷", // France
|
||||
"🇮🇹", // Italy
|
||||
"🇪🇸", // Spain
|
||||
"🇷🇺", // Russia
|
||||
"🇨🇳", // China
|
||||
"🇮🇳", // India
|
||||
"🇧🇷", // Brazil
|
||||
"🇦🇺", // Australia
|
||||
"🇲🇽", // Mexico
|
||||
"🇰🇷", // South Korea
|
||||
"🇿🇦", // South Africa
|
||||
"🇸🇪", // Sweden
|
||||
"🇳🇴", // Norway
|
||||
"🇳🇿", // New Zealand
|
||||
"🇮🇪", // Ireland
|
||||
];
|
||||
|
||||
// Function to render emoji grid
|
||||
// Define standard emoji categories using the Unicode CLDR categorization
|
||||
window.emojiLibrary.CATEGORIES = [
|
||||
{ id: 'all', name: 'All Emojis', icon: '🔍' },
|
||||
{ id: 'smileys', name: 'Smileys & Emotion', icon: '😀' },
|
||||
{ id: 'people', name: 'People & Body', icon: '👋' },
|
||||
{ id: 'animals', name: 'Animals & Nature', icon: '🐵' },
|
||||
{ id: 'food', name: 'Food & Drink', icon: '🍎' },
|
||||
{ id: 'travel', name: 'Travel & Places', icon: '🚗' },
|
||||
{ id: 'activities', name: 'Activities', icon: '⚽' },
|
||||
{ id: 'objects', name: 'Objects', icon: '💡' },
|
||||
{ id: 'symbols', name: 'Symbols', icon: '🔣' },
|
||||
{ id: 'flags', name: 'Flags', icon: '🏁' }
|
||||
];
|
||||
|
||||
// Function to render emoji grid with categories
|
||||
window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filteredList) {
|
||||
console.log('Rendering emoji grid to:', containerId);
|
||||
|
||||
@@ -290,36 +455,39 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte
|
||||
// Clear container
|
||||
container.innerHTML = '';
|
||||
|
||||
// Create grid note
|
||||
const gridNote = document.createElement('div');
|
||||
gridNote.className = 'emoji-grid-note';
|
||||
gridNote.innerHTML = '<i class="fas fa-magic"></i> Click any emoji to automatically copy your hidden message';
|
||||
container.appendChild(gridNote);
|
||||
|
||||
// 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';
|
||||
|
||||
// Force grid styling
|
||||
gridContainer.style.display = 'grid';
|
||||
gridContainer.style.gridTemplateColumns = 'repeat(auto-fill, minmax(50px, 1fr))';
|
||||
gridContainer.style.gap = '8px';
|
||||
gridContainer.style.padding = '15px';
|
||||
gridContainer.style.maxHeight = '300px';
|
||||
gridContainer.style.overflowY = 'auto';
|
||||
gridContainer.style.border = '1px solid #ccc';
|
||||
gridContainer.style.borderRadius = '4px';
|
||||
gridContainer.style.margin = '10px 0';
|
||||
// Combine all emojis for a larger selection
|
||||
const allEmojis = [...window.emojiLibrary.EMOJI_LIST, ...window.emojiLibrary.ADDITIONAL_EMOJIS];
|
||||
|
||||
// Add a message showing we're displaying all emojis
|
||||
const fullLibraryNote = document.createElement('div');
|
||||
fullLibraryNote.className = 'emoji-grid-note';
|
||||
fullLibraryNote.innerHTML = '<i class="fas fa-magic"></i> Click an emoji to automatically copy your hidden message';
|
||||
fullLibraryNote.style.padding = '10px';
|
||||
fullLibraryNote.style.marginBottom = '10px';
|
||||
fullLibraryNote.style.backgroundColor = 'rgba(0,0,0,0.05)';
|
||||
fullLibraryNote.style.borderRadius = '4px';
|
||||
fullLibraryNote.style.textAlign = 'center';
|
||||
container.appendChild(fullLibraryNote);
|
||||
|
||||
// Always use full emoji list - search removed
|
||||
// Use the provided filtered list if available, otherwise default to full list
|
||||
// This ensures we always show ALL emojis regardless of input state
|
||||
const emojisToShow = filteredList && filteredList.length > 0 ? filteredList : window.emojiLibrary.EMOJI_LIST;
|
||||
const emojisToShow = filteredList && filteredList.length > 0 ? filteredList : allEmojis;
|
||||
console.log(`Adding ${emojisToShow.length} emojis to grid`);
|
||||
|
||||
// Add emojis to grid with enforced styling
|
||||
@@ -329,32 +497,13 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte
|
||||
emojiButton.textContent = emoji; // Use textContent for better emoji handling
|
||||
emojiButton.title = 'Click to encode with this emoji';
|
||||
|
||||
// Force button styling
|
||||
emojiButton.style.fontSize = '24px';
|
||||
emojiButton.style.padding = '8px';
|
||||
emojiButton.style.border = '1px solid #ddd';
|
||||
emojiButton.style.borderRadius = '8px';
|
||||
emojiButton.style.cursor = 'pointer';
|
||||
emojiButton.style.backgroundColor = '#fff';
|
||||
emojiButton.style.transition = 'transform 0.1s';
|
||||
|
||||
// Add hover effect
|
||||
emojiButton.onmouseover = function() {
|
||||
this.style.transform = 'scale(1.1)';
|
||||
this.style.boxShadow = '0 0 5px rgba(0,0,0,0.2)';
|
||||
};
|
||||
emojiButton.onmouseout = function() {
|
||||
this.style.transform = 'scale(1)';
|
||||
this.style.boxShadow = 'none';
|
||||
};
|
||||
|
||||
emojiButton.addEventListener('click', () => {
|
||||
if (typeof onEmojiSelect === 'function') {
|
||||
onEmojiSelect(emoji);
|
||||
// Add visual feedback when clicked
|
||||
emojiButton.style.backgroundColor = '#e6f7ff';
|
||||
setTimeout(() => {
|
||||
emojiButton.style.backgroundColor = '#fff';
|
||||
emojiButton.style.backgroundColor = '';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
@@ -365,9 +514,199 @@ window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filte
|
||||
container.appendChild(gridContainer);
|
||||
console.log('Emoji grid rendering complete');
|
||||
|
||||
// Force container to be visible
|
||||
container.style.display = 'block !important';
|
||||
container.style.visibility = 'visible !important';
|
||||
// Helper function to categorize emojis using standard Unicode ranges
|
||||
function categorizeEmoji(emoji) {
|
||||
// Get the code point of the emoji
|
||||
const code = emoji.codePointAt(0);
|
||||
|
||||
// Smileys & Emotion (faces, emotions, hearts)
|
||||
if ((code >= 0x1F600 && code <= 0x1F64F) || // Emoticons
|
||||
(code >= 0x1F910 && code <= 0x1F92F) || // Face-hand
|
||||
(code >= 0x1F970 && code <= 0x1F97A) || // Faces
|
||||
(code >= 0x1F9D0 && code <= 0x1F9DF) || // Faces
|
||||
(code >= 0x2763 && code <= 0x2764) || // Hearts
|
||||
(code >= 0x1F48B && code <= 0x1F49F) || // Hearts and love
|
||||
(code >= 0x1F493 && code <= 0x1F49F) || // Hearts
|
||||
emoji === '😀' || emoji === '😃' || emoji === '😄' || emoji === '😁' || emoji === '😆' ||
|
||||
emoji === '😅' || emoji === '😂' || emoji === '🤣' || emoji === '☺️' || emoji === '😊') {
|
||||
return 'smileys';
|
||||
}
|
||||
|
||||
// People & Body (people, hands, body parts)
|
||||
if ((code >= 0x1F466 && code <= 0x1F487) || // People
|
||||
(code >= 0x1F9D1 && code <= 0x1F9DD) || // People
|
||||
(code >= 0x1F468 && code <= 0x1F469) || // Man/Woman
|
||||
(code >= 0x1F46E && code <= 0x1F9CF) || // People roles
|
||||
(code >= 0x1F44B && code <= 0x1F450) || // Hands
|
||||
(code >= 0x1F918 && code <= 0x1F91F) || // Hand symbols
|
||||
(code >= 0x1F926 && code <= 0x1F937) || // People gestures
|
||||
emoji.includes('👨') || emoji.includes('👩') || emoji.includes('🧑') ||
|
||||
emoji.includes('👶') || emoji.includes('👦') || emoji.includes('👧') ||
|
||||
emoji.includes('🧒') || emoji.includes('👴') || emoji.includes('👵') ||
|
||||
emoji.includes('🧓') || emoji.includes('👮') || emoji.includes('👷')) {
|
||||
return 'people';
|
||||
}
|
||||
|
||||
// Animals & Nature (animals, plants, weather)
|
||||
if ((code >= 0x1F400 && code <= 0x1F43F) || // Animals
|
||||
(code >= 0x1F980 && code <= 0x1F9AF) || // Animals
|
||||
(code >= 0x1F330 && code <= 0x1F33F) || // Plants
|
||||
(code >= 0x1F340 && code <= 0x1F37F) || // More plants
|
||||
(code >= 0x1F300 && code <= 0x1F32C) || // Weather
|
||||
emoji === '🐵' || emoji === '🐒' || emoji === '🦍' || emoji === '🦧' ||
|
||||
emoji === '🐶' || emoji === '🐕' || emoji === '🦮' || emoji === '🐩' ||
|
||||
emoji === '🐺' || emoji === '🦊' || emoji === '🦝' || emoji === '🐱' ||
|
||||
emoji === '🌱' || emoji === '🌲' || emoji === '🌳' || emoji === '🌴' ||
|
||||
emoji === '🌵' || emoji === '🌷' || emoji === '🌸' || emoji === '🌹') {
|
||||
return 'animals';
|
||||
}
|
||||
|
||||
// Food & Drink
|
||||
if ((code >= 0x1F32D && code <= 0x1F37F) || // Food items
|
||||
(code >= 0x1F95F && code <= 0x1F9AA) || // More food
|
||||
(code >= 0x1F950 && code <= 0x1F96F) || // More food
|
||||
emoji === '🍇' || emoji === '🍈' || emoji === '🍉' || emoji === '🍊' ||
|
||||
emoji === '🍋' || emoji === '🍌' || emoji === '🍍' || emoji === '🥭' ||
|
||||
emoji === '🍎' || emoji === '🍏' || emoji === '🍐' || emoji === '🍑' ||
|
||||
emoji === '🍒' || emoji === '🍓' || emoji === '🥝' || emoji === '🍅' ||
|
||||
emoji === '🥥' || emoji === '🥑' || emoji === '🍆' || emoji === '🥔') {
|
||||
return 'food';
|
||||
}
|
||||
|
||||
// Travel & Places (transportation, buildings, maps)
|
||||
if ((code >= 0x1F680 && code <= 0x1F6FF) || // Transport
|
||||
(code >= 0x1F30D && code <= 0x1F32C) || // Earth/Weather
|
||||
(code >= 0x1F3D7 && code <= 0x1F3DB) || // Buildings
|
||||
(code >= 0x1F3E0 && code <= 0x1F3F0) || // Buildings
|
||||
(code >= 0x26E9 && code <= 0x26F5) || // Buildings/Places
|
||||
emoji === '🚗' || emoji === '🚕' || emoji === '🚙' || emoji === '🚌' ||
|
||||
emoji === '🚎' || emoji === '🏎️' || emoji === '🚓' || emoji === '🚑' ||
|
||||
emoji === '🚒' || emoji === '🚐' || emoji === '🛻' || emoji === '🚚' ||
|
||||
emoji === '🚛' || emoji === '🚜' || emoji === '🛵' || emoji === '🏍️' ||
|
||||
emoji === '🛺' || emoji === '🚲' || emoji === '🛴' || emoji === '🚏') {
|
||||
return 'travel';
|
||||
}
|
||||
|
||||
// Activities (sports, music, arts, hobbies)
|
||||
if ((code >= 0x1F380 && code <= 0x1F3A0) || // Events
|
||||
(code >= 0x1F3A3 && code <= 0x1F3BE) || // Sports
|
||||
(code >= 0x1F3BF && code <= 0x1F3C9) || // Sports
|
||||
(code >= 0x1F3CF && code <= 0x1F3D6) || // Sports
|
||||
(code >= 0x1F3F8 && code <= 0x1F3FF) || // Activities
|
||||
(code >= 0x1F93A && code <= 0x1F94F) || // Sports
|
||||
emoji === '⚽' || emoji === '⚾' || emoji === '🏀' || emoji === '🏐' ||
|
||||
emoji === '🏈' || emoji === '🏉' || emoji === '🎾' || emoji === '🥏' ||
|
||||
emoji === '🎳' || emoji === '🏏' || emoji === '🏑' || emoji === '🏒' ||
|
||||
emoji === '🥍' || emoji === '🏓' || emoji === '🏸' || emoji === '🥊') {
|
||||
return 'activities';
|
||||
}
|
||||
|
||||
// Objects (household, office, tools)
|
||||
if ((code >= 0x1F4A1 && code <= 0x1F4CC) || // Office
|
||||
(code >= 0x1F4D0 && code <= 0x1F4F7) || // Office/Tools
|
||||
(code >= 0x1F4FF && code <= 0x1F53D) || // Various objects
|
||||
(code >= 0x1F56F && code <= 0x1F5A4) || // Objects
|
||||
(code >= 0x1F5D1 && code <= 0x1F5FF) || // Office objects
|
||||
(code >= 0x1F6D1 && code <= 0x1F6DF) || // Misc objects
|
||||
emoji === '⌚' || emoji === '📱' || emoji === '📲' || emoji === '💻' ||
|
||||
emoji === '⌨️' || emoji === '🖥️' || emoji === '🖨️' || emoji === '🖱️' ||
|
||||
emoji === '🖲️' || emoji === '🕹️' || emoji === '🗜️' || emoji === '💽' ||
|
||||
emoji === '💾' || emoji === '💿' || emoji === '📀' || emoji === '📼') {
|
||||
return 'objects';
|
||||
}
|
||||
|
||||
// Symbols (punctuation, alphanum, geometric, etc)
|
||||
if ((code >= 0x1F300 && code <= 0x1F320) || // Various symbols
|
||||
(code >= 0x1F170 && code <= 0x1F251) || // Enclosed characters
|
||||
(code >= 0x1F523 && code <= 0x1F5FF) || // Symbols
|
||||
(code >= 0x2600 && code <= 0x26FF) || // Misc symbols
|
||||
(code >= 0x2700 && code <= 0x27BF) || // Dingbats
|
||||
(code >= 0x1F5FB && code <= 0x1F64F) || // Symbols
|
||||
(code >= 0x1F680 && code <= 0x1F6FF) || // Transport symbols
|
||||
emoji === '💯' || emoji === '📛' || emoji === '🔰' || emoji === '⭕' ||
|
||||
emoji === '✅' || emoji === '☑️' || emoji === '✔️' || emoji === '❌' ||
|
||||
emoji === '❎' || emoji === '➰' || emoji === '➿' || emoji === '〽️' ||
|
||||
emoji === '✳️' || emoji === '✴️' || emoji === '❇️' || emoji === '©️') {
|
||||
return 'symbols';
|
||||
}
|
||||
|
||||
// Flags (country flags, flag symbols)
|
||||
if ((code >= 0x1F1E6 && code <= 0x1F1FF) || // Regional indicators for flags
|
||||
emoji === '🏁' || emoji === '🚩' || emoji === '🎌' || emoji === '🏴' ||
|
||||
emoji.includes('🏳️') || // Flag variants
|
||||
emoji.includes('🏴') || // Flag variants
|
||||
// Check for country flags (pairs of regional indicators)
|
||||
(emoji.length >= 2 &&
|
||||
emoji.codePointAt(0) >= 0x1F1E6 && emoji.codePointAt(0) <= 0x1F1FF &&
|
||||
emoji.codePointAt(2) >= 0x1F1E6 && emoji.codePointAt(2) <= 0x1F1FF)) {
|
||||
return 'flags';
|
||||
}
|
||||
|
||||
// Default to 'all' if we can't categorize
|
||||
return 'all';
|
||||
}
|
||||
|
||||
// Add event listeners to category tabs with actual filtering
|
||||
document.querySelectorAll('.emoji-category-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
// Remove active class from all tabs
|
||||
document.querySelectorAll('.emoji-category-tab').forEach(t => {
|
||||
t.classList.remove('active');
|
||||
});
|
||||
// Add active class to clicked tab
|
||||
this.classList.add('active');
|
||||
|
||||
const selectedCategory = this.getAttribute('data-category');
|
||||
console.log('Selected category:', selectedCategory);
|
||||
|
||||
// Get all emoji buttons
|
||||
const allEmojis = [...window.emojiLibrary.EMOJI_LIST, ...window.emojiLibrary.ADDITIONAL_EMOJIS];
|
||||
|
||||
// Filter emojis based on selected category
|
||||
let filteredEmojis = allEmojis;
|
||||
if (selectedCategory !== 'all') {
|
||||
filteredEmojis = allEmojis.filter(emoji => {
|
||||
const category = categorizeEmoji(emoji);
|
||||
console.log(`Emoji: ${emoji}, Category: ${category}`);
|
||||
return category === selectedCategory;
|
||||
});
|
||||
}
|
||||
|
||||
// Clear and rebuild the grid with filtered emojis
|
||||
const gridContainer = container.querySelector('.emoji-grid');
|
||||
if (gridContainer) {
|
||||
// Clear existing emojis
|
||||
gridContainer.innerHTML = '';
|
||||
|
||||
// Add filtered emojis
|
||||
filteredEmojis.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 = `${filteredEmojis.length} emojis available`;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Debug info - add count display
|
||||
const countDisplay = document.createElement('div');
|
||||
|
||||
@@ -13,7 +13,8 @@ function encodeForPreview(emoji, text) {
|
||||
const vs16 = '\ufe0f'; // emoji variation selector (1)
|
||||
|
||||
// Start with the emoji character
|
||||
let result = emoji;
|
||||
// Ensure the emoji has a presentation selector first to standardize it
|
||||
let result = emoji + vs16; // Add emoji presentation selector first
|
||||
|
||||
// Add variation selectors based on binary representation
|
||||
for (const bit of binary) {
|
||||
@@ -76,7 +77,8 @@ function encodeEmoji(emoji, text) {
|
||||
const vs16 = '\ufe0f'; // emoji variation selector (1)
|
||||
|
||||
// Start with the emoji character
|
||||
let result = emoji;
|
||||
// Ensure the emoji has a presentation selector first to standardize it
|
||||
let result = emoji + vs16; // Add emoji presentation selector first
|
||||
|
||||
// Add variation selectors based on binary representation
|
||||
for (const bit of binary) {
|
||||
@@ -94,23 +96,42 @@ 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);
|
||||
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, '');
|
||||
const matches = [...text.matchAll(/[\ufe0e\ufe0f]/g)];
|
||||
if (!matches.length) return '';
|
||||
|
||||
// Convert variation selectors to binary
|
||||
const binary = matches.map(m => m[0] === '\ufe0e' ? '0' : '1').join('');
|
||||
// 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];
|
||||
const pattern = new RegExp(`^${emojiChar}([\ufe0e\ufe0f]+)`, 'u');
|
||||
const emojiData = text.match(pattern);
|
||||
|
||||
if (!emojiData || !emojiData[1]) return '';
|
||||
|
||||
// Get only the variation selectors that follow the emoji directly
|
||||
const varSelectors = emojiData[1];
|
||||
// Skip the first variation selector as it's used for presentation
|
||||
const matches = [...varSelectors.matchAll(/[\ufe0e\ufe0f]/g)];
|
||||
if (matches.length <= 1) return ''; // Need at least one bit after the presentation selector
|
||||
|
||||
// Convert variation selectors to binary, skipping the first one (presentation selector)
|
||||
const binary = matches.slice(1).map(m => m[0] === '\ufe0e' ? '0' : '1').join('');
|
||||
|
||||
// Make sure we have complete bytes (multiples of 8 bits)
|
||||
const validBinaryLength = Math.floor(binary.length / 8) * 8;
|
||||
|
||||
// Convert binary to text
|
||||
let decoded = '';
|
||||
for (let i = 0; i < binary.length; i += 8) {
|
||||
for (let i = 0; i < validBinaryLength; i += 8) {
|
||||
const byte = binary.slice(i, i + 8);
|
||||
if (byte.length === 8) {
|
||||
decoded += String.fromCharCode(parseInt(byte, 2));
|
||||
const charCode = parseInt(byte, 2);
|
||||
// Only include printable ASCII characters
|
||||
if (charCode >= 32 && charCode <= 126) {
|
||||
decoded += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,14 +151,38 @@ function encodeInvisible(text) {
|
||||
function decodeInvisible(text) {
|
||||
if (!text) return '';
|
||||
|
||||
// Extract valid invisible characters
|
||||
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
|
||||
if (!matches.length) return '';
|
||||
|
||||
const bytes = new Uint8Array(
|
||||
matches.map(m => m[0].codePointAt(0) - 0xE0000)
|
||||
);
|
||||
// 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;
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(bytes);
|
||||
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
|
||||
|
||||
667
js/transforms.js
667
js/transforms.js
@@ -1,5 +1,52 @@
|
||||
// Text transformation functions
|
||||
const transforms = {
|
||||
// Invisible Text transform
|
||||
invisible_text: {
|
||||
name: 'Invisible Text',
|
||||
func: function(text) {
|
||||
if (!text) return '';
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
return Array.from(bytes)
|
||||
.map(byte => String.fromCodePoint(0xE0000 + byte))
|
||||
.join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return '[invisible]';
|
||||
},
|
||||
reverse: function(text) {
|
||||
if (!text) return '';
|
||||
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
|
||||
if (!matches.length) return '';
|
||||
|
||||
return matches
|
||||
.map(match => String.fromCharCode(match[0].codePointAt(0) - 0xE0000))
|
||||
.join('');
|
||||
}
|
||||
},
|
||||
// Invisible Text transform
|
||||
invisible_text: {
|
||||
name: 'Invisible Text',
|
||||
func: function(text) {
|
||||
if (!text) return '';
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
return Array.from(bytes)
|
||||
.map(byte => String.fromCodePoint(0xE0000 + byte))
|
||||
.join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return '[invisible]';
|
||||
},
|
||||
reverse: function(text) {
|
||||
if (!text) return '';
|
||||
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
|
||||
if (!matches.length) return '';
|
||||
|
||||
return matches
|
||||
.map(match => String.fromCharCode(match[0].codePointAt(0) - 0xE0000))
|
||||
.join('');
|
||||
}
|
||||
},
|
||||
|
||||
// Basic transforms
|
||||
upside_down: {
|
||||
name: 'Upside Down',
|
||||
@@ -42,11 +89,23 @@ const transforms = {
|
||||
'j': 'ᛃ', 'k': 'ᛲ', 'l': 'ᛚ', 'm': 'ᛗ', 'n': 'ᚾ', 'o': 'ᛟ', 'p': 'ᛈ', 'q': 'ᛲᛩ', 'r': 'ᚱ',
|
||||
's': 'ᛋ', 't': 'ᛏ', 'u': 'ᚢ', 'v': 'ᛩ', 'w': 'ᛩ', 'x': 'ᛲᛋ', 'y': 'ᛁ', 'z': 'ᛉ'
|
||||
},
|
||||
// Create reverse map for decoding
|
||||
reverseMap: function() {
|
||||
const revMap = {};
|
||||
for (const [key, value] of Object.entries(this.map)) {
|
||||
revMap[value] = key;
|
||||
}
|
||||
return revMap;
|
||||
},
|
||||
func: function(text) {
|
||||
return [...text.toLowerCase()].map(c => this.map[c] || c).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
const revMap = this.reverseMap();
|
||||
return [...text].map(c => revMap[c] || c).join('');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -57,6 +116,10 @@ const transforms = {
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Remove spaces between characters
|
||||
return text.replace(/ /g, '');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -193,6 +256,610 @@ const transforms = {
|
||||
}
|
||||
// Note: other transforms don't have reverse functions because they're not easily reversible
|
||||
// The universal decoder will only try to reverse transforms that have a reverse function
|
||||
,
|
||||
|
||||
// Additional transforms
|
||||
base64: {
|
||||
name: 'Base64',
|
||||
func: function(text) {
|
||||
return btoa(text);
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
try {
|
||||
return atob(text);
|
||||
} catch (e) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hex: {
|
||||
name: 'Hexadecimal',
|
||||
func: function(text) {
|
||||
return [...text].map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
const hexText = text.replace(/\s+/g, '');
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < hexText.length; i += 2) {
|
||||
const byte = hexText.substr(i, 2);
|
||||
if (byte.length === 2) {
|
||||
result += String.fromCharCode(parseInt(byte, 16));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
caesar: {
|
||||
name: 'Caesar Cipher',
|
||||
shift: 3, // Traditional Caesar shift is 3
|
||||
func: function(text) {
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
// Only shift letters, leave other characters unchanged
|
||||
if (code >= 65 && code <= 90) { // Uppercase letters
|
||||
return String.fromCharCode(((code - 65 + this.shift) % 26) + 65);
|
||||
} else if (code >= 97 && code <= 122) { // Lowercase letters
|
||||
return String.fromCharCode(((code - 97 + this.shift) % 26) + 97);
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
// For decoding, shift in the opposite direction
|
||||
const originalShift = this.shift;
|
||||
this.shift = 26 - (this.shift % 26); // Reverse the shift
|
||||
const result = this.func(text);
|
||||
this.shift = originalShift; // Restore original shift
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
rot13: {
|
||||
name: 'ROT13',
|
||||
func: function(text) {
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) { // Uppercase letters
|
||||
return String.fromCharCode(((code - 65 + 13) % 26) + 65);
|
||||
} else if (code >= 97 && code <= 122) { // Lowercase letters
|
||||
return String.fromCharCode(((code - 97 + 13) % 26) + 97);
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
// ROT13 is its own inverse
|
||||
return this.func(text);
|
||||
}
|
||||
},
|
||||
|
||||
leetspeak: {
|
||||
name: 'Leetspeak',
|
||||
map: {
|
||||
'a': '4', 'e': '3', 'i': '1', 'o': '0', 's': '5', 't': '7', 'l': '1',
|
||||
'A': '4', 'E': '3', 'I': '1', 'O': '0', 'S': '5', 'T': '7', 'L': '1'
|
||||
},
|
||||
func: function(text) {
|
||||
return [...text].map(c => this.map[c] || c).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
// Create reverse map for decoding
|
||||
reverseMap: function() {
|
||||
const revMap = {};
|
||||
for (const [key, value] of Object.entries(this.map)) {
|
||||
revMap[value] = key.toLowerCase();
|
||||
}
|
||||
return revMap;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const revMap = this.reverseMap();
|
||||
return [...text].map(c => revMap[c] || c).join('');
|
||||
}
|
||||
},
|
||||
|
||||
mirror: {
|
||||
name: 'Mirror Text',
|
||||
func: function(text) {
|
||||
return [...text].reverse().join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
return this.func(text); // Mirror is its own inverse
|
||||
}
|
||||
},
|
||||
|
||||
nato: {
|
||||
name: 'NATO Phonetic',
|
||||
map: {
|
||||
'a': 'Alpha', 'b': 'Bravo', 'c': 'Charlie', 'd': 'Delta', 'e': 'Echo',
|
||||
'f': 'Foxtrot', 'g': 'Golf', 'h': 'Hotel', 'i': 'India', 'j': 'Juliett',
|
||||
'k': 'Kilo', 'l': 'Lima', 'm': 'Mike', 'n': 'November', 'o': 'Oscar',
|
||||
'p': 'Papa', 'q': 'Quebec', 'r': 'Romeo', 's': 'Sierra', 't': 'Tango',
|
||||
'u': 'Uniform', 'v': 'Victor', 'w': 'Whiskey', 'x': 'X-ray', 'y': 'Yankee', 'z': 'Zulu',
|
||||
'0': 'Zero', '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four',
|
||||
'5': 'Five', '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine'
|
||||
},
|
||||
func: function(text) {
|
||||
return [...text.toLowerCase()].map(c => this.map[c] || c).join(' ');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
// Create reverse map for decoding
|
||||
reverseMap: function() {
|
||||
const revMap = {};
|
||||
for (const [key, value] of Object.entries(this.map)) {
|
||||
revMap[value.toLowerCase()] = key;
|
||||
}
|
||||
return revMap;
|
||||
},
|
||||
reverse: function(text) {
|
||||
const revMap = this.reverseMap();
|
||||
return text.split(/\s+/).map(word => revMap[word.toLowerCase()] || word).join('');
|
||||
}
|
||||
},
|
||||
|
||||
fullwidth: {
|
||||
name: 'Full Width',
|
||||
func: function(text) {
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
// Convert ASCII to full-width equivalents
|
||||
if (code >= 33 && code <= 126) {
|
||||
return String.fromCharCode(code + 0xFEE0);
|
||||
} else if (code === 32) { // Space
|
||||
return ' '; // Full-width space
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
// Convert full-width back to ASCII
|
||||
if (code >= 0xFF01 && code <= 0xFF5E) {
|
||||
return String.fromCharCode(code - 0xFEE0);
|
||||
} else if (code === 0x3000) { // Full-width space
|
||||
return ' '; // ASCII space
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}).join('');
|
||||
}
|
||||
},
|
||||
|
||||
strikethrough: {
|
||||
name: 'Strikethrough',
|
||||
func: function(text) {
|
||||
return [...text].map(c => c + '̶').join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
}
|
||||
},
|
||||
|
||||
underline: {
|
||||
name: 'Underline',
|
||||
func: function(text) {
|
||||
return [...text].map(c => c + '̲').join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
}
|
||||
},
|
||||
|
||||
medieval: {
|
||||
name: 'Medieval',
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
cursive: {
|
||||
name: 'Cursive',
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
monospace: {
|
||||
name: 'Monospace',
|
||||
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].map(c => this.map[c] || c).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
}
|
||||
},
|
||||
|
||||
doubleStruck: {
|
||||
name: 'Double-Struck',
|
||||
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].map(c => this.map[c] || c).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
}
|
||||
},
|
||||
|
||||
ascii85: {
|
||||
name: 'ASCII85',
|
||||
func: function(text) {
|
||||
// Simple ASCII85 encoding implementation
|
||||
let result = '<~';
|
||||
let buffer = 0;
|
||||
let bufferLength = 0;
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
buffer = (buffer << 8) | text.charCodeAt(i);
|
||||
bufferLength += 8;
|
||||
|
||||
if (bufferLength >= 32) {
|
||||
let value = buffer >>> (bufferLength - 32);
|
||||
buffer &= (1 << (bufferLength - 32)) - 1;
|
||||
bufferLength -= 32;
|
||||
|
||||
if (value === 0) {
|
||||
result += 'z';
|
||||
} else {
|
||||
for (let j = 4; j >= 0; j--) {
|
||||
const digit = (value / Math.pow(85, j)) % 85;
|
||||
result += String.fromCharCode(digit + 33);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle remaining bits
|
||||
if (bufferLength > 0) {
|
||||
buffer <<= (32 - bufferLength);
|
||||
let value = buffer;
|
||||
const bytes = Math.ceil(bufferLength / 8);
|
||||
|
||||
for (let j = 4; j >= (4 - bytes); j--) {
|
||||
const digit = (value / Math.pow(85, j)) % 85;
|
||||
result += String.fromCharCode(digit + 33);
|
||||
}
|
||||
}
|
||||
|
||||
return result + '~>';
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Check if it's a valid ASCII85 string
|
||||
if (!text.startsWith('<~') || !text.endsWith('~>')) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Remove delimiters and whitespace
|
||||
text = text.substring(2, text.length - 2).replace(/\s+/g, '');
|
||||
|
||||
let result = '';
|
||||
let i = 0;
|
||||
|
||||
while (i < text.length) {
|
||||
// Handle 'z' special case (represents 4 zero bytes)
|
||||
if (text[i] === 'z') {
|
||||
result += '\0\0\0\0';
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process a group of 5 characters
|
||||
if (i + 5 <= text.length || i + 1 <= text.length) {
|
||||
let value = 0;
|
||||
const limit = Math.min(i + 5, text.length);
|
||||
|
||||
// Convert the group to a 32-bit value
|
||||
for (let j = i; j < limit; j++) {
|
||||
value = value * 85 + (text.charCodeAt(j) - 33);
|
||||
}
|
||||
|
||||
// Pad with 'u' (84) if needed
|
||||
for (let j = limit; j < i + 5; j++) {
|
||||
value = value * 85 + 84;
|
||||
}
|
||||
|
||||
// Extract bytes from the value
|
||||
const bytesToWrite = limit - i - 1;
|
||||
for (let j = 3; j >= 4 - bytesToWrite; j--) {
|
||||
result += String.fromCharCode((value >>> (j * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
i = limit;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
reverse: {
|
||||
name: 'Reverse Text',
|
||||
func: function(text) {
|
||||
return [...text].reverse().join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
return this.func(text); // Reversing is its own inverse
|
||||
}
|
||||
},
|
||||
|
||||
url: {
|
||||
name: 'URL Encode',
|
||||
func: function(text) {
|
||||
return encodeURIComponent(text);
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
try {
|
||||
return decodeURIComponent(text);
|
||||
} catch (e) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
html: {
|
||||
name: 'HTML Entities',
|
||||
func: function(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, '\'');
|
||||
}
|
||||
},
|
||||
|
||||
pigLatin: {
|
||||
name: 'Pig Latin',
|
||||
func: function(text) {
|
||||
return text.split(/\s+/).map(word => {
|
||||
if (!word) return '';
|
||||
|
||||
// Check if the word starts with a vowel
|
||||
if (/^[aeiou]/i.test(word)) {
|
||||
return word + 'way';
|
||||
}
|
||||
|
||||
// Handle consonant clusters at the beginning
|
||||
const match = word.match(/^([^aeiou]+)(.*)/i);
|
||||
if (match) {
|
||||
return match[2] + match[1] + 'ay';
|
||||
}
|
||||
|
||||
return word;
|
||||
}).join(' ');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
return text.split(/\s+/).map(word => {
|
||||
if (!word) return '';
|
||||
|
||||
// Check if the word ends with 'way' (vowel case)
|
||||
if (word.endsWith('way')) {
|
||||
return word.slice(0, -3);
|
||||
}
|
||||
|
||||
// Check if the word ends with 'ay' (consonant case)
|
||||
if (word.endsWith('ay')) {
|
||||
// Extract the part before 'ay'
|
||||
const base = word.slice(0, -2);
|
||||
|
||||
// Find the last consonant cluster
|
||||
// In Pig Latin, the original first consonant cluster is moved to the end
|
||||
// So we need to move it back to the beginning
|
||||
for (let i = 1; i <= base.length; i++) {
|
||||
const possibleCluster = base.slice(-i);
|
||||
const possibleResult = possibleCluster + base.slice(0, -i);
|
||||
|
||||
// If this looks like a valid word, return it
|
||||
// This is a simple heuristic and might not work for all cases
|
||||
if (/^[bcdfghjklmnpqrstvwxyz]/i.test(possibleResult)) {
|
||||
return possibleResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return word;
|
||||
}).join(' ');
|
||||
}
|
||||
},
|
||||
|
||||
rainbow: {
|
||||
name: 'Rainbow Text',
|
||||
colors: ['#FF0000', '#FF7F00', '#FFFF00', '#00FF00', '#0000FF', '#4B0082', '#9400D3'],
|
||||
func: function(text) {
|
||||
// This is just a preview function that returns a description
|
||||
// The actual rainbow effect is applied in the UI
|
||||
return text;
|
||||
},
|
||||
preview: function(text) {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
|
||||
rot47: {
|
||||
name: 'ROT47',
|
||||
func: function(text) {
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
// ROT47 operates on a character set from ASCII 33 to ASCII 126
|
||||
if (code >= 33 && code <= 126) {
|
||||
return String.fromCharCode(33 + ((code - 33 + 14) % 94));
|
||||
}
|
||||
return c;
|
||||
}).join('');
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
reverse: function(text) {
|
||||
return [...text].map(c => {
|
||||
const code = c.charCodeAt(0);
|
||||
if (code >= 33 && code <= 126) {
|
||||
return String.fromCharCode(33 + ((code - 33 + 94 - 14) % 94));
|
||||
}
|
||||
return c;
|
||||
}).join('');
|
||||
}
|
||||
},
|
||||
|
||||
base32: {
|
||||
name: 'Base32',
|
||||
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
|
||||
func: function(text) {
|
||||
let result = '';
|
||||
let bits = 0;
|
||||
let value = 0;
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
value = (value << 8) | text.charCodeAt(i);
|
||||
bits += 8;
|
||||
|
||||
while (bits >= 5) {
|
||||
bits -= 5;
|
||||
result += this.alphabet[(value >> bits) & 0x1F];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle remaining bits
|
||||
if (bits > 0) {
|
||||
result += this.alphabet[(value << (5 - bits)) & 0x1F];
|
||||
}
|
||||
|
||||
// Add padding
|
||||
while (result.length % 8 !== 0) {
|
||||
result += '=';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
preview: function(text) {
|
||||
return this.func(text);
|
||||
},
|
||||
// Create reverse map for decoding
|
||||
reverseMap: function() {
|
||||
const revMap = {};
|
||||
for (let i = 0; i < this.alphabet.length; i++) {
|
||||
revMap[this.alphabet[i]] = i;
|
||||
}
|
||||
return revMap;
|
||||
},
|
||||
reverse: function(text) {
|
||||
// Remove padding and whitespace
|
||||
text = text.replace(/\s+/g, '').replace(/=+$/, '');
|
||||
|
||||
if (text.length === 0) return '';
|
||||
|
||||
const revMap = this.reverseMap();
|
||||
let result = '';
|
||||
let bits = 0;
|
||||
let value = 0;
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i].toUpperCase();
|
||||
if (revMap[char] === undefined) continue; // Skip invalid characters
|
||||
|
||||
value = (value << 5) | revMap[char];
|
||||
bits += 5;
|
||||
|
||||
while (bits >= 8) {
|
||||
bits -= 8;
|
||||
result += String.fromCharCode((value >> bits) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Export transforms for use in app.js
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import List, Dict, Optional
|
||||
from string import ascii_lowercase
|
||||
|
||||
# Import additional transformations
|
||||
from more_transforms_fixed import (
|
||||
from text_transforms import (
|
||||
to_upside_down, to_elder_futhark, to_vaporwave, to_zalgo,
|
||||
to_unicode_circled, to_small_caps, to_braille
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user