{"id":16876,"date":"2025-10-25T17:37:59","date_gmt":"2025-10-25T15:37:59","guid":{"rendered":"https:\/\/www.beseit.net\/?p=16876"},"modified":"2026-04-09T09:22:41","modified_gmt":"2026-04-09T07:22:41","slug":"%f0%9f%94%8a-conversor-de-text-a-veu","status":"publish","type":"post","link":"https:\/\/www.beseit.net\/?p=16876","title":{"rendered":"\u2721\ufe0f\u2721\ufe0f Conversor de text a veu"},"content":{"rendered":"<style>\n    body {<br \/>\n      font-family: Arial, sans-serif;<br \/>\n      margin: 0;<br \/>\n      padding: 0;<br \/>\n      display: flex;<br \/>\n      justify-content: center;<br \/>\n      align-items: flex-start;<br \/>\n      background-color: #f0f0f0;<br \/>\n      height: 100vh;<br \/>\n    }<\/p>\n<p>    .container {<br \/>\n      background-color: rgb(213, 225, 225);<br \/>\n      padding: 20px;<br \/>\n      border-radius: 10px;<br \/>\n      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);<br \/>\n      max-width: 440px;<br \/>\n      width: 100%;<br \/>\n      height: 60%;<br \/>\n      margin-top: 30px;<br \/>\n      box-sizing: border-box;<br \/>\n      display: flex;<br \/>\n      flex-direction: column;<br \/>\n      overflow: hidden;<br \/>\n    }<\/p>\n<p>    h2 {<br \/>\n      text-align: center;<br \/>\n      color: #333;<br \/>\n      margin: 0 0 15px 0;<br \/>\n      font-size: 1.4em;<br \/>\n    }<\/p>\n<p>    #editor {<br \/>\n      font-size: 1.3em;<br \/>\n      padding: 8px;<br \/>\n      margin: 8px 0;<br \/>\n      width: 100%;<br \/>\n      height: 320px;<br \/>\n      overflow-y: auto;<br \/>\n      border: 1px solid #ccc;<br \/>\n      white-space: pre-wrap;<br \/>\n      background: white;<br \/>\n      border-radius: 5px;<br \/>\n      box-sizing: border-box;<br \/>\n    }<\/p>\n<p>    select {<br \/>\n      font-size: 1em;<br \/>\n      padding: 6px;<br \/>\n      margin: 6px 4px 0 0;<br \/>\n      width: calc(50% - 8px);<br \/>\n      height: 36px;<br \/>\n      border: 1px solid #ccc;<br \/>\n      border-radius: 5px;<br \/>\n      box-sizing: border-box;<br \/>\n    }<\/p>\n<p>    #debugInfo {<br \/>\n      margin: 10px 0;<br \/>\n      padding: 8px;<br \/>\n      background: #fff;<br \/>\n      border-radius: 5px;<br \/>\n      font-size: 0.9em;<br \/>\n      color: #333;<br \/>\n      border: 1px solid #ccc;<br \/>\n    }<\/p>\n<p>    .controls {<br \/>\n      display: flex;<br \/>\n      justify-content: center;<br \/>\n      gap: 16px;<br \/>\n      margin-top: 12px;<br \/>\n    }<\/p>\n<p>    #speakBtn, #toggleBtn {<br \/>\n      font-size: 24px;<br \/>\n      background: #007bff;<br \/>\n      color: white;<br \/>\n      border: none;<br \/>\n      border-radius: 8px;<br \/>\n      width: 50px;<br \/>\n      height: 50px;<br \/>\n      cursor: pointer;<br \/>\n      display: flex;<br \/>\n      align-items: center;<br \/>\n      justify-content: center;<br \/>\n    }<\/p>\n<p>    #speakBtn:hover, #toggleBtn:hover {<br \/>\n      background: #0056b3;<br \/>\n    }<\/p>\n<p>    .linia {<br \/>\n      display: block;<br \/>\n      padding: 0.2em;<br \/>\n      transition: background-color 0.3s ease;<br \/>\n    }<\/p>\n<p>    .activa {<br \/>\n      background-color: hsl(180, 1%, 72%);<br \/>\n    }<br \/>\n  <\/style>\n<div class=\"container\">\n<h2><a href=\"https:\/\/www.beseit.net\/?p=17838\"><img decoding=\"async\" class=\"emoji\" role=\"img\" draggable=\"false\" src=\"https:\/\/s.w.org\/images\/core\/emoji\/17.0.2\/svg\/2721.svg\" alt=\"\u2721\ufe0f\" \/><\/a>\u00a0Text a veu<\/h2>\n<div id=\"editor\" contenteditable=\"true\">to run into somebody.\nhave a good trip.\nto pass on greetings.\nthere\u2019s a restroom in the caf\u00e9.<\/div>\n<div><select id=\"voiceSelect\">\n<option value=\"\">Idioma<\/option>\n<option value=\"en-US\">?? Angl\u00e8s (US)<\/option>\n<option value=\"en-GB\">?? Angl\u00e8s (UK)<\/option>\n<option value=\"fr-FR\">?? Franc\u00e8s<\/option>\n<option value=\"de-DE\">?? Alemany<\/option>\n<option value=\"it-IT\">?? Itali\u00e0<\/option>\n<option value=\"pt-PT\">?? Portugu\u00e8s<\/option>\n<option value=\"es-ES\">?? Espanyol<\/option>\n<option value=\"ca-ES\">?\ufe0f Catal\u00e0<\/option>\n<\/select><select id=\"pauseSelect\">\n<option value=\"1\">\u23f1\ufe0f Pausa x1<\/option>\n<option value=\"2\">\u23f1\ufe0f Pausa x2<\/option>\n<option value=\"3\">\u23f1\ufe0f Pausa x3<\/option>\n<option value=\"4\">\u23f1\ufe0f Pausa x4<\/option>\n<option value=\"10\">\u23f1\ufe0f Pausa x10<\/option>\n<\/select>\n\n<\/div>\n<!-- ? Indicador de diagn\u00f2stic -->\n<div id=\"debugInfo\">? Selecciona un idioma i prem &#8220;?&#8221; per veure quina veu s&#8217;utilitza.<\/div>\n<div class=\"controls\"><button id=\"speakBtn\" title=\"Escolta el text\">?<\/button>\n<button id=\"toggleBtn\" title=\"Atura o repr\u00e8n\">\u23f9\ufe0f<\/button><\/div>\n<\/div>\n<script><br \/>\n    document.addEventListener('DOMContentLoaded', () => {<br \/>\n      const editor = document.getElementById('editor');<br \/>\n      const speakBtn = document.getElementById('speakBtn');<br \/>\n      const toggleBtn = document.getElementById('toggleBtn');<br \/>\n      const voiceSelect = document.getElementById('voiceSelect');<br \/>\n      const pauseSelect = document.getElementById('pauseSelect');<br \/>\n      const debugInfo = document.getElementById('debugInfo');<\/p>\n<p>      let voices = [];<br \/>\n      let isSpeaking = false;<br \/>\n      let liniesGuardades = [];<br \/>\n      let currentIndex = 0;<\/p>\n<p>      function loadVoices() {<br \/>\n        voices = speechSynthesis.getVoices();<br \/>\n        \/\/ Opcional: mostra totes les veus disponibles al carregar (per depuraci\u00f3)<br \/>\n        \/\/ console.log('Veus disponibles:', voices.map(v => `${v.name} (${v.lang})`));<br \/>\n      }<\/p>\n<p>      speechSynthesis.onvoiceschanged = loadVoices;<br \/>\n      loadVoices();<\/p>\n<p>      const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');<br \/>\n      if (idiomaGuardat) {<br \/>\n        voiceSelect.value = idiomaGuardat;<br \/>\n      }<\/p>\n<p>      voiceSelect.addEventListener('change', () => {<br \/>\n        localStorage.setItem('idiomaSeleccionat', voiceSelect.value);<br \/>\n        \/\/ Actualitzem el missatge quan es canvia l'idioma<br \/>\n        debugInfo.textContent = `? Idioma seleccionat: ${voiceSelect.options[voiceSelect.selectedIndex].text}`;<br \/>\n      });<\/p>\n<p>      function getLinies() {<br \/>\n        const text = editor.innerText || editor.textContent;<br \/>\n        return text.split('\\n').map(l => l.trim()).filter(l => l);<br \/>\n      }<\/p>\n<p>      function marcarLinia(index) {<br \/>\n        const linies = editor.querySelectorAll('.linia');<br \/>\n        linies.forEach((el, i) => {<br \/>\n          el.classList.toggle('activa', i === index);<br \/>\n        });<br \/>\n      }<\/p>\n<p>      function prepararEditor() {<br \/>\n        const linies = getLinies();<br \/>\n        editor.innerHTML = '';<br \/>\n        linies.forEach((text, i) => {<br \/>\n          const span = document.createElement('span');<br \/>\n          span.className = 'linia';<br \/>\n          span.textContent = text;<br \/>\n          editor.appendChild(span);<br \/>\n          editor.appendChild(document.createElement('br'));<br \/>\n        });<br \/>\n      }<\/p>\n<p>      function getPausaEntreLinies() {<br \/>\n        const factor = parseInt(pauseSelect.value || '1');<br \/>\n        return 500 * factor;<br \/>\n      }<\/p>\n<p>      \/\/ ? Funci\u00f3 per trobar la millor veu i mostrar informaci\u00f3<br \/>\n      function getBestVoiceAndReport(langCode) {<br \/>\n        if (!langCode) {<br \/>\n          debugInfo.innerHTML = `\u2139\ufe0f Cap idioma seleccionat. S'utilitzar\u00e0 la veu per defecte.`;<br \/>\n          return null;<br \/>\n        }<\/p>\n<p>        \/\/ Normalitzem per a comparacions flexibles<br \/>\n        const normalizedTarget = langCode.replace('-', '_').toLowerCase();<\/p>\n<p>        \/\/ 1. Cerca exacta (amb normalitzaci\u00f3)<br \/>\n        let best = voices.find(v =><br \/>\n          v.lang.replace('-', '_').toLowerCase() === normalizedTarget<br \/>\n        );<\/p>\n<p>        if (best) {<br \/>\n          debugInfo.innerHTML = `\u2705 Idioma demanat: <strong>${langCode}<\/strong><br \/>?\ufe0f Veus trobada: <strong>${best.name}<\/strong> (${best.lang})`;<br \/>\n          return best;<br \/>\n        }<\/p>\n<p>        \/\/ 2. Cerca per prefix (p. ex., \"ca\" per a \"ca-ES\")<br \/>\n        const prefix = langCode.split('-')[0].toLowerCase();<br \/>\n        best = voices.find(v => v.lang.toLowerCase().startsWith(prefix));<\/p>\n<p>        if (best) {<br \/>\n          debugInfo.innerHTML = `\u26a0\ufe0f Idioma demanat: <strong>${langCode}<\/strong><br \/>?\ufe0f Veus aproximada: <strong>${best.name}<\/strong> (${best.lang})`;<br \/>\n          return best;<br \/>\n        }<\/p>\n<p>        \/\/ 3. No s'ha trobat cap veu<br \/>\n        debugInfo.innerHTML = `\u274c Idioma demanat: <strong>${langCode}<\/strong><br \/>? Cap veu trobada. Comprova que tens l'idioma instal\u00b7lat a Android.`;<br \/>\n        return null;<br \/>\n      }<\/p>\n<p>      function speakLinia() {<br \/>\n        if (currentIndex >= liniesGuardades.length) {<br \/>\n          isSpeaking = false;<br \/>\n          setTimeout(() => {<br \/>\n            speakText();<br \/>\n          }, 2000);<br \/>\n          return;<br \/>\n        }<\/p>\n<p>        const linia = liniesGuardades[currentIndex];<br \/>\n        const utterance = new SpeechSynthesisUtterance(linia);<\/p>\n<p>        const selectedLang = voiceSelect.value;<br \/>\n        const voice = getBestVoiceAndReport(selectedLang);<br \/>\n        if (voice) {<br \/>\n          utterance.voice = voice;<br \/>\n        }<\/p>\n<p>        utterance.rate = 1;<br \/>\n        utterance.pitch = 1;<\/p>\n<p>        utterance.onstart = () => {<br \/>\n          isSpeaking = true;<br \/>\n          marcarLinia(currentIndex);<br \/>\n        };<\/p>\n<p>        utterance.onend = () => {<br \/>\n          currentIndex++;<br \/>\n          setTimeout(() => {<br \/>\n            speakLinia();<br \/>\n          }, getPausaEntreLinies());<br \/>\n        };<\/p>\n<p>        speechSynthesis.speak(utterance);<br \/>\n      }<\/p>\n<p>      function speakText() {<br \/>\n        if (isSpeaking) return;<br \/>\n        prepararEditor();<br \/>\n        liniesGuardades = getLinies();<br \/>\n        currentIndex = 0;<br \/>\n        speechSynthesis.cancel();<br \/>\n        speakLinia();<br \/>\n      }<\/p>\n<p>      speakBtn.addEventListener('click', speakText);<\/p>\n<p>      toggleBtn.addEventListener('click', () => {<br \/>\n        if (isSpeaking) {<br \/>\n          speechSynthesis.cancel();<br \/>\n          isSpeaking = false;<br \/>\n        } else {<br \/>\n          speakLinia();<br \/>\n        }<br \/>\n      });<br \/>\n    });<br \/>\n  <\/script>\n\n&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n\n<!-- wp:paragraph MUT-->\n\n<!-- \/wp:post-content -->\n\n<!-- wp:enlighter\/codeblock -->\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"ca\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\"&gt;\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"&gt;\n  &lt;title&gt;Caixa Comod\u00ed - Text a veu&lt;\/title&gt;\n  &lt;link rel=\"icon\" href=\"calculadora.ico\" type=\"image\/x-icon\"&gt;\n  &lt;style&gt;\n    body {\n      font-family: Arial, sans-serif;\n      margin: 0;\n      padding: 0;\n      display: flex;\n      justify-content: center;\n      align-items: flex-start;\n      background-color: #f0f0f0;\n      height: 100vh;\n    }\n\n    .container {\n      background-color: rgb(213, 225, 225);\n      padding: 20px;\n      border-radius: 10px;\n      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n      max-width: 440px;\n      width: 100%;\n      height: 60%;\n      margin-top: 30px;\n      box-sizing: border-box;\n      display: flex;\n      flex-direction: column;\n      overflow: hidden;\n    }\n\n    h2 {\n      text-align: center;\n      color: #333;\n      margin: 0 0 15px 0;\n      font-size: 1.4em;\n    }\n\n    #editor {\n      font-size: 1.3em;\n      padding: 8px;\n      margin: 8px 0;\n      width: 100%;\n      height: 320px;\n      overflow-y: auto;\n      border: 1px solid #ccc;\n      white-space: pre-wrap;\n      background: white;\n      border-radius: 5px;\n      box-sizing: border-box;\n    }\n\n    select {\n      font-size: 1em;\n      padding: 6px;\n      margin: 6px 4px 0 0;\n      width: calc(50% - 8px);\n      height: 36px;\n      border: 1px solid #ccc;\n      border-radius: 5px;\n      box-sizing: border-box;\n    }\n\n    #debugInfo {\n      margin: 10px 0;\n      padding: 8px;\n      background: #fff;\n      border-radius: 5px;\n      font-size: 0.9em;\n      color: #333;\n      border: 1px solid #ccc;\n    }\n\n    .controls {\n      display: flex;\n      justify-content: center;\n      gap: 16px;\n      margin-top: 12px;\n    }\n\n    #speakBtn, #toggleBtn {\n      font-size: 24px;\n      background: #007bff;\n      color: white;\n      border: none;\n      border-radius: 8px;\n      width: 50px;\n      height: 50px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n    }\n\n    #speakBtn:hover, #toggleBtn:hover {\n      background: #0056b3;\n    }\n\n    .linia {\n      display: block;\n      padding: 0.2em;\n      transition: background-color 0.3s ease;\n    }\n\n    .activa {\n      background-color: hsl(180, 1%, 72%);\n    }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;h2&gt;? Text a veu&lt;\/h2&gt;\n\n    &lt;div id=\"editor\" contenteditable=\"true\" placeholder=\"Escriu el text que vols escoltar\"&gt;\nto run into somebody.&lt;br&gt;\nhave a good trip.&lt;br&gt;\nto pass on greetings.&lt;br&gt;\nthere\u2019s a restroom in the caf\u00e9.\n    &lt;\/div&gt;\n    &lt;div&gt;\n      &lt;select id=\"voiceSelect\"&gt;\n        &lt;option value=\"\"&gt;Idioma&lt;\/option&gt;\n        &lt;option value=\"en-US\"&gt;?? Angl\u00e8s (US)&lt;\/option&gt;\n        &lt;option value=\"en-GB\"&gt;?? Angl\u00e8s (UK)&lt;\/option&gt;\n        &lt;option value=\"fr-FR\"&gt;?? Franc\u00e8s&lt;\/option&gt;\n        &lt;option value=\"de-DE\"&gt;?? Alemany&lt;\/option&gt;\n        &lt;option value=\"it-IT\"&gt;?? Itali\u00e0&lt;\/option&gt;\n        &lt;option value=\"pt-PT\"&gt;?? Portugu\u00e8s&lt;\/option&gt;\n        &lt;option value=\"es-ES\"&gt;?? Espanyol&lt;\/option&gt;\n        &lt;option value=\"ca-ES\"&gt;?\ufe0f Catal\u00e0&lt;\/option&gt;\n      &lt;\/select&gt;\n\n      &lt;select id=\"pauseSelect\"&gt;\n        &lt;option value=\"1\"&gt;\u23f1\ufe0f Pausa x1&lt;\/option&gt;\n        &lt;option value=\"2\"&gt;\u23f1\ufe0f Pausa x2&lt;\/option&gt;\n        &lt;option value=\"3\"&gt;\u23f1\ufe0f Pausa x3&lt;\/option&gt;\n        &lt;option value=\"4\"&gt;\u23f1\ufe0f Pausa x4&lt;\/option&gt;\n        &lt;option value=\"10\"&gt;\u23f1\ufe0f Pausa x10&lt;\/option&gt;\n      &lt;\/select&gt;\n    &lt;\/div&gt;\n\n    &lt;!-- ? Indicador de diagn\u00f2stic --&gt;\n    &lt;div id=\"debugInfo\"&gt;\n      ? Selecciona un idioma i prem \"?\" per veure quina veu s'utilitza.\n    &lt;\/div&gt;\n\n    &lt;div class=\"controls\"&gt;\n      &lt;button id=\"speakBtn\" title=\"Escolta el text\"&gt;?&lt;\/button&gt;\n      &lt;button id=\"toggleBtn\" title=\"Atura o repr\u00e8n\"&gt;\u23f9\ufe0f&lt;\/button&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n\n  &lt;script&gt;\n    document.addEventListener('DOMContentLoaded', () =&gt; {\n      const editor = document.getElementById('editor');\n      const speakBtn = document.getElementById('speakBtn');\n      const toggleBtn = document.getElementById('toggleBtn');\n      const voiceSelect = document.getElementById('voiceSelect');\n      const pauseSelect = document.getElementById('pauseSelect');\n      const debugInfo = document.getElementById('debugInfo');\n\n      let voices = [];\n      let isSpeaking = false;\n      let liniesGuardades = [];\n      let currentIndex = 0;\n\n      function loadVoices() {\n        voices = speechSynthesis.getVoices();\n        \/\/ Opcional: mostra totes les veus disponibles al carregar (per depuraci\u00f3)\n        \/\/ console.log('Veus disponibles:', voices.map(v =&gt; `${v.name} (${v.lang})`));\n      }\n\n      speechSynthesis.onvoiceschanged = loadVoices;\n      loadVoices();\n\n      const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');\n      if (idiomaGuardat) {\n        voiceSelect.value = idiomaGuardat;\n      }\n\n      voiceSelect.addEventListener('change', () =&gt; {\n        localStorage.setItem('idiomaSeleccionat', voiceSelect.value);\n        \/\/ Actualitzem el missatge quan es canvia l'idioma\n        debugInfo.textContent = `? Idioma seleccionat: ${voiceSelect.options[voiceSelect.selectedIndex].text}`;\n      });\n\n      function getLinies() {\n        const text = editor.innerText || editor.textContent;\n        return text.split('\\n').map(l =&gt; l.trim()).filter(l =&gt; l);\n      }\n\n      function marcarLinia(index) {\n        const linies = editor.querySelectorAll('.linia');\n        linies.forEach((el, i) =&gt; {\n          el.classList.toggle('activa', i === index);\n        });\n      }\n\n      function prepararEditor() {\n        const linies = getLinies();\n        editor.innerHTML = '';\n        linies.forEach((text, i) =&gt; {\n          const span = document.createElement('span');\n          span.className = 'linia';\n          span.textContent = text;\n          editor.appendChild(span);\n          editor.appendChild(document.createElement('br'));\n        });\n      }\n\n      function getPausaEntreLinies() {\n        const factor = parseInt(pauseSelect.value || '1');\n        return 500 * factor;\n      }\n\n      \/\/ ? Funci\u00f3 per trobar la millor veu i mostrar informaci\u00f3\n      function getBestVoiceAndReport(langCode) {\n        if (!langCode) {\n          debugInfo.innerHTML = `\u2139\ufe0f Cap idioma seleccionat. S'utilitzar\u00e0 la veu per defecte.`;\n          return null;\n        }\n\n        \/\/ Normalitzem per a comparacions flexibles\n        const normalizedTarget = langCode.replace('-', '_').toLowerCase();\n\n        \/\/ 1. Cerca exacta (amb normalitzaci\u00f3)\n        let best = voices.find(v =&gt; \n          v.lang.replace('-', '_').toLowerCase() === normalizedTarget\n        );\n\n        if (best) {\n          debugInfo.innerHTML = `\u2705 Idioma demanat: &lt;strong&gt;${langCode}&lt;\/strong&gt;&lt;br&gt;?\ufe0f Veus trobada: &lt;strong&gt;${best.name}&lt;\/strong&gt; (${best.lang})`;\n          return best;\n        }\n\n        \/\/ 2. Cerca per prefix (p. ex., \"ca\" per a \"ca-ES\")\n        const prefix = langCode.split('-')[0].toLowerCase();\n        best = voices.find(v =&gt; v.lang.toLowerCase().startsWith(prefix));\n\n        if (best) {\n          debugInfo.innerHTML = `\u26a0\ufe0f Idioma demanat: &lt;strong&gt;${langCode}&lt;\/strong&gt;&lt;br&gt;?\ufe0f Veus aproximada: &lt;strong&gt;${best.name}&lt;\/strong&gt; (${best.lang})`;\n          return best;\n        }\n\n        \/\/ 3. No s'ha trobat cap veu\n        debugInfo.innerHTML = `\u274c Idioma demanat: &lt;strong&gt;${langCode}&lt;\/strong&gt;&lt;br&gt;? Cap veu trobada. Comprova que tens l'idioma instal\u00b7lat a Android.`;\n        return null;\n      }\n\n      function speakLinia() {\n        if (currentIndex &gt;= liniesGuardades.length) {\n          isSpeaking = false;\n          setTimeout(() =&gt; {\n            speakText();\n          }, 2000);\n          return;\n        }\n\n        const linia = liniesGuardades[currentIndex];\n        const utterance = new SpeechSynthesisUtterance(linia);\n\n        const selectedLang = voiceSelect.value;\n        const voice = getBestVoiceAndReport(selectedLang);\n        if (voice) {\n          utterance.voice = voice;\n        }\n\n        utterance.rate = 1;\n        utterance.pitch = 1;\n\n        utterance.onstart = () =&gt; {\n          isSpeaking = true;\n          marcarLinia(currentIndex);\n        };\n\n        utterance.onend = () =&gt; {\n          currentIndex++;\n          setTimeout(() =&gt; {\n            speakLinia();\n          }, getPausaEntreLinies());\n        };\n\n        speechSynthesis.speak(utterance);\n      }\n\n      function speakText() {\n        if (isSpeaking) return;\n        prepararEditor();\n        liniesGuardades = getLinies();\n        currentIndex = 0;\n        speechSynthesis.cancel();\n        speakLinia();\n      }\n\n      speakBtn.addEventListener('click', speakText);\n\n      toggleBtn.addEventListener('click', () =&gt; {\n        if (isSpeaking) {\n          speechSynthesis.cancel();\n          isSpeaking = false;\n        } else {\n          speakLinia();\n        }\n      });\n    });\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/pre>\n<!-- \/wp:enlighter\/codeblock -->\n\n<!-- wp:separator -->\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n<!-- \/wp:separator --><!-- wp:separator -->\n\nSimple text to speak\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n<!-- \/wp:separator --><!-- wp:paragraph -->\n\n<iframe loading=\"lazy\" src=\"https:\/\/www.beseit.net\/tts_edge\/speak_button.html\" width=\"100%\" height=\"400\" frameborder=\"0\" scrolling=\"yes\"><\/iframe>\n\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n\nPer afegir aquest codi de sota cal usar el connector <strong>Enlighter<\/strong>, afegir un bloc a la entrada amb el mateix nom i descarregat el codi del porta-retalls\n\n<!-- \/wp:paragraph -->\n\n<!-- wp:enlighter\/codeblock -->\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!DOCTYPE html&gt;\n&lt;!-- Declara que aquest document \u00e9s HTML5 --&gt;\n&lt;html lang=\"ca\"&gt;\n&lt;!-- L'idioma principal del contingut \u00e9s el catal\u00e0 --&gt;\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\"&gt;\n  &lt;!-- Assegura la correcta visualitzaci\u00f3 de car\u00e0cters especials i emojis --&gt;\n  &lt;title&gt;Conversor de text a veu&lt;\/title&gt;\n  &lt;!-- T\u00edtol que apareix a la pestanya del navegador --&gt;\n\n  &lt;style&gt;\n    \/* Estils generals de la p\u00e0gina *\/\n    body {\n      font-family: sans-serif;        \/* Tipus de lletra llegible *\/\n      padding: 2em;                   \/* Espai al voltant del contingut *\/\n      background-color: hsl(194, 91%, 91%); \/* Fons blau clar suau *\/\n    }\n\n    \/* Estil com\u00fa per al camp de text i el selector d'idioma *\/\n    input, select {\n      font-size: 1.2em;               \/* Mida de lletra una mica m\u00e9s gran *\/\n      padding: 0.5em;                 \/* Espai interior dins dels camps *\/\n      margin-top: 1em;                \/* Separaci\u00f3 superior *\/\n      width: 100%;                    \/* Ocupen tot l'ample disponible *\/\n      max-width: 500px;               \/* Per\u00f2 no m\u00e9s de 500px *\/\n    }\n\n    \/* Estil del bot\u00f3 d'altaveu (?) *\/\n    #speakBtn {\n      font-size: 2em;                 \/* Molt gran per facilitar la interacci\u00f3 *\/\n      background: none;               \/* Sense fons *\/\n      border: none;                   \/* Sense vores *\/\n      cursor: pointer;                \/* Cursor en forma de m\u00e0 en passar-hi *\/\n      margin-top: 1em;                \/* Separaci\u00f3 respecte als elements anteriors *\/\n    }\n\n    \/* Efecte visual quan l'usuari passa el ratol\u00ed pel bot\u00f3 *\/\n    #speakBtn:hover {\n      color: #c8e3f8;                 \/* Canvia a un blau m\u00e9s clar *\/\n    }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n\n&lt;body&gt;\n  &lt;!-- T\u00edtol visible de la p\u00e0gina amb un emoji d'altaveu --&gt;\n  &lt;h2&gt;? Conversor de text a veu&lt;\/h2&gt;\n\n  &lt;!-- Camp on l'usuari escriu el text a sintetitzar --&gt;\n  &lt;input type=\"text\" id=\"cercar\" placeholder=\"Escriu el text que vols escoltar\"&gt;\n\n  &lt;!-- Selector per triar la veu\/idioma desitjat --&gt;\n  &lt;select id=\"voiceSelect\"&gt;\n    &lt;option value=\"\"&gt;Selecciona idioma&lt;\/option&gt;\n    &lt;option value=\"en-US\"&gt;?? Angl\u00e8s (US)&lt;\/option&gt;\n    &lt;option value=\"en-GB\"&gt;?? Angl\u00e8s (UK)&lt;\/option&gt;\n    &lt;option value=\"fr-FR\"&gt;?? Franc\u00e8s&lt;\/option&gt;\n    &lt;option value=\"de-DE\"&gt;?? Alemany&lt;\/option&gt;\n    &lt;option value=\"it-IT\"&gt;?? Itali\u00e0&lt;\/option&gt;\n    &lt;option value=\"pt-PT\"&gt;?? Portugu\u00e8s&lt;\/option&gt;\n    &lt;option value=\"es-ES\"&gt;?? Espanyol&lt;\/option&gt;\n    &lt;option value=\"ca-ES\"&gt;?\ufe0f Catal\u00e0&lt;\/option&gt;\n    &lt;!-- Els valors (value) s\u00f3n codis d'idioma est\u00e0ndard BCP 47 --&gt;\n  &lt;\/select&gt;\n\n  &lt;!-- Bot\u00f3 per iniciar la s\u00edntesi de veu --&gt;\n  &lt;button id=\"speakBtn\" title=\"Escolta el text\"&gt;?&lt;\/button&gt;\n\n  &lt;script&gt;\n    \/\/ S'executa quan tot el contingut HTML ha carregat\n    document.addEventListener('DOMContentLoaded', () =&gt; {\n      \/\/ Refer\u00e8ncies als elements HTML que necessitarem\n      const inputElement = document.getElementById('cercar');      \/\/ Camp de text\n      const speakBtn = document.getElementById('speakBtn');        \/\/ Bot\u00f3 d'altaveu\n      const voiceSelect = document.getElementById('voiceSelect');  \/\/ Selector d'idioma\n      let voices = [];  \/\/ Array per emmagatzemar les veus disponibles\n\n      \/\/ Funci\u00f3 per carregar la llista de veus del sistema\n      function loadVoices() {\n        voices = speechSynthesis.getVoices();\n        \/\/ Aquesta API pot trigar a carregar les veus en alguns navegadors\n      }\n\n      \/\/ Assegurem-nos que les veus es carreguin, fins i tot si arriben m\u00e9s tard\n      speechSynthesis.onvoiceschanged = loadVoices;\n      loadVoices(); \/\/ Intentem carregar-les ja des del principi\n\n      \/\/ Funci\u00f3 principal: converteix el text a veu\n      function speakText() {\n        const text = inputElement.value.trim(); \/\/ Obtenim i netegem el text\n        if (!text) return; \/\/ Si est\u00e0 buit, no fem res\n\n        const selectedLang = voiceSelect.value; \/\/ Idioma seleccionat per l'usuari\n        const utterance = new SpeechSynthesisUtterance(text); \/\/ Creem l'objecte de parla\n\n        \/\/ Busquem una veu que coincideixi exactament amb l'idioma triat\n        const voice = voices.find(v =&gt; v.lang === selectedLang);\n\n        if (voice) {\n          \/\/ Si la trobem, l'assignem\n          utterance.voice = voice;\n          utterance.lang = voice.lang;\n        } else {\n          \/\/ Si no, mostrem un av\u00eds a la consola (per depuraci\u00f3)\n          console.log('No s\\'ha trobat cap veu per a ' + selectedLang);\n          \/\/ Nota: l'API pot usar una veu per defecte si no s'assigna cap\n        }\n\n        \/\/ Par\u00e0metres opcionals de parla\n        utterance.rate = 1;   \/\/ Velocitat normal (1 = 100%)\n        utterance.pitch = 1;  \/\/ To normal\n\n        \/\/ Cancel\u00b7lem qualsevol parla anterior per evitar solapaments\n        speechSynthesis.cancel();\n        \/\/ Iniciem la nova s\u00edntesi de veu\n        speechSynthesis.speak(utterance);\n      }\n\n      \/\/ Quan l'usuari fa clic al bot\u00f3, es llegeix el text\n      speakBtn.addEventListener('click', speakText);\n\n      \/\/ \u26a0\ufe0f Aquesta l\u00ednia fa que el text es llegeixi AUTOM\u00c0TICAMENT cada 10 segons\n      \/\/ Pot ser \u00fatil per a demostracions, per\u00f2 pot resultar molest en \u00fas normal\n      setInterval(speakText, 10000);\n    });\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/pre>\n<!-- \/wp:enlighter\/codeblock -->\n\n<!-- wp:paragraph -->\n\n<!-- \/wp:paragraph -->\n\n<!-- wp:enlighter\/codeblock -->\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"ca\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\"&gt;\n  &lt;title&gt;Conversor de text a veu&lt;\/title&gt;\n  &lt;style&gt;\n    body {\n      font-family: sans-serif;\n      padding: 2em;\n      background-color: #f4f4f4;\n    }\n    input {\n      font-size: 1.2em;\n      padding: 0.5em;\n      margin-top: 1em;\n      width: 100%;\n      max-width: 500px;\n    }\n    #speakBtn {\n      font-size: 2em;\n      background: none;\n      border: none;\n      cursor: pointer;\n      margin-top: 1em;\n    }\n    #speakBtn:hover {\n      color: #0078D7;\n    }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;h2&gt;? Conversor de text a veu&lt;\/h2&gt;\n  &lt;input type=\"text\" id=\"cercar\" placeholder=\"Escriu el text que vols escoltar\"&gt;\n  &lt;button id=\"speakBtn\" title=\"Escolta el text\"&gt;?&lt;\/button&gt;\n\n  &lt;script&gt;\n    document.addEventListener('DOMContentLoaded', () =&gt; {\n      const inputElement = document.getElementById('cercar');\n      const speakBtn = document.getElementById('speakBtn');\n      let voices = [];\n\n      function loadVoices() {\n        voices = speechSynthesis.getVoices();\n      }\n\n      speechSynthesis.onvoiceschanged = loadVoices;\n      loadVoices();\n\n      function speakText() {\n        const text = inputElement.value.trim();\n        if (!text) return;\n\n        const utterance = new SpeechSynthesisUtterance(text);\n        const zira = voices.find(v =&gt; v.name === 'Microsoft Zira - English (United States)');\n\n        if (zira) {\n          utterance.voice = zira;\n          utterance.lang = zira.lang;\n        }\n\n        utterance.rate = 1;\n        utterance.pitch = 1;\n\n        speechSynthesis.cancel();\n        speechSynthesis.speak(utterance);\n      }\n\n      speakBtn.addEventListener('click', speakText);\n\n      \/\/ ? Repetir cada 10 segons\n      setInterval(speakText, 10000);\n    });\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<!-- \/wp:enlighter\/codeblock -->","protected":false},"excerpt":{"rendered":"<p>\u00a0Text a veu to run into somebody. have a good trip. to pass on greetings. there\u2019s a restroom in the caf\u00e9. Idioma?? Angl\u00e8s (US)?? Angl\u00e8s (UK)?? Franc\u00e8s?? Alemany?? Itali\u00e0?? Portugu\u00e8s?? Espanyol?\ufe0f Catal\u00e0\u23f1\ufe0f Pausa x1\u23f1\ufe0f Pausa x2\u23f1\ufe0f Pausa x3\u23f1\ufe0f Pausa x4\u23f1\ufe0f &hellip; <a href=\"https:\/\/www.beseit.net\/?p=16876\">Continua llegint <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":3168,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[194,1],"tags":[],"class_list":["post-16876","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tts","category-bloc-de-notes"],"_links":{"self":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16876","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=16876"}],"version-history":[{"count":28,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16876\/revisions"}],"predecessor-version":[{"id":17955,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16876\/revisions\/17955"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/media\/3168"}],"wp:attachment":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=16876"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=16876"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=16876"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}