A Pen by mode-mercury on CodePen.
Created
June 2, 2025 21:03
-
-
Save mode-mercury/6079955f94b89e3287f8c847fbc39185 to your computer and use it in GitHub Desktop.
Untitled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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