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