pranit144's picture
Upload 6 files
4f3952b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎙️ Voice Translator </title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #8c6db0;
--primary-light: #b995e5;
--primary-dark: #6a4c91;
--accent-color: #30b8a8;
--accent-dark: #228b7f;
--gradient-start: #9575cd;
--gradient-end: #7986cb;
--text-color: #3a3a3a;
--text-secondary: #6b7280;
--light-bg: #f8f9fa;
--card-bg: #ffffff;
--panel-bg: #f5f7fb;
--border-radius: 16px;
--box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
}
body {
background: linear-gradient(135deg, var(--light-bg) 0%, #edf2f7 100%);
color: var(--text-color);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
min-height: 100vh;
overflow-x: hidden;
}
.app-container {
max-width: 900px;
margin: 40px auto;
padding: 0 20px;
}
.translator-card {
background: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 30px;
margin-bottom: 30px;
border: none;
position: relative;
overflow: hidden;
}
.translator-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, var(--gradient-start), var(--gradient-end), var(--accent-color));
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.app-header {
text-align: center;
margin-bottom: 30px;
position: relative;
}
.app-title {
font-weight: 700;
margin-bottom: 10px;
font-size: 2.5rem;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
}
.app-subtitle {
color: var(--text-secondary);
font-size: 1.1rem;
margin-bottom: 0;
}
.language-select-container {
position: relative;
margin-bottom: 30px;
}
.language-select {
background-color: var(--panel-bg);
border: 2px solid rgba(0, 0, 0, 0.05);
border-radius: var(--border-radius);
padding: 14px 20px;
color: var(--text-color);
font-size: 1.1rem;
transition: all 0.3s ease;
width: 100%;
appearance: none;
}
.language-select:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(48, 184, 168, 0.25);
outline: none;
}
.language-select-icon {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--accent-color);
pointer-events: none;
}
.control-buttons {
display: flex;
gap: 20px;
justify-content: center;
margin: 25px 0 35px;
}
.btn {
padding: 14px 30px;
border-radius: 50px;
font-weight: 600;
font-size: 1.1rem;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
z-index: 1;
}
.btn::after {
content: '';
position: absolute;
bottom: -50%;
left: -10%;
width: 120%;
height: 200%;
background: rgba(255, 255, 255, 0.3);
border-radius: 40%;
transform: scale(0);
transition: transform 0.5s;
z-index: -1;
}
.btn:hover::after {
transform: scale(1);
}
.btn:active {
transform: scale(0.97);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--gradient-start));
border: none;
box-shadow: 0 4px 15px rgba(140, 109, 176, 0.3);
color: white;
}
.btn-primary:hover {
box-shadow: 0 6px 20px rgba(140, 109, 176, 0.4);
background: linear-gradient(135deg, var(--primary-light), var(--gradient-start));
}
.btn-danger {
background: linear-gradient(135deg, var(--accent-dark), var(--accent-color));
border: none;
box-shadow: 0 4px 15px rgba(48, 184, 168, 0.3);
color: white;
}
.btn-danger:hover {
box-shadow: 0 6px 20px rgba(48, 184, 168, 0.4);
background: linear-gradient(135deg, var(--accent-color), #44d6c6);
}
.btn-icon {
margin-right: 10px;
font-size: 1.2rem;
}
.text-panel-container {
position: relative;
margin-bottom: 30px;
}
.text-panel {
border-radius: var(--border-radius);
background-color: var(--panel-bg);
padding: 25px;
min-height: 150px;
margin-top: 10px;
white-space: pre-wrap;
border: 2px solid rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
color: var(--text-color);
font-size: 1.1rem;
line-height: 1.7;
position: relative;
}
.text-panel.active {
border-color: var(--accent-color);
box-shadow: 0 0 20px rgba(48, 184, 168, 0.15);
}
.text-panel-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.text-panel-icon {
width: 42px;
height: 42px;
background: linear-gradient(135deg, var(--primary-light), var(--primary-color));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
color: white;
font-size: 1.3rem;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.panel-title {
font-weight: 600;
font-size: 1.2rem;
margin: 0;
letter-spacing: 0.5px;
color: var(--primary-dark);
}
.footer {
text-align: center;
margin-top: 40px;
color: var(--text-secondary);
font-size: 0.95rem;
opacity: 0.8;
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(48, 184, 168, 0.4);
}
70% {
box-shadow: 0 0 0 15px rgba(48, 184, 168, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(48, 184, 168, 0);
}
}
.status-indicator {
display: none;
align-items: center;
justify-content: center;
margin: 20px 0;
color: var(--accent-color);
font-weight: 500;
font-size: 1.1rem;
letter-spacing: 0.5px;
}
.status-indicator.active {
display: flex;
}
.audio-wave {
display: inline-flex;
align-items: flex-end;
height: 20px;
margin-right: 12px;
}
.audio-wave span {
display: inline-block;
width: 3px;
margin-right: 3px;
background: var(--accent-color);
animation: wave 1.2s infinite ease-in-out;
}
.audio-wave span:nth-child(1) { height: 8px; animation-delay: 0s; }
.audio-wave span:nth-child(2) { height: 16px; animation-delay: 0.2s; }
.audio-wave span:nth-child(3) { height: 12px; animation-delay: 0.3s; }
.audio-wave span:nth-child(4) { height: 20px; animation-delay: 0.4s; }
.audio-wave span:nth-child(5) { height: 14px; animation-delay: 0.5s; }
@keyframes wave {
0%, 100% { transform: scaleY(1); }
50% { transform: scaleY(0.5); }
}
.floating-icon {
position: absolute;
font-size: 8rem;
color: rgba(140, 109, 176, 0.07);
z-index: 0;
pointer-events: none;
}
.floating-icon.globe {
bottom: -2rem;
right: -2rem;
}
.floating-icon.mic {
top: -1rem;
left: -1rem;
transform: rotate(-15deg);
font-size: 6rem;
}
.translate-animation {
position: relative;
}
.translate-animation::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
transparent,
rgba(48, 184, 168, 0.2),
transparent);
transform: translateX(-100%);
animation: translateShimmer 2s infinite;
}
@keyframes translateShimmer {
100% { transform: translateX(100%); }
}
.spinner {
display: inline-block;
width: 18px;
height: 18px;
border: 3px solid rgba(48, 184, 168, 0.3);
border-top-color: var(--accent-color);
border-radius: 50%;
margin-right: 10px;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.language-badge {
position: absolute;
top: 15px;
right: 15px;
background: rgba(48, 184, 168, 0.1);
color: var(--accent-dark);
padding: 5px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 5px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.app-title {
font-size: 2rem;
}
.app-subtitle {
font-size: 0.95rem;
}
.control-buttons {
flex-direction: column;
gap: 15px;
}
.btn {
width: 100%;
padding: 12px;
}
.text-panel {
min-height: 120px;
padding: 20px;
font-size: 1rem;
}
}
/* Light scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: var(--panel-bg);
}
::-webkit-scrollbar-thumb {
background: var(--primary-light);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--primary-color);
}
</style>
</head>
<body>
<div class="app-container">
<div class="translator-card">
<div class="floating-icon mic">
<i class="fas fa-microphone"></i>
</div>
<div class="floating-icon globe">
<i class="fas fa-globe"></i>
</div>
<div class="app-header">
<h1 class="app-title"><i class="fas fa-microphone-alt"></i> Voice Translator </h1>
<p class="app-subtitle">Speak naturally. Translate instantly. Connect globally.</p>
</div>
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="language-select-container">
<select id="lang" class="form-select language-select">
{% for name, code in languages.items() %}
<option value="{{ code }}">{{ name }}</option>
{% endfor %}
</select>
<div class="language-select-icon">
<i class="fas fa-language fa-lg"></i>
</div>
</div>
</div>
</div>
<div class="control-buttons">
<button id="startBtn" class="btn btn-primary">
<i class="fas fa-play-circle btn-icon"></i> Start Listening
</button>
<button id="stopBtn" class="btn btn-danger" disabled>
<i class="fas fa-stop-circle btn-icon"></i> Stop & Translate
</button>
</div>
<div class="status-indicator" id="listeningIndicator">
<div class="audio-wave">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
Listening to your voice...
</div>
<div class="text-panel-container">
<div class="text-panel-header">
<div class="text-panel-icon">
<i class="fas fa-headphones"></i>
</div>
<h5 class="panel-title">Recognized Speech</h5>
</div>
<div class="language-badge">
<i class="fas fa-flag"></i> English
</div>
<div id="recognized" class="text-panel"></div>
</div>
<div class="text-panel-container">
<div class="text-panel-header">
<div class="text-panel-icon">
<i class="fas fa-language"></i>
</div>
<h5 class="panel-title">Translation</h5>
</div>
<div class="language-badge" id="targetLanguageBadge">
<i class="fas fa-flag"></i> <span id="langDisplay">Hindi</span>
</div>
<div id="translated" class="text-panel"></div>
</div>
</div>
<div class="footer">
<p>
<i class="fas fa-magic"></i> Powered by Flask
<br>
<small>© 2025 Voice Translator • Speak clearly for optimal recognition</small>
</p>
</div>
</div>
<script>
// Check for browser support
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
alert('Your browser does not support Speech Recognition API.');
}
const recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = 'en-US';
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const recognizedDiv = document.getElementById('recognized');
const translatedDiv = document.getElementById('translated');
const listeningIndicator = document.getElementById('listeningIndicator');
const langSelect = document.getElementById('lang');
const langDisplay = document.getElementById('langDisplay');
// Set initial language display
updateLanguageDisplay();
let finalTranscript = '';
function updateLanguageDisplay() {
const selectedOption = langSelect.options[langSelect.selectedIndex];
langDisplay.textContent = selectedOption.text;
}
langSelect.addEventListener('change', updateLanguageDisplay);
recognition.onresult = (event) => {
let interimTranscript = '';
for (let i = event.resultIndex; i < event.results.length; ++i) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript + ' ';
} else {
interimTranscript += transcript;
}
}
// Display live recognized speech
recognizedDiv.innerHTML = finalTranscript +
`<span style="opacity: 0.7;">${interimTranscript}</span>`;
};
recognition.onstart = () => {
listeningIndicator.classList.add('active');
recognizedDiv.classList.add('active');
recognizedDiv.classList.add('pulse-animation');
};
recognition.onend = () => {
listeningIndicator.classList.remove('active');
recognizedDiv.classList.remove('pulse-animation');
};
recognition.onerror = (event) => {
console.error('Speech recognition error', event.error);
listeningIndicator.classList.remove('active');
recognizedDiv.classList.remove('pulse-animation');
recognizedDiv.classList.remove('active');
if (event.error === 'no-speech') {
recognizedDiv.innerHTML = '<span style="color: var(--accent-color);">No speech detected. Please try again.</span>';
} else {
recognizedDiv.innerHTML = `<span style="color: var(--accent-color);">Error: ${event.error}. Please try again.</span>`;
}
};
startBtn.onclick = () => {
finalTranscript = '';
recognizedDiv.innerText = '';
translatedDiv.innerText = '';
translatedDiv.classList.remove('active');
recognition.start();
startBtn.disabled = true;
stopBtn.disabled = false;
// Add animation effects
startBtn.style.transform = 'scale(0.95)';
setTimeout(() => startBtn.style.transform = '', 200);
};
stopBtn.onclick = async () => {
recognition.stop();
startBtn.disabled = false;
stopBtn.disabled = true;
recognizedDiv.classList.remove('active');
// Add animation effects
stopBtn.style.transform = 'scale(0.95)';
setTimeout(() => stopBtn.style.transform = '', 200);
const textToTranslate = finalTranscript.trim();
if (!textToTranslate) {
translatedDiv.innerHTML = '<span style="color: var(--accent-color);">No speech detected. Please speak before stopping.</span>';
return;
}
// Show loading state
translatedDiv.innerHTML = '<div class="text-center"><span class="spinner"></span> Translating your speech...</div>';
translatedDiv.classList.add('translate-animation');
try {
// Send text for translation
const response = await fetch('/translate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: textToTranslate, target: document.getElementById('lang').value })
});
const data = await response.json();
setTimeout(() => {
translatedDiv.classList.remove('translate-animation');
translatedDiv.classList.add('active');
translatedDiv.innerText = data.translation;
}, 500);
} catch (error) {
translatedDiv.classList.remove('translate-animation');
translatedDiv.innerHTML = '<span style="color: var(--accent-color);">Error: Could not complete translation. Please try again.</span>';
}
};
// Add some fancy animations on page load
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.translator-card, .app-title, .app-subtitle, .text-panel-container').forEach((el, i) => {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
el.style.transition = `opacity 0.5s ease-out ${i * 0.1}s, transform 0.5s ease-out ${i * 0.1}s`;
setTimeout(() => {
el.style.opacity = '1';
el.style.transform = 'translateY(0)';
}, 100);
});
});
</script>
</body>
</html>