[{"data":1,"prerenderedAt":332},["ShallowReactive",2],{"\u002Fprojects\u002Fsimple-tts":3,"posts":242},{"id":4,"title":5,"body":6,"description":171,"extension":172,"featured":173,"images":174,"links":198,"meta":208,"navigation":173,"order":209,"path":210,"seo":211,"stack":212,"stats":225,"stem":238,"tag":239,"url":240,"__hash__":241},"projects\u002Fprojects\u002Fsimple-tts.md","Simple TTS",{"type":7,"value":8,"toc":158},"minimark",[9,14,79,83,88,97,101,104,108,115,119,122,126],[10,11,13],"h2",{"id":12},"qué-hace","Qué hace",[15,16,17,25,31,37,43,49,55,61,67,73],"ul",{},[18,19,20,24],"li",{},[21,22,23],"strong",{},"Leer lo que selecciones."," Clic derecho y «Leer texto seleccionado», o Alt+R.",[18,26,27,30],{},[21,28,29],{},"Leer el artículo entero."," Clic derecho en la página y «Leer artículo completo»: busca el cuerpo del artículo y deja fuera menús, barras laterales y anuncios.",[18,32,33,36],{},[21,34,35],{},"Cola de lectura."," «Agregar a la cola» suma otro texto, de la misma página o de otra, para que suene justo después.",[18,38,39,42],{},[21,40,41],{},"Tres modos de reproductor."," Flotante sobre la página (se mueve y se redimensiona), en una ventana independiente o en segundo plano, sin nada en pantalla.",[18,44,45,48],{},[21,46,47],{},"Las voces del navegador."," Un buscador agrupa las voces por idioma y deja oírlas antes de elegir. En Edge están sus voces naturales; en Chrome, las de Google.",[18,50,51,54],{},[21,52,53],{},"Velocidad, tono y volumen."," De 0,5× a 4×, también con atajo de teclado.",[18,56,57,60],{},[21,58,59],{},"Seguir la lectura."," La palabra que suena se resalta en el reproductor y, si quieres, también en la página, que se desplaza sola para no perderla.",[18,62,63,66],{},[21,64,65],{},"Progreso y tiempo restante",", y al volver a abrirla retoma donde lo dejaste.",[18,68,69,72],{},[21,70,71],{},"Estadísticas."," Sesiones, palabras leídas, tiempo escuchado y voz más usada.",[18,74,75,78],{},[21,76,77],{},"En seis idiomas",", con tema claro, oscuro o automático.",[10,80,82],{"id":81},"cómo-está-hecho","Cómo está hecho",[84,85,87],"h3",{"id":86},"sin-motor-de-voz-propio","Sin motor de voz propio",[89,90,91,92,96],"p",{},"Toda la voz sale de ",[93,94,95],"code",{},"chrome.tts",", la API de síntesis del navegador. No hay servidor ni API externa: el texto no pasa por ningún servidor mío, y las voces disponibles son las que ya trae Chrome, Edge o el sistema.",[84,98,100],{"id":99},"manifest-v3-con-wxt-y-vue","Manifest V3 con WXT y Vue",[89,102,103],{},"WXT genera el manifest y empaqueta cada pieza por separado. El service worker lleva el estado y el motor de lectura. El reproductor flotante es un componente Vue montado dentro de un Shadow DOM, para que los estilos de la página no lo rompan. El popup y la ventana independiente son páginas Vue que hablan con el service worker por mensajes.",[84,105,107],{"id":106},"lectura-frase-a-frase","Lectura frase a frase",[89,109,110,111,114],{},"El texto se trocea en frases con ",[93,112,113],{},"Intl.Segmenter",", guardando dónde empieza cada una. Así se puede saltar a la frase anterior o a la siguiente, cambiar la velocidad sin volver al principio y saber en todo momento qué parte del texto suena.",[84,116,118],{"id":117},"resaltado-sin-tocar-la-página","Resaltado sin tocar la página",[89,120,121],{},"El resaltado en la página usa la CSS Custom Highlight API: marca rangos de texto sin insertar ni un solo elemento en el HTML de la web.",[10,123,125],{"id":124},"problemas-que-resolví","Problemas que resolví",[15,127,128,134,140,146,152],{},[18,129,130,133],{},[21,131,132],{},"El service worker se dormía a mitad de un artículo largo."," Un documento offscreen le manda una señal cada 20 segundos mientras hay lectura, y se cierra al pausar o parar.",[18,135,136,139],{},[21,137,138],{},"Leía la frase del día anterior."," Tras reiniciar el ordenador, el primer «Leer texto seleccionado» empezaba antes de que terminara de restaurarse el estado guardado, y ese estado viejo pisaba al nuevo. Ahora todos los puntos de entrada esperan a la restauración, y un test reproduce el orden exacto de eventos.",[18,141,142,145],{},[21,143,144],{},"La voz se quedaba muda sin avisar."," Un vigilante compara el progreso con lo que debería tardar la frase; si se atasca, la repite hasta tres veces y después pasa a la siguiente.",[18,147,148,151],{},[21,149,150],{},"El resaltado se desfasaba de lo que se oía."," El texto seleccionado y el HTML de la página no tienen los mismos espacios ni saltos de línea. Ahora se comparan sin tenerlos en cuenta, se realinean cuando se separan y se parte de donde estaba tu selección.",[18,153,154,157],{},[21,155,156],{},"Cuatro etapas en ocho meses."," Empezó en JavaScript sin framework, pasó a Vue con Vite, luego a WXT y por último a TypeScript con tests. Cada migración trajo sus problemas, como el CORS del servidor de desarrollo o montar Vue dentro de un Shadow DOM.",{"title":159,"searchDepth":160,"depth":160,"links":161},"",2,[162,163,170],{"id":12,"depth":160,"text":13},{"id":81,"depth":160,"text":82,"children":164},[165,167,168,169],{"id":86,"depth":166,"text":87},3,{"id":99,"depth":166,"text":100},{"id":106,"depth":166,"text":107},{"id":117,"depth":166,"text":118},{"id":124,"depth":160,"text":125},"Extensión de Chrome y Edge que lee en voz alta cualquier texto de una web con las voces del propio navegador. Lee la selección o el artículo entero, tiene cola de lectura, tres modos de reproductor, resaltado de lo que se está leyendo y estadísticas. Sin servidores, sin APIs externas y gratis.","md",true,[175,180,185,190,195],{"src":176,"alt":177,"width":178,"height":179},"\u002Fimages\u002Fposts\u002Fsimple-tts\u002Fportada.webp","Un artículo leyéndose en voz alta, con el reproductor flotante y la palabra que suena resaltada en la página",1440,900,{"src":181,"alt":182,"width":183,"height":184},"\u002Fimages\u002Fposts\u002Fsimple-tts\u002Fflotante.webp","El reproductor flotante con el texto, el progreso, el tiempo restante y los controles",302,535,{"src":186,"alt":187,"width":188,"height":189},"\u002Fimages\u002Fposts\u002Fsimple-tts\u002Fpopup-ajustes.webp","Los ajustes de velocidad, tono, volumen, modo de reproductor, tema e idioma",360,681,{"src":191,"alt":192,"width":193,"height":194},"\u002Fimages\u002Fposts\u002Fsimple-tts\u002Fventana.webp","El reproductor en una ventana independiente",400,600,{"src":196,"alt":197,"width":178,"height":179},"\u002Fimages\u002Fposts\u002Fsimple-tts\u002Foscuro.webp","Simple TTS leyendo un artículo en modo oscuro",[199,202,205],{"label":200,"url":201},"Cómo se usa","\u002Fposts\u002Fsimple-tts-escucha-cualquier-web",{"label":203,"url":204},"Por dentro","\u002Fposts\u002Fsimple-tts-por-dentro",{"label":206,"url":207},"Web","https:\u002F\u002Fabdell1999.github.io\u002Fsimpletts-landing\u002F",{},7,"\u002Fprojects\u002Fsimple-tts",{"title":5,"description":171},[213,214,215,216,217,218,219,220,113,221,222,223,224],"WXT","Vue 3","TypeScript","Manifest V3","Chrome TTS API","Offscreen API","Shadow DOM","CSS Custom Highlight API","Chrome Storage (sync)","Chrome i18n","Vitest","Playwright",[226,229,232,235],{"value":227,"label":228},"0","servidores o APIs externas",{"value":230,"label":231},"112","tests (Vitest)",{"value":233,"label":234},"6","idiomas",{"value":236,"label":237},"2.4.6","versión","projects\u002Fsimple-tts","simple-tts","https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fsimple-tts\u002Fmnnhakddcadkcichdgimoennpkfkcalk","28oIEAiVLwVwiprWHZ9lI1FFPZKW57RbnichWY2VM0s",[243,255,263,272,281,286,293,299,307,313,319,325],{"title":244,"description":245,"date":246,"path":247,"tags":248,"type":254},"TranscribeLocal por dentro: Whisper en el navegador y cómo dejó de inventarse frases","Cómo ejecuta TranscribeLocal Whisper, la separación de hablantes y un modelo de lenguaje dentro del navegador con Transformers.js, por qué Whisper se inventaba frases en los silencios, qué hace falta para transcribir audios largos y por qué un service worker cortaba las descargas a los cinco minutos.","2026-10-22","\u002Fposts\u002Ftranscribe-local-por-dentro",[249,250,251,252,253],"transcribe-local","ia","arquitectura","typescript","nuxt","tecnico",{"title":256,"description":257,"date":246,"path":258,"tags":259,"type":262},"TranscribeLocal: transcribe y saca subtítulos de cualquier vídeo, sin salir de tu navegador","Una app web que convierte audio y vídeo en texto y subtítulos con Whisper, distingue quién habla y hace resúmenes, todo en tu ordenador. Sin subir nada a ningún servidor, sin límites de duración y gratis. Qué hace y cómo se usa.","\u002Fposts\u002Ftranscribe-local-subtitulos-en-tu-navegador",[249,250,260,261],"privacidad","proyectos","guia",{"title":264,"description":265,"date":266,"path":267,"tags":268,"type":262},"PokeStore: un tema de WordPress a medida para tiendas de cartas coleccionables","Un tema de WordPress y WooCommerce hecho desde cero para tiendas de Pokémon y One Piece TCG. Un recorrido por la tienda como la ve quien compra, de la portada a la lista de deseos, y por lo que puede ajustar quien la lleva.","2026-10-21","\u002Fposts\u002Fpokestore-tema-wordpress-para-tiendas-tcg",[269,270,271,261],"pokestore","wordpress","woocommerce",{"title":273,"description":274,"date":275,"path":276,"tags":277,"type":262},"YouTube Like Shortcut: da like con una tecla, como en TikTok","Una extensión de Chrome que da like al vídeo o Short de YouTube que estás viendo al pulsar la L. Cómo se instala, qué opciones tiene y por qué no se dispara mientras escribes.","2026-10-20","\u002Fposts\u002Fyoutube-like-shortcut-like-con-una-tecla",[278,279,280,261],"youtube-like-shortcut","youtube","productividad",{"title":282,"description":283,"date":275,"path":284,"tags":285,"type":254},"YouTube Like Shortcut por dentro: pulsar un botón en una web que cambia sin avisar","Cómo una extensión de una sola función llega antes que el reproductor de YouTube, encuentra el botón de like entre variantes ocultas y layouts que cambian con el ancho de la ventana, y se asegura de que el like ha entrado aunque YouTube esté reconstruyendo la página.","\u002Fposts\u002Fyoutube-like-shortcut-por-dentro",[278,279,251,252],{"title":287,"description":288,"date":289,"path":290,"tags":291,"type":262},"AI Upscaler: amplía imágenes y quita fondos con IA, sin salir de tu navegador","Una extensión de Chrome que amplía imágenes ×2 o ×4 y elimina fondos con inteligencia artificial, todo en tu equipo. Sin subir nada a ningún servidor, sin cuentas y gratis. Qué hace y cómo se usa.","2026-10-19","\u002Fposts\u002Fai-upscaler-imagenes-con-ia-en-tu-navegador",[292,250,260,261],"ai-upscaler",{"title":294,"description":295,"date":289,"path":296,"tags":297,"type":254},"AI Upscaler por dentro: dos redes neuronales en una extensión de Chrome","Cómo ejecuta AI Upscaler Real-ESRGAN y U²-Net dentro del navegador con ONNX Runtime Web, por qué la imagen se procesa en mosaicos con margen, qué hubo que parchear en el modelo de quitar fondos y qué obstáculos pone una extensión Manifest V3 a los workers y al WebAssembly.","\u002Fposts\u002Fai-upscaler-por-dentro",[292,250,251,252,298],"vue",{"title":300,"description":301,"date":302,"path":303,"tags":304,"type":262},"ABR Cipher: cifrado seguro, completamente en tu navegador","Una suite de cifrado que no envía nada a ningún servidor. Cifrar textos y archivos, compartir secretos con un enlace que caduca, guardar contraseñas y destruir archivos. Qué hace y cómo se usa.","2026-10-18","\u002Fposts\u002Fabr-cipher-cifrado-en-tu-navegador",[305,306,260,261],"cipher","seguridad",{"title":308,"description":309,"date":302,"path":310,"tags":311,"type":254},"ABR Cipher por dentro: criptografía sin servidor, de la URL al disco","Por qué ABR Cipher solo usa la Web Crypto API, cómo viaja un secreto en el fragmento de una URL, cómo se cifra un archivo de varios GB en el navegador y qué hace falta para destruir un archivo de verdad, con un compañero en Rust que lee NTFS.","\u002Fposts\u002Fabr-cipher-por-dentro",[305,306,251,252,312],"rust",{"title":314,"description":315,"date":302,"path":316,"tags":317,"type":254},"ABReader por dentro: el lector que no encontré, construido a mi medida","Voces neuronales que se ejecutan en el navegador, sincronización entre dispositivos sin servidor y once formatos detrás de un mismo contrato. Las decisiones técnicas de ABReader y por qué las tomé.","\u002Fposts\u002Fabreader-por-dentro",[318,251,252,253],"abreader",{"title":320,"description":321,"date":302,"path":322,"tags":323,"type":262},"ABReader: tu biblioteca de libros electrónicos en el navegador","Un lector de libros electrónicos que funciona en el navegador, sin cuentas ni servidores. Qué hace, cómo se usa y cómo probarlo en un minuto.","\u002Fposts\u002Fabreader-tu-biblioteca-en-el-navegador",[318,324,261],"lectura",{"title":326,"description":327,"date":302,"path":328,"tags":329,"type":254},"Cómo construí este blog: estático, sin servidor y sin coste de hosting","Por qué elegí Nuxt y Markdown en lugar de WordPress, cómo separo el código privado del sitio publicado y qué se rompió en el primer despliegue.","\u002Fposts\u002Farquitectura-de-este-blog",[253,298,330,331],"devops","meta",1792636040325]