/* — Estilos Globales de Oxymorai — */
.oxy-container {
font-family: ‘Inter’, sans-serif;
background: #111111; /* Fondo oscuro principal */
color: #e0e0e0; /* Texto claro */
border: 1px solid #333333;
border-radius: 16px;
padding: 2.5rem;
max-width: 550px; /* Un poco más ancho para el nuevo layout */
margin: 0 auto;
text-align: center;
box-shadow: 0 10px 40px rgba(0,0,0,0.6);
}
.oxy-title {
margin-bottom: 1.8rem;
font-size: 1.8rem; /* Un poco más grande */
font-weight: 700; /* Más peso */
color: #00bcd4; /* Color de acento de Oxymorai */
}
.oxy-subtitle {
font-size: 1rem;
color: #aaa;
margin-bottom: 2rem;
}
.oxy-btn {
border: none;
padding: 12px 28px;
border-radius: 10px; /* Bordes más suaves */
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
font-size: 1.05rem;
margin: 8px; /* Más espacio entre botones */
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px; /* Espacio entre icono y texto */
}
/* — Botones de Acción — */
.btn-record { background: #E91E63; color: white; } /* Rosa más vibrante */
.btn-record:hover { background: #D81B60; }
.btn-stop { background: #333333; color: #bbb; }
.btn-stop:hover { background: #444444; }
.btn-send { background: #00bcd4; color: white; width: 100%; margin-top: 20px;}
.btn-send:disabled { background: #555555; cursor: not-allowed; }
.btn-upload { background: #673AB7; color: white; } /* Morado */
.btn-upload:hover { background: #5E35B1; }
/* — Indicador de Grabación — */
.recording-pulse {
display: none;
width: 18px; /* Más grande */
height: 18px;
background-color: #E91E63;
border-radius: 50%;
margin: 0 auto 1.5rem auto; /* Más margen */
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(233, 30, 99, 0.7); }
70% { transform: scale(1); box-shadow: 0 0 0 15px rgba(233, 30, 99, 0); } /* Más expansión */
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(233, 30, 99, 0); }
}
/* — Inputs y Mensajes — */
.oxy-input {
width: 90%;
padding: 14px; /* Más padding */
margin-top: 18px;
border-radius: 10px;
border: 1px solid #444444;
background: #222222;
color: white;
font-size: 1rem;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
}
.oxy-input::placeholder { color: #888; }
#status-message {
margin-top: 20px;
font-size: 0.95rem;
min-height: 25px;
color: #00bcd4; /* Color de acento para mensajes importantes */
font-weight: 500;
}
.step-2 { display: none; margin-top: 25px; border-top: 1px solid #333333; padding-top: 25px; }
.option-separator {
margin: 20px 0;
color: #666;
font-size: 0.9em;
position: relative;
text-align: center;
}
.option-separator::before,
.option-separator::after {
content: »;
position: absolute;
top: 50%;
width: 40%;
height: 1px;
background: #333;
}
.option-separator::before { left: 0; }
.option-separator::after { right: 0; }
🎙️ Tu Newsletter, con Voz o Archivo
Habla libremente o sube un audio y recibe un borrador profesional al instante.
o
MP3, WAV, M4A, etc. Máximo 5MB.
¡Audio listo! Previsualiza y dinos dónde enviamos la newsletter:
let mediaRecorder;
let audioChunks = [];
let audioBlob = null; // Ahora puede ser de grabación o de subida
let audioFileName = ‘recording.webm’; // Nombre por defecto para grabación
const recordBtn = document.getElementById(‘recordBtn’);
const stopBtn = document.getElementById(‘stopBtn’);
const uploadBtn = document.getElementById(‘uploadBtn’);
const audioFileInput = document.getElementById(‘audioFileInput’);
const sendBtn = document.getElementById(‘sendBtn’);
const step1Options = document.getElementById(‘step-1-options’);
const step2 = document.getElementById(‘step-2’);
const indicator = document.getElementById(‘recording-indicator’);
const statusMsg = document.getElementById(‘status-message’);
const emailInput = document.getElementById(‘emailInput’);
const audioPlayback = document.getElementById(‘audioPlayback’);
// Poner AQUÍ tu URL de Webhook de n8n (Production URL)
const WEBHOOK_URL = ‘TU_WEBHOOK_DE_N8N_AQUI’;
function resetUIForNewAction() {
audioBlob = null;
audioFileName = ‘recording.webm’;
audioChunks = [];
recordBtn.style.display = ‘inline-flex’;
recordBtn.textContent = ‘Grabar Audio’;
stopBtn.style.display = ‘none’;
uploadBtn.style.display = ‘inline-flex’;
audioFileInput.value = »; // Resetear el input de archivo
audioPlayback.style.display = ‘none’;
step2.style.display = ‘none’;
indicator.style.display = ‘none’;
statusMsg.textContent = »;
sendBtn.disabled = false;
sendBtn.innerHTML = ‘ Generar Newsletter con IA’;
}
recordBtn.addEventListener(‘click’, async () => {
resetUIForNewAction(); // Limpiar antes de empezar
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = event => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = () => {
audioBlob = new Blob(audioChunks, { type: ‘audio/webm’ }); // webm es ligero y compatible
const audioUrl = URL.createObjectURL(audioBlob);
audioPlayback.src = audioUrl;
audioPlayback.style.display = ‘block’;
step2.style.display = ‘block’;
recordBtn.style.display = ‘inline-flex’;
recordBtn.textContent = «Grabar de nuevo»;
stopBtn.style.display = ‘none’;
indicator.style.display = ‘none’;
statusMsg.textContent = «Audio grabado. Ingresa tu email para generar.»;
uploadBtn.style.display = ‘none’; // Esconder opción de subir si ya grabó
};
mediaRecorder.start();
recordBtn.style.display = ‘none’;
stopBtn.style.display = ‘inline-flex’;
uploadBtn.style.display = ‘none’;
indicator.style.display = ‘block’;
statusMsg.textContent = «Grabando… Di lo que quieras contar.»;
step2.style.display = ‘none’;
} catch (err) {
console.error(«Error accediendo al microfono:», err);
statusMsg.textContent = «Error: No pudimos acceder al micrófono. Revisa los permisos del navegador.»;
}
});
stopBtn.addEventListener(‘click’, () => {
if (mediaRecorder && mediaRecorder.state !== ‘inactive’) {
mediaRecorder.stop();
}
});
uploadBtn.addEventListener(‘click’, () => {
resetUIForNewAction(); // Limpiar antes de subir
audioFileInput.click(); // Simula el click en el input type=»file»
});
audioFileInput.addEventListener(‘change’, (event) => {
const file = event.target.files[0];
if (file) {
if (file.size > 5 * 1024 * 1024) { // 5MB limit
statusMsg.textContent = «Error: El archivo es demasiado grande (Máx. 5MB).»;
resetUIForNewAction();
return;
}
audioBlob = file;
audioFileName = file.name;
const audioUrl = URL.createObjectURL(file);
audioPlayback.src = audioUrl;
audioPlayback.style.display = ‘block’;
step2.style.display = ‘block’;
recordBtn.style.display = ‘none’; // Esconder grabar si subió
uploadBtn.style.display = ‘inline-flex’;
uploadBtn.textContent = ‘Subir otro archivo’;
statusMsg.textContent = «Archivo listo. Ingresa tu email para generar.»;
}
});
sendBtn.addEventListener(‘click’, async () => {
const email = emailInput.value;
if (!email || !email.includes(‘@’)) {
statusMsg.textContent = «Por favor, introduce un email válido.»;
return;
}
if (!audioBlob) {
statusMsg.textContent = «No hay audio para enviar. Graba o sube uno.»;
return;
}
statusMsg.innerHTML = ‘Subiendo audio y procesando con IA…
(Esto puede tardar hasta 30 segundos)‘;
sendBtn.disabled = true;
sendBtn.innerHTML = ‘ Procesando…’; // Spinner
const formData = new FormData();
formData.append(‘data’, audioBlob, audioFileName);
formData.append(‘email’, email);
try {
const response = await fetch(WEBHOOK_URL, {
method: ‘POST’,
body: formData
});
if (response.ok) {
statusMsg.innerHTML = «✅ ¡Éxito! Revisa tu correo en unos minutos. También te hemos enviado algunos tips de automatización.»;
sendBtn.innerHTML = ‘✅ ¡Enviado!’;
emailInput.value = »; // Limpiar el campo de email
} else {
const errorText = await response.text();
console.error(«Error al enviar:», response.status, errorText);
statusMsg.textContent = «Hubo un error al enviar. Inténtalo de nuevo.»;
sendBtn.disabled = false;
sendBtn.innerHTML = ‘ Generar Newsletter con IA’;
}
} catch (error) {
console.error(«Error de red:», error);
statusMsg.textContent = «Error de conexión o servidor. Inténtalo de nuevo.»;
sendBtn.disabled = false;
sendBtn.innerHTML = ‘ Generar Newsletter con IA’;
}
});