CONSOLA SYRA
" + "1. Desplegar Monitor Flotante.
" + "2. INICIAR PROTOCOLO.
" + "3. Sigue el metrónomo. Habrá resumen final personalizado con tono pajeril."; let audioCtx, inicioSesionTime, intvPrincipal, intvLatido; let tSesion = "00:00", tCambio = "00:00", estado = "STOPPED"; let frasesDichas = new Set(); let bpmVirtual = 48, bpmObjetivo = 48; let noiseFilter, noiseGain, selectedVoiceURI = null; const canvas = document.getElementById("cv"); const ctx = canvas.getContext("2d"); const videoElem = document.getElementById("vd"); function initVoice() { const voices = window.speechSynthesis.getVoices(); const selectElem = document.getElementById("select-voz"); if (!selectElem) return; selectElem.innerHTML = ""; voices.forEach(function (v) { const option = document.createElement("option"); option.value = v.name; option.textContent = v.name + " (" + v.lang + ")"; if (v.lang.indexOf("es") !== -1) { option.selected = true; selectedVoiceURI = v.name; } selectElem.appendChild(option); }); if (!selectedVoiceURI && voices.length > 0) { selectedVoiceURI = voices[0].name; selectElem.value = selectedVoiceURI; } } document.getElementById("select-voz").addEventListener("change", function (e) { selectedVoiceURI = e.target.value; }); function speak(texto) { if (!texto) return; window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(texto); utterance.lang = "es-ES"; utterance.volume = 1.0; utterance.rate = 0.85; utterance.pitch = 1.0; const voices = window.speechSynthesis.getVoices(); const chosen = voices.find(function (v) { return v.name === selectedVoiceURI; }); if (chosen) utterance.voice = chosen; window.speechSynthesis.speak(utterance); } function dibujarMonitor() { ctx.fillStyle = "black"; ctx.fillRect(0, 0, 450, 300); ctx.strokeStyle = (estado === "RUNNING") ? "#ef4444" : "#1e40af"; ctx.lineWidth = 10; ctx.strokeRect(5, 5, 440, 290); ctx.fillStyle = "white"; ctx.font = "bold 16px Arial"; ctx.fillText("ID: " + CONFIG.idSesion + " | MÓDULO SYRA", 25, 35); if (estado === "RUNNING") { ctx.fillStyle = "white"; ctx.font = "bold 60px monospace"; ctx.fillText(tSesion, 25, 130); ctx.fillStyle = "#3b82f6"; ctx.font = "bold 45px monospace"; ctx.fillText(tCambio, 230, 130); ctx.fillStyle = "#ef4444"; ctx.font = "bold 40px Arial"; ctx.fillText("BPM: " + Math.round(bpmVirtual), 25, 200); ctx.fillStyle = "#ff4444"; ctx.font = "bold 20px Arial"; ctx.fillText("CERRAR (X) FINALIZA TODO", 85, 260); } else { ctx.fillStyle = "#3b82f6"; ctx.font = "bold 20px Arial"; ctx.fillText("SISTEMA LISTO", 145, 160); } } async function lanzarMonitor() { const stream = canvas.captureStream(); videoElem.srcObject = stream; dibujarMonitor(); await videoElem.play(); try { await videoElem.requestPictureInPicture(); document.getElementById("btn-monitor").style.display = "none"; document.getElementById("btn-video-container").style.display = "block"; videoElem.addEventListener("leavepictureinpicture", function () { if (estado === "RUNNING") finalizarTodo(); }); } catch (e) { alert("Use Chrome o Edge."); } } function setupNoise() { if (!audioCtx) return; const buffer = audioCtx.createBuffer(1, audioCtx.sampleRate * 3, audioCtx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1; const noise = audioCtx.createBufferSource(); noise.buffer = buffer; noise.loop = true; noiseFilter = audioCtx.createBiquadFilter(); noiseFilter.type = "lowpass"; noiseFilter.frequency.value = 380; noiseGain = audioCtx.createGain(); noiseGain.gain.value = 0.03; noise.connect(noiseFilter); noiseFilter.connect(noiseGain); noiseGain.connect(audioCtx.destination); noise.start(); } function loopMetronomo() { if (estado !== "RUNNING" || !audioCtx) return; bpmVirtual += (bpmObjetivo - bpmVirtual) * 0.06; const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.frequency.value = 300 + (bpmVirtual * 2); g.gain.value = 1.0; g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.04); o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + 0.05); setTimeout(loopMetronomo, 60000 / bpmVirtual); } function enviarRegistro(status, tiempo) { const datosSesion = { idSesion: CONFIG.idSesion, tiempo: tiempo, status: status, edges: numEdgesGlobal, orgasmo: esOrgasmoGlobal ? "SÍ" : "NO" }; const p = new URLSearchParams(datosSesion); navigator.sendBeacon(CONFIG.antenaURL + "?" + p.toString()); } function iniciarSesion() { estado = "RUNNING"; inicioSesionTime = Date.now(); audioCtx = new (window.AudioContext || window.webkitAudioContext)(); setupNoise(); loopMetronomo(); intvLatido = setInterval(function () { enviarRegistro("LATIDO_ACTIVO", Math.floor((Date.now() - inicioSesionTime) / 1000)); }, 60000); intvPrincipal = setInterval(motorLogico, 100); document.getElementById("ui-inicio").style.opacity = "0.2"; } function motorLogico() { const ahora = Date.now(); const totalSegs = Math.floor((ahora - inicioSesionTime) / 1000); const restante = Math.max(0, CONFIG.duracionTotalSegundos - totalSegs); tSesion = String(Math.floor(totalSegs / 60)).padStart(2, "0") + ":" + String(totalSegs % 60).padStart(2, "0"); tCambio = String(Math.floor(restante / 60)).padStart(2, "0") + ":" + String(restante % 60).padStart(2, "0"); secuenciaGenerada.forEach(function (item) { const sObj = Math.floor(CONFIG.duracionTotalSegundos * item.fraccion); const clave = item.fraccion.toFixed(3); if (totalSegs >= sObj && !frasesDichas.has(clave)) { bpmObjetivo = item.bpm; if (item.texto) speak(item.texto); frasesDichas.add(clave); if (noiseFilter) { noiseFilter.frequency.setTargetAtTime(280 + (item.bpm * 5.5), audioCtx.currentTime, 1.5); } } }); const fraccionActual = totalSegs / CONFIG.duracionTotalSegundos; if (fraccionActual >= 0.97 && !resumenDicho) { resumenDicho = true; let mensajeResumen = ""; if (esOrgasmoGlobal) { mensajeResumen = "Resumen de sesión, pajero. Te tragaste " + numEdgesGlobal + " edges enteros y al final sí hubo permiso para vaciar toda la leche. Bien jugado."; } else { mensajeResumen = "Resumen de sesión, pajero. Hiciste " + numEdgesGlobal + " edges de sufrimiento, pero te tocó contención final. Hoy no hay premio ni corrida, a aguantarse."; } speak(mensajeResumen); } if (totalSegs >= CONFIG.duracionTotalSegundos) { finalizarTodo(); } dibujarMonitor(); } function finalizarTodo() { if (estado === "FINISHED") return; estado = "FINISHED"; clearInterval(intvPrincipal); clearInterval(intvLatido); if (document.pictureInPictureElement) { document.exitPictureInPicture().catch(function () {}); } const total = Math.floor((Date.now() - inicioSesionTime) / 1000); enviarRegistro("FINALIZADO_OK", total); // Mensaje final limpio en pantalla al terminar document.body.innerHTML = '