Consola Syra - C.E.P.S. Control
"edge. no te corras",
"límite. aguanta ahí",
"edge. contén todo",
"borde. no cruces"
];
const avisosBajada = [
"disminuye velocidad",
"más despacio",
"baja el ritmo",
"suaviza ahora"
];
const avisosOrgasmo = [
"ahora sí. córrete",
"orgasmo. vacíate",
"permiso. córrete ahora",
"libérate. toda la leche"
];
const avisosContencion = [
"contención final. aguanta",
"no cruces el límite",
"mantente al borde",
"bloqueo. no te corras"
];
const avisosRitmo = [
"acelera un poco",
"sube el ritmo",
"más rápido",
"mantén este ritmo",
"sigue el metrónomo",
recordatorio
];
const zonas = [];
const gap = 0.85 / numEdgesGlobal;
for (let i = 0; i < numEdgesGlobal; i++) {
const start = 0.05 + i * gap;
zonas.push([start, start + (gap * 0.7)]);
}
const edges = zonas.map(function (z) {
return parseFloat((z[0] + Math.random() * (z[1] - z[0])).toFixed(3));
});
puntos.push({ fraccion: 0.00, bpm: 48 + Math.floor(Math.random() * 8), texto: recordatorio });
puntos.push({ fraccion: 0.03, bpm: 65 + Math.floor(Math.random() * 15), texto: "" });
for (let i = 0; i < numEdgesGlobal; i++) {
const edgeFrac = edges[i];
const zonaIni = zonas[i][0];
const mid1 = parseFloat((zonaIni + (edgeFrac - zonaIni) * 0.4).toFixed(3));
puntos.push({
fraccion: mid1,
bpm: 100 + Math.floor(Math.random() * 50) + i * 5,
texto: Math.random() < 0.6 ? random(avisosRitmo) : ""
});
const preFrac = parseFloat((edgeFrac - 0.02).toFixed(3));
if (preFrac > mid1) {
puntos.push({
fraccion: preFrac,
bpm: 170 + Math.floor(Math.random() * 40),
texto: ""
});
}
puntos.push({
fraccion: edgeFrac,
bpm: 250 + Math.floor(Math.random() * 55),
texto: random(avisosEdge)
});
const bajaFrac = Math.min(parseFloat((edgeFrac + 0.03).toFixed(3)), 0.87);
puntos.push({
fraccion: bajaFrac,
bpm: 48 + Math.floor(Math.random() * 22),
texto: random(avisosBajada)
});
}
puntos.push({
fraccion: 0.88,
bpm: 180 + Math.floor(Math.random() * 30),
texto: random(["acelera", "sube el ritmo", recordatorio])
});
esOrgasmoGlobal = Math.random() > 0.10;
const textoFinal = esOrgasmoGlobal ? random(avisosOrgasmo) : random(avisosContencion);
puntos.push({
fraccion: parseFloat((0.92 + Math.random() * 0.02).toFixed(3)),
bpm: esOrgasmoGlobal ? (290 + Math.floor(Math.random() * 30)) : 120,
texto: textoFinal
});
puntos.push({ fraccion: 0.96, bpm: 55, texto: "" });
puntos.push({ fraccion: 1.00, bpm: 42, texto: "" });
puntos.sort(function (a, b) { return a.fraccion - b.fraccion; });
return puntos;
}
const secuenciaGenerada = generarSecuencia();
document.getElementById("session-id").innerText = "SESION " + CONFIG.idSesion;
document.getElementById("instrucciones-ui").innerHTML =
"
" + CONFIG.idSesion + " — " + CONFIG.nombreSesion + "" +
"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 = '
PROTOCOLO FINALIZADO
';
document.body.style.overflow = "hidden";
}
window.addEventListener("beforeunload", function () {
if (estado === "RUNNING") {
enviarRegistro("CIERRE_ABRUPTO", Math.floor((Date.now() - inicioSesionTime) / 1000));
}
});
window.speechSynthesis.onvoiceschanged = initVoice;
setTimeout(initVoice, 100);
setInterval(dibujarMonitor, 100);