[{"data":1,"prerenderedAt":339},["ShallowReactive",2],{"\u002Fprojects\u002Fvideo-accessibility":3,"posts":237},{"id":4,"title":5,"body":6,"description":175,"extension":176,"featured":177,"images":178,"links":197,"meta":204,"navigation":177,"order":205,"path":206,"seo":207,"stack":208,"stats":221,"stem":233,"tag":234,"url":235,"__hash__":236},"projects\u002Fprojects\u002Fvideo-accessibility.md","Video Accessibility",{"type":7,"value":8,"toc":163},"minimark",[9,14,82,86,91,99,103,106,110,117,121],[10,11,13],"h2",{"id":12},"qué-hace","Qué hace",[15,16,17,58,64,70,76],"ul",{},[18,19,20,29,30,33,34,37,38,41,42,45,46,49,50,53,54,57],"li",{},[21,22,23,24,28],"strong",{},"Los atajos de YouTube, en cualquier ",[25,26,27],"code",{},"\u003Cvideo>","."," Del ",[25,31,32],{},"0"," al ",[25,35,36],{},"9"," para saltar del 0 al 90 %, espacio para pausar, flechas para avanzar, retroceder y cambiar el volumen, ",[25,39,40],{},"M"," para silenciar, ",[25,43,44],{},"F"," para pantalla completa, ",[25,47,48],{},"+"," y ",[25,51,52],{},"-"," para la velocidad y ",[25,55,56],{},"P"," para picture-in-picture.",[18,59,60,63],{},[21,61,62],{},"Lo que YouTube no trae."," Bucle A-B, filtros de brillo, contraste y saturación, y un modo cine que oscurece el resto de la página.",[18,65,66,69],{},[21,67,68],{},"Eventos personalizados."," Una tecla que pulsa u oculta cualquier elemento a partir de un selector CSS, por ejemplo el botón de \"me gusta\". Sin tecla, se ejecutan solos al aparecer el elemento.",[18,71,72,75],{},[21,73,74],{},"A tu medida."," Todas las teclas se cambian, se pueden tener atajos distintos por web y, desde el popup, apagar un atajo solo en la web en la que estás.",[18,77,78,81],{},[21,79,80],{},"Solo donde tú dices."," La lista de webs permitidas empieza vacía. Aviso en pantalla configurable, tema claro u oscuro y cinco idiomas. Sin telemetría, sin analíticas y sin servidores.",[10,83,85],{"id":84},"cómo-está-hecho","Cómo está hecho",[87,88,90],"h3",{"id":89},"el-script-solo-entra-en-tus-webs","El script solo entra en tus webs",[92,93,94,95,98],"p",{},"El manifiesto pide acceso a todas las webs para no tener que aceptar un permiso nuevo en cada una, pero el content script no se declara para ninguna. El service worker lo registra en tiempo de ejecución con ",[25,96,97],{},"chrome.scripting.registerContentScripts",", solo para los dominios de la lista, y lo vuelve a registrar únicamente cuando la lista cambia.",[87,100,102],{"id":101},"encontrar-el-vídeo-que-estás-viendo","Encontrar el vídeo que estás viendo",[92,104,105],{},"El script busca vídeos también dentro de shadow DOM y en los iframes cuyo dominio está en la lista. Si hay varios, elige el que se está reproduciendo y, si no, el más grande.",[87,107,109],{"id":108},"popup-y-opciones-en-vue","Popup y opciones en Vue",[92,111,112,113,116],{},"El popup y la página de opciones son Vue 3 con Tailwind. Los ajustes viven en ",[25,114,115],{},"chrome.storage",", y el content script los escucha para aplicar los cambios sin recargar.",[10,118,120],{"id":119},"problemas-que-resolví","Problemas que resolví",[15,122,123,129,143,157],{},[18,124,125,128],{},[21,126,127],{},"No robar teclas."," Si estás escribiendo en un campo, aunque esté dentro de shadow DOM, la extensión no hace nada. Tampoco con Ctrl, Alt o Cmd. Y un evento personalizado solo se queda la tecla si de verdad ha encontrado algo que pulsar.",[18,130,131,134,135,138,139,142],{},[21,132,133],{},"Clics que los reproductores sí escuchan."," Muchos botones reaccionan a ",[25,136,137],{},"mousedown"," y no a ",[25,140,141],{},"click",", así que el clic simulado lanza la secuencia completa.",[18,144,145,148,149,152,153,156],{},[21,146,147],{},"Un espacio que nunca coincidía."," El atajo por defecto para pausar se guardaba como ",[25,150,151],{},"' '"," y la tecla llegaba como ",[25,154,155],{},"space",". Ahora se normalizan los dos lados, también para los ajustes que ya estaban guardados.",[18,158,159,162],{},[21,160,161],{},"Un aviso sin estilos."," Al registrar el script en tiempo de ejecución, la hoja de estilos del aviso en pantalla se quedó fuera y el aviso salía como texto suelto debajo del vídeo. Ahora se registra junto al script, con un test que lo comprueba.",{"title":164,"searchDepth":165,"depth":165,"links":166},"",2,[167,168,174],{"id":12,"depth":165,"text":13},{"id":84,"depth":165,"text":85,"children":169},[170,172,173],{"id":89,"depth":171,"text":90},3,{"id":101,"depth":171,"text":102},{"id":108,"depth":171,"text":109},{"id":119,"depth":165,"text":120},"Extensión de Chrome que lleva los atajos de teclado de YouTube a cualquier web con un vídeo HTML. Saltar al 30 % con el 3, cambiar la velocidad, bucle A-B, filtros, modo cine y eventos personalizados para pulsar u ocultar cualquier botón con una tecla. Solo en las webs que tú eliges y todo en local.","md",true,[179,184,189,194],{"src":180,"alt":181,"width":182,"height":183},"\u002Fimages\u002Fposts\u002Fvideo-accessibility\u002Fportada.webp","Un vídeo en una web de demostración con el aviso \"Salto al 30%\" en el centro, tras pulsar la tecla 3",1440,660,{"src":185,"alt":186,"width":187,"height":188},"\u002Fimages\u002Fposts\u002Fvideo-accessibility\u002Fpopup-activo.webp","El popup de la extensión activo en la web, con los atajos básicos y un interruptor para apagar cada uno solo en ese sitio",300,739,{"src":190,"alt":191,"width":192,"height":193},"\u002Fimages\u002Fposts\u002Fvideo-accessibility\u002Fopciones-eventos.webp","La pestaña de eventos personalizados con un evento \"Me gusta\" que pulsa el botón .like-btn con Shift + L",1010,740,{"src":195,"alt":196,"width":182,"height":183},"\u002Fimages\u002Fposts\u002Fvideo-accessibility\u002Fcine.webp","El modo cine, con el resto de la página oscurecida y el vídeo destacado",[198,201],{"label":199,"url":200},"Cómo se usa","\u002Fposts\u002Fvideo-accessibility-atajos-de-youtube-en-cualquier-video",{"label":202,"url":203},"Por dentro","\u002Fposts\u002Fvideo-accessibility-por-dentro",{},10,"\u002Fprojects\u002Fvideo-accessibility",{"title":5,"description":175},[209,210,211,212,213,214,215,216,217,218,219,220],"WXT","Manifest V3","Vue 3","TypeScript","Tailwind CSS","Chrome Scripting API","Chrome Storage","MutationObserver","Shadow DOM","Vitest","jsdom","Vue Test Utils",[222,225,228,230],{"value":223,"label":224},"30","atajos de serie",{"value":226,"label":227},"5","idiomas",{"value":32,"label":229},"datos enviados",{"value":231,"label":232},"106","tests (Vitest)","projects\u002Fvideo-accessibility","video-accessibility","https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fvideo-accessibility\u002Fgkegicinlfihnoblalflbjhiamghfgap","mqsS-eTZhEldFOL6cC4mRaaNvTPeTr_9wkjOlygF4FM",[238,249,258,267,272,281,289,294,301,306,314,320,326,332],{"title":239,"description":240,"date":241,"path":242,"tags":243,"type":248},"Simple TTS: escucha cualquier artículo de la web en voz alta, sin salir de tu navegador","Una extensión gratuita para Chrome y Edge que lee en voz alta lo que selecciones o el artículo entero con las voces del propio navegador. Sin cuentas, sin servidores y sin APIs externas. Qué hace y cómo se usa.","2026-10-23","\u002Fposts\u002Fsimple-tts-escucha-cualquier-web",[244,245,246,247],"simple-tts","privacidad","productividad","proyectos","guia",{"title":250,"description":251,"date":241,"path":252,"tags":253,"type":257},"Simple TTS por dentro: mi primera extensión, de JavaScript a WXT con Vue","Cómo pasó Simple TTS de tres archivos de JavaScript a una extensión Manifest V3 con WXT, Vue 3 y TypeScript. Qué me hizo cambiar en cada etapa, cómo se mantiene vivo un service worker durante una lectura larga, por qué leía la frase del día anterior y cómo se consigue que el resaltado vaya a la par de la voz.","\u002Fposts\u002Fsimple-tts-por-dentro",[244,254,255,256],"arquitectura","typescript","vue","tecnico",{"title":259,"description":260,"date":261,"path":262,"tags":263,"type":257},"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",[264,265,254,255,266],"transcribe-local","ia","nuxt",{"title":268,"description":269,"date":261,"path":270,"tags":271,"type":248},"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",[264,265,245,247],{"title":273,"description":274,"date":275,"path":276,"tags":277,"type":248},"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",[278,279,280,247],"pokestore","wordpress","woocommerce",{"title":282,"description":283,"date":284,"path":285,"tags":286,"type":248},"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",[287,288,246,247],"youtube-like-shortcut","youtube",{"title":290,"description":291,"date":284,"path":292,"tags":293,"type":257},"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",[287,288,254,255],{"title":295,"description":296,"date":297,"path":298,"tags":299,"type":248},"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",[300,265,245,247],"ai-upscaler",{"title":302,"description":303,"date":297,"path":304,"tags":305,"type":257},"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",[300,265,254,255,256],{"title":307,"description":308,"date":309,"path":310,"tags":311,"type":248},"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",[312,313,245,247],"cipher","seguridad",{"title":315,"description":316,"date":309,"path":317,"tags":318,"type":257},"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",[312,313,254,255,319],"rust",{"title":321,"description":322,"date":309,"path":323,"tags":324,"type":257},"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",[325,254,255,266],"abreader",{"title":327,"description":328,"date":309,"path":329,"tags":330,"type":248},"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",[325,331,247],"lectura",{"title":333,"description":334,"date":309,"path":335,"tags":336,"type":257},"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",[266,256,337,338],"devops","meta",1792754864706]