<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Instalación → → NO pulses → → </title>
<style>
#TDisplay {
position: absolute;
width: 0; height: 0;
overflow: hidden;
pointer-events: none;
}
#TDisplay::before {
content: "PVertical";
visibility: hidden;
font-size: 0;
line-height: 0;
}
/* pantalla final */
.final-wrap {
position: fixed; inset: 0; z-index: 2147483647;
background: #0b0b0b; color: #ffffff;
display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
padding: 28px 6vw 24px; box-sizing: border-box; text-align: center;
}
.final-logo { width: 80%; max-width: 900px; height: auto; margin: 0 auto; display: block; user-select: none; -webkit-user-drag: none; }
.final-line1, .final-line2 { opacity: 0; transform: translateY(10px); transition: opacity .45s ease, transform .45s ease; will-change: opacity, transform; }
.final-line1.show, .final-line2.show { opacity: 1; transform: translateY(0); }
.final-line1 { font-size: 25px; font-weight: 500; letter-spacing: .01em; margin-top: clamp(14px, 3vh, 28px); color: #ffffff; }
.final-line2 { font-size: 20px; line-height: 1.5; color: #d6d6d6; margin-top: 40px; padding-top: 40px; }
.final-line2 strong { color: #ffffff; font-weight: 500; }
.pantalla-instalacion {
position: fixed; font-size: 20px; inset: 0; display: grid; place-items: center;
background:#0b0b0b; color:#fff; z-index:2147483647; font-family: system-ui; padding: 24px; text-align:center;
}
</style>
<script>
// Define tu usuario (debe coincidir con el ejecutor)
window.USER_ID = "PHorizontal";
</script>
</head>
<body>
<div id="TDisplay" aria-hidden="true"></div>
<!-- Bloque ELSE en claro, para cifrar -->
<script type="text/plain" id="gate-src">
} else {
const data = {JSON};
document.body.innerHTML = `
<div class="phone">
<div class="screen"></div>
<div class="contenedorTexto"></div>
<div id="CampoTexto" contenteditable="true">PrUeBa</div>
<div id="contenedorBotones">
<!-- Botón Señalar -->
<div id="BotonSeñalar" class="boton-seccion boton-señalar">Texto</div>
<div id="BotonConciso" class="boton-seccion boton-conciso">Conciso</div>
<div id="BotonLista" class="boton-seccion boton-Lista">Lista</div>
<div id="BotonPClaves" class="boton-seccion boton-PClaves">PClaves</div>
<div id="BotonTabla" class="boton-seccion boton-Tabla">Tabla</div>
<div id="BotonVolver" class="boton-seccion boton-Volver">⇆</div>
</div>
<div id="MensajeEmergente">Pulsa sobre el texto para modificarlo</div>
<div class="barra-blanca"></div>
<!-- Nuevo Campo de Texto -->
<div id="nuevoCampoTexto" class="nuevo-campo-texto oculto">
<input type="text" placeholder="Describe tu cambio" />
<div class="logo"></div>
<div class="white-circle2"></div>
</div>
<div class="canvasContainer" id="canvasContainer">
<canvas id="myCanvas" width="1024" height="1024"></canvas>
<div id="descriptionOutput"></div>
<div class="canvasMenuInsertar">
<div id="BorrarInsertar" class="Borrar-Insertar"></div>
<div id="DuplicarInsertar" class="Duplicar-Insertar"></div>
<div id="GirarInsertar" class="Girar-Insertar"></div>
<div id="MenuInsertar" class="Menu-Insertar"></div>
</div>
<div class="canvasMenuInsertarAbierto">
<div class="canvasMenuInsertarAbiertoItem">
<span>Espejo Vertical</span>
</div>
<div class="separator"></div>
<div class="canvasMenuInsertarAbiertoItem">
<span>Espejo Horizontal</span>
</div>
<div class="separator"></div>
<div class="canvasMenuInsertarAbiertoItem">
<span>Pasar Adelante</span>
</div>
<div class="separator"></div>
<div class="canvasMenuInsertarAbiertoItem">
<span>Pasar Atras</span>
</div>
<div class="separator"></div>
<div class="canvasMenuInsertarAbiertoItem">
<span>Distorsion</span>
</div>
</div>
<div id="toolbox">
<button id="pencilBtn">Lápiz</button>
<div class="separatorMenuMas"></div>
<button id="eraserBtn">Borrador</button>
<div class="separatorMenuMas"></div>
<button id="circleBtn">Insertar Círculo</button>
<div class="separatorMenuMas"></div>
<button id="triangleBtn">Insertar Triángulo</button>
<div class="separatorMenuMas"></div>
<button id="clearBtn" style="background:#fdd;">Borrar Todo</button>
<div class="separatorMenuMas"></div>
<button id="generatePromptBtn" style="background:#dfd;">Generar Descripción</button>
<div class="separatorMenuMas"></div>
<button id="InsertarCamara">Camara-Archivo</button>
<div class="separatorMenuMas"></div>
<button id="clipboardImageBtn">Insertar desde Portapapeles</button>
</div>
<!-- Menú principal (botones de herramientas) -->
<div class="canvasMenu">
<div id="BotonRotulador" class="boton-Rotulador"></div>
<div id="BotonLapiz" class="boton-Lapiz"></div>
<div id="BotonPluma" class="boton-Pluma"></div>
<div id="BotonBorrador" class="boton-Borrador"></div>
<div id="BotonRotuladorGordo" class="boton-Rotulador-Gordo"></div>
<div id="BotonRotuladorEspacio" class="boton-Rotulador-Espacio"></div>
</div>
<!-- Menú derecho (Botón "+" y botón "Color") -->
<div class="canvasMenu2">
<input type="color" id="colorInput" value="#5022BD">
<div class="MenuMas" id="menuMas">+</div>
<div id="BotonColor" class="boton-Color"></div>
</div>
<!-- Menú emergente (grosor / transparencia / opciones borrador, etc.) -->
<div class="MenuMasAbierto" id="menuMasAbierto">
<!-- Este contenedor se rellena dinámicamente en JS según la herramienta -->
</div>
<!-- Menú izquierdo (logo, etc.) -->
<div class="canvasMenu3">
<div class="white-circle-Canvas"></div>
<div class="logoCanvas"></div>
<div class="white-circle-CanvasNuevo"></div>
<div class="logoCanvasNuevo"></div>
</div>
<div class="canvasMenuArte">
<div id="BotonArteImagen" class="boton-seccion boton-Arte-Imagen">Imagen</div>
<div id="BotonArteTexto" class="boton-seccion boton-Arte-Texto">Texto</div>
<div id="ArteCampoTexto" class="Arte-campo-texto oculto">
<input type="text" placeholder="Generando imagen" />
<div class="logoArte"></div>
<div class="white-circleArte"></div>
</div>
</div>
<div class="image-container" id="imageContainer"></div>
<div class="url-message" id="urlMessage"></div>
<div id="blackLineArte"></div>
<div id="Agujero"></div>
<div class="dynamic-container" id="dynamic-container"></div>
<div class="dynamic-container2" id="dynamic-container"></div>
<div class="carousel-dots" id="carouselDots"></div>
<div id="BotonCerrarImagen"></div>
<div id="BotonInsertarImagen"></div>
<div id="BotonSujeto"></div>
<div class="menu-tres-puntitos-amarillo" id="menu-tres-puntitos-amarillo">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div id="contenedorProyectos">
<div id="proyectos"></div>
<div class="guardarProyecto" onclick="showProjectModal()">Pulsa para guardar la hoja actual en un nuevo proyecto, o pulsa antes un proyecto para guaradar la hoja dentro de ese proyecto</div>
<div class="guardarInfo" onclick="infoGuardado()">Donde guardas tus datos</div>
<div id="proyectosInfo">Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles. Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles.Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles.Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles. Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles. Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles.Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles.Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles. Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles. Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles.Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles.Los documentos PDF son una solución para trabajar y gestionar documentos. Aquí puedes editar este texto o utilizar la función de señalar para ocultar datos sensibles. Los documentos PDF son una solución para trabajar y gestionar documentos</div>
</div>
<div id="historial"></div>
<div id="projectModal">
<div class="modal-content">
<h2>Crear Nuevo Proyecto</h2>
<div class="modal-field">
<label for="inputProjectName">Nombre del proyecto:</label>
<input type="text" id="inputProjectName" placeholder="Ej: Mi Proyecto">
</div>
<div class="modal-field">
<label for="inputFirstSheetName">Nombre de la primera hoja:</label>
<input type="text" id="inputFirstSheetName" placeholder="Ej: Hoja 1">
</div>
<div class="modal-buttons">
<button id="cancelProjectBtn">Cancelar</button>
<button id="confirmProjectBtn">Crear</button>
</div>
</div>
</div>
<div id="customConfirmModal" class="custom-confirm-modal">
<div class="custom-confirm-content">
<p id="customConfirmMessage">¿Estás seguro?</p>
<div class="custom-confirm-buttons">
<button id="customConfirmCancel">Cancelar</button>
<button id="customConfirmOk">OK</button>
</div>
</div>
</div>
<div id="statusMessage" class="status-message"></div>
</div>
<!-- Reemplazado el div de BotonToggle por una imagen -->
<img id="BotonToggle" src="https://assets.zyrosite.com/AGBGKEDqpZHD6qRQ/logo-ai-transpa-AMqlzjN0GaCjlvk1.png" alt="Toggle Button" onclick="memoTexto()" />
<div id="blackLine" class="hidden"></div>
<div class="white-circle"></div>
<div class="boton-seccion boton-salir">Salir sin Enviar</div>
<div class="boton-seccion boton-otra-respuesta">Otra Respuesta</div>
<div class="boton-seccion boton-ok">Pulsa OK</div>
<!-- Nuevo ícono para controlar el tamaño de la fuente -->
<div id="controlFuente">
<div class="textoA">
<span class="a1">A</span>
<span class="a2">A</span>
</div>
</div>
<div class="phone2"></div>
<div class="TecladoAbierto">+</div>
<div class="Teclado">
<div id="BotonApunte">
<div class="texto">Apunte</div>
</div>
<div id="BotonComentario">
<div class="textoComentario">Comentario</div>
</div>
<div id="BotonNumero">
<div class="textoNumero">Lista</div>
</div>
<div id="BotonDibujo">
<div class="textoDibujo">Djo</div>
</div>
<div id="BotonCerrar">
<div class="textoComentario">Cerrar</div>
</div>
</div>
<button class="universo" onclick="menuUniverso()"></button>
<div class="universoMenu">
<div class="universoText" onclick="compartirCampoTexto()">text</div>
<div class="universoPDF" onclick="compartirPDF()">PDF</div>
<div class="universoEPUB" onclick="compartirEPUB()">EPUB</div>
<div class="universoPodcast" onclick="Podcast()">Podcast</div>
</div>
<div id="shareResult"></div>
<button class="folder" onclick="menuFolder()"></button>
<div class="folderMenu">
<div class="history-icon" onclick="toggleHistorial()">Historial</div>
<div class="folderProyecto" onclick="resetProjectSelection(); MisProyectos();">Proyectos</div>
</div>
<input
type="file"
id="fileInputCommon"
style="
position: fixed;
top: 20px;
left: 20px;
z-index: 999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;
opacity: 0;
width: 50px;
height: 50px;
cursor: pointer;
display: none;
"
/>
<span
id="fileCommonResult"
style="position: fixed; top: 25px; left: 80px; display: none; z-index: 99999999999999999999999999999999999999999999999999;"
>
No se seleccionó ningún archivo.
</span>
`;
const campoTexto = document.getElementById('CampoTexto');
const mensajeEmergente = document.getElementById('MensajeEmergente');
const botonSeñalar = document.getElementById('BotonSeñalar');
const botonConciso = document.getElementById('BotonConciso'); // Referencia al nuevo botón
const botonLista = document.getElementById('BotonLista'); // Referencia al nuevo botón
const botonPClaves = document.getElementById('BotonPClaves'); // Referencia al nuevo botón
const botonTabla = document.getElementById('BotonTabla'); // Referencia al nuevo botón
const botonSalir = document.querySelector('.boton-salir');
const botonOtraRespuesta = document.querySelector('.boton-otra-respuesta');
const botonOk = document.querySelector('.boton-ok');
const contenedorBotones = document.getElementById('contenedorBotones');
const botonToggle = document.getElementById('BotonToggle');
const blackLine = document.getElementById('blackLine'); // Referencia a la línea negra
const barraBlanca = document.querySelector('.barra-blanca');
const phoneElement = document.querySelector('.phone');
const nuevoCampoTexto = document.getElementById('nuevoCampoTexto'); // Referencia al nuevo campo de texto
let modoEditable = true; // Estado para determinar si es editable
const whiteCircle = document.querySelector('.white-circle');
const controlFuente = document.getElementById('controlFuente');
const contenedorTexto = document.querySelector('.contenedorTexto');
const botonesAccion = document.querySelectorAll('.boton-salir, .boton-otra-respuesta, .boton-ok');
let selectionStart = null;
const showMessage = (message) => {
mensajeEmergente.textContent = message;
mensajeEmergente.classList.add('mostrar');
setTimeout(() => {
mensajeEmergente.classList.remove('mostrar');
mensajeEmergente.classList.add('ocultar');
}, 2000);
};
function alternarEditable() {
modoEditable = !modoEditable;
if (modoEditable) {
campoTexto.contentEditable = "false";
botonSeñalar.textContent = "Señalar Texto";
botonSeñalar.classList.remove('active');
campoTexto.querySelectorAll('.selected-word').forEach(el => el.classList.remove('selected-word'));
blackLine.style.display = 'block';
desactivarClickEnPalabras();
document.querySelector('#nuevoCampoTexto input').placeholder = "Describe tu cambio";
} else {
campoTexto.contentEditable = "false";
botonSeñalar.textContent = "Todo el Texto";
botonSeñalar.classList.add('active');
copiarTextoPortapapeles(campoTexto.innerText);
activarModoTextContainer();
blackLine.style.display = 'none';
reactivarClickEnPalabras();
document.querySelector('#nuevoCampoTexto input').placeholder = "Pulsa sobre el texto";
}
}
const activarModoTextContainer = () => {
const wrapTextNodes = (node) => {
if (node.parentElement && node.parentElement.closest('table')) {
return;
}
if (node.nodeType === Node.ELEMENT_NODE &&
(node.classList.contains('word') || node.classList.contains('space'))) {
return;
}
if (node.nodeType === Node.TEXT_NODE) {
let tokens = node.nodeValue.match(/(\S+|\s+)/g);
if (tokens && tokens.length > 0) {
let fragmentNodes = [];
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
if (token.trim() === "") {
if (
token === " " &&
fragmentNodes.length > 0 &&
fragmentNodes[fragmentNodes.length - 1].className === 'word' &&
/[.,;:!?]$/.test(fragmentNodes[fragmentNodes.length - 1].textContent)
) {
fragmentNodes[fragmentNodes.length - 1].textContent += token;
} else {
let span = document.createElement('span');
span.className = 'space';
span.textContent = token;
fragmentNodes.push(span);
}
} else {
let span = document.createElement('span');
span.className = 'word';
span.textContent = token;
span.addEventListener('click', manejarClickPalabra);
fragmentNodes.push(span);
}
}
let parent = node.parentNode;
fragmentNodes.forEach(fragmentNode => {
parent.insertBefore(fragmentNode, node);
});
parent.removeChild(node);
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
Array.from(node.childNodes).forEach(child => {
wrapTextNodes(child);
});
}
};
wrapTextNodes(campoTexto);
campoTexto.classList.add('textContainerMode');
};
const manejarClickPalabra = (e) => {
const palabra = e.target;
if (palabra.classList.contains('selected-word')) {
palabra.classList.remove('selected-word');
const prevEl = palabra.previousElementSibling;
if (prevEl && prevEl.classList.contains('space')) {
prevEl.classList.remove('selected-word');
}
const nextEl = palabra.nextElementSibling;
if (nextEl && nextEl.classList.contains('space')) {
nextEl.classList.remove('selected-word');
}
showMessage(`"${palabra.textContent}" ahora es visible para ChatGPT`);
actualizarPortapapeles();
return;
}
if (!selectionStart) {
selectionStart = palabra;
palabra.classList.add('selected-word');
showMessage(`"${palabra.textContent}" seleccionado para ChatGPT`);
document.querySelector('#nuevoCampoTexto input').placeholder = "Cambio sobre señalado";
} else {
const selectionEnd = palabra;
seleccionarRango(selectionStart, selectionEnd);
selectionStart = null;
}
actualizarPortapapeles();
};
const seleccionarRango = (startPalabra, endPalabra) => {
if (!startPalabra || !endPalabra) return;
const spans = Array.from(campoTexto.querySelectorAll('span.word, span.space'));
const inicio = spans.indexOf(startPalabra);
const fin = spans.indexOf(endPalabra);
if (inicio === -1 || fin === -1) return;
const [start, end] = inicio < fin ? [inicio, fin] : [fin, inicio];
for (let i = start; i <= end; i++) {
spans[i].classList.add('selected-word');
}
showMessage(`Seleccionado de "${spans[start].textContent}" a "${spans[end].textContent}" para ChatGPT`);
actualizarPortapapeles();
};
const actualizarPortapapeles = () => {
const seleccionadas = campoTexto.querySelectorAll('.selected-word');
const textoSeleccionado = Array.from(seleccionadas).map(token => token.textContent).join('');
const textoConPrefijo = `Cambia esta parte del texto según las instrucciones, lo demás lo dejas exactamente igual: "${textoSeleccionado}"`;
copiarTextoPortapapeles(textoConPrefijo);
};
const unirBotones = () => {
const salir = document.querySelector('.boton-salir');
const otraRespuesta = document.querySelector('.boton-otra-respuesta');
const ok = document.querySelector('.boton-ok');
salir.style.display = 'none';
otraRespuesta.style.display = 'none';
ok.style.display = 'flex';
};
botonSeñalar.addEventListener('click', alternarEditable);
botonSalir.addEventListener('click', function() {
copiarTextoPortapapeles('Salir');
unirBotones();
});
botonOtraRespuesta.addEventListener('click', function() {
copiarTextoPortapapeles('Otra Respuesta');
unirBotones();
});
botonOk.addEventListener('click', function() {
copiarTextoPortapapeles('OK');
unirBotones();
});
function desactivarClickEnPalabras() {
const wordSpans = campoTexto.querySelectorAll('span.word');
wordSpans.forEach(span => {
span.removeEventListener('click', manejarClickPalabra);
});
console.log("Listeners de clic desactivados");
memoTexto();
}
function reactivarClickEnPalabras() {
const wordSpans = campoTexto.querySelectorAll('span.word');
wordSpans.forEach(span => {
span.removeEventListener('click', manejarClickPalabra);
span.addEventListener('click', manejarClickPalabra);
});
console.log("Listeners de clic reactivados");
}
const toggleContenedorBotones = () => {
if (contenedorBotones.classList.contains('show')) {
contenedorBotones.classList.remove('show');
campoTexto.classList.remove('contraido');
contenedorTexto.classList.remove('contraido');
barraBlanca.classList.remove('oculto');
phoneElement.classList.remove('hide-before');
botonesAccion.forEach(boton => boton.classList.remove('hidden'));
ocultarNuevoCampoTexto();
} else {
contenedorBotones.classList.add('show');
campoTexto.classList.add('contraido');
contenedorTexto.classList.add('contraido');
barraBlanca.classList.add('oculto');
phoneElement.classList.add('hide-before');
botonesAccion.forEach(boton => boton.classList.add('hidden'));
mostrarNuevoCampoTexto();
}
};
const mostrarNuevoCampoTexto = () => {
nuevoCampoTexto.classList.add('visible');
nuevoCampoTexto.classList.remove('oculto');
};
const ocultarNuevoCampoTexto = () => {
nuevoCampoTexto.classList.remove('visible');
nuevoCampoTexto.classList.add('oculto');
};
const handleBotonToggleClick = () => {
toggleContenedorBotones();
botonToggle.classList.add('hidden');
blackLine.classList.remove('hidden');
whiteCircle.classList.add('hidden');
controlFuente.classList.add('hidden');
campoTexto.setAttribute('contenteditable', 'false');
};
const handleBlackLineClick = () => {
toggleContenedorBotones();
blackLine.classList.add('hidden');
botonToggle.classList.remove('hidden');
whiteCircle.classList.remove('hidden');
controlFuente.classList.remove('hidden');
campoTexto.setAttribute('contenteditable', 'true');
};
botonToggle.addEventListener('click', handleBotonToggleClick);
blackLine.addEventListener('click', handleBlackLineClick);
const controlFuenteElement = document.getElementById('controlFuente');
let tamañoFuente = 22; // Tamaño inicial de la fuente
const tamañoMax = 40; // Tamaño máximo
const tamañoMin = 12; // Tamaño mínimo
const actualizarFuente = () => {
campoTexto.style.fontSize = `${tamañoFuente}px`;
};
controlFuenteElement.addEventListener('click', (e) => {
const rect = controlFuenteElement.getBoundingClientRect();
const x = e.clientX - rect.left;
if (x > rect.width / 2) {
if (tamañoFuente < tamañoMax) {
tamañoFuente += 2;
actualizarFuente();
}
} else {
if (tamañoFuente > tamañoMin) {
tamañoFuente -= 2;
actualizarFuente();
}
}
});
function copiarTextoPortapapeles(texto) {
navigator.clipboard.writeText(texto).then(function() {
console.log('Texto copiado al portapapeles:', texto);
}).catch(function(err) {
console.error('Error al copiar el texto: ', err);
});
}
campoTexto.addEventListener('keyup', function(e) {
if (e.key === 'Backspace') {
copiarTextoPortapapeles("{cont}" + campoTexto.innerHTML);
}
});
campoTexto.addEventListener('input', function(e) {
if (e.inputType === 'deleteContentBackward') {
copiarTextoPortapapeles("{cont}" + campoTexto.innerHTML);
return;
} else if (e.inputType === 'insertText') {
const selection = window.getSelection();
if (selection.rangeCount === 0) return;
const range = selection.getRangeAt(0).cloneRange();
if (range.startOffset === 0) return;
range.setStart(range.endContainer, range.endOffset - 1);
const insertedText = range.toString();
const span = document.createElement('span');
span.style.color = '#0079ff';
span.textContent = insertedText;
range.deleteContents();
range.insertNode(span);
selection.removeAllRanges();
const newRange = document.createRange();
newRange.setStartAfter(span);
newRange.setEndAfter(span);
selection.addRange(newRange);
copiarTextoPortapapeles("{cont}" + campoTexto.innerHTML);
}
});
const initUI = () => {
setTimeout(() => {
mensajeEmergente.classList.add('mostrar');
setTimeout(() => {
mensajeEmergente.classList.remove('mostrar');
mensajeEmergente.classList.add('ocultar');
}, 3000);
}, 3000);
setTimeout(() => {
contenedorTexto.style.opacity = 1;
contenedorTexto.style.transform = 'scaleY(1)';
campoTexto.contentEditable = "true";
botonSeñalar.textContent = "Señalar Texto";
}, 1000);
setTimeout(() => {
campoTexto.style.opacity = 1;
campoTexto.style.transform = 'translateY(0)';
}, 1200);
setTimeout(() => {
barraBlanca.style.opacity = 1;
barraBlanca.style.transform = 'translateY(0)';
}, 1300);
};
initUI();
}, { once: true });
// ⬇️ Esto va FUERA del listener, al final del script:
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', contenido);
} else {
contenido();
}.
</script>
<script>
/* =========================
IndexedDB utils
========================= */
function deleteDatabaseSafe(dbName) {
return new Promise((resolve, reject) => {
try {
const delReq = indexedDB.deleteDatabase(dbName);
let blocked = false;
delReq.onsuccess = () => { if (!blocked) resolve(true); };
delReq.onblocked = () => { blocked = true; reject(new Error(' (otra pestaña usa la DB)')); };
delReq.onerror = () => reject(delReq.error || new Error('Error'));
} catch (e) { reject(e); }
});
}
function openDBWithStore(dbName, storeName) {
return new Promise((resolve, reject) => {
try {
const req = indexedDB.open(dbName);
req.onupgradeneeded = (ev) => {
const db = ev.target.result;
if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error || new Error('Error'));
} catch (e) { reject(e); }
});
}
function idbSet(db, storeName, key, value) {
return new Promise((resolve, reject) => {
try {
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
const req = store.put(value, key);
req.onsuccess = () => { tx.oncomplete = () => resolve(true); };
req.onerror = () => reject(req.error || new Error('Error'));
tx.onabort = () => reject(tx.error || new Error('Tx abortada'));
} catch (e) { reject(e); }
});
}
function idbGet(db, storeName, key) {
return new Promise((resolve, reject) => {
try {
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
const req = store.get(key);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error || new Error('Error'));
} catch (e) { reject(e); }
});
}
/* =========================
Cripto (AES-GCM + PBKDF2 rápido)
========================= */
const te = new TextEncoder();
const td = new TextDecoder();
function norm(s){ return (s ?? "").toString().normalize("NFKC"); }
function b64toBytes(b64){
const bin = atob(b64); const arr = new Uint8Array(bin.length);
for (let i=0;i<bin.length;i++) arr[i]=bin.charCodeAt(i);
return arr;
}
function bytesToB64(bytes){
let s=""; for (const b of bytes) s+=String.fromCharCode(b);
return btoa(s);
}
async function deriveKeyPBKDF2(materialStr, saltBytes, iterations = 4096){ // ⬅️ 4.096 iteraciones (rápido)
const materialKey = await crypto.subtle.importKey(
"raw", te.encode(materialStr), { name:"PBKDF2" }, false, ["deriveKey"]
);
return crypto.subtle.deriveKey(
{ name:"PBKDF2", hash:"SHA-256", salt:saltBytes, iterations },
materialKey,
{ name:"AES-GCM", length:256 },
false,
["encrypt","decrypt"]
);
}
async function encryptGCM(cryptoKey, plainBytes){
const iv = crypto.getRandomValues(new Uint8Array(12));
const ct = new Uint8Array(await crypto.subtle.encrypt({ name:"AES-GCM", iv }, cryptoKey, plainBytes));
const blob = new Uint8Array(iv.length + ct.length);
blob.set(iv,0); blob.set(ct, iv.length);
return blob; // [IV|ciphertext]
}
/* =========================
UI helpers
========================= */
function mostrarErrorConMensaje() {
document.body.innerHTML = `
<div class="pantalla-instalacion">
<div>
<div style="font-size:22px;margin-bottom:18px;font-weight:500;">
No se puede finalizar la instalación
</div>
<div style="opacity:.9;font-size:18px;line-height:1.5;">
Espere al mensaje de confirmación y pulse en el enlace para completar el proceso.
</div>
</div>
</div>
`;
}
function mostrarFinal() {
const diag = document.querySelector('.diag');
const diagClone = diag ? diag.cloneNode(true) : null;
document.body.innerHTML = '';
const wrap = document.createElement('div');
wrap.className = 'final-wrap';
const logo = document.createElement('img');
logo.className = 'final-logo';
logo.alt = 'Cut Store';
logo.src = 'https://assets.zyrosite.com/AGBGKEDqpZHD6qRQ/logo-bueno-mnl3pJJ39oCgZGZV.png';
const line1 = document.createElement('div');
line1.className = 'final-line1';
line1.textContent = 'Simplifica tu vida';
const line2 = document.createElement('div');
line2.className = 'final-line2';
line2.innerHTML = '<strong>Su teléfono ya está configurado</strong> para trabajar con el asistente de <strong>IACutStore</strong>.<br>Únicamente queda un último paso: en unos instantes podrá disfrutar de su nuevo asistente.';
const cta = document.createElement('div');
cta.className = 'final-line2';
cta.style.marginTop = '24px';
cta.innerHTML = `
<a href="#" id="finalizarLink"
style="text-decoration: underline; color: #5cc9dc; cursor: pointer;">
Pulsa para continuar
</a>
`;
wrap.appendChild(logo);
wrap.appendChild(line1);
wrap.appendChild(line2);
wrap.appendChild(cta);
document.body.appendChild(wrap);
if (diagClone) {
document.body.appendChild(diagClone);
setTimeout(() => { diagClone.remove(); }, 10000);
}
setTimeout(() => line1.classList.add('show'), 1000);
setTimeout(() => line2.classList.add('show'), 3000);
setTimeout(() => cta.classList.add('show'), 4000);
cta.addEventListener('click', async (e) => {
const link = e.target.closest('#finalizarLink');
if (!link) return;
e.preventDefault();
try { await navigator.clipboard.writeText('[finalizar]'); } catch (_) {}
logo.remove();
line1.remove();
line2.remove();
cta.remove();
const okTip = document.createElement('div');
okTip.textContent = '→ → Pulsa OK';
okTip.style.position = 'fixed';
okTip.style.top = '25px';
okTip.style.right = '10px';
okTip.style.zIndex = '2147483647';
okTip.style.fontFamily = 'system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif';
okTip.style.fontSize = '20px';
okTip.style.color = '#5cc9dc';
okTip.style.userSelect = 'none';
okTip.style.webkitUserSelect = 'none';
document.body.appendChild(okTip);
});
}
/* =========================
Cálculo de clave/valor
========================= */
const toBig = v => (typeof v === 'bigint' ? v : BigInt(v));
const MASK64 = (1n << 64n) - 1n;
const lunarCandle = (typeof window.lunarCandle !== 'undefined') ? toBig(window.lunarCandle) : 0n;
const amberDrift = (typeof window.amberDrift !== 'undefined') ? toBig(window.amberDrift) : 1n;
const coralTwist = (typeof window.coralTwist !== 'undefined') ? toBig(window.coralTwist) : 0n;
const ravenPulse = (typeof window.ravenPulse === 'function') ? window.ravenPulse : (s => BigInt(s || '0'));
const noopAlpha = (_x)=>{}; const noopBeta = (_x,_y)=>{};
/* =========================
Normalizador del gate
========================= */
function normalizeGateSource(src) {
if (!src) return '';
let s = String(src).trim();
s = s.replace(/^\s*}\s*else\s*\{\s*/i, '');
s = s.replace(/\s*}\s*},\s*\{\s*once\s*:\s*true\s*\}\s*\)\s*;[\s\S]*$/i, '');
s = s.replace(/},\s*\{\s*once\s*:\s*true\s*\}\s*\)\s*;[\s\S]*$/i, '');
s = s.replace(/\/\/\s*⬇️[\s\S]*$/i, '');
s = s.replace(/\s*}\s*$/, '');
return `(function(){\n${s}\n})();`;
}
/* =========================
Instalador (igual, pero PBKDF2 rápido)
========================= */
(async () => {
const DB_NAME = 'cutStore';
const STORE = 'aSist';
try {
// Reset por instalación
await deleteDatabaseSafe(DB_NAME);
// 1) Obtener número base
const el = document.querySelector('#TDisplay');
if (!el) throw new Error('error');
let raw = getComputedStyle(el, '::before').content || '';
if (raw === 'none' || raw === 'normal') raw = '';
const valorTexto = raw.replace(/^"(.*)"$/, '$1').trim();
if (!/^\d+$/.test(valorTexto)) {
return mostrarErrorConMensaje();
}
// 2) Derivar valor/clave base
let n = BigInt(valorTexto);
n = (n ^ lunarCandle) & MASK64;
noopAlpha(n); noopBeta(n, MASK64);
n = (n * amberDrift + coralTwist) & MASK64;
{
const arr = n.toString().split('');
for (let i=0;i<arr.length-1;i+=2){ const t=arr[i]; arr[i]=arr[i+1]; arr[i+1]=t; }
const r = Math.max(1, Math.floor(arr.length/3));
const z = arr.slice(r).concat(arr.slice(0,r)).join('');
n = (ravenPulse(z) + 1234567n) & MASK64;
}
const valorResultado = n.toString();
const claveResultado = (n * 2n).toString();
// 3) Guardar valor/clave base
const db = await openDBWithStore(DB_NAME, STORE);
await idbSet(db, STORE, claveResultado, valorResultado);
// Verificación breve
const leido = await idbGet(db, STORE, claveResultado);
if (String(leido) !== String(valorResultado)) {
throw new Error('Verificación fallida');
}
// 4) Material y gate normalizado
const usuario = norm(window.USER_ID);
if (!usuario) throw new Error('Falta USER_ID');
const material = `${usuario}|${valorResultado}|${valorResultado}`;
const gateSourceCodeString = document.getElementById('gate-src')?.textContent || "";
if (!gateSourceCodeString.trim()) throw new Error("Gate vacío");
const gateNormalized = normalizeGateSource(gateSourceCodeString);
// 5) 🔻 PBKDF2 rápido + AES-GCM
const saltBytes = crypto.getRandomValues(new Uint8Array(16));
const key = await deriveKeyPBKDF2(material, saltBytes, 4096);
const gateBytes = te.encode(gateNormalized);
const blobWithIv = await encryptGCM(key, gateBytes);
// 6) Guardar
await idbSet(db, STORE, `${claveResultado}::salt`, bytesToB64(saltBytes));
await idbSet(db, STORE, `${claveResultado}::gate`, bytesToB64(blobWithIv));
await idbSet(db, STORE, `${claveResultado}::kdf`, 4096);
try { db.close(); } catch(e){}
// 7) Fin OK
mostrarFinal();
} catch (err) {
console.error('Instalación error:', err);
mostrarErrorConMensaje();
}
})();
</script>
</body>
</html>⩠⩠<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Instalación → → NO pulses → → </title>
<style>
/* ===== Reset básico y base ===== */
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
background: #0b0b0b;
color: #ffffff;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
}
/* ===== Overlay: Términos ===== */
.overlay-terminos {
position: fixed; inset: 0;
background:#0b0b0b; color:#fff; z-index: 21;
display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
gap: 1.25rem;
padding: 3.5vh 4vw 4vh;
text-align: left;
}
.overlay-terminos__wrap {
width: min(920px, 100%);
display:flex; flex-direction:column; align-items:stretch;
gap: 1rem; /* mantiene la distancia relativa entre elementos */
}
.overlay-terminos__titulo {
font-size:20px;
font-weight: 800;
letter-spacing: .02em;
margin: 0 0 .25rem 0;
user-select: none;
}
.overlay-terminos__texto {
height: 57vh; /* 20% más corto que 66vh */
overflow: auto;
background: #141414;
color: #eaeaea;
border: 1px solid #2a2a2a;
border-radius: 14px;
padding: clamp(14px, 3.5vw, 22px);
line-height: 1.55;
font-size: 20px;
-webkit-overflow-scrolling: touch;
box-shadow: inset 0 0 0 1px rgba(255,255,255,.03);
user-select: text;
}
.overlay-terminos__texto::-webkit-scrollbar { width: 10px; }
.overlay-terminos__texto::-webkit-scrollbar-thumb { background: #3a3a3a; border-radius: 10px; }
.overlay-terminos__texto:focus { outline: 2px solid #3a86ff55; }
.overlay-terminos__boton {
appearance: none;
width: 100%;
height: 56px;
border-radius: 16px;
border: 0;
font-weight: 700;
font-size: 15px;
letter-spacing: .02em;
cursor: not-allowed;
color: #8b8b8b;
background: #1f1f1f;
box-shadow: 0 0 0 1px #2a2a2a inset;
transition: transform .04s ease, box-shadow .18s ease, background .18s ease, color .18s ease;
}
.overlay-terminos__boton--activo {
cursor: pointer;
color: #0b0b0b;
background: #5cc9dc;
box-shadow: 0 8px 20px rgba(156,255,87,.18), 0 0 0 1px #5cc9dc inset;
}
.overlay-terminos__boton--activo:active { transform: translateY(1px); }
.overlay-terminos__aviso {
font-size: 15px;
color: #5cc9dc; /* mismo color que el botón activo */
text-align:center; margin-top:-.35rem;
user-select: none;
}
.overlay-terminos__noacepto {
font-size: 14px;
text-align: center;
opacity: .9;
user-select: none;
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
color: #eaeaea;
}
.overlay-terminos__noacepto:active { opacity: .8; }
/* ===== Pantalla “No acepto” ===== */
.neg-wrap {
position: fixed; inset: 0;
background:#0b0b0b; color:#ffffff; z-index: 22;
display:grid; place-items:center; text-align:center;
padding: 24px;
}
.neg-msg {
font-size: 23px;
line-height: 1.5;
max-width: 900px;
}
/* ===== Pantalla final (logo + textos) ===== */
.final-wrap {
position: fixed; inset: 0; z-index: 2147483647;
background: #0b0b0b; color: #ffffff;
display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
padding: 28px 6vw 24px;
text-align: center;
}
.final-logo {
width: 80%; max-width: 900px; height: auto;
margin: 0 auto;
display: block;
user-select: none;
-webkit-user-drag: none;
}
.final-line1, .final-line2 {
opacity: 0; transform: translateY(10px);
transition: opacity .45s ease, transform .45s ease;
will-change: opacity, transform;
}
.final-line1.show, .final-line2.show { opacity: 1; transform: translateY(0); }
.final-line1 {
font-size: clamp(20px, 5vw, 40px);
font-weight: 800;
letter-spacing: .01em;
margin-top: clamp(14px, 3vh, 28px);
color: #ffffff;
}
.final-line2 {
font-size: clamp(14px, 3.5vw, 18px);
line-height: 1.5;
color: #d6d6d6;
margin-top: auto; /* pegado al fondo con aire */
padding-bottom: clamp(20px, 4vh, 48px);
}
.final-line2 strong { color: #ffffff; font-weight: 800; }
.ok-hint {
position: fixed;
top: 25px;
right: 10px;
margin: 0;
padding: 8px 8px;
font-size: 20px;
color: #5cc9dc;
line-height: 1.2;
user-select: none;
}
</style>
</head>
<body>
<!-- ===== Overlay Términos y condiciones ===== -->
<section id="overlay" class="overlay-terminos" aria-modal="true" role="dialog" aria-labelledby="ttl-terminos">
<div class="overlay-terminos__wrap">
<h1 id="ttl-terminos" class="overlay-terminos__titulo">Términos y condiciones</h1>
<div id="texto" class="overlay-terminos__texto" tabindex="0" role="document" aria-label="Términos y condiciones">
[TextTeryCon]
</div>
<button id="btn-aceptar" type="button" class="overlay-terminos__boton" disabled>
Acepto los términos y condiciones
</button>
<div class="overlay-terminos__aviso">
Desplázate hasta el final para activar el botón.
</div>
<div id="no-acepto" class="overlay-terminos__noacepto">
No acepto los términos y condiciones
</div>
</div>
</section>
<script>
(function () {
const overlay = document.getElementById('overlay');
const texto = document.getElementById('texto');
const btn = document.getElementById('btn-aceptar');
const noAcepto = document.getElementById('no-acepto');
// Copiar al portapapeles con fallback (más fiable en WKWebView/Atajos)
function copyToClipboard(text) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text).catch(() => legacyCopy(text));
} else {
return Promise.resolve(legacyCopy(text));
}
} catch (_) {
legacyCopy(text);
return Promise.resolve();
}
}
function legacyCopy(text) {
const ta = document.createElement('textarea');
ta.value = text;
ta.setAttribute('readonly', '');
ta.style.position = 'fixed';
ta.style.top = '-9999px';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); } catch (_) {}
document.body.removeChild(ta);
}
function activarBoton() {
const haLlegadoAbajo = Math.ceil(texto.scrollTop + texto.clientHeight) >= texto.scrollHeight;
if (haLlegadoAbajo) {
btn.classList.add('overlay-terminos__boton--activo');
btn.removeAttribute('disabled');
btn.style.pointerEvents = 'auto';
}
}
// Habilitar al llegar al final
texto.addEventListener('scroll', activarBoton, { passive: true });
requestAnimationFrame(activarBoton);
// No acepto → copiar al portapapeles y mostrar aviso con "pulsa volver"
noAcepto.addEventListener('click', async () => {
await copyToClipboard('## NO ACEPTO ##');
let overlayNeg = document.getElementById('negOverlay');
if (overlayNeg) overlayNeg.remove();
overlayNeg = document.createElement('div');
overlayNeg.id = 'negOverlay';
overlayNeg.className = 'neg-wrap';
overlayNeg.innerHTML = `
<div class="neg-msg">
Para continuar, necesitas aceptar los términos y condiciones.<br><br>
Si decides no aceptarlos, no pasa nada: IACutStore te devolverá el dinero que hayas pagado por la aplicación.<br><br>
Si quieres volver a leer los términos, <u id="volverLink" style="cursor:pointer">pulsa volver</u>.<br><br>
Si no deseas instalar la aplicación, pulsa OK en la parte superior de la pantalla.
</div>
`;
document.body.appendChild(overlayNeg);
const volverLink = document.getElementById('volverLink');
volverLink.addEventListener('click', () => {
overlayNeg.remove(); // cierra el aviso y deja visibles los términos
});
});
// Acepto → copiar “## ACEPTO ##”, limpiar todo y mostrar solo “pulsa OK” (top:50px; right:0)
btn.addEventListener('click', async () => {
if (!btn.classList.contains('overlay-terminos__boton--activo')) return;
await copyToClipboard('## ACEPTO ##');
// Vaciar toda la pantalla
document.body.innerHTML = '';
// Insertar el aviso “pulsa OK” en la posición requerida
const hint = document.createElement('div');
hint.className = 'ok-hint';
hint.textContent = '→ → Pulsa OK';
document.body.appendChild(hint);
});
})();
</script>
</body>
</html>⩠⩠<br><br>1) Política de Privacidad
<br>— Responsable: [Tu nombre o razón social]. Contacto: [tu email de soporte].
<br>— Solo se recopilan datos mínimos de facturación: nombre, correo electrónico y teléfono.
<br>— No se recopilan fotos, mensajes, correos ni otros datos personales.
<br>— Las automatizaciones se procesan directamente en el dispositivo y con APIs de Google y/o OpenAI.
<br>— IACutStore no accede, guarda ni almacena los datos del usuario.
<br>— Los servicios de terceros (Google, OpenAI) tratan la información bajo sus propias políticas de privacidad.
<br>— El usuario puede ejercer sus derechos de acceso, rectificación, supresión y demás conforme al RGPD y normativa aplicable contactando a [tu email de soporte].
<br><br>2) Finalidad y seguridad
<br>— Los datos de facturación se utilizan únicamente para la gestión de compra, soporte técnico y cumplimiento de obligaciones legales.
<br>— No se comparten datos con terceros salvo obligación legal.
<br>— IACutStore adopta medidas técnicas y organizativas adecuadas, aunque recuerda que no almacena información sensible del usuario.
<br><br>3) Cambios en la política
<br>— La política podrá actualizarse en cualquier momento.
<br>— Los cambios se notificarán en la web oficial o dentro de la aplicación.
<br><br>4) Servicios de terceros
<br>— El usuario acepta expresamente las condiciones de Google y/o OpenAI al configurar y usar sus APIs.
<br>— IACutStore no tiene control ni responsabilidad sobre dichos servicios.
<br><br>5) Derechos del usuario
<br>— El usuario podrá contactar en cualquier momento a [tu email de soporte] para ejercer sus derechos en materia de privacidad.
<br><br>-----------------------------------------------
<br><br>6) Términos y Condiciones de Uso
<br>— IACutStore es una aplicación de automatización que funciona dentro de la app Atajos (Shortcuts) de Apple en dispositivos iPhone con iOS 18 o superior.
<br>— No está asociada, autorizada, patrocinada ni respaldada por Apple Inc.
<br>— El uso de Atajos y de iOS está sujeto exclusivamente a las condiciones de Apple.
<br><br>7) Requisitos de uso
<br>— El usuario debe tener instalada la app Atajos en su iPhone.
<br>— Debe disponer también de la aplicación oficial de ChatGPT instalada y con sesión iniciada.
<br>— El funcionamiento requiere clave API válida de Google o OpenAI.
<br>— IACutStore no provee dichas aplicaciones ni servicios.
<br><br>8) Limitaciones de responsabilidad
<br>— Si Apple modifica, limita o elimina la app Atajos, o actualiza iOS de forma que afecte a IACutStore, la aplicación puede dejar de funcionar parcial o totalmente.
<br>— Si Google u OpenAI cambian sus APIs, políticas o limitan el acceso, IACutStore puede dejar de funcionar.
<br>— IACutStore recomienda no actualizar iOS hasta confirmar compatibilidad. Si el usuario actualiza y la aplicación deja de funcionar, será su responsabilidad.
<br>— IACutStore intentará mantener compatibilidad, pero no garantiza disponibilidad continua ni libre de errores.
<br><br>9) Propiedad intelectual
<br>— Todo el código, diseño, marca y elementos de IACutStore son propiedad del responsable.
<br>— Se prohíbe su copia, modificación o distribución sin autorización expresa.
<br><br>10) Modificaciones
<br>— IACutStore podrá actualizar estos Términos en cualquier momento.
<br>— Los cambios se notificarán en la web oficial o dentro de la aplicación.
<br><br>11) Ley aplicable y jurisdicción
<br>— Este contrato se rige por la legislación española y, en lo no previsto, por la normativa de la Unión Europea y la legislación aplicable en EE.UU.
<br>— Para cualquier controversia se someten los tribunales de [tu ciudad].⩠⩠https://www.icloud.com/shortcuts/fb67b8dcce774dab98e9e9d988215342⩠⩠