/* — 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’; } });