Skip to content

Instantly share code, notes, and snippets.

@mode-mercury
Created June 2, 2025 21:03
Show Gist options
  • Select an option

  • Save mode-mercury/6079955f94b89e3287f8c847fbc39185 to your computer and use it in GitHub Desktop.

Select an option

Save mode-mercury/6079955f94b89e3287f8c847fbc39185 to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>Sentence Transformer</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: '#0AE275', // Cyberpunk green
secondary: '#FF1493', // Cyberpunk pink
accent: '#00FFFF', // Cyberpunk cyan
success: '#33DD58',
warning: '#FFD700',
info: '#36A7F0',
error: '#FF3D5A',
dark: '#121212', // Darker background
'dark-900': '#0A0A0A', // Even darker for panels
'dark-800': '#141414' // For content areas
},
fontFamily: {
sans: ['Rajdhani', 'system-ui', 'sans-serif'], // More sci-fi looking font
mono: ['Share Tech Mono', 'monospace']
},
boxShadow: {
'neon': '0 0 5px rgba(10, 226, 117, 0.7), 0 0 10px rgba(10, 226, 117, 0.5), 0 0 15px rgba(10, 226, 117, 0.3)',
'neon-pink': '0 0 5px rgba(255, 20, 147, 0.7), 0 0 10px rgba(255, 20, 147, 0.5), 0 0 15px rgba(255, 20, 147, 0.3)',
'neon-blue': '0 0 5px rgba(0, 255, 255, 0.7), 0 0 10px rgba(0, 255, 255, 0.5), 0 0 15px rgba(0, 255, 255, 0.3)',
'terminal': '0 0 10px rgba(0, 0, 0, 0.9) inset, 0 0 5px rgba(10, 226, 117, 0.5)'
},
borderRadius: {
'xl': '1rem',
'2xl': '1.5rem'
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&display=swap');
* {
touch-action: manipulation;
}
input,
textarea,
select,
button {
font-size: 16px;
/* Prevent zoom on focus on mobile */
}
/* Modern scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #0AE275;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #09c266;
}
/* Custom animations */
@keyframes pulse-border {
0% {
box-shadow: 0 0 0 0 rgba(93, 92, 222, 0.4);
}
70% {
box-shadow: 0 0 0 5px rgba(93, 92, 222, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(93, 92, 222, 0);
}
}
.animate-pulse-border {
animation: pulse-border 1.5s infinite;
}
/* Fluid transitions */
.btn-transition {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-transition:hover {
transform: translateY(-1px);
}
.btn-transition:active {
transform: translateY(1px);
}
</style>
</head>
<body class="min-h-screen bg-white dark:bg-dark text-gray-800 dark:text-gray-100 transition-colors duration-200">
<div class="container mx-auto px-2 py-4 max-w-xl">
<!-- Removed title to save space -->
<style>
/* Styles for Cryptanalysis tab to match the terminal theme */
.crypto-theme {
--terminal-green: #33ff33;
--terminal-amber: #ffbf00;
--dark-bg: rgba(10, 15, 10, 0.9);
--panel-bg: rgba(17, 24, 16, 0.8);
--border-color: #1d3315;
--highlight-color: #33ff33;
font-family: 'Courier New', monospace;
}
.dark .crypto-theme {
--dark-bg: rgba(5, 10, 5, 0.9);
--panel-bg: rgba(12, 18, 12, 0.9);
}
.crypto-panel {
background-color: var(--panel-bg);
border: 1px solid var(--border-color);
box-shadow: inset 0 0 5px rgba(0, 40, 0, 0.5);
color: var(--terminal-green);
}
.crypto-btn {
background-color: rgba(0, 40, 0, 0.6) !important;
border: 1px solid var(--border-color) !important;
color: var(--terminal-green) !important;
transition: all 0.2s;
text-shadow: 0 0 3px rgba(51, 255, 51, 0.5);
}
.crypto-btn:hover {
background-color: rgba(0, 60, 0, 0.8) !important;
color: var(--terminal-amber) !important;
box-shadow: 0 0 5px rgba(51, 255, 51, 0.5);
}
.crypto-input,
.crypto-textarea,
.crypto-select {
background-color: rgba(0, 20, 0, 0.7) !important;
border: 1px solid var(--border-color) !important;
color: var(--terminal-green) !important;
font-family: 'Courier New', monospace !important;
}
.crypto-title {
text-shadow: 0 0 5px rgba(51, 255, 51, 0.5);
}
.crypto-result {
background-color: rgba(0, 10, 0, 0.7) !important;
border: 1px solid var(--border-color) !important;
}
.scanline {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
opacity: 0.15;
background: linear-gradient(to bottom,
transparent 0%,
rgba(51, 255, 51, 0.1) 50%,
transparent 100%);
animation: scanline 6s linear infinite;
}
.crt-effect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
background: repeating-linear-gradient(0deg,
rgba(0, 0, 0, 0.1),
rgba(0, 0, 0, 0.1) 1px,
transparent 1px,
transparent 2px);
}
@keyframes scanline {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(100%);
}
}
.copy-btn {
background-color: rgba(0, 40, 0, 0.6) !important;
border: 1px solid var(--border-color) !important;
color: var(--terminal-green) !important;
position: absolute;
right: 0.5rem;
top: 0.5rem;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
z-index: 10;
opacity: 0.8;
transition: all 0.2s;
}
.copy-btn:hover {
opacity: 1;
background-color: rgba(0, 60, 0, 0.8) !important;
}
.textarea-container {
position: relative;
}
.tab-content-container {
position: relative;
}
</style>
<!-- Tab Navigation -->
<div class="flex border-b border-gray-300 dark:border-gray-600 mb-4">
<button id="tab-transform" class="py-2 px-4 text-sm font-medium border-b-2 border-primary bg-gray-100 dark:bg-gray-800 flex-1">Transform</button>
<button id="tab-encrypt" class="py-2 px-4 text-sm font-medium border-b-2 border-transparent hover:text-primary flex-1">Encrypt/Decrypt</button>
<button id="tab-pattern" class="py-2 px-4 text-sm font-medium border-b-2 border-transparent hover:text-primary flex-1">Patterns</button>
<button id="tab-crypto" class="py-2 px-4 text-sm font-medium border-b-2 border-transparent hover:text-primary flex-1">Cryptanalysis</button>
</div>
<!-- Transform Tab Content -->
<div id="content-transform" class="bg-gray-100 dark:bg-dark-800 rounded-lg p-4 shadow-md">
<!-- Input Area -->
<div class="mb-2">
<label for="sentence" class="block text-sm font-medium mb-1">Enter text:</label>
<textarea id="sentence" rows="2" class="w-full px-3 py-2 text-base border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-primary bg-white dark:bg-gray-700" placeholder="Enter text to transform..."></textarea>
</div>
<!-- Text Input Options -->
<div class="grid grid-cols-2 gap-1 mb-2">
<button id="dictate-text-btn" class="bg-purple-600 hover:bg-purple-700 text-gray-900 font-semibold py-1 px-1 text-sm rounded transition-colors duration-200 shadow-neon-pink border border-purple-500/50 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
</svg>
Dictate Text
</button>
<div class="relative">
<input type="file" id="image-upload" accept="image/*" class="hidden" />
<button id="scan-text-btn" class="bg-blue-500 hover:bg-blue-600 text-gray-900 font-semibold py-1 px-1 text-sm rounded transition-colors duration-200 shadow-neon-blue border border-blue-400/50 flex items-center justify-center w-full">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Upload Image
</button>
</div>
</div>
<!-- Main Action Buttons -->
<div class="grid grid-cols-4 gap-1 mb-3">
<button id="transform-btn" class="bg-primary hover:bg-opacity-90 text-gray-900 font-semibold py-1 px-1 text-sm rounded transition-colors duration-200 shadow-neon border border-primary/50">Transform</button>
<button id="analyze-btn" class="bg-secondary hover:bg-opacity-90 text-gray-900 font-semibold py-1 px-1 text-sm rounded transition-colors duration-200 shadow-neon-pink border border-secondary/50">Analyze</button>
<button id="custom-map-btn" class="bg-accent hover:bg-opacity-90 text-gray-900 font-semibold py-1 px-1 text-sm rounded transition-colors duration-200 shadow-neon-blue border border-accent/50">Custom Map</button>
<button id="restore-btn" class="bg-amber-500 hover:bg-amber-600 text-gray-900 font-semibold py-1 px-1 text-sm rounded transition-colors duration-200 border border-amber-500/50">Restore</button>
</div>
<!-- Text Analysis Area (Collapsible) -->
<div class="p-3 mb-3 bg-gradient-to-r from-gray-800/70 to-gray-900/70 dark:from-gray-900/70 dark:to-black/70 rounded-lg shadow-terminal border border-primary/30 backdrop-blur-sm">
<div class="flex items-center justify-between cursor-pointer" id="analysis-section-header">
<h3 class="text-sm font-semibold text-primary">Text Analysis</h3>
<button class="text-primary focus:outline-none" aria-label="Toggle section">
<span class="transform transition-transform duration-200 inline-block" id="analysis-section-triangle">▼</span>
</button>
</div>
<div id="analysis-section-content" class="mt-2 hidden">
<div class="grid grid-cols-2 gap-x-4 gap-y-2 mb-2 text-xs text-gray-300">
<div>
<div class="flex justify-between">
<span>Characters:</span>
<span id="analysis-chars" class="font-mono text-primary">0</span>
</div>
<div class="flex justify-between">
<span>Words:</span>
<span id="analysis-words" class="font-mono text-primary">0</span>
</div>
<div class="flex justify-between">
<span>Vowels:</span>
<span id="analysis-vowels" class="font-mono text-primary">0</span>
</div>
<div class="flex justify-between">
<span>Consonants:</span>
<span id="analysis-consonants" class="font-mono text-primary">0</span>
</div>
</div>
<div>
<div class="flex justify-between">
<span>Numbers:</span>
<span id="analysis-numbers" class="font-mono text-primary">0</span>
</div>
<div class="flex justify-between">
<span>Punctuation:</span>
<span id="analysis-punct" class="font-mono text-primary">0</span>
</div>
<div class="flex justify-between">
<span>Uppercase:</span>
<span id="analysis-upper" class="font-mono text-primary">0</span>
</div>
<div class="flex justify-between">
<span>Lowercase:</span>
<span id="analysis-lower" class="font-mono text-primary">0</span>
</div>
</div>
</div>
<div class="text-xs text-gray-300 mb-2">
<div class="flex justify-between mb-1">
<span>Letter Frequency:</span>
<span id="analysis-top-letters" class="font-mono text-primary">—</span>
</div>
<div class="w-full h-[60px] bg-gray-900/50 rounded-md p-1">
<canvas id="letter-frequency-chart" height="50"></canvas>
</div>
</div>
<div class="text-xs text-gray-300">
<div class="flex justify-between mb-1">
<span>Word Frequency:</span>
<button id="view-all-words-btn" class="text-xs text-primary/80 hover:text-primary">View All</button>
</div>
<div id="word-freq-container" class="max-h-[80px] overflow-y-auto">
<table class="w-full text-left text-xs">
<thead class="bg-gray-900">
<tr>
<th class="py-1 px-2">Word</th>
<th class="py-1 px-2 text-right">Count</th>
<th class="py-1 px-2 text-right">%</th>
</tr>
</thead>
<tbody id="word-freq-table">
<tr>
<td colspan="3" class="py-1 px-2 text-center">No words to analyze</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Basic Transformations - Always Visible -->
<div class="grid grid-cols-3 gap-1 mb-2">
<button id="reverse-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Reverse</button>
<button id="mirror-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Mirror</button>
<button id="upside-down-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Upside Down</button>
</div>
<div class="grid grid-cols-3 gap-1 mb-3">
<button id="remove-spaces-btn" class="bg-green-500 hover:bg-green-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Remove Spaces</button>
<button id="separate-letters-btn" class="bg-purple-600 hover:bg-purple-700 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Separate ▾</button>
<button id="clear-result-btn" class="bg-red-500 hover:bg-red-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clear Result</button>
</div>
<!-- Separation Options Dropdown -->
<div id="separate-options" class="absolute mt-[-40px] ml-[120px] bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden w-40">
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">Separation Options:</div>
<button id="separate-1x1" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">1x1 (each letter)</button>
<button id="separate-3x1" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">3x1 pattern</button>
<button id="separate-2x1" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">2x1 pattern</button>
</div>
<!-- Advanced Functions (Collapsible) -->
<div class="p-3 mb-3 bg-gradient-to-r from-gray-800/70 to-gray-900/70 dark:from-gray-900/70 dark:to-black/70 rounded-lg shadow-terminal border border-primary/30 backdrop-blur-sm">
<div class="flex items-center justify-between cursor-pointer" id="advanced-section-header">
<h3 class="text-sm font-semibold text-primary">Advanced Functions</h3>
<button class="text-primary focus:outline-none" aria-label="Toggle section">
<span class="transform transition-transform duration-200 inline-block" id="advanced-section-triangle">▼</span>
</button>
</div>
<div id="advanced-section-content" class="mt-2 hidden">
<div class="grid grid-cols-2 gap-2 mb-3">
<div>
<label for="transformation" class="block text-xs font-medium mb-1 text-gray-300">Letter transformation:</label>
<select id="transformation" class="w-full px-2 py-1 text-xs border border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary bg-gray-800 text-gray-200">
<option value="none">No letter transformation</option>
<option value="method1">Remove first 3, leave next 3, repeat</option>
<option value="method2">Remove letters 4,5,6 then 10,11,12</option>
<option value="method3">Separate first 3 letters, clump next 3</option>
<option value="method4">Method 1 reversed from punctuation</option>
<option value="method5">Method 2 reversed from punctuation</option>
<option value="method6">Method 3 reversed from punctuation</option>
<option value="method7">Add vowels after consonants</option>
<option value="method8">Method 7 reversed from punctuation</option>
</select>
</div>
<div>
<label for="word-transformation" class="block text-xs font-medium mb-1 text-gray-300">Word transformation:</label>
<select id="word-transformation" class="w-full px-2 py-1 text-xs border border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary bg-gray-800 text-gray-200">
<option value="none">No word transformation</option>
<option value="word-method1">Remove first 3 words, leave next 3</option>
<option value="word-method2">Remove words 4,5,6 then 10,11,12</option>
<option value="word-method3">Separate first 3 words, clump next 3</option>
<option value="word-method4">Method 1 reversed from punctuation</option>
<option value="word-method5">Method 2 reversed from punctuation</option>
<option value="word-method6">Method 3 reversed from punctuation</option>
<option value="word-clump2x2">Clump 2x2 words</option>
<option value="word-clump1x1x1x3">Clump 1x1x1x3 words</option>
<option value="word-clump4x2">Clump 4x2 words</option>
</select>
</div>
</div>
<div class="grid grid-cols-3 gap-1 mb-2">
<button id="clump2x2-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 2x2</button>
<button id="clump1x1x1x3-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 1x1x1x3</button>
<button id="clump4x2-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 4x2</button>
</div>
<div class="grid grid-cols-2 gap-1 mb-2">
<div class="relative">
<button id="reversed-spell-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Rev Spell ▾</button>
<div id="reversed-spell-options" class="absolute left-0 mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden w-40">
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">Reverse Spelling:</div>
<button id="reversed-punct" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Reverse Punct Names</button>
<button id="reversed-letters" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Reverse Letter Names</button>
</div>
</div>
<button id="mirror-with-spaces-btn" class="bg-teal-600 hover:bg-teal-700 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Mirror w/Spaces</button>
</div>
<div class="grid grid-cols-2 gap-1 mb-2">
<button id="insert-ay-ya-btn" class="bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Insert "ay"/"ya"</button>
<button id="append-reverse-btn" class="bg-teal-500 hover:bg-teal-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Append Reversed</button>
</div>
<div class="grid grid-cols-2 gap-1 mb-2">
<button id="show-mapping-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Show Mapping Table</button>
<div id="mapping-display" class="hidden absolute left-0 right-0 mt-8 mx-2 p-2 overflow-auto max-h-[300px] bg-white dark:bg-gray-800 rounded shadow-lg border border-gray-300 dark:border-gray-600 z-20"></div>
</div>
<div class="grid grid-cols-2 gap-1 mb-2">
<div class="relative">
<button id="numbers-to-text-btn" class="w-full bg-amber-500 hover:bg-amber-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Numbers → Text ▾</button>
<div id="numbers-options" class="absolute left-0 mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden w-40">
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">Number Options:</div>
<button id="numbers-forward" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Forward (12 → twelve)</button>
<button id="numbers-reverse" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Reverse (twelve → 12)</button>
</div>
</div>
<div class="relative">
<button id="punct-to-text-btn" class="w-full bg-teal-500 hover:bg-teal-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Punct → Text ▾</button>
<div id="punct-options" class="absolute right-0 mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden w-40">
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">Punctuation Options:</div>
<button id="punct-forward" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Forward (, → comma)</button>
<button id="punct-reverse" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Reverse (comma → ,)</button>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-1 mb-2">
<div class="relative">
<button id="short-vowels-btn" class="w-full bg-pink-500 hover:bg-pink-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Short IPA</button>
</div>
<div class="relative">
<button id="long-vowels-btn" class="w-full bg-purple-500 hover:bg-purple-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Long IPA</button>
</div>
<div class="relative">
<button id="custom-phonemes-btn" class="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">EasyRead ▾</button>
<div id="easyread-options" class="absolute right-0 mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden w-40">
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">EasyRead Options:</div>
<button id="easyread-auto" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Auto (Context)</button>
<button id="easyread-short" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Short Vowels</button>
<button id="easyread-long" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Long Vowels</button>
</div>
</div>
</div>
</div>
</div>
<!-- Character Insertion Section (Collapsible) -->
<div class="p-3 mb-3 bg-gradient-to-r from-gray-800/70 to-gray-900/70 dark:from-gray-900/70 dark:to-black/70 rounded-lg shadow-terminal border border-primary/30 backdrop-blur-sm">
<div class="flex items-center justify-between cursor-pointer" id="insertion-section-header">
<h3 class="text-sm font-semibold text-primary">Insert Characters</h3>
<button class="text-primary focus:outline-none" aria-label="Toggle section">
<span class="transform transition-transform duration-200 inline-block" id="insertion-section-triangle">▼</span>
</button>
</div>
<div id="insertion-section-content" class="mt-2 hidden">
<div class="grid grid-cols-3 gap-2 mb-2">
<div class="col-span-2">
<label class="block text-xs font-medium mb-1 text-gray-300">Type</label>
<select id="insertion-type" class="w-full text-xs rounded-md border-gray-600 focus:border-primary focus:ring focus:ring-primary/20 bg-gray-800 text-gray-200">
<option value="vowels">Vowels (a,e,i,o,u)</option>
<option value="vowels2">2 Vowels</option>
<option value="consonants">Consonants</option>
<option value="custom">Custom Text</option>
</select>
</div>
<div>
<label class="block text-xs font-medium mb-1 text-gray-300">Custom Char</label>
<input type="text" id="custom-char" class="w-full text-xs rounded-md border-gray-600 focus:border-primary focus:ring focus:ring-primary/20 bg-gray-800 text-gray-200" placeholder="X" maxlength="1">
</div>
<div id="custom-insert-container" class="hidden col-span-3">
<label class="block text-xs font-medium mb-1 text-gray-300">Custom Text</label>
<input type="text" id="custom-insert-text" class="w-full text-xs rounded-md border-gray-600 focus:border-primary focus:ring focus:ring-primary/20 bg-gray-800 text-gray-200" placeholder="Text to insert">
</div>
</div>
<div class="grid grid-cols-2 gap-2 mb-2">
<div>
<label class="block text-xs font-medium mb-1 text-gray-300">Insert Between</label>
<select id="insertion-count" class="w-full text-xs rounded-md border-gray-600 focus:border-primary focus:ring focus:ring-primary/20 bg-gray-800 text-gray-200">
<option value="1">Every 1 letter</option>
<option value="2">Every 2 letters</option>
<option value="3">Every 3 letters</option>
<option value="4">Every 4 letters</option>
<option value="5">Every 5 letters</option>
<option value="6">Every 6 letters</option>
<option value="7">Every 7 letters</option>
<option value="8">Every 8 letters</option>
</select>
</div>
<div>
<label class="block text-xs font-medium mb-1 text-gray-300">Order</label>
<select id="insertion-order" class="w-full text-xs rounded-md border-gray-600 focus:border-primary focus:ring focus:ring-primary/20 bg-gray-800 text-gray-200">
<option value="cycle">Cycle through</option>
<option value="ascending">Ascending (a→z)</option>
<option value="descending">Descending (z→a)</option>
<option value="random">Random</option>
</select>
</div>
</div>
<button id="apply-insertion-btn" class="w-full bg-primary hover:bg-opacity-80 text-white font-medium py-1 px-2 text-xs rounded transition-colors duration-200">Apply Insertion</button>
</div>
</div>
<!-- Result Area -->
<div class="mt-3">
<label class="block text-sm font-medium mb-1">Result:</label>
<div id="result" class="p-3 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md min-h-[80px] max-h-[250px] overflow-auto whitespace-pre-wrap break-words text-sm"></div>
</div>
<!-- Word Frequency Modal (hidden by default) -->
<div id="word-freq-modal" class="fixed inset-0 flex items-center justify-center z-50 hidden">
<div class="absolute inset-0 bg-black/60" id="word-freq-modal-backdrop"></div>
<div class="relative bg-gray-800 rounded-lg p-4 max-w-lg w-full max-h-[80vh] flex flex-col shadow-lg border border-gray-700">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-primary">Word Frequency Analysis</h3>
<button id="word-freq-modal-close" class="text-gray-400 hover:text-white">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
<div class="overflow-auto flex-1">
<table class="w-full text-left text-sm">
<thead class="bg-gray-900">
<tr>
<th class="py-2 px-3 sticky top-0 bg-gray-900">Word</th>
<th class="py-2 px-3 text-right sticky top-0 bg-gray-900">Count</th>
<th class="py-2 px-3 text-right sticky top-0 bg-gray-900">%</th>
</tr>
</thead>
<tbody id="word-freq-modal-table" class="text-gray-300">
<!-- Word frequency data will be inserted here -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Encrypt Tab Content -->
<div id="content-encrypt" class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4 shadow-md hidden">
<div class="mb-4">
<label for="encrypt-text" class="block text-sm font-medium mb-1">Enter text to encrypt/decrypt:</label>
<textarea id="encrypt-text" rows="3" class="w-full px-3 py-2 text-base border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-primary bg-white dark:bg-gray-700" placeholder="Type here and see real-time transformation..."></textarea>
</div>
<div class="flex justify-between mb-3">
<button id="encrypt-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-3 text-sm rounded transition-colors duration-200">Encrypt ↓</button>
<button id="decrypt-btn" class="bg-teal-500 hover:bg-teal-600 text-white font-medium py-1 px-3 text-sm rounded transition-colors duration-200">Decrypt ↓</button>
<button id="clear-encrypt-btn" class="bg-red-500 hover:bg-red-600 text-white font-medium py-1 px-3 text-sm rounded transition-colors duration-200">Clear All</button>
</div>
<div class="flex items-center mb-3">
<input type="checkbox" id="reverse-result-checkbox" class="w-4 h-4 text-primary focus:ring-primary border-gray-300 rounded">
<label for="reverse-result-checkbox" class="ml-2 text-sm font-medium">Reverse text output</label>
</div>
<div class="grid grid-cols-2 gap-1 mb-3">
<button id="encrypt-ay-ya-btn" class="bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Insert "ay"/"ya"</button>
<button id="encrypt-remove-spaces-btn" class="bg-green-500 hover:bg-green-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Remove Spaces</button>
</div>
<div class="grid grid-cols-3 gap-1 mb-3">
<button id="encrypt-clump2x2-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 2x2</button>
<button id="encrypt-clump1x1x1x3-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 1x1x1x3</button>
<button id="encrypt-clump4x2-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 4x2</button>
</div>
<div class="grid grid-cols-3 gap-1 mb-3">
<div class="relative">
<button id="encrypt-short-vowels-btn" class="w-full bg-pink-500 hover:bg-pink-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Short IPA</button>
</div>
<div class="relative">
<button id="encrypt-long-vowels-btn" class="w-full bg-purple-500 hover:bg-purple-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Long IPA</button>
</div>
<div class="relative">
<button id="encrypt-custom-phonemes-btn" class="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">EasyRead ▾</button>
<div id="encrypt-easyread-options" class="absolute right-0 mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden w-40">
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">EasyRead Options:</div>
<button id="encrypt-easyread-auto" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Auto (Context)</button>
<button id="encrypt-easyread-short" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Short Vowels</button>
<button id="encrypt-easyread-long" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Long Vowels</button>
</div>
</div>
</div>
<div class="mb-3">
<label for="show-encrypt-mapping-btn" class="block text-sm font-medium mb-1">
<button id="show-encrypt-mapping-btn" class="text-primary hover:underline inline-flex items-center">
<span>View Character Mapping</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-1">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
</button>
</label>
<div id="encrypt-mapping-display" class="overflow-auto hidden max-h-[200px]"></div>
</div>
<div class="mt-3">
<label class="block text-sm font-medium mb-1">Result:</label>
<div id="encrypt-result" class="p-3 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md min-h-[80px] max-h-[250px] overflow-auto whitespace-pre-wrap break-words text-sm"></div>
</div>
</div>
<!-- Patterns Tab Content -->
<div id="content-pattern" class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4 shadow-md hidden">
<div class="textarea-container mb-4">
<label for="pattern-text" class="block text-sm font-medium mb-1">Enter text to apply patterns:</label>
<textarea id="pattern-text" rows="3" class="w-full px-3 py-2 text-base border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-primary bg-white dark:bg-gray-700" placeholder="Type text here to apply pattern insertions..."></textarea>
<button id="pattern-text-copy-btn" class="copy-btn" title="Copy text">Copy</button>
</div>
<div class="mb-1">
<div class="text-sm font-semibold mb-2">Pattern Insertion:</div>
<div class="grid grid-cols-1 gap-2 mb-3">
<button id="pattern1-btn" class="bg-rose-500 hover:bg-rose-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Pattern 1: G...R.A...A...R.D...</button>
<button id="pattern2-btn" class="bg-amber-500 hover:bg-amber-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Pattern 2: G...I.A...A...C.D...</button>
<button id="pattern3-btn" class="bg-lime-600 hover:bg-lime-700 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Pattern 3: A...O.T.O.A...</button>
</div>
</div>
<div class="mb-3">
<button id="pattern-mirror-btn" class="w-full bg-teal-500 hover:bg-teal-600 text-white font-medium py-1 px-2 text-sm rounded transition-colors duration-200 mb-3">Add Mirrored Text</button>
</div>
<div class="grid grid-cols-2 gap-1 mb-3">
<button id="pattern-custom-map-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Apply Custom Mapping</button>
<button id="pattern-ay-ya-btn" class="bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Insert "ay"/"ya"</button>
</div>
<div class="grid grid-cols-3 gap-1 mb-3">
<button id="pattern-clump2x2-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 2x2</button>
<button id="pattern-clump1x1x1x3-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 1x1x1x3</button>
<button id="pattern-clump4x2-btn" class="bg-primary hover:bg-opacity-90 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Clump 4x2</button>
</div>
<div class="grid grid-cols-3 gap-1 mb-3">
<div class="relative">
<button id="pattern-short-vowels-btn" class="w-full bg-pink-500 hover:bg-pink-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Short IPA</button>
</div>
<div class="relative">
<button id="pattern-long-vowels-btn" class="w-full bg-purple-500 hover:bg-purple-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">Long IPA</button>
</div>
<div class="relative">
<button id="pattern-custom-phonemes-btn" class="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-medium py-1 px-1 text-xs rounded transition-colors duration-200">EasyRead ▾</button>
<div id="pattern-easyread-options" class="absolute right-0 mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden w-40">
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">EasyRead Options:</div>
<button id="pattern-easyread-auto" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Auto (Context)</button>
<button id="pattern-easyread-short" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Short Vowels</button>
<button id="pattern-easyread-long" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Long Vowels</button>
</div>
</div>
</div>
<div class="mb-3">
<button id="pattern-clear-btn" class="w-full bg-red-500 hover:bg-red-600 text-white font-medium py-1 px-2 text-sm rounded transition-colors duration-200">Clear All</button>
</div>
<div class="mt-3 textarea-container">
<label class="block text-sm font-medium mb-1">Result:</label>
<div id="pattern-result" class="p-3 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md min-h-[80px] max-h-[250px] overflow-auto whitespace-pre-wrap break-words text-sm"></div>
<button id="pattern-result-copy-btn" class="copy-btn" title="Copy result">Copy</button>
</div>
</div>
<!-- Cryptanalysis Tab Content -->
<div id="content-crypto" class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4 shadow-md hidden tab-content-container crypto-theme">
<div class="scanline"></div>
<div class="crt-effect"></div>
<!-- Input Area -->
<div class="crypto-panel p-3 mb-3 rounded textarea-container">
<div class="flex justify-between items-center mb-1">
<h2 class="text-sm sm:text-base font-bold crypto-title">> INPUT</h2>
<div class="flex gap-1">
<button id="crypto-clear-btn" class="crypto-btn text-xs p-1 rounded" title="Clear">CLEAR</button>
<button id="crypto-analyze-btn" class="crypto-btn text-xs p-1 rounded" title="Analyze">SCAN</button>
</div>
</div>
<textarea id="crypto-input" class="w-full h-16 p-1 text-xs sm:text-sm rounded crypto-textarea" placeholder="Enter text to analyze or decrypt..."></textarea>
<button id="crypto-input-copy-btn" class="copy-btn" title="Copy input">Copy</button>
<div class="flex flex-wrap gap-1 mt-2 justify-between">
<div class="flex flex-wrap gap-1">
<button id="crypto-reverse-btn" class="crypto-transform-btn crypto-btn text-xs px-1 py-0.5 rounded">REV</button>
<button id="crypto-rot13-btn" class="crypto-transform-btn crypto-btn text-xs px-1 py-0.5 rounded">ROT13</button>
<button id="crypto-caesar-btn" class="crypto-transform-btn crypto-btn text-xs px-1 py-0.5 rounded">CAESAR</button>
<button id="crypto-base64-encode-btn" class="crypto-transform-btn crypto-btn text-xs px-1 py-0.5 rounded">B64→</button>
<button id="crypto-base64-decode-btn" class="crypto-transform-btn crypto-btn text-xs px-1 py-0.5 rounded">←B64</button>
</div>
</div>
</div>
<!-- Caesar Cipher Tool (initially hidden) -->
<div id="crypto-caesar-panel" class="crypto-panel p-3 mb-3 rounded hidden">
<div class="flex justify-between items-center mb-1">
<h2 class="text-sm font-bold crypto-title">> CAESAR SHIFT</h2>
<button id="crypto-close-caesar" class="crypto-btn text-xs p-1 rounded">CLOSE</button>
</div>
<div class="grid grid-cols-5 gap-1">
<button data-shift="1" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">+1</button>
<button data-shift="2" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">+2</button>
<button data-shift="3" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">+3</button>
<button data-shift="5" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">+5</button>
<button data-shift="13" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">+13</button>
<button data-shift="-1" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">-1</button>
<button data-shift="-2" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">-2</button>
<button data-shift="-3" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">-3</button>
<button data-shift="-5" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">-5</button>
<button data-shift="-13" class="crypto-caesar-shift-btn crypto-btn text-xs p-1 rounded">-13</button>
</div>
<div class="mt-1">
<button id="crypto-bruteforce-caesar" class="crypto-btn w-full text-xs p-1 rounded">ALL SHIFTS</button>
</div>
</div>
<!-- All Shifts Results (Hidden initially) -->
<div id="crypto-all-shifts-panel" class="crypto-panel p-3 mb-3 rounded hidden">
<div class="flex justify-between items-center mb-1">
<h2 class="text-sm font-bold crypto-title">> ALL CAESAR SHIFTS</h2>
<button id="crypto-close-all-shifts" class="crypto-btn text-xs p-1 rounded">CLOSE</button>
</div>
<div id="crypto-all-shifts-results" class="text-xs overflow-y-auto max-h-40 p-1 crypto-result rounded">
Calculating shifts...
</div>
</div>
<!-- Cipher Controls -->
<div class="crypto-panel p-3 mb-3 rounded">
<div class="flex justify-between items-center mb-1">
<h2 class="text-sm sm:text-base font-bold crypto-title">> CIPHER TOOLS</h2>
<div class="flex gap-1">
<select id="crypto-cipher-select" class="text-xs p-1 rounded crypto-select">
<option value="caesar">CAESAR</option>
<option value="vigenere">VIGENÈRE</option>
<option value="atbash">ATBASH</option>
<option value="base64">BASE64</option>
<option value="binary">BINARY</option>
<option value="morse">MORSE</option>
</select>
</div>
</div>
<div id="crypto-cipher-params" class="mb-2">
<!-- Vigenere params -->
<div id="crypto-vigenere-params" class="hidden">
<input id="crypto-vigenere-key" type="text" placeholder="Enter key" class="w-full text-xs p-1 rounded crypto-input">
</div>
<!-- Morse params -->
<div id="crypto-morse-params" class="hidden">
<div class="text-xs">Separator: <input id="crypto-morse-separator" type="text" value=" " class="crypto-input w-12 text-center"></div>
</div>
</div>
<div class="flex gap-1">
<button id="crypto-encode-btn" class="crypto-btn text-xs p-1 rounded flex-1">ENCODE</button>
<button id="crypto-decode-btn" class="crypto-btn text-xs p-1 rounded flex-1">DECODE</button>
</div>
</div>
<!-- Analysis Results -->
<div class="crypto-panel p-3 mb-3 rounded">
<div class="flex justify-between items-center mb-1">
<h2 class="text-sm sm:text-base font-bold crypto-title">> ANALYSIS</h2>
<div>
<button id="crypto-toggle-freq" class="crypto-btn text-xs p-1 rounded">FREQ</button>
</div>
</div>
<div id="crypto-frequency-panel" class="hidden">
<canvas id="crypto-frequency-chart" height="100" class="w-full mb-1"></canvas>
<div class="text-xs text-terminal-green opacity-80" id="crypto-ic-value">Index of Coincidence: —</div>
</div>
<div id="crypto-analysis-results" class="text-xs">
<div class="p-1 crypto-result rounded">
Run analysis on input text to detect patterns...
</div>
</div>
</div>
<!-- Results -->
<div class="crypto-panel p-3 rounded textarea-container">
<div class="flex justify-between items-center mb-1">
<h2 class="text-sm sm:text-base font-bold crypto-title">> RESULTS</h2>
<div class="flex gap-1">
<button id="crypto-copy-result" class="crypto-btn text-xs p-1 rounded">COPY</button>
<button id="crypto-use-result" class="crypto-btn text-xs p-1 rounded">USE</button>
</div>
</div>
<textarea id="crypto-result-text" class="w-full h-16 p-1 text-xs sm:text-sm rounded crypto-textarea" readonly></textarea>
<button id="crypto-result-copy-btn" class="copy-btn" title="Copy result">Copy</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Set dark mode by default
document.documentElement.classList.add('dark');
// Still listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
if (event.matches) {
document.documentElement.classList.add('dark');
} else {
// Keep dark mode even if system preference changes to light
// document.documentElement.classList.remove('dark');
}
});
// Tab switching functionality
document.getElementById('tab-transform').addEventListener('click', () => {
document.getElementById('content-transform').classList.remove('hidden');
document.getElementById('content-encrypt').classList.add('hidden');
document.getElementById('content-pattern').classList.add('hidden');
document.getElementById('content-crypto').classList.add('hidden');
document.getElementById('tab-transform').classList.add('border-primary');
document.getElementById('tab-transform').classList.add('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-encrypt').classList.remove('border-primary');
document.getElementById('tab-encrypt').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-encrypt').classList.add('border-transparent');
document.getElementById('tab-pattern').classList.remove('border-primary');
document.getElementById('tab-pattern').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-pattern').classList.add('border-transparent');
document.getElementById('tab-crypto').classList.remove('border-primary');
document.getElementById('tab-crypto').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-crypto').classList.add('border-transparent');
});
document.getElementById('tab-encrypt').addEventListener('click', () => {
document.getElementById('content-transform').classList.add('hidden');
document.getElementById('content-encrypt').classList.remove('hidden');
document.getElementById('content-pattern').classList.add('hidden');
document.getElementById('content-crypto').classList.add('hidden');
document.getElementById('tab-encrypt').classList.add('border-primary');
document.getElementById('tab-encrypt').classList.add('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-transform').classList.remove('border-primary');
document.getElementById('tab-transform').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-transform').classList.add('border-transparent');
document.getElementById('tab-pattern').classList.remove('border-primary');
document.getElementById('tab-pattern').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-pattern').classList.add('border-transparent');
document.getElementById('tab-crypto').classList.remove('border-primary');
document.getElementById('tab-crypto').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-crypto').classList.add('border-transparent');
});
document.getElementById('tab-pattern').addEventListener('click', () => {
document.getElementById('content-transform').classList.add('hidden');
document.getElementById('content-encrypt').classList.add('hidden');
document.getElementById('content-pattern').classList.remove('hidden');
document.getElementById('content-crypto').classList.add('hidden');
document.getElementById('tab-pattern').classList.add('border-primary');
document.getElementById('tab-pattern').classList.add('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-transform').classList.remove('border-primary');
document.getElementById('tab-transform').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-transform').classList.add('border-transparent');
document.getElementById('tab-encrypt').classList.remove('border-primary');
document.getElementById('tab-encrypt').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-encrypt').classList.add('border-transparent');
document.getElementById('tab-crypto').classList.remove('border-primary');
document.getElementById('tab-crypto').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-crypto').classList.add('border-transparent');
});
document.getElementById('tab-crypto').addEventListener('click', () => {
document.getElementById('content-transform').classList.add('hidden');
document.getElementById('content-encrypt').classList.add('hidden');
document.getElementById('content-pattern').classList.add('hidden');
document.getElementById('content-crypto').classList.remove('hidden');
document.getElementById('tab-crypto').classList.add('border-primary');
document.getElementById('tab-crypto').classList.add('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-transform').classList.remove('border-primary');
document.getElementById('tab-transform').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-transform').classList.add('border-transparent');
document.getElementById('tab-encrypt').classList.remove('border-primary');
document.getElementById('tab-encrypt').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-encrypt').classList.add('border-transparent');
document.getElementById('tab-pattern').classList.remove('border-primary');
document.getElementById('tab-pattern').classList.remove('bg-gray-100', 'dark:bg-gray-800');
document.getElementById('tab-pattern').classList.add('border-transparent');
});
// Transformation methods
function method1(text) {
let result = '';
for (let i = 0; i < text.length; i += 6) {
// Skip 3, take 3
if (i + 3 < text.length) {
const chunk = text.substring(i + 3, Math.min(i + 6, text.length));
result += chunk;
}
}
return result;
}
function method2(text) {
let result = '';
for (let i = 0; i < text.length; i++) {
// Skip positions 4,5,6 then 10,11,12 etc.
const pos = i + 1; // Convert to 1-based for easier calculation
const inSkipRange =
(pos % 12 >= 4 && pos % 12 <= 6) || // 4,5,6
(pos % 12 >= 10 && pos % 12 <= 0); // 10,11,12 (mod is weird here, so we use 0 for 12)
if (!inSkipRange) {
result += text[i];
}
}
return result;
}
function method3(text) {
let result = '';
for (let i = 0; i < text.length; i += 6) {
// First 3 with spaces
for (let j = i; j < Math.min(i + 3, text.length); j++) {
result += text[j] + ' ';
}
// Next 3 clumped
if (i + 3 < text.length) {
const chunk = text.substring(i + 3, Math.min(i + 6, text.length));
result += chunk;
}
if (i + 6 < text.length) {
result += ' ';
}
}
return result.trim();
}
function findPunctuationIndex(text) {
const punctuation = /[.,\/#!$%\^&\*;:{}=\-_`~()]/;
for (let i = 0; i < text.length; i++) {
if (punctuation.test(text[i])) {
return i;
}
}
return -1; // No punctuation found
}
function method4(text) {
const punctIndex = findPunctuationIndex(text);
if (punctIndex === -1) {
return method1(text);
}
const beforePunct = text.substring(0, punctIndex);
const punc = text[punctIndex];
const afterPunct = text.substring(punctIndex + 1);
const reversedAfter = afterPunct.split('').reverse().join('');
return method1(beforePunct) + punc + method1(reversedAfter);
}
function method5(text) {
const punctIndex = findPunctuationIndex(text);
if (punctIndex === -1) {
return method2(text);
}
const beforePunct = text.substring(0, punctIndex);
const punc = text[punctIndex];
const afterPunct = text.substring(punctIndex + 1);
const reversedAfter = afterPunct.split('').reverse().join('');
return method2(beforePunct) + punc + method2(reversedAfter);
}
function method6(text) {
const punctIndex = findPunctuationIndex(text);
if (punctIndex === -1) {
return method3(text);
}
const beforePunct = text.substring(0, punctIndex);
const punc = text[punctIndex];
const afterPunct = text.substring(punctIndex + 1);
const reversedAfter = afterPunct.split('').reverse().join('');
return method3(beforePunct) + punc + method3(reversedAfter);
}
function method7(text) {
const customChar = document.getElementById('custom-char').value.trim();
// Use custom character if provided, otherwise use vowels
const vowels = customChar ? [customChar] : ['a', 'e', 'i', 'o', 'u'];
let result = '';
for (let i = 0; i < text.length; i++) {
result += text[i];
// Check if current character is a consonant
if (/[bcdfghjklmnpqrstvwxyz]/i.test(text[i])) {
// Count vowels after this consonant
let vowelCount = 0;
for (let j = i + 1; j < Math.min(i + 3, text.length); j++) {
if (/[aeiou]/i.test(text[j])) {
vowelCount++;
}
}
// Add missing vowels or custom character
for (let k = 0; k < 2 - vowelCount; k++) {
result += vowels[(i + k) % vowels.length]; // Cycle through vowels or use custom char
}
}
}
return result;
}
function method8(text) {
const punctIndex = findPunctuationIndex(text);
if (punctIndex === -1) {
return method7(text);
}
const beforePunct = text.substring(0, punctIndex);
const punc = text[punctIndex];
const afterPunct = text.substring(punctIndex + 1);
const reversedAfter = afterPunct.split('').reverse().join('');
return method7(beforePunct) + punc + method7(reversedAfter);
}
// Text manipulation methods
function reverseText(text) {
return text.split('').reverse().join('');
}
function mirrorText(text) {
// Create a mirrored version (original + reversed)
return text + reverseText(text);
}
function upsideDownText(text) {
// Map for upside-down characters
const flipMap = {
'a': 'ɐ',
'b': 'q',
'c': 'ɔ',
'd': 'p',
'e': 'ǝ',
'f': 'ɟ',
'g': 'ƃ',
'h': 'ɥ',
'i': 'ᴉ',
'j': 'ɾ',
'k': 'ʞ',
'l': 'l',
'm': 'ɯ',
'n': 'u',
'o': 'o',
'p': 'd',
'q': 'b',
'r': 'ɹ',
's': 's',
't': 'ʇ',
'u': 'n',
'v': 'ʌ',
'w': 'ʍ',
'x': 'x',
'y': 'ʎ',
'z': 'z',
'A': '∀',
'B': 'B',
'C': 'Ɔ',
'D': 'D',
'E': 'Ǝ',
'F': 'Ⅎ',
'G': 'פ',
'H': 'H',
'I': 'I',
'J': 'ſ',
'K': 'K',
'L': '˥',
'M': 'W',
'N': 'N',
'O': 'O',
'P': 'Ԁ',
'Q': 'Q',
'R': 'R',
'S': 'S',
'T': '┴',
'U': '∩',
'V': 'Λ',
'W': 'M',
'X': 'X',
'Y': '⅄',
'Z': 'Z',
'0': '0',
'1': 'Ɩ',
'2': 'ᄅ',
'3': 'Ɛ',
'4': 'ㄣ',
'5': 'ϛ',
'6': '9',
'7': 'ㄥ',
'8': '8',
'9': '6',
'.': '˙',
',': '\'',
'?': '¿',
'!': '¡',
'"': ',,',
'\'': ',',
'(': ')',
')': '(',
'[': ']',
']': '[',
'{': '}',
'}': '{',
'<': '>',
'>': '<',
'&': '⅋',
'_': '‾',
'^': 'v',
'/': '\\',
'\\': '/',
' ': ' '
};
// Reverse the text and flip each character
return text.split('').reverse().map(char =>
flipMap[char] !== undefined ? flipMap[char] : char
).join('');
}
// Insert "ay" and "ya" alternately
function insertAyYa(text, startWithYa = false, appendLast = true) {
if (!text || text.trim() === '') return text;
const words = text.split(' ');
let result = '';
let useAy = !startWithYa; // Start with ay or ya based on parameter
for (let i = 0; i < words.length; i++) {
if (words[i].trim() === '') continue; // Skip empty words
// Add appropriate prefix before each word
if (i === 0) {
result += (useAy ? " ay " : " ya ") + words[i];
} else {
// Alternate between ya and ay
useAy = !useAy; // Toggle for next time
result += (useAy ? " ay " : " ya ") + words[i];
}
}
// Append the next alternating pattern after the last word
if (appendLast) {
useAy = !useAy; // Toggle once more for the final append
result += (useAy ? " ay" : " ya");
}
return result.trim();
}
// Vowel phoneme conversion functions
function toShortVowelPhonemes(text) {
return text.replace(/[aeiouAEIOU]/g, function(match) {
switch (match.toLowerCase()) {
case 'a':
return match === 'a' ? 'æ' : 'Æ';
case 'e':
return match === 'e' ? 'ɛ' : 'Ɛ';
case 'i':
return match === 'i' ? 'ɪ' : 'Ɪ';
case 'o':
return match === 'o' ? 'ɒ' : 'Ɒ';
case 'u':
return match === 'u' ? 'ʌ' : 'Ʌ';
default:
return match;
}
});
}
function toLongVowelPhonemes(text) {
return text.replace(/[aeiouAEIOU]/g, function(match) {
switch (match.toLowerCase()) {
case 'a':
return match === 'a' ? 'eɪ' : 'EƖ';
case 'e':
return match === 'e' ? 'iː' : 'Iː';
case 'i':
return match === 'i' ? 'aɪ' : 'AƖ';
case 'o':
return match === 'o' ? 'əʊ' : 'ƏƱ';
case 'u':
return match === 'u' ? 'uː' : 'Uː';
default:
return match;
}
});
}
// EasyRead phoneme conversion
// EasyRead phoneme conversion functions - split into separate short and long
function toEasyReadPhonemesLong(text) {
return text.replace(/[aeiouAEIOU]/g, function(match) {
// Always use long vowel sounds
switch (match.toLowerCase()) {
case 'a':
return match === 'a' ? 'ay' : 'AY';
case 'e':
return match === 'e' ? 'ee' : 'EE';
case 'i':
return match === 'i' ? 'igh' : 'IGH';
case 'o':
return match === 'o' ? 'oua' : 'OUA';
case 'u':
return match === 'u' ? 'yuw' : 'YUW';
default:
return match;
}
});
}
function toEasyReadPhonemesShort(text) {
return text.replace(/[aeiouAEIOU]/g, function(match) {
// Always use short vowel sounds
switch (match.toLowerCase()) {
case 'a':
return match === 'a' ? 'ah' : 'AH';
case 'e':
return match === 'e' ? 'eh' : 'EH';
case 'i':
return match === 'i' ? 'ih' : 'IH';
case 'o':
return match === 'o' ? 'aw' : 'AW';
case 'u':
return match === 'u' ? 'uh' : 'UH';
default:
return match;
}
});
}
function toEasyReadPhonemes(text) {
return text.replace(/[aeiouAEIOU]/g, function(match) {
// Check if it's likely a long vowel (simplified heuristic)
const isLongVowel = /[aeiou][^aeiou]e$/i.test(match) ||
/[aeiou]{2}/i.test(match);
if (isLongVowel) {
// Long vowel sounds
switch (match.toLowerCase()) {
case 'a':
return match === 'a' ? 'ay' : 'AY';
case 'e':
return match === 'e' ? 'ee' : 'EE';
case 'i':
return match === 'i' ? 'igh' : 'IGH';
case 'o':
return match === 'o' ? 'oua' : 'OUA';
case 'u':
return match === 'u' ? 'yuw' : 'YUW';
default:
return match;
}
} else {
// Short vowel sounds
switch (match.toLowerCase()) {
case 'a':
return match === 'a' ? 'ah' : 'AH';
case 'e':
return match === 'e' ? 'eh' : 'EH';
case 'i':
return match === 'i' ? 'ih' : 'IH';
case 'o':
return match === 'o' ? 'aw' : 'AW';
case 'u':
return match === 'u' ? 'uh' : 'UH';
default:
return match;
}
}
});
}
// Custom character mapping
const customCharMap = {
'a': 'e',
'b': 'q',
'c': 'o',
'd': 'p',
'e': 'a',
'f': 'j',
'g': 'b',
'h': 'y',
'i': 'l',
'j': 'f',
'k': 'x',
'l': 'l',
'm': 'w',
'n': 'u',
'o': 'o',
'p': 'd',
'q': 'b',
'r': 'u',
's': 's',
't': 'f', // Changed 'r' from 'i' to 'u'
'u': 'n',
'v': 'n',
'w': 'm',
'x': 'x',
'y': 'h',
'z': 'z',
'A': 'V',
'B': 'EI',
'C': 'O',
'D': 'CI',
'E': '3',
'F': 't',
'G': 'O',
'H': 'H',
'I': 'I',
'J': 'F',
'K': 'X',
'L': 'T',
'M': 'W',
'N': 'N',
'O': 'O',
'P': 'd',
'Q': 'O',
'R': 'Y',
'S': 'S',
'T': 'L',
'U': 'A',
'V': 'A',
'W': 'M',
'X': 'X',
'Y': 'h',
'Z': 'Z',
'1': 'l',
'2': 'Z',
'3': 'E',
'4': 'x',
'5': 'S',
'6': 'g',
'7': 'L',
'8': 'B',
'9': 'G',
'0': '0',
' ': ' '
};
// Create reverse map for decryption
const reverseCustomCharMap = {};
for (const key in customCharMap) {
const value = customCharMap[key];
// Some characters map to the same value, we'll just use the first one we encounter
if (!reverseCustomCharMap[value]) {
reverseCustomCharMap[value] = key;
}
}
// Special cases for decryption
reverseCustomCharMap['b'] = 'b'; // 'c' next to 'l' becomes 'b', but we default to 'b'
reverseCustomCharMap['u'] = 'r'; // 'r' next to 'l' or 'i' becomes 'u'
function customMapText(text) {
let result = '';
for (let i = 0; i < text.length; i++) {
const char = text[i];
const prevChar = i > 0 ? text[i - 1] : '';
const nextChar = i < text.length - 1 ? text[i + 1] : '';
if (char === 'c' && (nextChar === 'l' || prevChar === 'l')) {
// Special case: c next to l becomes b instead of o
result += 'b';
} else if (char === 'r' && (nextChar === 'l' || nextChar === 'i' || prevChar === 'l' || prevChar === 'i')) {
// Special case: r next to l or i becomes u instead of i
result += 'u';
} else if (customCharMap[char] !== undefined) {
result += customCharMap[char];
} else {
result += char; // Keep characters not in the mapping
}
}
return result;
}
function decryptText(text) {
let result = '';
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (reverseCustomCharMap[char] !== undefined) {
result += reverseCustomCharMap[char];
} else {
result += char; // Keep characters not in the mapping
}
}
return result;
}
function generateMappingTable() {
let result = '<table class="w-full text-left border-collapse">';
result += '<thead><tr><th class="py-2 px-3 bg-gray-200 dark:bg-gray-600">Original</th><th class="py-2 px-3 bg-gray-200 dark:bg-gray-600">Mapped</th></tr></thead>';
result += '<tbody>';
// Add all the mappings in the table
const groups = [{
start: 'a',
end: 'z',
title: 'Lowercase Letters'
},
{
start: 'A',
end: 'Z',
title: 'Uppercase Letters'
},
{
start: '0',
end: '9',
title: 'Numbers'
}
];
for (const group of groups) {
result += `<tr><td colspan="2" class="py-2 px-3 font-bold bg-gray-100 dark:bg-gray-700">${group.title}</td></tr>`;
for (let charCode = group.start.charCodeAt(0); charCode <= group.end.charCodeAt(0); charCode++) {
const char = String.fromCharCode(charCode);
let mappedChar = customCharMap[char] || char;
// Add special cases as notes
let notes = '';
if (char === 'c') {
notes = ' (becomes "b" if next to "l")';
} else if (char === 'r') {
notes = ' (becomes "u" if next to "l" or "i")';
}
result += `<tr>
<td class="py-1 px-3 border-t border-gray-300 dark:border-gray-600">${char}</td>
<td class="py-1 px-3 border-t border-gray-300 dark:border-gray-600">${mappedChar}${notes}</td>
</tr>`;
}
}
result += '</tbody></table>';
return result;
}
function generateReverseMappingTable() {
let result = '<table class="w-full text-left border-collapse">';
result += '<thead><tr><th class="py-2 px-3 bg-gray-200 dark:bg-gray-600">Encrypted</th><th class="py-2 px-3 bg-gray-200 dark:bg-gray-600">Decrypted</th></tr></thead>';
result += '<tbody>';
// Group by character type
const groups = [{
title: 'Letters & Symbols',
chars: Object.keys(reverseCustomCharMap).filter(c => /[a-zA-Z]/.test(c) || !/[0-9]/.test(c))
},
{
title: 'Numbers',
chars: Object.keys(reverseCustomCharMap).filter(c => /[0-9]/.test(c))
}
];
for (const group of groups) {
result += `<tr><td colspan="2" class="py-2 px-3 font-bold bg-gray-100 dark:bg-gray-700">${group.title}</td></tr>`;
// Sort characters for better readability
const sortedChars = group.chars.sort();
for (const char of sortedChars) {
const originalChar = reverseCustomCharMap[char];
// Add special cases notes
let notes = '';
if (char === 'b' && originalChar === 'b') {
notes = ' (could be "c" next to "l")';
} else if (char === 'u' && originalChar === 'r') {
notes = ' (likely "r" next to "l" or "i")';
}
result += `<tr>
<td class="py-1 px-3 border-t border-gray-300 dark:border-gray-600">${char}</td>
<td class="py-1 px-3 border-t border-gray-300 dark:border-gray-600">${originalChar}${notes}</td>
</tr>`;
}
}
result += '</tbody></table>';
return result;
}
// New clumping methods
function clump2x2(text) {
let result = '';
for (let i = 0; i < text.length; i += 4) {
const chunk = text.substring(i, Math.min(i + 4, text.length));
if (chunk.length > 0) {
// Split into 2x2
if (chunk.length >= 2) {
result += chunk.substring(0, 2);
result += ' ';
}
if (chunk.length > 2) {
result += chunk.substring(2);
result += ' ';
}
}
}
return result.trim();
}
// Word-based transformation methods
function wordMethod1(text) {
const words = text.split(/\s+/);
let result = [];
for (let i = 0; i < words.length; i += 6) {
// Skip first 3 words, take next 3
if (i + 3 < words.length) {
const chunk = words.slice(i + 3, Math.min(i + 6, words.length));
result = result.concat(chunk);
}
}
return result.join(' ');
}
function wordMethod2(text) {
const words = text.split(/\s+/);
let result = [];
for (let i = 0; i < words.length; i++) {
// Skip positions 4,5,6 then 10,11,12 etc.
const pos = i + 1; // Convert to 1-based for easier calculation
const inSkipRange =
(pos % 12 >= 4 && pos % 12 <= 6) || // 4,5,6
(pos % 12 >= 10 && pos % 12 <= 0); // 10,11,12
if (!inSkipRange) {
result.push(words[i]);
}
}
return result.join(' ');
}
function wordMethod3(text) {
const words = text.split(/\s+/);
let result = '';
for (let i = 0; i < words.length; i += 6) {
// First 3 words with extra spaces
for (let j = i; j < Math.min(i + 3, words.length); j++) {
result += words[j] + ' '; // Using multiple spaces to separate
}
// Next 3 clumped
if (i + 3 < words.length) {
const chunk = words.slice(i + 3, Math.min(i + 6, words.length));
result += chunk.join('');
}
if (i + 6 < words.length) {
result += ' ';
}
}
return result.trim();
}
function findPunctuationInSentence(text) {
// Find first punctuation that ends a sentence
const matches = text.match(/[.!?]/);
if (matches && matches.index !== undefined) {
return matches.index;
}
return -1;
}
function wordMethod4(text) {
const punctIndex = findPunctuationInSentence(text);
if (punctIndex === -1) {
return wordMethod1(text);
}
const beforePunct = text.substring(0, punctIndex);
const punc = text[punctIndex];
const afterPunct = text.substring(punctIndex + 1);
const reversedWordsAfter = afterPunct.split(/\s+/).reverse().join(' ');
return wordMethod1(beforePunct) + punc + wordMethod1(reversedWordsAfter);
}
function wordMethod5(text) {
const punctIndex = findPunctuationInSentence(text);
if (punctIndex === -1) {
return wordMethod2(text);
}
const beforePunct = text.substring(0, punctIndex);
const punc = text[punctIndex];
const afterPunct = text.substring(punctIndex + 1);
const reversedWordsAfter = afterPunct.split(/\s+/).reverse().join(' ');
return wordMethod2(beforePunct) + punc + wordMethod2(reversedWordsAfter);
}
function wordMethod6(text) {
const punctIndex = findPunctuationInSentence(text);
if (punctIndex === -1) {
return wordMethod3(text);
}
const beforePunct = text.substring(0, punctIndex);
const punc = text[punctIndex];
const afterPunct = text.substring(punctIndex + 1);
const reversedWordsAfter = afterPunct.split(/\s+/).reverse().join(' ');
return wordMethod3(beforePunct) + punc + wordMethod3(reversedWordsAfter);
}
function wordClump2x2(text) {
const words = text.split(/\s+/);
let result = [];
for (let i = 0; i < words.length; i += 4) {
// Handle 2x2 word groupings
if (i + 1 < words.length) {
result.push(words[i] + " " + words[i + 1]);
} else if (i < words.length) {
result.push(words[i]);
}
if (i + 3 < words.length) {
result.push(words[i + 2] + " " + words[i + 3]);
} else if (i + 2 < words.length) {
result.push(words[i + 2]);
}
}
return result.join(" | ");
}
function wordClump1x1x1x3(text) {
const words = text.split(/\s+/);
let result = [];
for (let i = 0; i < words.length; i += 6) {
// First 3 words individually
for (let j = i; j < Math.min(i + 3, words.length); j++) {
result.push(words[j]);
}
// Next 3 words clumped together
if (i + 3 < words.length) {
const clumpedWords = words.slice(i + 3, Math.min(i + 6, words.length)).join("-");
result.push(clumpedWords);
}
}
return result.join(" | ");
}
function wordClump4x2(text) {
const words = text.split(/\s+/);
let result = [];
for (let i = 0; i < words.length; i += 6) {
// First 4 words clumped
if (i + 3 < words.length) {
result.push(words.slice(i, i + 4).join("-"));
} else if (i < words.length) {
result.push(words.slice(i).join("-"));
}
// Next 2 words clumped
if (i + 5 < words.length) {
result.push(words[i + 4] + "-" + words[i + 5]);
} else if (i + 4 < words.length) {
result.push(words[i + 4]);
}
}
return result.join(" | ");
}
function clump1x1x1x3(text) {
let result = '';
for (let i = 0; i < text.length; i += 6) {
const chunk = text.substring(i, Math.min(i + 6, text.length));
// Add individual letters with spaces
for (let j = 0; j < Math.min(3, chunk.length); j++) {
result += chunk[j] + ' ';
}
// Add the 3-letter clump if there are enough characters
if (chunk.length > 3) {
result += chunk.substring(3) + ' ';
}
}
return result.trim();
}
function clump4x2(text) {
let result = '';
for (let i = 0; i < text.length; i += 6) {
const chunk = text.substring(i, Math.min(i + 6, text.length));
// Add 4-letter clump
if (chunk.length >= 4) {
result += chunk.substring(0, 4);
result += ' ';
// Add 2-letter clump if there are enough characters
if (chunk.length > 4) {
result += chunk.substring(4);
result += ' ';
}
} else {
// If less than 4 letters, just add what we have
result += chunk + ' ';
}
}
return result.trim();
}
// Store original text for restoration
let originalText = '';
// Text manipulation button event listeners
document.getElementById('reverse-btn').addEventListener('click', () => {
const textarea = document.getElementById('sentence');
if (!originalText) {
originalText = textarea.value;
}
textarea.value = reverseText(textarea.value);
});
document.getElementById('mirror-btn').addEventListener('click', () => {
const textarea = document.getElementById('sentence');
if (!originalText) {
originalText = textarea.value;
}
textarea.value = mirrorText(textarea.value);
});
document.getElementById('upside-down-btn').addEventListener('click', () => {
const textarea = document.getElementById('sentence');
if (!originalText) {
originalText = textarea.value;
}
textarea.value = upsideDownText(textarea.value);
});
document.getElementById('restore-btn').addEventListener('click', () => {
const textarea = document.getElementById('sentence');
if (originalText) {
textarea.value = originalText;
document.getElementById('result').textContent = '';
}
});
document.getElementById('clear-result-btn').addEventListener('click', () => {
document.getElementById('result').textContent = '';
});
document.getElementById('remove-spaces-btn').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent;
if (text.trim() === '') return;
resultElement.textContent = text.replace(/\s+/g, '');
});
// Create a dropdown menu for the ay/ya insertion options
const ayYaOptionsContainer = document.createElement('div');
ayYaOptionsContainer.id = 'ay-ya-options';
ayYaOptionsContainer.className = 'absolute mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden';
ayYaOptionsContainer.innerHTML = `
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">Options:</div>
<button id="ay-ya-option-1" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ay"</button>
<button id="ay-ya-option-2" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ya"</button>
<button id="ay-ya-option-3" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ay" + append final</button>
<button id="ay-ya-option-4" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ya" + append final</button>
`;
// Insert after the button
const ayYaBtn = document.getElementById('insert-ay-ya-btn');
ayYaBtn.parentNode.style.position = 'relative';
ayYaBtn.parentNode.appendChild(ayYaOptionsContainer);
// Show options when clicking the button
document.getElementById('insert-ay-ya-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('ay-ya-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// Close the menu when clicking elsewhere
document.addEventListener('click', () => {
// Close ay/ya option menus
const ayYaOptions = [
'ay-ya-options',
'encrypt-ay-ya-options',
'pattern-ay-ya-options'
];
// Close EasyRead option menus
const easyReadOptions = [
'easyread-options',
'encrypt-easyread-options',
'pattern-easyread-options'
];
// Close numbers and punctuation option menus
const conversionOptions = [
'numbers-options',
'punct-options'
];
// Close all dropdown menus
[...ayYaOptions, ...easyReadOptions, ...conversionOptions].forEach(menuId => {
const menu = document.getElementById(menuId);
if (menu && !menu.classList.contains('hidden')) {
menu.classList.add('hidden');
}
});
});
// Option handlers
document.getElementById('ay-ya-option-1').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, false, false);
document.getElementById('ay-ya-options').classList.add('hidden');
});
document.getElementById('ay-ya-option-2').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, true, false);
document.getElementById('ay-ya-options').classList.add('hidden');
});
document.getElementById('ay-ya-option-3').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, false, true);
document.getElementById('ay-ya-options').classList.add('hidden');
});
document.getElementById('ay-ya-option-4').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, true, true);
document.getElementById('ay-ya-options').classList.add('hidden');
});
document.getElementById('append-reverse-btn').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent;
if (text.trim() === '') return;
const reversed = reverseText(text);
resultElement.textContent = text + ' ' + reversed;
});
document.getElementById('short-vowels-btn').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = toShortVowelPhonemes(text);
});
// Number-to-text dropdown handling
document.getElementById('numbers-to-text-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('numbers-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// Forward number conversion (123 -> one hundred twenty-three)
document.getElementById('numbers-forward').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = convertNumbersToText(text);
document.getElementById('numbers-options').classList.add('hidden');
});
// Reverse number conversion (one hundred twenty-three -> 123)
document.getElementById('numbers-reverse').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = convertTextToNumbers(text);
document.getElementById('numbers-options').classList.add('hidden');
});
// Punctuation-to-text dropdown handling
document.getElementById('punct-to-text-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('punct-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// Forward punctuation conversion (, -> comma)
document.getElementById('punct-forward').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = convertPunctuationToText(text);
document.getElementById('punct-options').classList.add('hidden');
});
// Reverse punctuation conversion (comma -> ,)
document.getElementById('punct-reverse').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = convertTextToPunctuation(text);
document.getElementById('punct-options').classList.add('hidden');
});
document.getElementById('long-vowels-btn').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = toLongVowelPhonemes(text);
});
// Show EasyRead options menu
document.getElementById('custom-phonemes-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('easyread-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// EasyRead option handlers
document.getElementById('easyread-auto').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemes(text);
document.getElementById('easyread-options').classList.add('hidden');
});
document.getElementById('easyread-short').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemesShort(text);
document.getElementById('easyread-options').classList.add('hidden');
});
document.getElementById('easyread-long').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemesLong(text);
document.getElementById('easyread-options').classList.add('hidden');
});
// Main transformation event listener
document.getElementById('transform-btn').addEventListener('click', () => {
const text = document.getElementById('sentence').value;
const letterTransformation = document.getElementById('transformation').value;
const wordTransformation = document.getElementById('word-transformation').value;
let result = '';
// Apply letter transformation first
switch (letterTransformation) {
case 'method1':
result = method1(text);
break;
case 'method2':
result = method2(text);
break;
case 'method3':
result = method3(text);
break;
case 'method4':
result = method4(text);
break;
case 'method5':
result = method5(text);
break;
case 'method6':
result = method6(text);
break;
case 'method7':
result = method7(text);
break;
case 'method8':
result = method8(text);
break;
default:
result = text;
}
// Then apply word transformation if selected
if (wordTransformation !== 'none') {
switch (wordTransformation) {
case 'word-method1':
result = wordMethod1(result);
break;
case 'word-method2':
result = wordMethod2(result);
break;
case 'word-method3':
result = wordMethod3(result);
break;
case 'word-method4':
result = wordMethod4(result);
break;
case 'word-method5':
result = wordMethod5(result);
break;
case 'word-method6':
result = wordMethod6(result);
break;
case 'word-clump2x2':
result = wordClump2x2(result);
break;
case 'word-clump1x1x1x3':
result = wordClump1x1x1x3(result);
break;
case 'word-clump4x2':
result = wordClump4x2(result);
break;
}
}
document.getElementById('result').textContent = result;
});
// New clumping button event listeners
document.getElementById('clump2x2-btn').addEventListener('click', () => {
const text = document.getElementById('result').textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
const result = clump2x2(text);
document.getElementById('result').textContent = result;
});
document.getElementById('clump1x1x1x3-btn').addEventListener('click', () => {
const text = document.getElementById('result').textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
const result = clump1x1x1x3(text);
document.getElementById('result').textContent = result;
});
document.getElementById('clump4x2-btn').addEventListener('click', () => {
const text = document.getElementById('result').textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
const result = clump4x2(text);
document.getElementById('result').textContent = result;
});
// Custom mapping button event listeners
document.getElementById('custom-map-btn').addEventListener('click', () => {
const text = document.getElementById('result').textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
const result = customMapText(text);
document.getElementById('result').textContent = result;
});
document.getElementById('show-mapping-btn').addEventListener('click', () => {
const mappingDisplay = document.getElementById('mapping-display');
if (mappingDisplay.classList.contains('hidden')) {
// Show mapping table
mappingDisplay.innerHTML = generateMappingTable();
mappingDisplay.classList.remove('hidden');
document.getElementById('show-mapping-btn').textContent = 'Hide Mapping Table';
} else {
// Hide mapping table
mappingDisplay.innerHTML = '';
mappingDisplay.classList.add('hidden');
document.getElementById('show-mapping-btn').textContent = 'Show Mapping Table';
}
});
// Encryption tab event listeners
const encryptTextarea = document.getElementById('encrypt-text');
// Real-time translation as user types
encryptTextarea.addEventListener('input', () => {
updateEncryptResult();
});
// Add event listener for the reverse checkbox to update results in real-time
document.getElementById('reverse-result-checkbox').addEventListener('change', () => {
updateEncryptResult();
});
function updateEncryptResult() {
const text = encryptTextarea.value;
if (text.trim() === '') {
document.getElementById('encrypt-result').textContent = '';
return;
}
// By default, we'll encrypt
let result = customMapText(text);
// If the reverse checkbox is checked, reverse the result
if (document.getElementById('reverse-result-checkbox').checked) {
result = reverseText(result);
}
document.getElementById('encrypt-result').textContent = result;
}
// Encryption tab transformation buttons
// Encryption tab ay/ya button dropdown
const encryptAyYaOptionsContainer = document.createElement('div');
encryptAyYaOptionsContainer.id = 'encrypt-ay-ya-options';
encryptAyYaOptionsContainer.className = 'absolute mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden';
encryptAyYaOptionsContainer.innerHTML = `
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">Options:</div>
<button id="encrypt-ay-ya-option-1" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ay"</button>
<button id="encrypt-ay-ya-option-2" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ya"</button>
<button id="encrypt-ay-ya-option-3" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ay" + append final</button>
<button id="encrypt-ay-ya-option-4" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ya" + append final</button>
`;
// Insert after the button
const encryptAyYaBtn = document.getElementById('encrypt-ay-ya-btn');
encryptAyYaBtn.parentNode.style.position = 'relative';
encryptAyYaBtn.parentNode.appendChild(encryptAyYaOptionsContainer);
// Show options when clicking the button
document.getElementById('encrypt-ay-ya-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('encrypt-ay-ya-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// Option handlers for encryption tab
document.getElementById('encrypt-ay-ya-option-1').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, false, false);
document.getElementById('encrypt-ay-ya-options').classList.add('hidden');
});
document.getElementById('encrypt-ay-ya-option-2').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, true, false);
document.getElementById('encrypt-ay-ya-options').classList.add('hidden');
});
document.getElementById('encrypt-ay-ya-option-3').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, false, true);
document.getElementById('encrypt-ay-ya-options').classList.add('hidden');
});
document.getElementById('encrypt-ay-ya-option-4').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, true, true);
document.getElementById('encrypt-ay-ya-options').classList.add('hidden');
});
document.getElementById('encrypt-remove-spaces-btn').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent;
if (text.trim() === '') return;
resultElement.textContent = text.replace(/\s+/g, '');
});
// Clumping buttons for encryption tab
document.getElementById('encrypt-clump2x2-btn').addEventListener('click', () => {
const text = document.getElementById('encrypt-result').textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
const result = clump2x2(text);
document.getElementById('encrypt-result').textContent = result;
});
document.getElementById('encrypt-clump1x1x1x3-btn').addEventListener('click', () => {
const text = document.getElementById('encrypt-result').textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
const result = clump1x1x1x3(text);
document.getElementById('encrypt-result').textContent = result;
});
document.getElementById('encrypt-clump4x2-btn').addEventListener('click', () => {
const text = document.getElementById('encrypt-result').textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
const result = clump4x2(text);
document.getElementById('encrypt-result').textContent = result;
});
// Phoneme buttons for encryption tab
document.getElementById('encrypt-short-vowels-btn').addEventListener('click', () => {
const text = document.getElementById('encrypt-result').textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
document.getElementById('encrypt-result').textContent = toShortVowelPhonemes(text);
});
document.getElementById('encrypt-long-vowels-btn').addEventListener('click', () => {
const text = document.getElementById('encrypt-result').textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
document.getElementById('encrypt-result').textContent = toLongVowelPhonemes(text);
});
// Show EasyRead options menu for encrypt tab
document.getElementById('encrypt-custom-phonemes-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('encrypt-easyread-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// EasyRead option handlers for encrypt tab
document.getElementById('encrypt-easyread-auto').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemes(text);
document.getElementById('encrypt-easyread-options').classList.add('hidden');
});
document.getElementById('encrypt-easyread-short').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemesShort(text);
document.getElementById('encrypt-easyread-options').classList.add('hidden');
});
document.getElementById('encrypt-easyread-long').addEventListener('click', () => {
const resultElement = document.getElementById('encrypt-result');
const text = resultElement.textContent || document.getElementById('encrypt-text').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemesLong(text);
document.getElementById('encrypt-easyread-options').classList.add('hidden');
});
document.getElementById('encrypt-btn').addEventListener('click', () => {
const text = encryptTextarea.value;
if (text.trim() === '') return;
let result = customMapText(text);
// Apply reversal if checkbox is checked
if (document.getElementById('reverse-result-checkbox').checked) {
result = reverseText(result);
}
document.getElementById('encrypt-result').textContent = result;
});
document.getElementById('decrypt-btn').addEventListener('click', () => {
const text = encryptTextarea.value;
if (text.trim() === '') return;
let result;
// If reverse is checked, we need to first reverse the input text and then decrypt
if (document.getElementById('reverse-result-checkbox').checked) {
const reversed = reverseText(text);
result = decryptText(reversed);
} else {
// Normal decryption without reversing
result = decryptText(text);
}
document.getElementById('encrypt-result').textContent = result;
});
document.getElementById('clear-encrypt-btn').addEventListener('click', () => {
encryptTextarea.value = '';
document.getElementById('encrypt-result').textContent = '';
});
document.getElementById('show-encrypt-mapping-btn').addEventListener('click', () => {
const mappingDisplay = document.getElementById('encrypt-mapping-display');
if (mappingDisplay.classList.contains('hidden')) {
// Show both encryption and decryption tables
let tableHtml = '<div class="mb-3"><h3 class="font-bold text-sm mb-1">Encryption Mapping</h3>';
tableHtml += generateMappingTable();
tableHtml += '</div><div><h3 class="font-bold text-sm mb-1">Decryption Mapping</h3>';
tableHtml += generateReverseMappingTable();
tableHtml += '</div>';
mappingDisplay.innerHTML = tableHtml;
mappingDisplay.classList.remove('hidden');
document.getElementById('show-encrypt-mapping-btn').innerHTML = 'Hide Character Mapping <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-1 transform rotate-180"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>';
} else {
// Hide mapping table
mappingDisplay.innerHTML = '';
mappingDisplay.classList.add('hidden');
document.getElementById('show-encrypt-mapping-btn').innerHTML = 'View Character Mapping <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-1"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>';
}
});
// Pattern Insertion Functions
function applyPattern1(text) {
// Pattern 1: G...R.A...A...R.D... (where dots represent counting out letters)
// G capitalized before the first letter,
// count three letters out as the letter R capitalized,
// count out one letter then the letter a capitalized,
// count out three letters, then the letter A capitalized,
// count out three letters, then the letter R capitalized,
// count out one letter then the capital D,
// count out three letters and repeat
if (!text || text.trim() === '') return text;
let result = '';
let position = 0;
while (position < text.length) {
// Add 'G' before first letter or after pattern cycle
result += 'G';
// Add the next character if available
if (position < text.length) {
result += text[position++];
} else break;
// Count 3 letters then add 'R'
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
result += 'R';
// Count 1 letter then add 'A'
if (position < text.length) {
result += text[position++];
} else break;
result += 'A';
// Count 3 letters then add 'A'
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
result += 'A';
// Count 3 letters then add 'R'
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
result += 'R';
// Count 1 letter then add 'D'
if (position < text.length) {
result += text[position++];
} else break;
result += 'D';
// Count 3 more letters for the cycle
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
}
return result;
}
function applyPattern2(text) {
// Pattern 2: G...I.A...A...C.D...
// Insert G capitalized before the first letter,
// count out three letters, then inserts in a capitalized I,
// count out one letter then another A capitalized,
// count out three letters then another A capitalized,
// count out three letters, then a C,
// count out one letter, then D and count out three letters and repeat
if (!text || text.trim() === '') return text;
let result = '';
let position = 0;
while (position < text.length) {
// Add 'G' before first letter or after pattern cycle
result += 'G';
// Add the next character if available
if (position < text.length) {
result += text[position++];
} else break;
// Count 3 letters then add 'I'
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
result += 'I';
// Count 1 letter then add 'A'
if (position < text.length) {
result += text[position++];
} else break;
result += 'A';
// Count 3 letters then add 'A'
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
result += 'A';
// Count 3 letters then add 'C'
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
result += 'C';
// Count 1 letter then add 'D'
if (position < text.length) {
result += text[position++];
} else break;
result += 'D';
// Count 3 more letters for the cycle
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
}
return result;
}
function applyPattern3(text) {
// Pattern 3: A...O.T.O.A...
// Insert letter A before the first letter,
// count out three letters, then an O capitalized,
// count out one letter then a T capitalized,
// then one O capitalized, then one letter,
// then A capitalized, then three letters and repeat
if (!text || text.trim() === '') return text;
let result = '';
let position = 0;
while (position < text.length) {
// Add 'A' before first letter or after pattern cycle
result += 'A';
// Add the next character if available
if (position < text.length) {
result += text[position++];
} else break;
// Count 3 letters then add 'O'
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
result += 'O';
// Count 1 letter then add 'T'
if (position < text.length) {
result += text[position++];
} else break;
result += 'T';
// Add 'O' (no counting)
result += 'O';
// Count 1 letter then add 'A'
if (position < text.length) {
result += text[position++];
} else break;
result += 'A';
// Count 3 more letters for the cycle
for (let i = 0; i < 3 && position < text.length; i++) {
result += text[position++];
}
}
return result;
}
// Pattern Tab Event Listeners
document.getElementById('pattern1-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = applyPattern1(text);
});
document.getElementById('pattern2-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = applyPattern2(text);
});
document.getElementById('pattern3-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = applyPattern3(text);
});
document.getElementById('pattern-mirror-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-result').textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = mirrorText(text);
});
document.getElementById('pattern-custom-map-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-result').textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = customMapText(text);
});
// Pattern tab ay/ya button dropdown
const patternAyYaOptionsContainer = document.createElement('div');
patternAyYaOptionsContainer.id = 'pattern-ay-ya-options';
patternAyYaOptionsContainer.className = 'absolute mt-1 bg-white dark:bg-gray-800 rounded-md shadow-lg p-1 z-10 border border-gray-300 dark:border-gray-600 hidden';
patternAyYaOptionsContainer.innerHTML = `
<div class="text-xs font-medium mb-1 text-gray-600 dark:text-gray-300 px-2">Options:</div>
<button id="pattern-ay-ya-option-1" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ay"</button>
<button id="pattern-ay-ya-option-2" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ya"</button>
<button id="pattern-ay-ya-option-3" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ay" + append final</button>
<button id="pattern-ay-ya-option-4" class="block w-full text-left px-3 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-700">Start with "ya" + append final</button>
`;
// Insert after the button
const patternAyYaBtn = document.getElementById('pattern-ay-ya-btn');
patternAyYaBtn.parentNode.style.position = 'relative';
patternAyYaBtn.parentNode.appendChild(patternAyYaOptionsContainer);
// Show options when clicking the button
document.getElementById('pattern-ay-ya-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('pattern-ay-ya-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// Option handlers for pattern tab
document.getElementById('pattern-ay-ya-option-1').addEventListener('click', () => {
const resultElement = document.getElementById('pattern-result');
const text = resultElement.textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, false, false);
document.getElementById('pattern-ay-ya-options').classList.add('hidden');
});
document.getElementById('pattern-ay-ya-option-2').addEventListener('click', () => {
const resultElement = document.getElementById('pattern-result');
const text = resultElement.textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, true, false);
document.getElementById('pattern-ay-ya-options').classList.add('hidden');
});
document.getElementById('pattern-ay-ya-option-3').addEventListener('click', () => {
const resultElement = document.getElementById('pattern-result');
const text = resultElement.textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, false, true);
document.getElementById('pattern-ay-ya-options').classList.add('hidden');
});
document.getElementById('pattern-ay-ya-option-4').addEventListener('click', () => {
const resultElement = document.getElementById('pattern-result');
const text = resultElement.textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
resultElement.textContent = insertAyYa(text, true, true);
document.getElementById('pattern-ay-ya-options').classList.add('hidden');
});
// Pattern clumping buttons
document.getElementById('pattern-clump2x2-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-result').textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = clump2x2(text);
});
document.getElementById('pattern-clump1x1x1x3-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-result').textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = clump1x1x1x3(text);
});
document.getElementById('pattern-clump4x2-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-result').textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = clump4x2(text);
});
// Pattern phoneme buttons
document.getElementById('pattern-short-vowels-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-result').textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = toShortVowelPhonemes(text);
});
document.getElementById('pattern-long-vowels-btn').addEventListener('click', () => {
const text = document.getElementById('pattern-result').textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
document.getElementById('pattern-result').textContent = toLongVowelPhonemes(text);
});
// Show EasyRead options menu for pattern tab
document.getElementById('pattern-custom-phonemes-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('pattern-easyread-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// EasyRead option handlers for pattern tab
document.getElementById('pattern-easyread-auto').addEventListener('click', () => {
const resultElement = document.getElementById('pattern-result');
const text = resultElement.textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemes(text);
document.getElementById('pattern-easyread-options').classList.add('hidden');
});
document.getElementById('pattern-easyread-short').addEventListener('click', () => {
const resultElement = document.getElementById('pattern-result');
const text = resultElement.textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemesShort(text);
document.getElementById('pattern-easyread-options').classList.add('hidden');
});
document.getElementById('pattern-easyread-long').addEventListener('click', () => {
const resultElement = document.getElementById('pattern-result');
const text = resultElement.textContent || document.getElementById('pattern-text').value;
if (text.trim() === '') return;
resultElement.textContent = toEasyReadPhonemesLong(text);
document.getElementById('pattern-easyread-options').classList.add('hidden');
});
document.getElementById('pattern-clear-btn').addEventListener('click', () => {
document.getElementById('pattern-text').value = '';
document.getElementById('pattern-result').textContent = '';
});
// Cryptanalysis Tab Functionality
let cryptoFrequencyChart = null;
// Copy buttons functionality
function setupCopyButtons() {
// Add copy buttons to Transform tab
document.getElementById('sentence').parentNode.classList.add('textarea-container');
const sentenceCopyBtn = document.createElement('button');
sentenceCopyBtn.id = 'sentence-copy-btn';
sentenceCopyBtn.className = 'copy-btn';
sentenceCopyBtn.textContent = 'Copy';
sentenceCopyBtn.title = 'Copy text';
document.getElementById('sentence').parentNode.appendChild(sentenceCopyBtn);
document.getElementById('result').parentNode.classList.add('textarea-container');
const resultCopyBtn = document.createElement('button');
resultCopyBtn.id = 'result-copy-btn';
resultCopyBtn.className = 'copy-btn';
resultCopyBtn.textContent = 'Copy';
resultCopyBtn.title = 'Copy result';
document.getElementById('result').parentNode.appendChild(resultCopyBtn);
// Add copy buttons to Encrypt tab
document.getElementById('encrypt-text').parentNode.classList.add('textarea-container');
const encryptTextCopyBtn = document.createElement('button');
encryptTextCopyBtn.id = 'encrypt-text-copy-btn';
encryptTextCopyBtn.className = 'copy-btn';
encryptTextCopyBtn.textContent = 'Copy';
encryptTextCopyBtn.title = 'Copy text';
document.getElementById('encrypt-text').parentNode.appendChild(encryptTextCopyBtn);
document.getElementById('encrypt-result').parentNode.classList.add('textarea-container');
const encryptResultCopyBtn = document.createElement('button');
encryptResultCopyBtn.id = 'encrypt-result-copy-btn';
encryptResultCopyBtn.className = 'copy-btn';
encryptResultCopyBtn.textContent = 'Copy';
encryptResultCopyBtn.title = 'Copy result';
document.getElementById('encrypt-result').parentNode.appendChild(encryptResultCopyBtn);
// Add event listeners for all copy buttons
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', function() {
let textToCopy = '';
const parentElement = this.parentElement;
const textarea = parentElement.querySelector('textarea');
const div = parentElement.querySelector('div[id$="-result"]');
if (textarea) {
textToCopy = textarea.value;
} else if (div) {
textToCopy = div.textContent;
}
if (textToCopy) {
navigator.clipboard.writeText(textToCopy).then(() => {
const originalText = this.textContent;
this.textContent = 'Copied!';
setTimeout(() => {
this.textContent = originalText;
}, 1000);
}).catch(err => {
console.error('Could not copy text: ', err);
});
}
});
});
}
// Add collapsible functionality
document.getElementById('insertion-section-header').addEventListener('click', function() {
const content = document.getElementById('insertion-section-content');
const triangle = document.getElementById('insertion-section-triangle');
content.classList.toggle('hidden');
triangle.classList.toggle('rotate-[-90deg]');
});
// Number to text conversion
function convertNumbersToText(text) {
const numberWords = {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
'5': 'five',
'6': 'six',
'7': 'seven',
'8': 'eight',
'9': 'nine',
'10': 'ten',
'11': 'eleven',
'12': 'twelve',
'13': 'thirteen',
'14': 'fourteen',
'15': 'fifteen',
'16': 'sixteen',
'17': 'seventeen',
'18': 'eighteen',
'19': 'nineteen',
'20': 'twenty',
'30': 'thirty',
'40': 'forty',
'50': 'fifty',
'60': 'sixty',
'70': 'seventy',
'80': 'eighty',
'90': 'ninety',
'100': 'one hundred',
'1000': 'one thousand',
'1000000': 'one million',
'1000000000': 'one billion'
};
return text.replace(/\b\d+\b/g, function(match) {
const num = parseInt(match, 10);
if (num <= 20) {
return numberWords[match] || match;
} else if (num < 100) {
const tens = Math.floor(num / 10) * 10;
const ones = num % 10;
return ones > 0 ?
numberWords[tens] + '-' + numberWords[ones] :
numberWords[tens];
} else if (num < 1000) {
const hundreds = Math.floor(num / 100);
const remainder = num % 100;
let result = numberWords[hundreds] + ' hundred';
if (remainder > 0) {
result += ' and ' + convertNumbersToText(remainder.toString());
}
return result;
} else if (num < 1000000) {
const thousands = Math.floor(num / 1000);
const remainder = num % 1000;
let result = convertNumbersToText(thousands.toString()) + ' thousand';
if (remainder > 0) {
result += ' ' + convertNumbersToText(remainder.toString());
}
return result;
} else {
// For very large numbers, just return the original
return match;
}
});
}
// Convert spelled-out numbers back to digits
function convertTextToNumbers(text) {
// Create a reverse mapping
const wordToNumber = {
'zero': '0',
'one': '1',
'two': '2',
'three': '3',
'four': '4',
'five': '5',
'six': '6',
'seven': '7',
'eight': '8',
'nine': '9',
'ten': '10',
'eleven': '11',
'twelve': '12',
'thirteen': '13',
'fourteen': '14',
'fifteen': '15',
'sixteen': '16',
'seventeen': '17',
'eighteen': '18',
'nineteen': '19',
'twenty': '20',
'thirty': '30',
'forty': '40',
'fifty': '50',
'sixty': '60',
'seventy': '70',
'eighty': '80',
'ninety': '90'
};
// First handle hyphenated number words (e.g., twenty-one)
text = text.replace(/\b(twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)-(\w+)\b/gi, function(match, tensWord, onesWord) {
const tens = wordToNumber[tensWord.toLowerCase()];
const ones = wordToNumber[onesWord.toLowerCase()];
if (tens && ones) {
return (parseInt(tens) + parseInt(ones)).toString();
}
return match;
});
// Handle hundreds, thousands, etc.
text = text.replace(/\b(\w+) hundred(?: and (\w+(?:-\w+)?))?\b/gi, function(match, hundredsWord, remainder) {
const hundreds = wordToNumber[hundredsWord.toLowerCase()];
if (!hundreds) return match;
if (!remainder) return parseInt(hundreds) * 100 + '';
// Try to convert the remainder
const remainderNum = wordToNumber[remainder.toLowerCase()];
if (remainderNum) {
return (parseInt(hundreds) * 100 + parseInt(remainderNum)) + '';
}
return match;
});
// Handle thousands
text = text.replace(/\b(\w+) thousand(?: (\w+))?\b/gi, function(match, thousandsWord, remainder) {
const thousands = wordToNumber[thousandsWord.toLowerCase()];
if (!thousands) return match;
if (!remainder) return parseInt(thousands) * 1000 + '';
// Try to convert the remainder (simplified)
return match;
});
// Handle simple number words
return text.replace(/\b(zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)\b/gi, function(match) {
return wordToNumber[match.toLowerCase()] || match;
});
}
// Convert punctuation names to actual punctuation
function convertTextToPunctuation(text) {
const punctTextToSymbol = {
'period': '.',
'comma': ',',
'semicolon': ';',
'colon': ':',
'exclamation mark': '!',
'question mark': '?',
'apostrophe': '\'',
'quote': '"',
'dash': '-',
'underscore': '_',
'open parenthesis': '(',
'close parenthesis': ')',
'open bracket': '[',
'close bracket': ']',
'open brace': '{',
'close brace': '}',
'slash': '/',
'backslash': '\\',
'at sign': '@',
'hash': '#',
'dollar sign': '$',
'percent': '%',
'caret': '^',
'ampersand': '&',
'asterisk': '*',
'plus': '+',
'equals': '=',
'less than': '<',
'greater than': '>',
'vertical bar': '|',
'tilde': '~'
};
// Replace each punctuation text with its symbol
for (const [text, symbol] of Object.entries(punctTextToSymbol)) {
const regex = new RegExp(`\\s${text}\\s`, 'gi');
const regexStart = new RegExp(`^${text}\\s`, 'gi');
const regexEnd = new RegExp(`\\s${text}$`, 'gi');
// Replace in the middle of text
text = text.replace(regex, symbol);
// Replace at the start of text
text = text.replace(regexStart, symbol);
// Replace at the end of text
text = text.replace(regexEnd, symbol);
}
return text;
}
// Punctuation to text conversion
function convertPunctuationToText(text) {
const punctMap = {
'.': ' period ',
',': ' comma ',
';': ' semicolon ',
':': ' colon ',
'!': ' exclamation mark ',
'?': ' question mark ',
'\'': ' apostrophe ',
'"': ' quote ',
'-': ' dash ',
'_': ' underscore ',
'(': ' open parenthesis ',
')': ' close parenthesis ',
'[': ' open bracket ',
']': ' close bracket ',
'{': ' open brace ',
'}': ' close brace ',
'/': ' slash ',
'\\': ' backslash ',
'@': ' at sign ',
'#': ' hash ',
'$': ' dollar sign ',
'%': ' percent ',
'^': ' caret ',
'&': ' ampersand ',
'*': ' asterisk ',
'+': ' plus ',
'=': ' equals ',
'<': ' less than ',
'>': ' greater than ',
'|': ' vertical bar ',
'~': ' tilde '
};
// Replace each punctuation mark with its text representation
for (const [punct, word] of Object.entries(punctMap)) {
text = text.replace(new RegExp('\\' + punct, 'g'), word);
}
return text;
}
// Initialize cryptanalysis functionality
function initCryptoTab() {
// Clear button
document.getElementById('crypto-clear-btn').addEventListener('click', function() {
document.getElementById('crypto-input').value = '';
document.getElementById('crypto-result-text').value = '';
document.getElementById('crypto-analysis-results').innerHTML = '<div class="p-1 crypto-result rounded">Run analysis on input text to detect patterns...</div>';
if (cryptoFrequencyChart) {
cryptoFrequencyChart.destroy();
cryptoFrequencyChart = null;
}
document.getElementById('crypto-ic-value').textContent = 'Index of Coincidence: —';
});
// Analyze button
document.getElementById('crypto-analyze-btn').addEventListener('click', function() {
const text = document.getElementById('crypto-input').value.trim();
if (!text) {
alert('Please enter text to analyze.');
return;
}
analyzeText(text);
});
// Caesar panel
document.getElementById('crypto-caesar-btn').addEventListener('click', function() {
document.getElementById('crypto-caesar-panel').classList.toggle('hidden');
});
document.getElementById('crypto-close-caesar').addEventListener('click', function() {
document.getElementById('crypto-caesar-panel').classList.add('hidden');
});
// Caesar shift buttons
document.querySelectorAll('.crypto-caesar-shift-btn').forEach(btn => {
btn.addEventListener('click', function() {
const shift = parseInt(this.getAttribute('data-shift'));
const text = document.getElementById('crypto-input').value;
if (!text) {
alert('Please enter text to encode/decode.');
return;
}
const result = applyCryptoCaesar(text, shift);
document.getElementById('crypto-result-text').value = result;
});
});
// Bruteforce Caesar button
document.getElementById('crypto-bruteforce-caesar').addEventListener('click', function() {
const text = document.getElementById('crypto-input').value;
if (!text) {
alert('Please enter text to decode.');
return;
}
bruteforceCaesar(text);
});
// Close All Shifts panel
document.getElementById('crypto-close-all-shifts').addEventListener('click', function() {
document.getElementById('crypto-all-shifts-panel').classList.add('hidden');
});
// Transform buttons
document.getElementById('crypto-reverse-btn').addEventListener('click', function() {
const text = document.getElementById('crypto-input').value;
if (!text) return;
document.getElementById('crypto-result-text').value = text.split('').reverse().join('');
});
document.getElementById('crypto-rot13-btn').addEventListener('click', function() {
const text = document.getElementById('crypto-input').value;
if (!text) return;
document.getElementById('crypto-result-text').value = applyCryptoCaesar(text, 13);
});
document.getElementById('crypto-base64-encode-btn').addEventListener('click', function() {
const text = document.getElementById('crypto-input').value;
if (!text) return;
try {
document.getElementById('crypto-result-text').value = btoa(text);
} catch (e) {
document.getElementById('crypto-result-text').value = 'Error: ' + e.message;
}
});
document.getElementById('crypto-base64-decode-btn').addEventListener('click', function() {
const text = document.getElementById('crypto-input').value;
if (!text) return;
try {
document.getElementById('crypto-result-text').value = atob(text);
} catch (e) {
document.getElementById('crypto-result-text').value = 'Error: ' + e.message;
}
});
// Copy result button
document.getElementById('crypto-copy-result').addEventListener('click', function() {
const result = document.getElementById('crypto-result-text');
result.select();
document.execCommand('copy');
// Flash button as feedback
this.textContent = 'COPIED';
setTimeout(() => this.textContent = 'COPY', 1000);
});
// Use result as input button
document.getElementById('crypto-use-result').addEventListener('click', function() {
const result = document.getElementById('crypto-result-text').value;
document.getElementById('crypto-input').value = result;
});
// Toggle frequency chart
document.getElementById('crypto-toggle-freq').addEventListener('click', function() {
const panel = document.getElementById('crypto-frequency-panel');
if (panel.classList.contains('hidden')) {
panel.classList.remove('hidden');
const text = document.getElementById('crypto-input').value;
if (text) {
updateFrequencyChart(text);
}
} else {
panel.classList.add('hidden');
}
});
// Initialize cipher controls
const cipherSelect = document.getElementById('crypto-cipher-select');
cipherSelect.addEventListener('change', function() {
updateCipherParams(this.value);
});
// Encode button
document.getElementById('crypto-encode-btn').addEventListener('click', function() {
processCipher('encode');
});
// Decode button
document.getElementById('crypto-decode-btn').addEventListener('click', function() {
processCipher('decode');
});
// Initialize with default cipher
updateCipherParams(cipherSelect.value);
}
// Update cipher parameters based on selection
function updateCipherParams(cipher) {
const paramsDiv = document.getElementById('crypto-cipher-params');
// Hide all params
document.querySelectorAll('[id^="crypto-"][id$="-params"]').forEach(el => {
el.classList.add('hidden');
});
// Show selected cipher params
const selectedParams = document.getElementById(`crypto-${cipher}-params`);
if (selectedParams) {
selectedParams.classList.remove('hidden');
}
}
// Process cipher encode/decode
function processCipher(operation) {
const inputText = document.getElementById('crypto-input').value;
if (!inputText) {
alert('Please enter text to process.');
return;
}
const cipher = document.getElementById('crypto-cipher-select').value;
let result = '';
try {
switch (cipher) {
case 'caesar':
const shift = 13; // Default ROT13
result = applyCryptoCaesar(inputText, operation === 'encode' ? shift : -shift);
break;
case 'vigenere':
const key = document.getElementById('crypto-vigenere-key').value;
if (!key) {
alert('Please enter a key for Vigenère cipher.');
return;
}
result = applyVigenere(inputText, key, operation);
break;
case 'atbash':
result = applyAtbash(inputText); // Same for encode/decode
break;
case 'base64':
result = operation === 'encode' ? btoa(inputText) : atob(inputText);
break;
case 'binary':
result = operation === 'encode' ? textToBinary(inputText) : binaryToText(inputText);
break;
case 'morse':
const separator = document.getElementById('crypto-morse-separator').value;
result = operation === 'encode' ? textToMorse(inputText, separator) : morseToText(inputText, separator);
break;
default:
result = 'Cipher not implemented.';
}
document.getElementById('crypto-result-text').value = result;
} catch (error) {
document.getElementById('crypto-result-text').value = 'Error: ' + error.message;
}
}
// Analyze text
function analyzeText(text) {
// Update frequency chart
updateFrequencyChart(text);
// Calculate basic stats
const charCount = text.length;
const letterCount = (text.match(/[a-zA-Z]/g) || []).length;
const digitCount = (text.match(/[0-9]/g) || []).length;
const spaceCount = (text.match(/\s/g) || []).length;
const punctCount = (text.match(/[.,;:!?()[\]{}"'`-]/g) || []).length;
const upperCount = (text.match(/[A-Z]/g) || []).length;
const lowerCount = (text.match(/[a-z]/g) || []).length;
// Calculate Index of Coincidence
const ic = calculateIC(text);
document.getElementById('crypto-ic-value').textContent = `Index of Coincidence: ${ic.toFixed(4)}`;
// Detect likely encoding/cipher
const analysis = detectEncoding(text, ic);
let resultsHtml = `
<div class="grid grid-cols-2 gap-1 mb-1">
<div class="p-1 crypto-result rounded">
<div>CHARS: ${charCount}</div>
<div>LETTERS: ${letterCount}</div>
<div>NUMBERS: ${digitCount}</div>
</div>
<div class="p-1 crypto-result rounded">
<div>UPPER: ${upperCount}</div>
<div>LOWER: ${lowerCount}</div>
<div>SPACES: ${spaceCount}</div>
</div>
</div>
<div class="p-1 crypto-result rounded">
<div class="font-bold mb-0.5">ANALYSIS:</div>
<div>${analysis}</div>
</div>
`;
document.getElementById('crypto-analysis-results').innerHTML = resultsHtml;
}
// Update frequency chart
function updateFrequencyChart(text) {
// Extract letters only
const letters = text.toUpperCase().match(/[A-Z]/g) || [];
if (letters.length === 0) {
if (cryptoFrequencyChart) {
cryptoFrequencyChart.destroy();
cryptoFrequencyChart = null;
}
return;
}
// Count letter frequencies
const counts = {};
for (let i = 0; i < 26; i++) {
counts[String.fromCharCode(65 + i)] = 0;
}
letters.forEach(letter => {
counts[letter]++;
});
// Calculate percentages
const total = letters.length;
const data = Object.keys(counts).map(letter => {
return {
letter,
count: counts[letter],
percentage: (counts[letter] / total) * 100
};
});
// Create or update chart
const ctx = document.getElementById('crypto-frequency-chart').getContext('2d');
if (cryptoFrequencyChart) {
cryptoFrequencyChart.destroy();
}
cryptoFrequencyChart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.map(d => d.letter),
datasets: [{
label: 'Frequency %',
data: data.map(d => d.percentage),
backgroundColor: 'rgba(51, 255, 51, 0.5)',
borderColor: 'rgba(51, 255, 51, 0.8)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
color: 'rgba(51, 255, 51, 0.8)'
},
grid: {
color: 'rgba(51, 255, 51, 0.2)'
}
},
x: {
ticks: {
color: 'rgba(51, 255, 51, 0.8)'
},
grid: {
color: 'rgba(51, 255, 51, 0.2)'
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
}
// Calculate Index of Coincidence
function calculateIC(text) {
const letters = text.toUpperCase().match(/[A-Z]/g) || [];
if (letters.length <= 1) return 0;
const counts = {};
for (let i = 0; i < 26; i++) {
counts[String.fromCharCode(65 + i)] = 0;
}
letters.forEach(letter => {
counts[letter]++;
});
let sum = 0;
Object.values(counts).forEach(count => {
sum += count * (count - 1);
});
return sum / (letters.length * (letters.length - 1));
}
// Detect likely encoding or cipher
function detectEncoding(text, ic) {
// Check if text is mostly letters (potential simple substitution or transposition)
const letters = text.match(/[a-zA-Z]/g) || [];
const isLetterDominant = letters.length / text.length > 0.8;
// Check if text is mostly digits (potential ASCII or other numeric encoding)
const digits = text.match(/[0-9]/g) || [];
const isDigitDominant = digits.length / text.length > 0.8;
// Check if text has only 0s and 1s (potential binary)
const binary = text.match(/[01\s]/g) || [];
const isBinary = binary.length / text.length > 0.9 && /[01]/.test(text);
// Check if text has base64 characters
const base64Chars = text.match(/[A-Za-z0-9+/=]/g) || [];
const isBase64 = base64Chars.length / text.length > 0.9 && text.length % 4 === 0;
// Check for morse code characters
const morseChars = text.match(/[.\-\s]/g) || [];
const isMorse = morseChars.length / text.length > 0.9 && /[\.\-]/.test(text);
// Check for hex characters
const hexChars = text.match(/[0-9A-Fa-f\s]/g) || [];
const isHex = hexChars.length / text.length > 0.9 && /[0-9A-Fa-f]/.test(text);
if (isBinary) {
return "Likely BINARY encoding. Try binary decode.";
} else if (isBase64) {
return "Likely BASE64 encoding. Try Base64 decode.";
} else if (isMorse) {
return "Likely MORSE CODE. Try Morse decode.";
} else if (isHex) {
return "Likely HEXADECIMAL. Try Hex decode.";
} else if (isLetterDominant) {
// Analyze IC for letter-dominant text
if (ic > 0.06) {
return "High Index of Coincidence (IC). Likely MONOALPHABETIC SUBSTITUTION (e.g. Caesar, Atbash).";
} else if (ic > 0.04) {
return "Medium Index of Coincidence (IC). Possible POLYALPHABETIC cipher (e.g. Vigenère).";
} else {
return "Low Index of Coincidence (IC). Possible TRANSPOSITION cipher or modern encryption.";
}
} else if (isDigitDominant) {
return "Mostly NUMERIC. Possible ASCII codes or other numerical encoding.";
} else {
return "Mixed character types. Try different decoding methods.";
}
}
// Brute force all Caesar shifts
function bruteforceCaesar(text) {
const results = [];
for (let shift = 0; shift < 26; shift++) {
const decoded = applyCryptoCaesar(text, -shift);
const score = calculateEnglishScore(decoded);
results.push({
shift,
text: decoded,
score
});
}
// Sort by score (higher is better)
results.sort((a, b) => b.score - a.score);
// Display results
let html = '';
results.forEach(result => {
const scoreClass = result.score > 0.6 ? 'text-terminal-amber' : '';
html += `
<div class="mb-1 p-1 crypto-result rounded">
<div class="flex justify-between">
<span>SHIFT ${result.shift}</span>
<span class="${scoreClass}">SCORE: ${result.score.toFixed(2)}</span>
</div>
<div class="mt-0.5 p-0.5 bg-black/50 rounded whitespace-nowrap overflow-x-auto">${result.text.substring(0, 40)}${result.text.length > 40 ? '...' : ''}</div>
<button class="crypto-use-shift-btn mt-0.5 crypto-btn text-xs p-0.5 rounded w-full" data-text="${result.text}">USE THIS</button>
</div>
`;
});
const resultsDiv = document.getElementById('crypto-all-shifts-results');
resultsDiv.innerHTML = html;
// Add event listeners to buttons
document.querySelectorAll('.crypto-use-shift-btn').forEach(btn => {
btn.addEventListener('click', function() {
const text = this.getAttribute('data-text');
document.getElementById('crypto-result-text').value = text;
});
});
// Show the panel
document.getElementById('crypto-all-shifts-panel').classList.remove('hidden');
}
// Basic English score for bruteforce
function calculateEnglishScore(text) {
// Common English words to check for
const commonWords = ['THE', 'AND', 'THAT', 'HAVE', 'FOR', 'NOT', 'WITH', 'YOU', 'THIS', 'BUT', 'HIS', 'FROM', 'THEY', 'SAY', 'SHE', 'WILL', 'ONE', 'ALL', 'WOULD', 'THERE', 'THEIR', 'WHAT', 'ABOUT', 'WHICH', 'WHEN', 'MAKE', 'LIKE', 'TIME', 'JUST', 'KNOW', 'PEOPLE', 'YEAR', 'YOUR', 'GOOD', 'SOME', 'COULD', 'THEM', 'THAN', 'THEN', 'NOW', 'LOOK', 'ONLY', 'COME', 'OVER', 'THINK', 'ALSO', 'BACK', 'AFTER', 'WORK', 'FIRST', 'WELL', 'EVEN', 'WANT', 'BECAUSE', 'THESE', 'GIVE', 'MOST'];
// Letter frequency in English
const englishFreq = {
'E': 12.7,
'T': 9.1,
'A': 8.2,
'O': 7.5,
'I': 7.0,
'N': 6.7,
'S': 6.3,
'H': 6.1,
'R': 6.0,
'D': 4.3,
'L': 4.0,
'C': 2.8,
'U': 2.8,
'M': 2.4,
'W': 2.4,
'F': 2.2,
'G': 2.0,
'Y': 2.0,
'P': 1.9,
'B': 1.5,
'V': 0.9,
'K': 0.8,
'J': 0.2,
'X': 0.2,
'Q': 0.1,
'Z': 0.1
};
// Count letter frequency
const letters = text.toUpperCase().match(/[A-Z]/g) || [];
if (letters.length === 0) return 0;
const counts = {};
for (let i = 0; i < 26; i++) {
counts[String.fromCharCode(65 + i)] = 0;
}
letters.forEach(letter => {
counts[letter]++;
});
// Calculate frequency score
let freqScore = 0;
const total = letters.length;
for (const letter in counts) {
const actual = counts[letter] / total * 100;
const expected = englishFreq[letter];
freqScore += (1 - Math.min(1, Math.abs(actual - expected) / expected));
}
freqScore /= 26;
// Check for common words
const words = text.toUpperCase().match(/[A-Z]{2,}/g) || [];
let wordScore = 0;
if (words.length > 0) {
let matches = 0;
for (const word of words) {
if (commonWords.includes(word)) {
matches++;
}
}
wordScore = matches / Math.min(words.length, 20);
}
// Calculate vowel ratio (English is typically ~40% vowels)
const vowels = text.toUpperCase().match(/[AEIOU]/g) || [];
const vowelRatio = vowels.length / letters.length;
const vowelScore = 1 - Math.min(1, Math.abs(vowelRatio - 0.4) / 0.4);
// Combine scores
return (freqScore * 0.5) + (wordScore * 0.3) + (vowelScore * 0.2);
}
// Caesar cipher implementation
function applyCryptoCaesar(text, shift) {
// Ensure shift is between 0-25
shift = ((shift % 26) + 26) % 26;
return text.replace(/[a-zA-Z]/g, function(char) {
const code = char.charCodeAt(0);
const isUpperCase = code >= 65 && code <= 90;
const offset = isUpperCase ? 65 : 97;
// Apply shift
return String.fromCharCode((code - offset + shift) % 26 + offset);
});
}
// Vigenère cipher implementation
function applyVigenere(text, key, operation) {
key = key.toUpperCase().replace(/[^A-Z]/g, '');
if (key.length === 0) return text;
let result = '';
let keyIndex = 0;
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (!/[a-zA-Z]/.test(char)) {
result += char;
continue;
}
const isUpperCase = char === char.toUpperCase();
const charCode = char.toUpperCase().charCodeAt(0) - 65;
const keyChar = key[keyIndex % key.length];
const keyCode = keyChar.charCodeAt(0) - 65;
let newCharCode;
if (operation === 'encode') {
newCharCode = (charCode + keyCode) % 26;
} else {
newCharCode = (charCode - keyCode + 26) % 26;
}
const newChar = String.fromCharCode(newCharCode + 65);
result += isUpperCase ? newChar : newChar.toLowerCase();
keyIndex++;
}
return result;
}
// Atbash cipher implementation (same for encrypt/decrypt)
function applyAtbash(text) {
return text.replace(/[a-zA-Z]/g, function(char) {
const code = char.charCodeAt(0);
if (code >= 65 && code <= 90) {
// Uppercase: A -> Z, B -> Y, etc.
return String.fromCharCode(155 - code);
} else {
// Lowercase: a -> z, b -> y, etc.
return String.fromCharCode(219 - code);
}
});
}
// Binary conversion functions
function textToBinary(text) {
let result = '';
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i);
let binary = charCode.toString(2);
// Pad with zeros to make 8 bits
while (binary.length < 8) {
binary = '0' + binary;
}
result += binary + ' ';
}
return result.trim();
}
function binaryToText(binary) {
// Remove spaces and other characters
binary = binary.replace(/[^01]/g, '');
let result = '';
// Process in chunks of 8 bits
for (let i = 0; i < binary.length; i += 8) {
if (i + 8 <= binary.length) {
const chunk = binary.substr(i, 8);
const charCode = parseInt(chunk, 2);
result += String.fromCharCode(charCode);
}
}
return result;
}
// Morse code functions
function textToMorse(text, separator = ' ') {
const morseCode = {
'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': '----.',
'.': '.-.-.-',
',': '--..--',
'?': '..--..',
"'": '.----.',
'!': '-.-.--',
'/': '-..-.',
'(': '-.--.',
')': '-.--.-',
'&': '.-...',
':': '---...',
';': '-.-.-.',
'=': '-...-',
'+': '.-.-.',
'-': '-....-',
'_': '..--.-',
'"': '.-..-.',
'$': '...-..-',
'@': '.--.-.'
};
let result = '';
for (let i = 0; i < text.length; i++) {
const char = text[i].toUpperCase();
if (char === ' ') {
// Word separator
result += ' '; // Two spaces between words
} else if (morseCode[char]) {
result += morseCode[char] + separator;
}
}
return result.trim();
}
function morseToText(morse, separator = ' ') {
const morseCode = {
'.-': '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',
'.-.-.-': '.',
'--..--': ',',
'..--..': '?',
'.----.': "'",
'-.-.--': '!',
'-..-.': '/',
'-.--.': '(',
'-.--.-': ')',
'.-...': '&',
'---...': ':',
'-.-.-.': ';',
'-...-': '=',
'.-.-.': '+',
'-....-': '-',
'..--.-': '_',
'.-..-.': '"',
'...-..-': '$',
'.--.-.': '@'
};
// Split by word separator (multiple spaces)
const words = morse.split(' ');
let result = '';
for (let i = 0; i < words.length; i++) {
// Split by letter separator
const letters = words[i].split(separator);
for (let j = 0; j < letters.length; j++) {
const letter = letters[j].trim();
if (letter) {
result += morseCode[letter] || '?';
}
}
if (i < words.length - 1) {
result += ' ';
}
}
return result;
}
// Initialization
// Text Insertion Feature
document.getElementById('insertion-type').addEventListener('change', function() {
const customContainer = document.getElementById('custom-insert-container');
if (this.value === 'custom') {
customContainer.classList.remove('hidden');
} else {
customContainer.classList.add('hidden');
}
});
// Apply text insertion
document.getElementById('apply-insertion-btn').addEventListener('click', function() {
const text = document.getElementById('result').textContent || document.getElementById('sentence').value;
if (!text || text.trim() === '') {
alert('Please enter text first');
return;
}
const insertionType = document.getElementById('insertion-type').value;
const insertEvery = parseInt(document.getElementById('insertion-count').value);
const insertOrder = document.getElementById('insertion-order').value;
let insertChars = [];
// Determine characters to insert based on type
switch (insertionType) {
case 'vowels':
insertChars = ['a', 'e', 'i', 'o', 'u'];
break;
case 'vowels2':
insertChars = ['ae', 'ei', 'io', 'ou', 'ua', 'ai', 'eo', 'iu', 'oa', 'ue'];
break;
case 'consonants':
insertChars = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'];
break;
case 'custom':
const customText = document.getElementById('custom-insert-text').value;
if (!customText) {
alert('Please enter custom text to insert');
return;
}
insertChars = [customText]; // Use as a single insert unit
break;
}
// Sort chars if needed
if (insertOrder === 'ascending') {
insertChars.sort();
} else if (insertOrder === 'descending') {
insertChars.sort().reverse();
}
const result = insertCharacters(text, insertChars, insertEvery, insertOrder);
document.getElementById('result').textContent = result;
});
function insertCharacters(text, chars, insertEvery, insertOrder) {
let result = '';
let insertIndex = 0; // Index in the chars array
for (let i = 0; i < text.length; i++) {
result += text[i];
// Insert after every nth character
if ((i + 1) % insertEvery === 0 && i < text.length - 1) {
// Choose character to insert
let charToInsert;
if (insertOrder === 'random') {
// Random selection
charToInsert = chars[Math.floor(Math.random() * chars.length)];
} else {
// Cycle, ascending, or descending (already sorted)
charToInsert = chars[insertIndex % chars.length];
insertIndex++;
}
result += charToInsert;
}
}
return result;
}
// Function to separate each letter with spaces (1x1)
function separateLetters1x1(text) {
let result = '';
for (let i = 0; i < text.length; i++) {
result += text[i] + ' ';
}
return result.trim();
}
// Function to separate letters in 3x1 pattern
function separateLetters3x1(text) {
let result = '';
let count = 0;
for (let i = 0; i < text.length; i++) {
result += text[i];
count++;
if (count === 3) {
// After 3 letters, add a space and the next letter
result += ' ';
if (i + 1 < text.length) {
result += text[i + 1] + ' ';
i++; // Skip this letter in the next iteration
}
count = 0;
}
}
return result.trim();
}
// Function to separate letters in 2x1 pattern
function separateLetters2x1(text) {
let result = '';
let count = 0;
for (let i = 0; i < text.length; i++) {
result += text[i];
count++;
if (count === 2) {
// After 2 letters, add a space and the next letter
result += ' ';
if (i + 1 < text.length) {
result += text[i + 1] + ' ';
i++; // Skip this letter in the next iteration
}
count = 0;
}
}
return result.trim();
}
// Function for reversed punctuation spelling
function reversedPunctuationSpelling(text) {
const punctMap = {
'.': 'doirep',
',': 'ammoc',
';': 'nolocimes',
':': 'noloc',
'!': 'kram noitamalcxe',
'?': 'kram noitseuq',
'\'': 'ehportsopa',
'"': 'etouq',
'-': 'hsad',
'_': 'erocssrednu',
'(': 'sisehtnerap nepo',
')': 'sisehtnerap esolc',
'[': 'tekcarb nepo',
']': 'tekcarb esolc',
'{': 'ecarb nepo',
'}': 'ecarb esolc',
'/': 'hsals',
'\\': 'hsalskcab',
'@': 'ngis ta',
'#': 'hsah',
'$': 'ngis rallod',
'%': 'tnecrep',
'^': 'terac',
'&': 'dnasrepma',
'*': 'ksiretsa',
'+': 'sulp',
'=': 'slauqe',
'<': 'naht ssel',
'>': 'naht retaerg',
'|': 'rab lacitrev',
'~': 'edlit'
};
// Replace each punctuation mark with its reversed text representation
for (const [punct, word] of Object.entries(punctMap)) {
text = text.replace(new RegExp('\\' + punct, 'g'), ` ${word} `);
}
return text.trim();
}
// Function for reversed letter spelling
function reversedLetterSpelling(text) {
const letterMap = {
'a': 'ya',
'b': 'eeb',
'c': 'ees',
'd': 'eed',
'e': 'ee',
'f': 'fe',
'g': 'eeg',
'h': 'hcta',
'i': 'ya',
'j': 'yaj',
'k': 'yak',
'l': 'le',
'm': 'me',
'n': 'ne',
'o': 'ho',
'p': 'eep',
'q': 'uyq',
'r': 'ra',
's': 'sse',
't': 'eet',
'u': 'uy',
'v': 'eev',
'w': 'elboud-uy',
'x': 'ske',
'y': 'yaw',
'z': 'eez'
};
// Create uppercase versions
const upperLetterMap = {};
for (const [letter, word] of Object.entries(letterMap)) {
upperLetterMap[letter.toUpperCase()] = word.charAt(0).toUpperCase() + word.slice(1);
}
// Replace each letter with its reversed name
for (const [letter, word] of Object.entries({
...letterMap,
...upperLetterMap
})) {
text = text.replace(new RegExp(letter, 'g'), ` ${word} `);
}
return text.trim().replace(/\s+/g, ' ');
}
// Initialize text analysis functionality and create chart
function initTextAnalysis() {
let textAnalysisChart = null;
// Toggle dropdown sections
document.getElementById('analysis-section-header').addEventListener('click', function() {
const content = document.getElementById('analysis-section-content');
const triangle = document.getElementById('analysis-section-triangle');
content.classList.toggle('hidden');
triangle.classList.toggle('rotate-[-90deg]');
});
document.getElementById('advanced-section-header').addEventListener('click', function() {
const content = document.getElementById('advanced-section-content');
const triangle = document.getElementById('advanced-section-triangle');
content.classList.toggle('hidden');
triangle.classList.toggle('rotate-[-90deg]');
});
// Analyze text when the analyze button is clicked
document.getElementById('analyze-btn').addEventListener('click', function() {
const text = document.getElementById('result').textContent || document.getElementById('sentence').value;
if (!text || text.trim() === '') {
alert('Please enter text to analyze');
return;
}
// Show the analysis section
document.getElementById('analysis-section-content').classList.remove('hidden');
document.getElementById('analysis-section-triangle').classList.remove('rotate-[-90deg]');
// Perform analysis
analyzeText(text);
});
// View all words button
document.getElementById('view-all-words-btn').addEventListener('click', function() {
const text = document.getElementById('result').textContent || document.getElementById('sentence').value;
if (!text || text.trim() === '') {
alert('No text to analyze');
return;
}
showWordFrequencyModal(text);
});
// Close word frequency modal
document.getElementById('word-freq-modal-close').addEventListener('click', function() {
document.getElementById('word-freq-modal').classList.add('hidden');
});
document.getElementById('word-freq-modal-backdrop').addEventListener('click', function() {
document.getElementById('word-freq-modal').classList.add('hidden');
});
function analyzeText(text) {
// Basic statistics
const charCount = text.length;
const words = text.trim().split(/\s+/).filter(word => word.length > 0);
const wordCount = words.length;
const letters = text.match(/[a-zA-Z]/g) || [];
const letterCount = letters.length;
const vowels = text.match(/[aeiouAEIOU]/g) || [];
const vowelCount = vowels.length;
const consonants = text.match(/[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]/g) || [];
const consonantCount = consonants.length;
const numbers = text.match(/[0-9]/g) || [];
const numberCount = numbers.length;
const punctuation = text.match(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g) || [];
const punctCount = punctuation.length;
const uppercase = text.match(/[A-Z]/g) || [];
const uppercaseCount = uppercase.length;
const lowercase = text.match(/[a-z]/g) || [];
const lowercaseCount = lowercase.length;
// Update the basic statistics
document.getElementById('analysis-chars').textContent = charCount;
document.getElementById('analysis-words').textContent = wordCount;
document.getElementById('analysis-vowels').textContent = vowelCount;
document.getElementById('analysis-consonants').textContent = consonantCount;
document.getElementById('analysis-numbers').textContent = numberCount;
document.getElementById('analysis-punct').textContent = punctCount;
document.getElementById('analysis-upper').textContent = uppercaseCount;
document.getElementById('analysis-lower').textContent = lowercaseCount;
// Letter frequency analysis
const letterFreq = {};
for (let i = 0; i < 26; i++) {
letterFreq[String.fromCharCode(97 + i)] = 0; // Initialize with lowercase a-z
}
// Count occurrences of each letter
letters.forEach(letter => {
const lowerLetter = letter.toLowerCase();
letterFreq[lowerLetter] = (letterFreq[lowerLetter] || 0) + 1;
});
// Get top 3 most common letters
const sortedLetters = Object.entries(letterFreq)
.filter(([_, count]) => count > 0)
.sort((a, b) => b[1] - a[1]);
const topLetters = sortedLetters.slice(0, 3).map(([letter, count]) =>
`${letter}: ${count} (${(count / letterCount * 100).toFixed(1)}%)`
).join(', ');
document.getElementById('analysis-top-letters').textContent = topLetters || 'None';
// Update the letter frequency chart
updateLetterFrequencyChart(letterFreq, letters.length);
// Word frequency analysis
const wordFreq = {};
words.forEach(word => {
// Normalize word (lowercase, remove punctuation)
const normalizedWord = word.toLowerCase().replace(/[^\w\s]|_/g, "");
if (normalizedWord.length > 0) {
wordFreq[normalizedWord] = (wordFreq[normalizedWord] || 0) + 1;
}
});
// Sort words by frequency
const sortedWords = Object.entries(wordFreq)
.sort((a, b) => b[1] - a[1])
.slice(0, 5); // Get top 5
// Update the word frequency table
const wordFreqTable = document.getElementById('word-freq-table');
if (sortedWords.length === 0) {
wordFreqTable.innerHTML = '<tr><td colspan="3" class="py-1 px-2 text-center">No words to analyze</td></tr>';
} else {
wordFreqTable.innerHTML = sortedWords.map(([word, count]) => {
const percentage = (count / wordCount * 100).toFixed(1);
return `<tr>
<td class="py-1 px-2">${word}</td>
<td class="py-1 px-2 text-right">${count}</td>
<td class="py-1 px-2 text-right">${percentage}%</td>
</tr>`;
}).join('');
}
}
function updateLetterFrequencyChart(letterFreq, totalLetters) {
// Filter to only include letters that appear
const labels = [];
const data = [];
Object.entries(letterFreq)
.filter(([_, count]) => count > 0)
.sort((a, b) => b[1] - a[1]) // Sort by frequency
.forEach(([letter, count]) => {
labels.push(letter);
data.push((count / totalLetters * 100).toFixed(1));
});
const ctx = document.getElementById('letter-frequency-chart').getContext('2d');
// Destroy existing chart if it exists
if (textAnalysisChart) {
textAnalysisChart.destroy();
}
// Create new chart
textAnalysisChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: 'rgba(10, 226, 117, 0.6)', // Cyberpunk green with opacity
borderColor: 'rgba(10, 226, 117, 0.8)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `${context.formattedValue}%`;
}
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
color: 'rgba(10, 226, 117, 0.8)',
font: {
family: "'Share Tech Mono', monospace",
size: 9
},
callback: function(value) {
return value + '%';
}
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
x: {
ticks: {
color: 'rgba(10, 226, 117, 0.8)',
font: {
family: "'Share Tech Mono', monospace",
size: 9
}
},
grid: {
display: false
}
}
}
}
});
}
function showWordFrequencyModal(text) {
const words = text.trim().split(/\s+/).filter(word => word.length > 0);
const wordCount = words.length;
// Word frequency analysis
const wordFreq = {};
words.forEach(word => {
// Normalize word (lowercase, remove punctuation)
const normalizedWord = word.toLowerCase().replace(/[^\w\s]|_/g, "");
if (normalizedWord.length > 0) {
wordFreq[normalizedWord] = (wordFreq[normalizedWord] || 0) + 1;
}
});
// Sort words by frequency
const sortedWords = Object.entries(wordFreq)
.sort((a, b) => b[1] - a[1]);
// Update the modal table
const modalTable = document.getElementById('word-freq-modal-table');
if (sortedWords.length === 0) {
modalTable.innerHTML = '<tr><td colspan="3" class="py-2 px-3 text-center">No words to analyze</td></tr>';
} else {
modalTable.innerHTML = sortedWords.map(([word, count]) => {
const percentage = (count / wordCount * 100).toFixed(1);
return `<tr>
<td class="py-1 px-3 border-t border-gray-700">${word}</td>
<td class="py-1 px-3 text-right border-t border-gray-700">${count}</td>
<td class="py-1 px-3 text-right border-t border-gray-700">${percentage}%</td>
</tr>`;
}).join('');
}
// Show the modal
document.getElementById('word-freq-modal').classList.remove('hidden');
}
}
// Speech recognition for dictation
let recognition;
let isRecognizing = false;
function initSpeechRecognition() {
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
alert('Speech recognition is not supported in your browser. Try Chrome or Edge.');
return;
}
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = 'en-US';
recognition.onstart = function() {
isRecognizing = true;
const dictateBtn = document.getElementById('dictate-text-btn');
dictateBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1 animate-pulse" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
</svg>
Stop Dictating
`;
dictateBtn.classList.remove('bg-purple-600', 'hover:bg-purple-700');
dictateBtn.classList.add('bg-red-500', 'hover:bg-red-600');
};
recognition.onend = function() {
isRecognizing = false;
const dictateBtn = document.getElementById('dictate-text-btn');
dictateBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
</svg>
Dictate Text
`;
dictateBtn.classList.remove('bg-red-500', 'hover:bg-red-600');
dictateBtn.classList.add('bg-purple-600', 'hover:bg-purple-700');
};
recognition.onresult = function(event) {
const textarea = document.getElementById('sentence');
let transcript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
if (event.results[i].isFinal) {
transcript += event.results[i][0].transcript + ' ';
}
}
if (transcript) {
if (textarea.value) {
textarea.value += ' ' + transcript.trim();
} else {
textarea.value = transcript.trim();
}
}
};
recognition.onerror = function(event) {
console.error('Speech recognition error:', event.error);
stopRecognition();
};
}
function toggleRecognition() {
if (isRecognizing) {
stopRecognition();
} else {
startRecognition();
}
}
function startRecognition() {
if (!recognition) {
initSpeechRecognition();
}
try {
recognition.start();
} catch (e) {
console.error('Speech recognition error:', e);
}
}
function stopRecognition() {
if (recognition) {
recognition.stop();
}
}
// Image text extraction using OCR
function setupImageToText() {
const imageUploadBtn = document.getElementById('scan-text-btn');
const imageInput = document.getElementById('image-upload');
imageUploadBtn.addEventListener('click', function() {
imageInput.click();
});
imageInput.addEventListener('change', function(e) {
if (e.target.files.length === 0) return;
const file = e.target.files[0];
if (!file.type.match('image.*')) {
alert('Please select an image file');
return;
}
// Show loading state
const textarea = document.getElementById('sentence');
const originalText = textarea.value;
textarea.value = "Scanning image for text...";
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
// Create a canvas and convert image to grayscale
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
// Draw image to canvas
ctx.drawImage(img, 0, 0);
// Use Claude for OCR
extractTextFromImage(img, textarea, originalText);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
}
function extractTextFromImage(img, textarea, originalText) {
// Use Claude to extract text
extractTextUsingClaude(img, textarea, originalText);
}
async function extractTextUsingClaude(img, textarea, originalText) {
try {
// For Canvas apps, we can use Poe API to call Claude with the image
const imageBlob = await fetch(img.src).then(r => r.blob());
const imageFile = new File([imageBlob], "image.png", {
type: imageBlob.type
});
const handlerId = "ocr-handler-" + Date.now();
// Register handler for OCR response
window.Poe.registerHandler(handlerId, (result) => {
const message = result.responses[0];
if (message.status === "error") {
textarea.value = "Error: " + message.statusText;
return;
}
if (message.status === "complete") {
textarea.value = message.content.trim();
}
});
// Send image to Claude for OCR
await window.Poe.sendUserMessage(
"@Claude-3.7-Sonnet Please extract all text from this image. Return ONLY the extracted text with no explanations or additional content.", {
handler: handlerId,
stream: false,
openChat: false,
attachments: [imageFile]
}
);
} catch (error) {
console.error("Error with Claude OCR:", error);
textarea.value = originalText;
alert("Error extracting text from image: " + error.message);
}
}
document.addEventListener('DOMContentLoaded', () => {
initCryptoTab();
setupCopyButtons();
initTextAnalysis();
// Initialize dictation and image scanning
document.getElementById('dictate-text-btn').addEventListener('click', toggleRecognition);
setupImageToText();
// Separate options menu toggle
document.getElementById('separate-letters-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('separate-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// Reversed spelling options menu toggle
document.getElementById('reversed-spell-btn').addEventListener('click', (e) => {
const optionsMenu = document.getElementById('reversed-spell-options');
optionsMenu.classList.toggle('hidden');
e.stopPropagation();
});
// Separate letters 1x1
document.getElementById('separate-1x1').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = separateLetters1x1(text);
document.getElementById('separate-options').classList.add('hidden');
});
// Separate letters 3x1
document.getElementById('separate-3x1').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = separateLetters3x1(text);
document.getElementById('separate-options').classList.add('hidden');
});
// Separate letters 2x1
document.getElementById('separate-2x1').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = separateLetters2x1(text);
document.getElementById('separate-options').classList.add('hidden');
});
// Reversed punctuation spellings
document.getElementById('reversed-punct').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = reversedPunctuationSpelling(text);
document.getElementById('reversed-spell-options').classList.add('hidden');
});
// Reversed letter spellings
document.getElementById('reversed-letters').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
resultElement.textContent = reversedLetterSpelling(text);
document.getElementById('reversed-spell-options').classList.add('hidden');
});
// Mirror with spaces
document.getElementById('mirror-with-spaces-btn').addEventListener('click', () => {
const resultElement = document.getElementById('result');
const text = resultElement.textContent || document.getElementById('sentence').value;
if (text.trim() === '') return;
// Create a mirrored version with spaces between characters
let result = '';
// First add the original text with spaces
for (let i = 0; i < text.length; i++) {
result += text[i] + ' ';
}
// Then add the reversed text with spaces
for (let i = text.length - 1; i >= 0; i--) {
result += text[i] + (i > 0 ? ' ' : '');
}
resultElement.textContent = result.trim();
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment