🔊 Text a veu
to run into somebody.
have a good trip.
to pass on greetings.
there’s a restroom in the café.
have a good trip.
to pass on greetings.
there’s a restroom in the café.
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8"
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Caixa Comodí - Text a veu</title>
<link rel="icon" href="calculadora.ico" type="image/x-icon">
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: flex-start;
background-color: #f0f0f0;
height: 100vh;
}
.container {
background-color: rgb(213, 225, 225);
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
max-width: 440px;
width: 100%;
height: 60%;
margin-top: 30px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
h2 {
text-align: center;
color: #333;
margin: 0 0 15px 0;
font-size: 1.4em;
}
#editor {
font-size: 1.3em; /*tamany del text del contenedor */
padding: 8px;
margin: 8px 0;
width: 100%;
height: 320px; /* ← 2,5 vegades més alt que abans (120px → 300px) */
overflow-y: auto;
border: 1px solid #ccc;
white-space: pre-wrap;
background: white;
border-radius: 5px;
box-sizing: border-box;
}
select {
font-size: 1em;
padding: 6px;
margin: 6px 4px 0 0;
width: calc(50% - 8px);
height: 36px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
}
.controls {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 12px;
}
#speakBtn, #toggleBtn {
font-size: 24px;
background: #007bff;
color: white;
border: none;
border-radius: 8px;
width: 50px;
height: 50px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
#speakBtn:hover, #toggleBtn:hover {
background: #0056b3;
}
.linia {
display: block;
padding: 0.2em;
transition: background-color 0.3s ease;
}
.activa {
background-color: hsl(180, 1%, 72%);
}
</style>
</head>
<body>
<div class="container">
<h2>🔊 Text a veu</h2>
<div id="editor" contenteditable="true" placeholder="Escriu el text que vols escoltar">
to run into somebody.<br>
have a good trip.<br>
to pass on greetings.<br>
there’s a restroom in the café.
</div>
<div>
<select id="voiceSelect">
<option value="">Idioma</option>
<option value="en-US">🇺🇸 Anglès (US)</option>
<option value="en-GB">🇬🇧 Anglès (UK)</option>
<option value="fr-FR">🇫🇷 Francès</option>
<option value="de-DE">🇩🇪 Alemany</option>
<option value="it-IT">🇮🇹 Italià</option>
<option value="pt-PT">🇵🇹 Portuguès</option>
<option value="es-ES">🇪🇸 Espanyol</option>
<option value="ca-ES">🎗️ Català</option>
</select>
<select id="pauseSelect">
<option value="1">⏱️ Pausa x1</option>
<option value="2">⏱️ Pausa x2</option>
<option value="3">⏱️ Pausa x3</option>
<option value="4">⏱️ Pausa x4</option>
<option value="10">⏱️ Pausa x10</option>
</select>
</div>
<div class="controls">
<button id="speakBtn" title="Escolta el text">🔊</button>
<button id="toggleBtn" title="Atura o reprèn">⏹️</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const editor = document.getElementById('editor');
const speakBtn = document.getElementById('speakBtn');
const toggleBtn = document.getElementById('toggleBtn');
const voiceSelect = document.getElementById('voiceSelect');
const pauseSelect = document.getElementById('pauseSelect');
let voices = [];
let isSpeaking = false;
let liniesGuardades = [];
let currentIndex = 0;
function loadVoices() {
voices = speechSynthesis.getVoices();
}
speechSynthesis.onvoiceschanged = loadVoices;
loadVoices();
const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');
if (idiomaGuardat) {
voiceSelect.value = idiomaGuardat;
}
voiceSelect.addEventListener('change', () => {
localStorage.setItem('idiomaSeleccionat', voiceSelect.value);
});
function getLinies() {
const text = editor.innerText || editor.textContent;
return text.split('\n').map(l => l.trim()).filter(l => l);
}
function marcarLinia(index) {
const linies = editor.querySelectorAll('.linia');
linies.forEach((el, i) => {
el.classList.toggle('activa', i === index);
});
}
function prepararEditor() {
const linies = getLinies();
editor.innerHTML = '';
linies.forEach((text, i) => {
const span = document.createElement('span');
span.className = 'linia';
span.textContent = text;
editor.appendChild(span);
editor.appendChild(document.createElement('br'));
});
}
function getPausaEntreLinies() {
const factor = parseInt(pauseSelect.value || '1');
return 500 * factor;
}
function speakLinia() {
if (currentIndex >= liniesGuardades.length) {
isSpeaking = false;
setTimeout(() => {
speakText();
}, 2000);
return;
}
const linia = liniesGuardades[currentIndex];
const utterance = new SpeechSynthesisUtterance(linia);
const voice = voices.find(v => v.lang === voiceSelect.value);
if (voice) {
utterance.voice = voice;
utterance.lang = voice.lang;
}
utterance.rate = 1;
utterance.pitch = 1;
utterance.onstart = () => {
isSpeaking = true;
marcarLinia(currentIndex);
};
utterance.onend = () => {
currentIndex++;
setTimeout(() => {
speakLinia();
}, getPausaEntreLinies());
};
speechSynthesis.speak(utterance);
}
function speakText() {
if (isSpeaking) return;
prepararEditor();
liniesGuardades = getLinies();
currentIndex = 0;
speechSynthesis.cancel();
speakLinia();
}
speakBtn.addEventListener('click', speakText);
toggleBtn.addEventListener('click', () => {
if (isSpeaking) {
speechSynthesis.cancel();
isSpeaking = false;
} else {
speakLinia();
}
});
});
</script>
</body>
</html>