| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Stenography Translator</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .dropzone { |
| border: 2px dashed #94a3b8; |
| transition: all 0.3s ease; |
| } |
| .dropzone.active { |
| border-color: #3b82f6; |
| background-color: #f8fafc; |
| } |
| .progress-bar { |
| transition: width 0.3s ease; |
| } |
| .result-container { |
| max-height: 300px; |
| overflow-y: auto; |
| } |
| |
| .result-container::-webkit-scrollbar { |
| width: 8px; |
| } |
| .result-container::-webkit-scrollbar-track { |
| background: #f1f1f1; |
| } |
| .result-container::-webkit-scrollbar-thumb { |
| background: #cbd5e1; |
| border-radius: 4px; |
| } |
| .result-container::-webkit-scrollbar-thumb:hover { |
| background: #94a3b8; |
| } |
| .highlight { |
| background-color: #fef08a; |
| padding: 0 2px; |
| border-radius: 2px; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| <div class="container mx-auto px-4 py-8 max-w-4xl"> |
| <header class="text-center mb-12"> |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2"> |
| <i class="fas fa-keyboard mr-2"></i>Stenography Translator |
| </h1> |
| <p class="text-gray-600">Convert your stenography notes to readable English text</p> |
| </header> |
|
|
| <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8"> |
| <div class="p-6"> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> |
| |
| <div class="space-y-2"> |
| <label class="block text-sm font-medium text-gray-700 mb-1"> |
| <i class="fas fa-file-alt mr-1"></i> Stenography Notes File |
| </label> |
| <div id="notes-dropzone" class="dropzone rounded-lg p-6 text-center cursor-pointer"> |
| <div class="flex flex-col items-center justify-center"> |
| <i class="fas fa-cloud-upload-alt text-3xl text-indigo-500 mb-2"></i> |
| <p class="text-sm text-gray-500">Drag & drop your stenography notes file here</p> |
| <p class="text-xs text-gray-400 mt-1">or click to browse</p> |
| <input type="file" id="notes-file" class="hidden" accept=".txt,.json,.csv"> |
| </div> |
| </div> |
| <div id="notes-file-info" class="text-sm text-gray-500 mt-2 hidden"> |
| <i class="fas fa-check-circle text-green-500 mr-1"></i> |
| <span id="notes-file-name"></span> |
| <span id="notes-file-size" class="text-gray-400 ml-2"></span> |
| </div> |
| <div class="mt-2"> |
| <a href="#" id="download-sample-notes" class="text-xs text-indigo-600 hover:text-indigo-800 flex items-center"> |
| <i class="fas fa-download mr-1"></i> Download sample notes file |
| </a> |
| </div> |
| </div> |
|
|
| |
| <div class="space-y-2"> |
| <label class="block text-sm font-medium text-gray-700 mb-1"> |
| <i class="fas fa-exchange-alt mr-1"></i> Stenography-to-English Mapping File |
| </label> |
| <div id="mapping-dropzone" class="dropzone rounded-lg p-6 text-center cursor-pointer"> |
| <div class="flex flex-col items-center justify-center"> |
| <i class="fas fa-cloud-upload-alt text-3xl text-indigo-500 mb-2"></i> |
| <p class="text-sm text-gray-500">Drag & drop your mapping file here</p> |
| <p class="text-xs text-gray-400 mt-1">or click to browse</p> |
| <input type="file" id="mapping-file" class="hidden" accept=".json,.csv,.txt"> |
| </div> |
| </div> |
| <div id="mapping-file-info" class="text-sm text-gray-500 mt-2 hidden"> |
| <i class="fas fa-check-circle text-green-500 mr-1"></i> |
| <span id="mapping-file-name"></span> |
| <span id="mapping-file-size" class="text-gray-400 ml-2"></span> |
| </div> |
| <div class="mt-2"> |
| <a href="#" id="download-sample-mapping" class="text-xs text-indigo-600 hover:text-indigo-800 flex items-center"> |
| <i class="fas fa-download mr-1"></i> Download sample mapping file |
| </a> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="mb-6"> |
| <button id="toggle-options" class="flex items-center text-sm text-indigo-600 hover:text-indigo-800"> |
| <i class="fas fa-cog mr-1"></i> Advanced Options |
| <i id="options-chevron" class="fas fa-chevron-down ml-1 text-xs"></i> |
| </button> |
| <div id="advanced-options" class="mt-3 hidden space-y-3"> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1"> |
| <i class="fas fa-code mr-1"></i> File Format |
| </label> |
| <select id="file-format" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm"> |
| <option value="auto">Auto-detect</option> |
| <option value="json">JSON</option> |
| <option value="csv">CSV</option> |
| <option value="plain">Plain Text</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1"> |
| <i class="fas fa-font mr-1"></i> Case Sensitivity |
| </label> |
| <select id="case-sensitivity" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm"> |
| <option value="sensitive">Case Sensitive</option> |
| <option value="insensitive">Case Insensitive</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-sm font-medium text-gray-700 mb-1"> |
| <i class="fas fa-search mr-1"></i> Matching Mode |
| </label> |
| <select id="matching-mode" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm"> |
| <option value="exact">Exact Match</option> |
| <option value="partial">Partial Match</option> |
| </select> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="progress-container" class="mb-6 hidden"> |
| <div class="flex justify-between text-sm text-gray-600 mb-1"> |
| <span>Processing...</span> |
| <span id="progress-percent">0%</span> |
| </div> |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> |
| <div id="progress-bar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="flex flex-wrap gap-3 justify-center"> |
| <button id="translate-btn" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center disabled:opacity-50 disabled:cursor-not-allowed" disabled> |
| <i class="fas fa-language mr-2"></i> Translate |
| </button> |
| <button id="clear-btn" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition flex items-center"> |
| <i class="fas fa-broom mr-2"></i> Clear All |
| </button> |
| <button id="example-btn" class="px-6 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition flex items-center"> |
| <i class="fas fa-lightbulb mr-2"></i> Load Example |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="results-section" class="bg-white rounded-xl shadow-lg overflow-hidden hidden"> |
| <div class="p-6"> |
| <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> |
| <i class="fas fa-file-alt mr-2"></i> Translation Results |
| </h2> |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| |
| <div> |
| <h3 class="text-sm font-medium text-gray-700 mb-2 flex items-center"> |
| <i class="fas fa-keyboard mr-1"></i> Original Stenography |
| </h3> |
| <div id="original-text" class="result-container bg-gray-50 p-4 rounded-lg border border-gray-200 text-sm font-mono whitespace-pre-wrap"></div> |
| </div> |
| |
| |
| <div> |
| <h3 class="text-sm font-medium text-gray-700 mb-2 flex items-center"> |
| <i class="fas fa-globe mr-1"></i> Translated English |
| </h3> |
| <div id="translated-text" class="result-container bg-indigo-50 p-4 rounded-lg border border-indigo-200 text-sm whitespace-pre-wrap"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-6 pt-6 border-t border-gray-200 flex flex-wrap justify-between items-center"> |
| <div id="stats" class="text-sm text-gray-600"> |
| <span id="word-count" class="mr-3"><i class="fas fa-font mr-1"></i> Words: 0</span> |
| <span id="time-taken"><i class="fas fa-stopwatch mr-1"></i> Time: 0ms</span> |
| </div> |
| <div class="flex gap-2"> |
| <button id="download-txt" class="px-4 py-1.5 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200 transition flex items-center"> |
| <i class="fas fa-download mr-1"></i> TXT |
| </button> |
| <button id="download-json" class="px-4 py-1.5 bg-gray-100 text-gray-700 rounded text-sm hover:bg-gray-200 transition flex items-center"> |
| <i class="fas fa-download mr-1"></i> JSON |
| </button> |
| <button id="copy-clipboard" class="px-4 py-1.5 bg-indigo-100 text-indigo-700 rounded text-sm hover:bg-indigo-200 transition flex items-center"> |
| <i class="fas fa-copy mr-1"></i> Copy |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-8 bg-white rounded-xl shadow-lg overflow-hidden"> |
| <div class="p-6"> |
| <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> |
| <i class="fas fa-question-circle mr-2"></i> How It Works |
| </h2> |
| <div class="space-y-4 text-sm text-gray-600"> |
| <p>This tool helps you convert stenography notes to English text using a mapping file that defines the relationship between stenography symbols and English words.</p> |
| |
| <div class="bg-blue-50 p-4 rounded-lg border border-blue-100"> |
| <h3 class="font-medium text-blue-700 mb-2 flex items-center"> |
| <i class="fas fa-info-circle mr-1"></i> Mapping File Format |
| </h3> |
| <p>The mapping file should be in JSON or CSV format with stenography symbols as keys and English words as values.</p> |
| <p class="mt-2 font-mono text-xs bg-white p-2 rounded border"> |
| {<br> |
| "TPH": "the",<br> |
| "KPW": "and",<br> |
| "EU": "you"<br> |
| } |
| </p> |
| </div> |
| |
| <p>For CSV files, use the format: <span class="font-mono">steno_symbol,english_word</span></p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const notesDropzone = document.getElementById('notes-dropzone'); |
| const notesFileInput = document.getElementById('notes-file'); |
| const notesFileInfo = document.getElementById('notes-file-info'); |
| const notesFileName = document.getElementById('notes-file-name'); |
| const notesFileSize = document.getElementById('notes-file-size'); |
| |
| const mappingDropzone = document.getElementById('mapping-dropzone'); |
| const mappingFileInput = document.getElementById('mapping-file'); |
| const mappingFileInfo = document.getElementById('mapping-file-info'); |
| const mappingFileName = document.getElementById('mapping-file-name'); |
| const mappingFileSize = document.getElementById('mapping-file-size'); |
| |
| const translateBtn = document.getElementById('translate-btn'); |
| const clearBtn = document.getElementById('clear-btn'); |
| const exampleBtn = document.getElementById('example-btn'); |
| |
| const progressContainer = document.getElementById('progress-container'); |
| const progressBar = document.getElementById('progress-bar'); |
| const progressPercent = document.getElementById('progress-percent'); |
| |
| const resultsSection = document.getElementById('results-section'); |
| const originalText = document.getElementById('original-text'); |
| const translatedText = document.getElementById('translated-text'); |
| |
| const wordCount = document.getElementById('word-count'); |
| const timeTaken = document.getElementById('time-taken'); |
| |
| const downloadTxt = document.getElementById('download-txt'); |
| const downloadJson = document.getElementById('download-json'); |
| const copyClipboard = document.getElementById('copy-clipboard'); |
| |
| const toggleOptions = document.getElementById('toggle-options'); |
| const optionsChevron = document.getElementById('options-chevron'); |
| const advancedOptions = document.getElementById('advanced-options'); |
| |
| const downloadSampleNotes = document.getElementById('download-sample-notes'); |
| const downloadSampleMapping = document.getElementById('download-sample-mapping'); |
| |
| |
| let notesFile = null; |
| let mappingFile = null; |
| let mappingData = {}; |
| |
| |
| const sampleNotes = `TPH EU KPW TPH-FP TPH EU |
| KPW TPH-FP TPH EU KPW TPH-FP |
| TPH EU KPW TPH-FP TPH EU |
| KPW TPH-FP TPH EU KPW TPH-FP`; |
| |
| const sampleMapping = { |
| "TPH": "the", |
| "EU": "you", |
| "KPW": "and", |
| "TPH-FP": "that" |
| }; |
| |
| |
| setupDropzone(notesDropzone, notesFileInput, (file) => { |
| notesFile = file; |
| notesFileName.textContent = file.name; |
| notesFileSize.textContent = formatFileSize(file.size); |
| notesFileInfo.classList.remove('hidden'); |
| updateTranslateButton(); |
| }); |
| |
| |
| setupDropzone(mappingDropzone, mappingFileInput, (file) => { |
| mappingFile = file; |
| mappingFileName.textContent = file.name; |
| mappingFileSize.textContent = formatFileSize(file.size); |
| mappingFileInfo.classList.remove('hidden'); |
| updateTranslateButton(); |
| }); |
| |
| |
| clearBtn.addEventListener('click', function() { |
| notesFile = null; |
| mappingFile = null; |
| mappingData = {}; |
| |
| notesFileInfo.classList.add('hidden'); |
| mappingFileInfo.classList.add('hidden'); |
| resultsSection.classList.add('hidden'); |
| |
| notesFileInput.value = ''; |
| mappingFileInput.value = ''; |
| |
| updateTranslateButton(); |
| }); |
| |
| |
| exampleBtn.addEventListener('click', function() { |
| |
| originalText.textContent = sampleNotes; |
| |
| |
| const mappingBlob = new Blob([JSON.stringify(sampleMapping, null, 2)], { type: 'application/json' }); |
| const mappingFile = new File([mappingBlob], "sample_mapping.json", { type: 'application/json' }); |
| |
| |
| const notesBlob = new Blob([sampleNotes], { type: 'text/plain' }); |
| const notesFile = new File([notesBlob], "sample_notes.txt", { type: 'text/plain' }); |
| |
| |
| notesFileName.textContent = notesFile.name; |
| notesFileSize.textContent = formatFileSize(notesFile.size); |
| notesFileInfo.classList.remove('hidden'); |
| |
| mappingFileName.textContent = mappingFile.name; |
| mappingFileSize.textContent = formatFileSize(mappingFile.size); |
| mappingFileInfo.classList.remove('hidden'); |
| |
| |
| notesFile = notesFile; |
| mappingFile = mappingFile; |
| mappingData = sampleMapping; |
| |
| updateTranslateButton(); |
| }); |
| |
| |
| downloadSampleNotes.addEventListener('click', function(e) { |
| e.preventDefault(); |
| const blob = new Blob([sampleNotes], { type: 'text/plain' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = 'sample_steno_notes.txt'; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| }); |
| |
| |
| downloadSampleMapping.addEventListener('click', function(e) { |
| e.preventDefault(); |
| const blob = new Blob([JSON.stringify(sampleMapping, null, 2)], { type: 'application/json' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = 'sample_steno_mapping.json'; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| }); |
| |
| |
| translateBtn.addEventListener('click', function() { |
| if (!notesFile || !mappingFile) return; |
| |
| progressContainer.classList.remove('hidden'); |
| progressBar.style.width = '0%'; |
| progressPercent.textContent = '0%'; |
| |
| |
| let progress = 0; |
| const progressInterval = setInterval(() => { |
| progress += 5; |
| progressBar.style.width = `${progress}%`; |
| progressPercent.textContent = `${progress}%`; |
| |
| if (progress >= 100) { |
| clearInterval(progressInterval); |
| setTimeout(() => { |
| processFiles(); |
| }, 500); |
| } |
| }, 100); |
| }); |
| |
| |
| toggleOptions.addEventListener('click', function() { |
| advancedOptions.classList.toggle('hidden'); |
| optionsChevron.classList.toggle('fa-chevron-down'); |
| optionsChevron.classList.toggle('fa-chevron-up'); |
| }); |
| |
| |
| function setupDropzone(dropzone, fileInput, callback) { |
| dropzone.addEventListener('click', function() { |
| fileInput.click(); |
| }); |
| |
| fileInput.addEventListener('change', function() { |
| if (fileInput.files.length > 0) { |
| callback(fileInput.files[0]); |
| } |
| }); |
| |
| dropzone.addEventListener('dragover', function(e) { |
| e.preventDefault(); |
| dropzone.classList.add('active'); |
| }); |
| |
| dropzone.addEventListener('dragleave', function() { |
| dropzone.classList.remove('active'); |
| }); |
| |
| dropzone.addEventListener('drop', function(e) { |
| e.preventDefault(); |
| dropzone.classList.remove('active'); |
| |
| if (e.dataTransfer.files.length > 0) { |
| callback(e.dataTransfer.files[0]); |
| fileInput.files = e.dataTransfer.files; |
| } |
| }); |
| } |
| |
| function formatFileSize(bytes) { |
| if (bytes === 0) return '0 Bytes'; |
| |
| const k = 1024; |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
| } |
| |
| function updateTranslateButton() { |
| translateBtn.disabled = !(notesFile && mappingFile); |
| } |
| |
| function processFiles() { |
| const startTime = performance.now(); |
| |
| |
| |
| const notesContent = sampleNotes; |
| const mappingContent = sampleMapping; |
| |
| |
| originalText.textContent = notes |
| </html> |