stenotest / index.html
techguy1's picture
Add 3 files
0a48338 verified
<!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;
}
/* Custom scrollbar */
.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">
<!-- Stenography Notes Upload -->
<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>
<!-- Mapping File Upload -->
<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>
<!-- Advanced Options -->
<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>
<!-- Progress Bar -->
<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>
<!-- Action Buttons -->
<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>
<!-- Results Section -->
<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">
<!-- Original Text -->
<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>
<!-- Translated Text -->
<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>
<!-- Stats and Download -->
<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>
<!-- Help Section -->
<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>
&nbsp;&nbsp;"TPH": "the",<br>
&nbsp;&nbsp;"KPW": "and",<br>
&nbsp;&nbsp;"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() {
// DOM Elements
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');
// File handling
let notesFile = null;
let mappingFile = null;
let mappingData = {};
// Sample data
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"
};
// Setup dropzone events for notes file
setupDropzone(notesDropzone, notesFileInput, (file) => {
notesFile = file;
notesFileName.textContent = file.name;
notesFileSize.textContent = formatFileSize(file.size);
notesFileInfo.classList.remove('hidden');
updateTranslateButton();
});
// Setup dropzone events for mapping file
setupDropzone(mappingDropzone, mappingFileInput, (file) => {
mappingFile = file;
mappingFileName.textContent = file.name;
mappingFileSize.textContent = formatFileSize(file.size);
mappingFileInfo.classList.remove('hidden');
updateTranslateButton();
});
// Clear button
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();
});
// Example button
exampleBtn.addEventListener('click', function() {
// Load sample data
originalText.textContent = sampleNotes;
// Create a sample mapping file
const mappingBlob = new Blob([JSON.stringify(sampleMapping, null, 2)], { type: 'application/json' });
const mappingFile = new File([mappingBlob], "sample_mapping.json", { type: 'application/json' });
// Create a sample notes file
const notesBlob = new Blob([sampleNotes], { type: 'text/plain' });
const notesFile = new File([notesBlob], "sample_notes.txt", { type: 'text/plain' });
// Simulate file selection
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');
// Set the files
notesFile = notesFile;
mappingFile = mappingFile;
mappingData = sampleMapping;
updateTranslateButton();
});
// Download sample notes
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);
});
// Download sample mapping
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);
});
// Translate button
translateBtn.addEventListener('click', function() {
if (!notesFile || !mappingFile) return;
progressContainer.classList.remove('hidden');
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
// Simulate progress
let progress = 0;
const progressInterval = setInterval(() => {
progress += 5;
progressBar.style.width = `${progress}%`;
progressPercent.textContent = `${progress}%`;
if (progress >= 100) {
clearInterval(progressInterval);
setTimeout(() => {
processFiles();
}, 500);
}
}, 100);
});
// Toggle advanced options
toggleOptions.addEventListener('click', function() {
advancedOptions.classList.toggle('hidden');
optionsChevron.classList.toggle('fa-chevron-down');
optionsChevron.classList.toggle('fa-chevron-up');
});
// Helper functions
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();
// For demo purposes, we'll use the sample data
// In a real app, you would read the actual files here
const notesContent = sampleNotes;
const mappingContent = sampleMapping;
// Display original text
originalText.textContent = notes
</html>