{"id":17004,"date":"2025-10-27T20:38:07","date_gmt":"2025-10-27T18:38:07","guid":{"rendered":"https:\/\/www.beseit.net\/?page_id=17004"},"modified":"2025-11-03T11:25:55","modified_gmt":"2025-11-03T09:25:55","slug":"speak_button_responsive","status":"publish","type":"page","link":"https:\/\/www.beseit.net\/?page_id=17004","title":{"rendered":"Speak_button_responsive (codi)"},"content":{"rendered":"<!DOCTYPE html>\n<html lang=\"ca\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Caixa Comod\u00ed &#8211; Text a veu<\/title>\n  <link rel=\"icon\" href=\"calculadora.ico\" type=\"image\/x-icon\">\n  <style>\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; \/*tamany del text del contenedor *\/\n      padding: 8px;\n      margin: 8px 0;\n      width: 100%;\n      height: 320px; \/* \u2190 2,5 vegades m\u00e9s alt que abans (120px \u2192 300px) *\/\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    .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  <\/style>\n<\/head>\n<body>\n  <div class=\"container\">\n    <h2>? Text a veu<\/h2>\n\n    <div id=\"editor\" contenteditable=\"true\" placeholder=\"Escriu el text que vols escoltar\">\nto run into somebody.<br>\nhave a good trip.<br>\nto pass on greetings.<br>\nthere\u2019s a restroom in the caf\u00e9.\n    <\/div>\n<div>\n    <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>\n\n    <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<\/div>\n    <div class=\"controls\">\n      <button id=\"speakBtn\" title=\"Escolta el text\">?<\/button>\n      <button id=\"toggleBtn\" title=\"Atura o repr\u00e8n\">\u23f9\ufe0f<\/button>\n    <\/div>\n  <\/div>\n\n  <script>\n    document.addEventListener('DOMContentLoaded', () => {\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\n      let voices = [];\n      let isSpeaking = false;\n      let liniesGuardades = [];\n      let currentIndex = 0;\n\n      function loadVoices() {\n        voices = speechSynthesis.getVoices();\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', () => {\n        localStorage.setItem('idiomaSeleccionat', voiceSelect.value);\n      });\n\n      function getLinies() {\n        const text = editor.innerText || editor.textContent;\n        return text.split('\\n').map(l => l.trim()).filter(l => l);\n      }\n\n      function marcarLinia(index) {\n        const linies = editor.querySelectorAll('.linia');\n        linies.forEach((el, i) => {\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) => {\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      function speakLinia() {\n        if (currentIndex >= liniesGuardades.length) {\n          isSpeaking = false;\n          setTimeout(() => {\n            speakText();\n          }, 2000);\n          return;\n        }\n\n        const linia = liniesGuardades[currentIndex];\n        const utterance = new SpeechSynthesisUtterance(linia);\n        const voice = voices.find(v => v.lang === voiceSelect.value);\n        if (voice) {\n          utterance.voice = voice;\n          utterance.lang = voice.lang;\n        }\n\n        utterance.rate = 1;\n        utterance.pitch = 1;\n\n        utterance.onstart = () => {\n          isSpeaking = true;\n          marcarLinia(currentIndex);\n        };\n\n        utterance.onend = () => {\n          currentIndex++;\n          setTimeout(() => {\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', () => {\n        if (isSpeaking) {\n          speechSynthesis.cancel();\n          isSpeaking = false;\n        } else {\n          speakLinia();\n        }\n      });\n    });\n  <\/script>\n<\/body>\n<\/html>\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n<p><!-- wp:enlighter\/codeblock MUT --><\/p>\n<\/p>\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\"\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; \/*tamany del text del contenedor *\/\n      padding: 8px;\n      margin: 8px 0;\n      width: 100%;\n      height: 320px; \/* \u2190 2,5 vegades m\u00e9s alt que abans (120px \u2192 300px) *\/\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    .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    &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\n      let voices = [];\n      let isSpeaking = false;\n      let liniesGuardades = [];\n      let currentIndex = 0;\n\n      function loadVoices() {\n        voices = speechSynthesis.getVoices();\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      });\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      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        const voice = voices.find(v =&gt; v.lang === voiceSelect.value);\n        if (voice) {\n          utterance.voice = voice;\n          utterance.lang = voice.lang;\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<p><!-- \/wp:post-content --><\/p>\n<p><!-- wp:separator --><\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n<!-- \/wp:separator --><\/p>","protected":false},"excerpt":{"rendered":"<p>Caixa Comod\u00ed &#8211; Text a veu ? Text 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 &hellip; <a href=\"https:\/\/www.beseit.net\/?page_id=17004\">Continua llegint <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":3167,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"categories":[],"class_list":["post-17004","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages\/17004","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/types\/page"}],"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=17004"}],"version-history":[{"count":6,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages\/17004\/revisions"}],"predecessor-version":[{"id":17069,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/pages\/17004\/revisions\/17069"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/media\/3167"}],"wp:attachment":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=17004"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=17004"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}