RD5 GOLD WEB EDITOR
📄
📄 2-RB4-3CC.HTML
📄 222.JPG
📄 3CC-BASIC1.HTML
📄 Microsoft Paint.exe
📄 R2C-3.HTML
📄 R2C-4.HTML
📄 RB4-3C.HTML
📄 RB4-3CC.HTML
📄 RB4-RETRO.HTML
📄 RB4-RETRO.html
📄 RD5-3CC.html
📄 RL-4.html
📄 TARS.html
📄 TES10.mp3
📄 TES11.mp3
📄 TRISHA-OK.html
📄 TRISHA-WIN.HTML
📄 Tiny-demo.html
📄 Tinybj7-blindado.html
📄 Tinybj7-publica.html
📄 aplicaciones-rd5.html
📄 app1.html
📄 app2.html
📄 app3.html
📄 appb1.html
📄 appb2.html
📄 appb3.html
📄 appb4.html
📄 appb5.html
📄 appb6.html
📄 c-julio.JPG
📄 caratula.gif
📄 caratula02.gif
📄 caratula2.gif
📄 chokurei2.jpg
📄 columna-julio.html
📄 columna-stella.html
📄 columna-stellab.html
📄 demos.html
📄 df3.html
📄 dni.gif
📄 donaciones.gif
📄 ecualizador.html
📄 ed-podcast.JPG
📄 ed-radio.JPG
📄 editorv.html
📄 editorv1.html
📄 editorv2.html
📄 fondo5d.jpg
📄 fresi-clip.html
📄 gold.php
📄 gold62.php
📄 gold93.php
📄 googlefd161ffccea5f6ab.html
📄 gw.php
📄 index-anterior.html
📄 index.html
📄 index2.html
📄 logo.png
📄 logo.webp
📄 magazine.html
📄 manager3.php
📄 mic2.jpg
📄 mini1.html
📄 minib2a.html
📄 mn.php
📄 muestra.JPG
📄 p.html
📄 pm.html
📄 pm2.html
📄 pod-rd5.html
📄 podcast-stops.gif
📄 podcast2.mp3
📄 pp.html
📄 promo-tinybj7.html
📄 radio.html
📄 radio_gold.html
📄 rd5-fresia-4 canvashtml
📄 rd5-fresia-ok.html
📄 rd5-fresia.html
📄 rd5-plus-anterior.html
📄 rd5-plus.html
📄 rd5-plus2.html
📄 rd5-radioplayer.html
📄 rd5-tv.html
📄 rd5.html
📄 sst.html
📄 st.html
📄 starclip.html
📄 stella-2.JPG
📄 stella.JPG
📄 stt.html
📄 t-rd5.html.html
📄 t-rd6.html
📄 t-rd7.html
📄 t-tiny.html
📄 tes0.jpg
📄 tes1.jpg
📄 tes2.jpg
📄 tes4.jpg
📄 tes4.mp3
📄 tes5.jpg
📄 tes7.jpg
📄 tes8.jpg
📄 tienda.html
📄 tiendaaps.html
📄 tiendapps.html
📄 tiny-bj6.html
📄 tiny-bj7-full.html
📄 tiny-bj7.html
📄 tiny3-bj2.html
📄 tinybj7-blindado.html
📄 tinydemo.html
📄 titulo.jpg
📄 tnw2.html
📄 trisha-basic.html
📄 trisha-cafe.html
📄 trisha-mini.html
📄 tv-pantalla.php
📄 tv.html
📄 xdni.html
CÓDIGO (OSCURO GOLD)
VISTA PREVIA
💾 GUARDAR
Tema: Monokai
Tema: Dracula
Tema: Material
Tema: Blackboard
Tema: Abyss
Monospace
Courier
12px
14px
16px
18px
20px
22px
24px
26px
28px
30px
32px
↷ Rehacer
↶ Deshacer
🔍 BUSCAR
+ REPRO RADIO
SIG.
X
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <title>EDITOR PRO COMPLETO - TODO FIX</title> <style> body { background:#111; color:#fff; font-family:Arial; margin:10px; } #top { display:flex; gap:10px; } #player { display:flex; justify-content:center; align-items:center; background:black; } #monitor { background:black; border:1px solid #333; padding:5px; line-height: 0; } canvas { width:260px; height:150px; background:black; display: block; } #player:fullscreen { width:100vw; height:100vh; } #player:fullscreen canvas { width: auto !important; height: 100vh !important; max-width: 100vw; object-fit: contain; } #timeline { display:flex; gap:6px; flex-wrap:wrap; max-width:500px; margin-top: 10px; } .clip { width:90px; height:55px; background:#222; position:relative; cursor:pointer; border:1px solid #333; overflow: hidden; } .clip.active { outline:2px solid #00ffcc; } .clip img { width:100%; height:100%; object-fit:cover; display: block; } .clip button { position:absolute; top:2px; right:2px; background:#900; border:none; color:white; font-size:10px; cursor:pointer; z-index: 10; } button { background:#111; color:white; border:1px solid #444; padding:6px; margin-top:10px; cursor: pointer; } #renderBtn { background: #28a745; border: none; font-weight: bold; } .effect-controls { display:none; margin-top:10px; background: #1a1a1a; padding: 10px; border-radius: 4px; } .effect-btn.active { background:#007bff; } </style> </head> <body> <div id="top"> <div> <div id="player"> <div id="monitor"><canvas id="canvas"></canvas></div> </div> <button id="fullscreen">PANTALLA GRANDE</button> </div> <div id="timeline"></div> </div> <button id="play">PLAY</button> <button id="stop">STOP</button> <button id="renderBtn">RENDERIZAR</button> <input type="file" id="file" multiple> <div> <button class="effect-btn" data-effect="transform">AJUSTE</button> <button class="effect-btn" data-effect="contrast">CONTRASTE</button> <button class="effect-btn" data-effect="fade">FADE</button> <button class="effect-btn" data-effect="trim">RECORTE</button> </div> <div id="transform" class="effect-controls"> Zoom <input type="range" id="zoom" min="0.1" max="5" step="0.1" value="1"> X <input type="range" id="posX" min="-1000" max="1000" value="0"> Y <input type="range" id="posY" min="-1000" max="1000" value="0"> </div> <div id="contrast" class="effect-controls"> Contraste <input type="range" id="contrastVal" min="0" max="200" value="100"> <span id="contrastLabel">100%</span> </div> <div id="fade" class="effect-controls"> Fade In <input type="number" id="fadeIn" step="0.1" value="0"> Fade Out <input type="number" id="fadeOut" step="0.1" value="0"> </div> <div id="trim" class="effect-controls"> Inicio <input type="number" id="trimStart" step="0.1"> Fin <input type="number" id="trimEnd" step="0.1"> </div> <script> const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const player = document.getElementById("player"); let clips=[], currentIndex=0, playing=false, raf=null, imgStart=0; let audioCtx, destination; function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); destination = audioCtx.createMediaStreamDestination(); } } fullscreen.onclick = () => { if(!document.fullscreenElement) player.requestFullscreen(); else document.exitFullscreen(); }; file.onchange = async e => { initAudio(); for(let f of e.target.files){ let type = f.type.startsWith("image/") ? "img" : "video"; let el = document.createElement(type); let url = URL.createObjectURL(f); el.src = url; el.crossOrigin = "anonymous"; if(type === "video"){ await new Promise(r => el.onloadedmetadata = r); const source = audioCtx.createMediaElementSource(el); source.connect(audioCtx.destination); source.connect(destination); if(clips.length === 0){ canvas.width = el.videoWidth; canvas.height = el.videoHeight; } const thumb = await createThumb(url); clips.push({el, type, duration:el.duration, thumb, trimStart:0, trimEnd:el.duration, contrast:100, zoom:1, posX:0, posY:0}); } else { await new Promise(r => el.onload = r); if(clips.length === 0){ canvas.width = el.width; canvas.height = el.height; } clips.push({el, type, duration:3, thumb: url, contrast:100, zoom:1, posX:0, posY:0}); } } renderTimeline(); }; async function createThumb(url) { return new Promise(res => { const v = document.createElement("video"); v.src = url; v.muted = true; v.currentTime = 0.5; v.onseeked = () => { const c = document.createElement("canvas"); c.width = 180; c.height = 110; c.getContext("2d").drawImage(v, 0, 0, 180, 110); res(c.toDataURL()); }; v.onerror = () => res(""); }); } function renderTimeline(){ timeline.innerHTML=""; clips.forEach((c,i)=>{ let div=document.createElement("div"); div.className="clip" + (i===currentIndex?" active":""); let img=document.createElement("img"); img.src = c.thumb; let del=document.createElement("button"); del.innerText="X"; del.onclick=e=>{ e.stopPropagation(); clips.splice(i,1); renderTimeline(); }; div.onclick=()=>{ currentIndex=i; loadUI(); renderTimeline(); draw(); }; div.appendChild(img); div.appendChild(del); timeline.appendChild(div); }); } function loadUI(){ let c=clips[currentIndex]; if(!c) return; contrastVal.value=c.contrast; contrastLabel.innerText=c.contrast+"%"; zoom.value=c.zoom; posX.value=c.posX; posY.value=c.posY; fadeIn.value=c.fadeIn||0; fadeOut.value=c.fadeOut||0; trimStart.value=c.trimStart||0; trimEnd.value=c.trimEnd||c.duration; } function draw(){ ctx.filter = "none"; ctx.globalAlpha = 1; ctx.fillStyle="black"; ctx.fillRect(0,0,canvas.width,canvas.height); let c=clips[currentIndex]; if(!c) return; let rawTime = c.type==="video" ? c.el.currentTime : (performance.now()-imgStart)/1000; let start=c.trimStart||0; let end=c.trimEnd||c.duration; let t=rawTime-start; let duration=end-start; ctx.filter=`contrast(${c.contrast}%)`; let alpha=1; if(c.fadeIn && t<c.fadeIn) alpha=t/c.fadeIn; if(c.fadeOut && t>duration-c.fadeOut) alpha=(duration-t)/c.fadeOut; ctx.globalAlpha=Math.max(0,Math.min(1,alpha)); let iw=c.el.videoWidth||c.el.width; let ih=c.el.videoHeight||c.el.height; let scale=Math.min(canvas.width/iw, canvas.height/ih) * c.zoom; let nw=iw*scale; let nh=ih*scale; ctx.drawImage(c.el, (canvas.width-nw)/2+c.posX, (canvas.height-nh)/2+c.posY, nw, nh); } function loop(){ if(!playing) return; let c=clips[currentIndex]; if(!c) return; if(c.type==="video" && c.el.currentTime >= (c.trimEnd||c.duration)){ currentIndex++; cambiarClip(); return; } if(c.type==="img" && (performance.now()-imgStart)/1000 >= 3){ currentIndex++; cambiarClip(); return; } draw(); raf=requestAnimationFrame(loop); } function cambiarClip(){ if(currentIndex>=clips.length){ stop(); return; } let c=clips[currentIndex]; if(c.type==="video"){ c.el.currentTime=c.trimStart||0; c.el.play(); } else{ imgStart=performance.now(); } renderTimeline(); loadUI(); raf=requestAnimationFrame(loop); } play.onclick=()=>{ if(!clips.length) return; initAudio(); stop(); playing=true; currentIndex=0; cambiarClip(); }; function stop(){ playing=false; if(raf) cancelAnimationFrame(raf); clips.forEach(c=>c.el.pause && c.el.pause()); } stop.onclick=stop; renderBtn.onclick=async ()=>{ initAudio(); stop(); const stream = canvas.captureStream(30); const combined = new MediaStream([...stream.getVideoTracks(), ...destination.stream.getAudioTracks()]); const rec = new MediaRecorder(combined, { mimeType: 'video/webm;codecs=vp8,opus', videoBitsPerSecond: 20000000 }); let chunks=[]; rec.ondataavailable=e=>chunks.push(e.data); rec.onstop=()=>{ let a=document.createElement("a"); a.href=URL.createObjectURL(new Blob(chunks, {type:'video/webm'})); a.download="video_final.webm"; a.click(); }; rec.start(); playing=true; currentIndex=0; cambiarClip(); let total=clips.reduce((acc,c)=> acc + ((c.trimEnd||c.duration)-(c.trimStart||0)), 0); setTimeout(()=>{ rec.stop(); stop(); }, total*1000 + 800); }; /* SYNC DATOS Y UI */ setInterval(()=>{ let c=clips[currentIndex]; if(!c) return; c.contrast=parseInt(contrastVal.value); contrastLabel.innerText = c.contrast + "%"; // ESTO FALTABA c.zoom=parseFloat(zoom.value); c.posX=parseFloat(posX.value); c.posY=parseFloat(posY.value); c.fadeIn=parseFloat(fadeIn.value); c.fadeOut=parseFloat(fadeOut.value); c.trimStart=parseFloat(trimStart.value); c.trimEnd=parseFloat(trimEnd.value); if(!playing) draw(); },100); document.querySelectorAll(".effect-btn").forEach(btn=>{ btn.onclick=()=>{ document.querySelectorAll(".effect-btn").forEach(b=>b.classList.remove("active")); btn.classList.add("active"); document.querySelectorAll(".effect-controls").forEach(c=>c.style.display="none"); document.getElementById(btn.dataset.effect).style.display="block"; }; }); /* --- MÓDULO DE ZOOM Y REENCUADRE DINÁMICO --- */ (function() { // 1. Inyectar Botón en la interfaz const menu = document.querySelector('.effect-btn').parentNode; const btnZoom = document.createElement('button'); btnZoom.className = 'effect-btn'; btnZoom.dataset.effect = 'zoom_modulo'; btnZoom.innerText = 'LENTE / ZOOM'; menu.appendChild(btnZoom); // 2. Inyectar Panel de Control const panel = document.createElement('div'); panel.id = 'zoom_modulo'; panel.className = 'effect-controls'; panel.innerHTML = ` Escala: <input type="range" id="z_scale" min="1" max="500" value="100"> <span id="z_label">100%</span><br> Giro: <input type="range" id="z_rotate" min="-180" max="180" value="0"> <br> Espejo: <button id="z_flip" style="margin-top:5px; padding:2px 10px;">FLIP H</button> `; document.body.appendChild(panel); // 3. Evento para el botón de menú btnZoom.onclick = () => { document.querySelectorAll(".effect-btn").forEach(b => b.classList.remove("active")); btnZoom.classList.add("active"); document.querySelectorAll(".effect-controls").forEach(c => c.style.display = "none"); panel.style.display = "block"; }; // 4. Lógica de Espejo (Flip) document.getElementById("z_flip").onclick = () => { let c = clips[currentIndex]; if(c) { c.z_flip = !c.z_flip; if(!playing) draw(); } }; // 5. Extender Draw para Transformaciones Avanzadas const lastDraw = window.draw; window.draw = function() { let c = clips[currentIndex]; if (c) { ctx.save(); // Movemos al centro para rotar y escalar correctamente ctx.translate(canvas.width / 2 + (c.posX || 0), canvas.height / 2 + (c.posY || 0)); // Aplicar Rotación if(c.z_rotate) ctx.rotate((c.z_rotate * Math.PI) / 180); // Aplicar Espejo y Escala let scaleX = (c.z_scale || 100) / 100; let scaleY = scaleX; if(c.z_flip) scaleX *= -1; ctx.scale(scaleX, scaleY); // Volvemos al origen relativo para que el draw original pinte ahí ctx.translate(-(canvas.width / 2), -(canvas.height / 2)); // Limpiamos los offsets temporales para que no se dupliquen con el draw original let tempX = c.posX; let tempY = c.posY; c.posX = 0; c.posY = 0; lastDraw(); // Restauramos valores c.posX = tempX; c.posY = tempY; ctx.restore(); } else { lastDraw(); } }; // 6. Sincronización setInterval(() => { let c = clips[currentIndex]; if (c && document.getElementById("zoom_modulo").style.display === "block") { c.z_scale = document.getElementById("z_scale").value; c.z_rotate = document.getElementById("z_rotate").value; document.getElementById("z_label").innerText = c.z_scale + "%"; if (!playing) draw(); } }, 100); // 7. Carga de UI const lastLoadUI = window.loadUI; window.loadUI = function() { lastLoadUI(); let c = clips[currentIndex]; if (c) { document.getElementById("z_scale").value = c.z_scale || 100; document.getElementById("z_rotate").value = c.z_rotate || 0; document.getElementById("z_label").innerText = (c.z_scale || 100) + "%"; } }; })(); /* --- FIN DEL MÓDULO --- */ /* --- MÓDULO UNIFICADO: CONTRASTE + BLANCO Y NEGRO --- */ (function() { // 1. Inyectar Botón en el menú principal const menu = document.querySelector('.effect-btn').parentNode; const btnC_BW = document.createElement('button'); btnC_BW.className = 'effect-btn'; btnC_BW.dataset.effect = 'panel_cbw'; btnC_BW.innerText = 'IMAGEN'; // Engloba Contraste y B&N menu.appendChild(btnC_BW); // 2. Inyectar Panel de Control const panel = document.createElement('div'); panel.id = 'panel_cbw'; panel.className = 'effect-controls'; panel.innerHTML = ` Contraste: <input type="range" id="u_contrast" min="0" max="300" value="100"> <span id="u_c_lab">100%</span><br> Blanco y Negro: <input type="range" id="u_bw" min="0" max="100" value="0"> <span id="u_bw_lab">0%</span> `; document.body.appendChild(panel); // 3. Lógica para mostrar/ocultar btnC_BW.onclick = () => { document.querySelectorAll(".effect-btn").forEach(b => b.classList.remove("active")); btnC_BW.classList.add("active"); document.querySelectorAll(".effect-controls").forEach(c => c.style.display = "none"); panel.style.display = "block"; }; // 4. SOBREESCRIBIR DRAW PARA UNIFICAR FILTROS // Esta es la parte clave: combinamos ambos en una sola sentencia de CSS const oldDraw = window.draw; window.draw = function() { let c = clips[currentIndex]; if (c) { // Combinamos los valores guardados en el clip let con = c.u_con !== undefined ? c.u_con : 100; let gray = c.u_bw !== undefined ? c.u_bw : 0; // Aplicamos ambos de un solo golpe ctx.filter = `contrast(${con}%) grayscale(${gray}%)`; oldDraw(); } else { oldDraw(); } }; // 5. Sincronización en tiempo real (Loop) setInterval(() => { let c = clips[currentIndex]; if (c && document.getElementById("panel_cbw").style.display === "block") { // Capturar valores de los inputs c.u_con = document.getElementById("u_contrast").value; c.u_bw = document.getElementById("u_bw").value; // Actualizar etiquetas de texto document.getElementById("u_c_lab").innerText = c.u_con + "%"; document.getElementById("u_bw_lab").innerText = c.u_bw + "%"; if (!playing) draw(); } }, 100); // 6. Cargar datos al seleccionar clip const oldLoad = window.loadUI; window.loadUI = function() { oldLoad(); let c = clips[currentIndex]; if (c) { document.getElementById("u_contrast").value = c.u_con || 100; document.getElementById("u_bw").value = c.u_bw || 0; document.getElementById("u_c_lab").innerText = (c.u_con || 100) + "%"; document.getElementById("u_bw_lab").innerText = (c.u_bw || 0) + "%"; } }; })(); /* --- FIN DEL MÓDULO --- */ /* --- MONITOR MAESTRO DE FILTROS (CONSOLIDADO) --- */ (function() { // Sobrescribimos el draw una última vez para unificar todo const drawFinal = window.draw; window.draw = function() { let c = clips[currentIndex]; if (c) { // Recolectamos todos los valores de los módulos let contrast = c.contrast || 100; let brightness = c.c_bright || 100; let saturate = c.c_saturate || 100; let grayscale = c.bw_int || 0; let sepia = (c.of_int || 0) * 0.8; // Si el módulo de cine viejo existe let extraContrast = c.bw_contrast ? (c.bw_contrast / 100) : 1; // CONSTRUIMOS LA CADENA ÚNICA DE FILTRO // Importante: El orden afecta el resultado ctx.filter = ` brightness(${brightness}%) contrast(${contrast * extraContrast}%) grayscale(${grayscale}%) saturate(${saturate}%) sepia(${sepia}%) `.replace(/\n/g, ' '); // Limpiamos saltos de línea // Ejecutamos el dibujado base (que ya tiene los traslados de Zoom) drawFinal(); // Si hay tinte RGB (del módulo de Color), lo aplicamos encima if(c.c_red !== 100 || c.c_green !== 100 || c.c_blue !== 100) { ctx.save(); ctx.globalCompositeOperation = "multiply"; ctx.fillStyle = `rgb(${c.c_red*2.55}, ${c.c_green*2.55}, ${c.c_blue*2.55})`; ctx.globalAlpha = 0.15; ctx.fillRect(0,0, canvas.width, canvas.height); ctx.restore(); } } else { drawFinal(); } }; })(); /* --- FIN DEL MAESTRO --- */ /* --- OPTIMIZADOR DE RENDIMIENTO PARA RENDER --- */ (function() { // Forzamos que el canvas use aceleración por hardware si está disponible const contextAttributes = { alpha: false, desynchronized: true }; const tempCtx = canvas.getContext("2d", contextAttributes); // Ajustamos el renderizador para que espere un poco más entre clips const originalRender = document.getElementById("renderBtn").onclick; document.getElementById("renderBtn").onclick = async function() { console.log("Iniciando Renderizado con módulos activos..."); // Bajamos la calidad de la previsualización durante el render para dar prioridad al encoder canvas.style.opacity = "0.5"; await originalRender(); canvas.style.opacity = "1"; }; })(); /* --- TITULADORA INTEGRADA (SIN ERRORES DE PANTALLA) --- */ (function() { // 1. Buscamos donde están tus otros controles para no crear paneles nuevos raros const contenedorFiltros = document.querySelector('.effect-controls'); // 2. Creamos el HTML de la tituladora pero lo mantenemos OCULTO y ORDENADO const divTexto = document.createElement('div'); divTexto.id = 'modulo_texto_limpio'; divTexto.style.display = 'none'; // Se activa solo al tocar el botón divTexto.innerHTML = ` <div style="background:#111; padding:10px; border:1px solid #333; margin-top:10px;"> <input type="text" id="t_input" placeholder="TEXTO AQUÍ" style="width:40%; margin-bottom:10px;"> <div style="display:flex; gap:10px; align-items:center;"> Size: <input type="number" id="t_size" value="50" style="width:50px"> Color: <input type="color" id="t_color" value="#ffffff"> Pos Y: <input type="range" id="t_y" min="0" max="100" value="80"> </div> </div> `; // Lo metemos dentro de tu interfaz actual, no volando por ahí document.body.appendChild(divTexto); // 3. Función de dibujo (Esta no falla) const drawOriginal = window.draw; window.draw = function() { drawOriginal(); // Dibuja el video primero let c = clips[currentIndex]; if (c && c.t_text) { ctx.save(); ctx.filter = "none"; // Limpia el B&N o Contraste del texto ctx.fillStyle = c.t_color || "#fff"; ctx.font = `bold ${c.t_size || 50}px Arial`; ctx.textAlign = "center"; ctx.shadowColor = "black"; ctx.shadowBlur = 5; ctx.fillText(c.t_text, canvas.width / 2, (c.t_y / 100) * canvas.height); ctx.restore(); } }; // 4. Botón que activa/desactiva el panel const btnMenu = document.querySelector('.effect-btn').parentNode; const btnT = document.createElement('button'); btnT.className = 'effect-btn'; btnT.innerText = 'TITULO'; btnT.onclick = () => { const p = document.getElementById('modulo_texto_limpio'); p.style.display = p.style.display === 'none' ? 'block' : 'none'; }; btnMenu.appendChild(btnT); // 5. Guardar los datos en el clip setInterval(() => { let c = clips[currentIndex]; if (c && document.getElementById('modulo_texto_limpio').style.display === 'block') { c.t_text = document.getElementById('t_input').value; c.t_size = document.getElementById('t_size').value; c.t_color = document.getElementById("t_color").value; c.t_y = document.getElementById("t_y").value; if(!playing) draw(); } }, 200); })(); /* --- MÓDULO DE VELOCIDAD (TIME REMAPPING) --- */ (function() { // 1. Inyectar Botón en la interfaz const menu = document.querySelector('.effect-btn').parentNode; const btnSpeed = document.createElement('button'); btnSpeed.className = 'effect-btn'; btnSpeed.dataset.effect = 'speed_modulo'; btnSpeed.innerText = 'VELOCIDAD'; menu.appendChild(btnSpeed); // 2. Inyectar Panel de Control const panel = document.createElement('div'); panel.id = 'speed_modulo'; panel.className = 'effect-controls'; panel.innerHTML = ` Velocidad: <input type="range" id="v_rate" min="0.1" max="4" step="0.1" value="1"> <span id="v_label">1.0x</span><br> <small style="color:#888">Afecta solo a clips de video.</small> `; document.body.appendChild(panel); // 3. Evento para mostrar el panel btnSpeed.onclick = () => { document.querySelectorAll(".effect-btn").forEach(b => b.classList.remove("active")); btnSpeed.classList.add("active"); document.querySelectorAll(".effect-controls").forEach(c => c.style.display = "none"); panel.style.display = "block"; }; // 4. Aplicar velocidad al cambiar de clip const _originalCambiarClip = window.cambiarClip; window.cambiarClip = function() { _originalCambiarClip(); let c = clips[currentIndex]; if (c && c.type === "video") { c.el.playbackRate = c.v_speed || 1.0; } }; // 5. Sincronización constante setInterval(() => { let c = clips[currentIndex]; if (c && document.getElementById("speed_modulo").style.display === "block") { const val = parseFloat(document.getElementById("v_rate").value); c.v_speed = val; document.getElementById("v_label").innerText = val.toFixed(1) + "x"; // Actualizar el video actual si está reproduciéndose if (c.type === "video") { c.el.playbackRate = val; } } }, 100); // 6. Carga de datos al seleccionar clip const _originalLoadUI = window.loadUI; window.loadUI = function() { _originalLoadUI(); let c = clips[currentIndex]; if (c) { const speed = c.v_speed || 1.0; document.getElementById("v_rate").value = speed; document.getElementById("v_label").innerText = speed.toFixed(1) + "x"; } }; })(); /* --- FIN DEL MÓDULO --- */ </script> </body> </html>