🔊 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é.
      📌 Selecciona un idioma i prem “🔊” per veure quina veu s’utilitza.
    
    <!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;
      padding: 8px;
      margin: 8px 0;
      width: 100%;
      height: 320px;
      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;
    }
    #debugInfo {
      margin: 10px 0;
      padding: 8px;
      background: #fff;
      border-radius: 5px;
      font-size: 0.9em;
      color: #333;
      border: 1px solid #ccc;
    }
    .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>
    <!-- 🔍 Indicador de diagnòstic -->
    <div id="debugInfo">
      📌 Selecciona un idioma i prem "🔊" per veure quina veu s'utilitza.
    </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');
      const debugInfo = document.getElementById('debugInfo');
      let voices = [];
      let isSpeaking = false;
      let liniesGuardades = [];
      let currentIndex = 0;
      function loadVoices() {
        voices = speechSynthesis.getVoices();
        // Opcional: mostra totes les veus disponibles al carregar (per depuració)
        // console.log('Veus disponibles:', voices.map(v => `${v.name} (${v.lang})`));
      }
      speechSynthesis.onvoiceschanged = loadVoices;
      loadVoices();
      const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');
      if (idiomaGuardat) {
        voiceSelect.value = idiomaGuardat;
      }
      voiceSelect.addEventListener('change', () => {
        localStorage.setItem('idiomaSeleccionat', voiceSelect.value);
        // Actualitzem el missatge quan es canvia l'idioma
        debugInfo.textContent = `📌 Idioma seleccionat: ${voiceSelect.options[voiceSelect.selectedIndex].text}`;
      });
      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;
      }
      // 🔍 Funció per trobar la millor veu i mostrar informació
      function getBestVoiceAndReport(langCode) {
        if (!langCode) {
          debugInfo.innerHTML = `ℹ️ Cap idioma seleccionat. S'utilitzarà la veu per defecte.`;
          return null;
        }
        // Normalitzem per a comparacions flexibles
        const normalizedTarget = langCode.replace('-', '_').toLowerCase();
        // 1. Cerca exacta (amb normalització)
        let best = voices.find(v => 
          v.lang.replace('-', '_').toLowerCase() === normalizedTarget
        );
        if (best) {
          debugInfo.innerHTML = `✅ Idioma demanat: <strong>${langCode}</strong><br>🗣️ Veus trobada: <strong>${best.name}</strong> (${best.lang})`;
          return best;
        }
        // 2. Cerca per prefix (p. ex., "ca" per a "ca-ES")
        const prefix = langCode.split('-')[0].toLowerCase();
        best = voices.find(v => v.lang.toLowerCase().startsWith(prefix));
        if (best) {
          debugInfo.innerHTML = `⚠️ Idioma demanat: <strong>${langCode}</strong><br>🗣️ Veus aproximada: <strong>${best.name}</strong> (${best.lang})`;
          return best;
        }
        // 3. No s'ha trobat cap veu
        debugInfo.innerHTML = `❌ Idioma demanat: <strong>${langCode}</strong><br>🚫 Cap veu trobada. Comprova que tens l'idioma instal·lat a Android.`;
        return null;
      }
      function speakLinia() {
        if (currentIndex >= liniesGuardades.length) {
          isSpeaking = false;
          setTimeout(() => {
            speakText();
          }, 2000);
          return;
        }
        const linia = liniesGuardades[currentIndex];
        const utterance = new SpeechSynthesisUtterance(linia);
        const selectedLang = voiceSelect.value;
        const voice = getBestVoiceAndReport(selectedLang);
        if (voice) {
          utterance.voice = voice;
        }
        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>
Si s’executa des d’android s’ha d’ajusta la llengua desde configuració/config adicional/idioma/sortida TTS