diff --git a/css/main.css b/css/main.css index df33319..10c84da 100644 --- a/css/main.css +++ b/css/main.css @@ -4011,7 +4011,7 @@ body.miniplayer-active #fab-container { overflow-y: auto; display: flex; flex-direction: column; - gap: 1rem; + gap: 1.5rem; /* Aumentamos el espacio entre mensajes */ } .chat-messages::-webkit-scrollbar { @@ -4027,10 +4027,47 @@ body.miniplayer-active #fab-container { border-radius: 4px; } +/* NUEVO: Contenedores para alinear mensajes y avatares */ +.message-wrapper { + display: flex; + align-items: flex-end; + gap: 0.75rem; + max-width: 90%; + animation: slide-in-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; +} + +.assistant-wrapper { + align-self: flex-start; +} + +.user-wrapper { + align-self: flex-end; + flex-direction: row-reverse; /* Mensaje a la izquierda del (inexistente) avatar */ +} + +/* NUEVO: Estilo del avatar del asistente */ +.avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--gradient); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: 0 0 10px rgba(0, 224, 255, 0.3); +} + +.avatar svg { + width: 20px; + height: 20px; + color: var(--primary); +} + +/* MEJORADO: Estilos de las burbujas de mensaje */ .message { padding: 0.8rem 1.2rem; border-radius: var(--border-radius-md); - max-width: 85%; line-height: 1.6; word-wrap: break-word; } @@ -4039,31 +4076,38 @@ body.miniplayer-active #fab-container { white-space: pre-wrap; word-break: break-word; } -.message p:last-child { - margin-bottom: 0; -} .user-message { background: var(--gradient); color: var(--primary); - align-self: flex-end; - border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; /* Cola de la burbuja */ } .assistant-message { background-color: var(--secondary); color: var(--text-primary); - align-self: flex-start; - border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; /* Cola de la burbuja */ +} + +/* MEJORADO: Estilo de las acciones contextuales */ +.chat-item-actions { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--glass-border); +} + +.chat-action-title { + display: block; + font-weight: 600; + color: var(--accent); + margin-bottom: 0.75rem; + font-size: 0.9rem; } .chat-action-buttons { display: flex; flex-wrap: wrap; gap: .5rem; - margin-top: .8rem; - padding-top: .8rem; - border-top: 1px solid var(--glass-border); } .chat-action-buttons button { @@ -4081,42 +4125,42 @@ body.miniplayer-active #fab-container { background-color: var(--accent); color: var(--primary); border-color: var(--accent); + transform: translateY(-2px); } -.assistant-message code { - background-color: var(--primary); - padding: 0.2em 0.4em; - margin: 0; - font-size: 85%; - border-radius: 6px; - font-family: monospace; +.chat-download-all { + margin-top: 1rem; } -.typing-indicator { +/* MEJORADO: Indicador de escritura */ +.typing-indicator-bubble { display: flex; - gap: 5px; + gap: 6px; + align-items: center; + padding: 14px 16px; } -.typing-indicator span { +.typing-indicator-bubble span { width: 8px; height: 8px; background-color: var(--text-secondary); border-radius: 50%; - animation: typing 1s infinite; + animation: typing-pulse 1.4s infinite; } -.typing-indicator span:nth-child(2) { animation-delay: 0.2s; } -.typing-indicator span:nth-child(3) { animation-delay: 0.4s; } +.typing-indicator-bubble span:nth-child(2) { animation-delay: 0.2s; } +.typing-indicator-bubble span:nth-child(3) { animation-delay: 0.4s; } -@keyframes typing { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-5px); } +@keyframes typing-pulse { + 0%, 100% { opacity: 0.2; transform: scale(0.8); } + 50% { opacity: 1; transform: scale(1); } } -.chat-input-form { +/* MEJORADO: Formulario de entrada */ +#chat-input-form { display: flex; padding: 0.8rem; border-top: 1px solid var(--glass-border); gap: 0.8rem; - background-color: rgba(255,255,255,0.05); + background: linear-gradient(to top, rgba(16, 17, 22, 0.8), rgba(16, 17, 22, 0.5)); align-items: flex-end; } @@ -4125,16 +4169,19 @@ body.miniplayer-active #fab-container { background: var(--glass); border: 1px solid var(--glass-border); border-radius: 20px; - padding: 0.6rem 1rem; + padding: 0.7rem 1.2rem; color: var(--text-primary); resize: none; font-family: 'Montserrat', sans-serif; font-size: 0.95rem; + transition: all 0.3s ease; } + .chat-input:focus { outline: none; border-color: var(--accent); - box-shadow: 0 0 0 2px rgba(0, 224, 255, 0.2); + box-shadow: 0 0 15px rgba(0, 224, 255, 0.2); + background: var(--secondary); } .chat-send-btn { @@ -4142,20 +4189,47 @@ body.miniplayer-active #fab-container { background: var(--accent); color: var(--primary); border: none; - border-radius: 50%; - width: 40px; - height: 40px; - font-size: 1rem; + border-radius: 12px; /* Más tecnológico que un círculo */ + width: 42px; + height: 42px; + font-size: 1.1rem; cursor: pointer; transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; +} +.chat-send-btn svg { + transition: transform 0.3s ease; } .chat-send-btn:hover { background: var(--accent-dark); transform: scale(1.1); + box-shadow: 0 0 10px rgba(0, 224, 255, 0.4); +} +.chat-send-btn:hover svg { + transform: translateX(2px) rotate(-15deg); } .chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; + transform: none; + box-shadow: none; +} +.chat-send-btn:disabled:hover svg { + transform: none; +} + +/* NUEVO: Animación para los mensajes */ +@keyframes slide-in-bottom { + 0% { + transform: translateY(20px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } } @media (max-width: 768px) { diff --git a/js/chat.js b/js/chat.js index 58a0c15..09fd38a 100644 --- a/js/chat.js +++ b/js/chat.js @@ -100,10 +100,20 @@ export class Chat { } addMessage(text, sender, isError = false) { + const wrapper = document.createElement('div'); + wrapper.className = `message-wrapper ${sender}-wrapper`; + const messageEl = document.createElement('div'); messageEl.classList.add('message', `${sender}-message`); if(isError) messageEl.style.color = 'var(--danger)'; + if (sender === 'assistant') { + const avatar = document.createElement('div'); + avatar.className = 'avatar'; + avatar.innerHTML = ``; + wrapper.appendChild(avatar); + } + const p = document.createElement('p'); p.textContent = text; messageEl.appendChild(p); @@ -112,16 +122,27 @@ export class Chat { this.addMediaActionButtons(messageEl, text); } - this.dom.messagesContainer.appendChild(messageEl); + wrapper.appendChild(messageEl); + this.dom.messagesContainer.appendChild(wrapper); this.scrollToBottom(); } addTypingIndicator() { + const wrapper = document.createElement('div'); + wrapper.id = 'typing-indicator'; + wrapper.className = 'message-wrapper assistant-wrapper'; + + const avatar = document.createElement('div'); + avatar.className = 'avatar'; + avatar.innerHTML = ``; + wrapper.appendChild(avatar); + const indicator = document.createElement('div'); - indicator.id = 'typing-indicator'; - indicator.classList.add('message', 'assistant-message', 'typing-indicator'); + indicator.classList.add('message', 'assistant-message', 'typing-indicator-bubble'); indicator.innerHTML = ''; - this.dom.messagesContainer.appendChild(indicator); + + wrapper.appendChild(indicator); + this.dom.messagesContainer.appendChild(wrapper); this.scrollToBottom(); } diff --git a/js/php-script-generator.js b/js/php-script-generator.js deleted file mode 100644 index 1e7c10d..0000000 --- a/js/php-script-generator.js +++ /dev/null @@ -1,59 +0,0 @@ -import { showNotification, _ } from './utils.js'; - -export const phpScriptGenerator = (() => { - let dom = {}; - - function cacheDom() { - const settingsModal = document.getElementById('settingsModal'); - if (!settingsModal) return false; - - dom.secretKeyCheck = settingsModal.querySelector('#phpSecretKeyCheck'); - dom.secretKey = settingsModal.querySelector('#phpSecretKey'); - dom.savePath = settingsModal.querySelector('#phpSavePath'); - dom.filename = settingsModal.querySelector('#phpFilename'); - dom.fileActionAppendRadio = settingsModal.querySelector('#phpFileActionAppend'); - dom.generatedCode = settingsModal.querySelector('#generatedPhpCode'); - dom.generateBtn = settingsModal.querySelector('#generatePhpScriptBtn'); - dom.copyBtn = settingsModal.querySelector('#copyPhpScriptBtn'); - - return dom.generateBtn && dom.copyBtn; - } - - function init() { - if (!cacheDom()) { - return; - } - dom.generateBtn.addEventListener('click', generatePhpScript); - dom.copyBtn.addEventListener('click', copyScript); - } - - function generatePhpScript() { - const useSecretKey = dom.secretKeyCheck.checked; - const secretKey = dom.secretKey.value.trim(); - const savePath = dom.savePath.value.trim(); - const filename = dom.filename.value.trim() || 'CinePlex_Playlist.m3u'; - const appendToFile = dom.fileActionAppendRadio.checked; - - let script = ` $success, 'message' => $message, 'filename' => $filename]);\n exit;\n}\n`; - if (useSecretKey) { - script += `\n$auth_key = isset($_SERVER['HTTP_X_SECRET_KEY']) ? $_SERVER['HTTP_X_SECRET_KEY'] : '';\nif (!defined('SECRET_KEY') || SECRET_KEY === '' || $auth_key !== SECRET_KEY) {\n sendResponse(false, 'Acceso no autorizado. Clave secreta inválida o no proporcionada.', '', 403);\n}\n`; - } - script += `\n$json_data = file_get_contents('php://input');\n$data = json_decode($json_data, true);\n\nif (json_last_error() !== JSON_ERROR_NONE) {\n sendResponse(false, 'Error: Datos JSON inválidos.');\n}\n\nif (!isset($data['streams']) || !is_array($data['streams']) || empty($data['streams'])) {\n sendResponse(false, 'Error: El JSON debe contener un array "streams" no vacío.');\n}\n\n$save_dir = SAVE_DIRECTORY !== '' ? rtrim(SAVE_DIRECTORY, '/\\') : __DIR__;\n\nif (!is_dir($save_dir) || !is_writable($save_dir)) {\n sendResponse(false, 'Error del servidor: El directorio de destino no existe o no tiene permisos de escritura.', '', 500);\n}\n\n$safe_filename = preg_replace('/[^\\w\\s._-]/', '', basename(FILENAME));\n$safe_filename = preg_replace('/\\s+/', '_', $safe_filename);\n$target_path = $save_dir . DIRECTORY_SEPARATOR . $safe_filename;\n\n$content_to_write = "";\n\nif (FILE_ACTION_APPEND) {\n $file_exists = file_exists($target_path);\n if (!$file_exists) {\n $content_to_write .= "#EXTM3U\\n";\n }\n foreach ($data['streams'] as $stream) {\n if (isset($stream['extinf'], $stream['url'])) {\n $content_to_write .= trim($stream['extinf']) . "\\n";\n $content_to_write .= trim($stream['url']) . "\\n";\n }\n }\n if (file_put_contents($target_path, $content_to_write, FILE_APPEND | LOCK_EX) !== false) {\n sendResponse(true, 'Streams añadidos correctamente al archivo.', $safe_filename, 200);\n } else {\n sendResponse(false, 'Error del servidor: No se pudo añadir contenido al archivo.', '', 500);\n }\n} else { // Overwrite mode\n $content_to_write = "#EXTM3U\\n";\n foreach ($data['streams'] as $stream) {\n if (isset($stream['extinf'], $stream['url'])) {\n $content_to_write .= trim($stream['extinf']) . "\\n";\n $content_to_write .= trim($stream['url']) . "\\n";\n }\n }\n if (file_put_contents($target_path, $content_to_write, LOCK_EX) !== false) {\n sendResponse(true, 'Archivo de streams sobrescrito correctamente.', $safe_filename, 201);\n } else {\n sendResponse(false, 'Error del servidor: No se pudo escribir el archivo.', '', 500);\n }\n}\n?>`; - dom.generatedCode.value = script; - showNotification(_("scriptGenerated"), "success"); - } - - function copyScript() { - if (!dom.generatedCode.value || dom.generatedCode.value.trim() === '') { - showNotification(_("errorGeneratingScript"), "warning"); - return; - } - navigator.clipboard.writeText(dom.generatedCode.value).then(() => { - showNotification(_("scriptCopied"), "success"); - }).catch(err => { - showNotification(_("errorCopyingScript"), "error"); - }); - } - - return { init }; -})(); \ No newline at end of file