@@ -962,6 +975,15 @@ function Sala({ roomId, name }: { roomId: string | null; name: string }) {
+ {/* Solo en pantallas chicas: ahí el chat no cabe al lado del preview,
+ así que pasa a ser una pestaña más. En escritorio se esconde por
+ CSS, porque ahí el chat vive en su columna y no es una vista. */}
+ setTab("chat")}
+ >
+ {t.elChat}
+
setTab("app")}>
{t.laApp}
diff --git a/web/src/i18n.tsx b/web/src/i18n.tsx
index 52d605b..2a68018 100644
--- a/web/src/i18n.tsx
+++ b/web/src/i18n.tsx
@@ -103,6 +103,8 @@ const TEXTOS = {
imagenNoSePudo: "no se pudo preparar la imagen",
// Pestañas
+ /** Pestaña del chat: solo se ve en pantallas chicas. */
+ elChat: "El chat",
laApp: "La app",
elBack: "El back",
@@ -280,6 +282,7 @@ const TEXTOS = {
imagenNoSePudo: "couldn't prepare the image",
// Pestañas
+ elChat: "Chat",
laApp: "The app",
elBack: "The back",
diff --git a/web/src/styles.css b/web/src/styles.css
index 5e123a4..8a6f4c3 100644
--- a/web/src/styles.css
+++ b/web/src/styles.css
@@ -1,1714 +1,1722 @@
-/* Multi — la sala. Paleta crepúsculo (Gengar), chat estilo Discord.
- Traducido de app/sala.html. */
-:root {
- --fondo: #33365a;
- --fondo2: #2b2e4c;
- --panel: #3b3e66;
- --texto: #eef0fb;
- --dim: #a9abd0;
- --linea: #4a4d78;
- --lavanda: #b9a8e3;
- --ambar: #ffc37a;
- --rosa: #c393c9;
-}
-
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-body {
- font-family: "Segoe UI", system-ui, sans-serif;
- color: var(--texto);
- background: linear-gradient(180deg, var(--fondo) 0%, var(--fondo2) 100%);
- height: 100vh;
- overflow: hidden;
-}
-
-/* El grano del hero — el mismo de la landing, más sutil para no cansar la vista
- en una pantalla donde se pasan horas. Va sobre todo pero no intercepta clicks
- (el preview y los cursores tienen que seguir respondiendo). */
-body::after {
- content: "";
- position: fixed;
- inset: 0;
- pointer-events: none;
- opacity: 0.035;
- z-index: 90;
- background-image: repeating-conic-gradient(#fff 0 0.0001%, transparent 0 0.0002%);
-}
-
-#root {
- height: 100vh;
-}
-
-/* ── Pantallas de entrada ─────────────────────────────────────────────── */
-
-.center-screen {
- height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.card {
- text-align: center;
- padding: 48px 56px;
- background: rgba(43, 46, 76, 0.5);
- border: 1px solid var(--linea);
- border-radius: 10px;
- max-width: 420px;
-}
-
-.brand {
- font-family: "Bodoni MT", "Didot", Georgia, serif;
- font-size: 64px;
- letter-spacing: 6px;
-}
-
-.sub {
- color: var(--dim);
- font-size: 14px;
- margin-top: 8px;
-}
-
-.room-name {
- font-family: "Bodoni MT", "Didot", Georgia, serif;
- font-size: 28px;
- margin: 8px 0 20px;
- color: var(--lavanda);
-}
-
-.cta {
- margin-top: 28px;
- font-size: 13px;
- font-weight: 700;
- letter-spacing: 2px;
- text-transform: uppercase;
- padding: 15px 32px;
- border-radius: 4px;
- border: none;
- background: var(--texto);
- color: var(--fondo2);
- cursor: pointer;
- transition: opacity 0.15s;
-}
-.cta:hover:not(:disabled) {
- opacity: 0.85;
-}
-.cta:disabled {
- opacity: 0.5;
- cursor: default;
-}
-
-.hint {
- margin-top: 18px;
- font-size: 12px;
- color: var(--dim);
-}
-
-.name-input {
- margin-top: 8px;
- width: 100%;
- padding: 12px 14px;
- border-radius: 6px;
- border: 1px solid var(--linea);
- background: var(--panel);
- color: var(--texto);
- font-size: 14px;
-}
-.name-input:focus {
- outline: none;
- border-color: var(--lavanda);
-}
-
-/* ── La sala: 2 columnas ──────────────────────────────────────────────── */
-
-.sala {
- display: flex;
- height: 100vh;
-}
-
-/* Chat izquierda — una sola línea vertical, sin cruces (estilo Discord) */
-.chat {
- width: 340px;
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- border-right: 1px solid var(--linea);
- background: rgba(43, 46, 76, 0.5);
- transition: width 0.22s ease, opacity 0.16s ease;
-}
-
-/* Colapsado: el preview se queda con toda la pantalla.
- Se anima el ancho en vez de quitar el nodo para que el iframe NO se
- desmonte: recargarlo costaría segundos y perdería el estado de la app. */
-.sala.chat-colapsado .chat {
- width: 0;
- opacity: 0;
- overflow: hidden;
- border-right: none;
- pointer-events: none;
-}
-
-/* El botón de colapsar, primero en la barra. Discreto: es un control de vista,
- no una acción de la sala. */
-.colapsar-btn {
- background: none;
- border: 1px solid var(--linea);
- border-radius: 3px;
- color: var(--dim);
- cursor: pointer;
- font-size: 12px;
- line-height: 1;
- padding: 8px 10px;
- flex-shrink: 0;
- transition: color 0.15s, border-color 0.15s;
-}
-.colapsar-btn:hover {
- color: var(--texto);
- border-color: var(--dim);
-}
-
-.sala-cab {
- padding: 16px 18px 12px;
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18);
- display: flex;
- align-items: flex-start;
- gap: 10px;
-}
-.sala-titulo {
- min-width: 0;
- /* Empuja el botón de crear sala hasta la derecha de la cabecera. */
- flex: 1;
-}
-
-/* Crear otra sala. Vive aquí y no dentro del menú porque era justo lo que
- nadie encontraba: escondido, el único camino visible para llegar a una sala
- era crear otra, y así se acumulaban las vacías. */
-.sala-nueva {
- flex-shrink: 0;
- width: 28px;
- height: 28px;
- border: 1px solid var(--linea);
- border-radius: 6px;
- background: transparent;
- color: var(--dim);
- font-size: 18px;
- line-height: 1;
- cursor: pointer;
- transition: color 0.15s, border-color 0.15s;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-.sala-nueva:hover {
- color: var(--texto);
- border-color: var(--dim);
-}
-.sala-nueva:disabled {
- opacity: 0.4;
- cursor: default;
-}
-
-/* Sin sala abierta no hay nombre: se deja el hueco con el texto apagado, para
- que la cabecera no se colapse y se vea que falta elegir. */
-.sala-nombre-vacio {
- color: var(--dim);
- font-style: italic;
-}
-
-/* ── Menú de salas ────────────────────────────────────────────────────── */
-/* Desplegable y no sidebar: la lista se abre una o dos veces por sesión, y un
- panel permanente le quitaría ancho al preview, que es lo que todos miran. */
-.menu-salas {
- position: relative;
- flex-shrink: 0;
-}
-.menu-btn {
- background: none;
- border: none;
- cursor: pointer;
- padding: 6px 4px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-/* Las tres rayas, con pseudo-elementos: sin SVG ni fuente de iconos. */
-.menu-icono,
-.menu-icono::before,
-.menu-icono::after {
- display: block;
- width: 15px;
- height: 1.5px;
- background: var(--dim);
- border-radius: 1px;
- transition: background 0.15s;
-}
-.menu-icono {
- position: relative;
-}
-.menu-icono::before,
-.menu-icono::after {
- content: "";
- position: absolute;
- left: 0;
-}
-.menu-icono::before {
- top: -5px;
-}
-.menu-icono::after {
- top: 5px;
-}
-.menu-btn:hover .menu-icono,
-.menu-btn:hover .menu-icono::before,
-.menu-btn:hover .menu-icono::after {
- background: var(--texto);
-}
-
-.menu-panel {
- position: absolute;
- top: calc(100% + 8px);
- left: 0;
- z-index: 60;
- width: 268px;
- max-height: 380px;
- overflow-y: auto;
- background: var(--panel);
- border: 1px solid var(--linea);
- border-radius: 8px;
- box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
- padding: 8px;
-}
-.menu-cab {
- font-size: 9px;
- letter-spacing: 2px;
- text-transform: uppercase;
- color: var(--dim);
- padding: 4px 8px 8px;
-}
-.menu-vacio {
- font-size: 11.5px;
- color: var(--dim);
- font-style: italic;
- padding: 4px 8px 10px;
- line-height: 1.5;
-}
-/* Confirma que la sala se borró de verdad, no solo de esta lista. Aparece
- suave para que se note que es nuevo, y se quita solo a los segundos. */
-.menu-borrada {
- font-size: 11.5px;
- color: var(--dim);
- padding: 2px 8px 8px;
- animation: aparecer 0.2s ease-out;
-}
-@keyframes aparecer {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
-}
-.menu-lista {
- list-style: none;
- display: flex;
- flex-direction: column;
- gap: 1px;
-}
-.menu-item {
- display: flex;
- align-items: center;
- border-radius: 5px;
-}
-.menu-item:hover {
- background: rgba(255, 255, 255, 0.05);
-}
-.menu-link {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 2px;
- padding: 7px 8px;
- text-decoration: none;
- color: var(--texto);
-}
-.menu-sala-id {
- font-size: 12.5px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-.menu-sala-fecha {
- font-size: 10px;
- color: var(--dim);
-}
-/* La × aparece al pasar el cursor: quitar una sala es raro, y un botón visible
- en cada fila compite con el link, que es lo que sí quieres tocar. */
-.menu-olvidar {
- background: none;
- border: none;
- color: var(--dim);
- cursor: pointer;
- font-size: 15px;
- line-height: 1;
- padding: 6px 9px;
- opacity: 0;
- transition: opacity 0.12s, color 0.12s;
-}
-.menu-item:hover .menu-olvidar,
-.menu-olvidar:focus-visible {
- opacity: 1;
-}
-.menu-olvidar:hover {
- color: var(--rojo);
-}
-
-/* Es un (crea la sala ahí mismo), así que hay que apagarle lo que el
- navegador le pone de fábrica: fondo gris, borde y ancho de contenido. */
-.menu-nueva {
- display: block;
- width: 100%;
- text-align: left;
- background: transparent;
- border: none;
- cursor: pointer;
- font: inherit;
- margin-top: 8px;
- padding: 9px 8px 4px;
- border-top: 1px solid var(--linea);
- font-size: 11.5px;
- color: var(--dim);
- text-decoration: none;
-}
-.menu-nueva:hover {
- color: var(--texto);
-}
-.menu-nueva:disabled {
- cursor: default;
- opacity: 0.6;
-}
-/* Es un para que se pueda renombrar con el teclado y lo anuncien los
- lectores de pantalla, así que hay que apagarle lo que trae de fábrica. */
-.sala-nombre {
- font-family: "Bodoni MT", "Didot", Georgia, serif;
- font-size: 19px;
- letter-spacing: 1px;
- color: var(--texto);
- background: transparent;
- border: none;
- padding: 0;
- text-align: left;
- cursor: text;
- /* El subrayado invisible reserva su espacio desde el principio: sin esto el
- título brinca un pixel al pasar el mouse. */
- border-bottom: 1px solid transparent;
-}
-/* Que se note que se puede tocar. Sin esto nadie descubre que es editable:
- un título se ve igual sea editable o no. */
-.sala-nombre:hover,
-.sala-nombre:focus-visible {
- border-bottom-color: var(--dim);
- outline: none;
-}
-/* El input hereda la misma tipografía y tamaño para que renombrar no mueva
- nada de sitio: escribes donde estaba el nombre, no en otra caja. */
-.sala-nombre-input {
- font-family: "Bodoni MT", "Didot", Georgia, serif;
- font-size: 19px;
- letter-spacing: 1px;
- color: var(--texto);
- background: transparent;
- border: none;
- border-bottom: 1px solid var(--lavanda);
- padding: 0;
- width: 100%;
- outline: none;
-}
-.sala-nombre-input::placeholder {
- color: var(--dim);
-}
-.sala-meta {
- font-size: 9px;
- letter-spacing: 2.5px;
- text-transform: uppercase;
- color: var(--dim);
- margin-top: 3px;
-}
-
-/* ── Lista de agentes ─────────────────────────────────────────────────── */
-/* Tres estados con peso visual distinto: waiting es NEUTRO (está en fila,
- no es un problema); solo stuck usa color de alerta. */
-.agent-list {
- padding: 8px 14px;
- display: flex;
- flex-direction: column;
- gap: 4px;
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18);
-}
-
-.agent-row {
- display: flex;
- align-items: center;
- gap: 7px;
- font-size: 11px;
- padding: 3px 0;
-}
-.agent-dot {
- width: 7px;
- height: 7px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-.agent-working .agent-dot {
- animation: pulso 1.6s ease-in-out infinite;
-}
-@keyframes pulso {
- 50% {
- opacity: 0.35;
- }
-}
-.agent-name {
- font-weight: 700;
- flex-shrink: 0;
-}
-.agent-status {
- color: var(--dim);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-/* Esperando: neutro, atenuado. NO es alarma. */
-.agent-waiting .agent-dot {
- background: var(--dim) !important;
-}
-.agent-waiting .agent-status {
- font-style: italic;
-}
-/* Atorado: esto SÍ pide atención. */
-.agent-stuck .agent-dot {
- background: #d95d63 !important;
-}
-.agent-stuck .agent-status {
- color: #d95d63;
-}
-.agent-idle {
- opacity: 0.5;
-}
-.agent-fold {
- font-size: 10px;
- color: var(--dim);
- cursor: pointer;
- padding: 2px 0;
-}
-.agent-fold:hover {
- color: var(--texto);
-}
-
-/* ── Historial (la línea de tiempo) ───────────────────────────────────── */
-.historial {
- padding: 10px 24px 16px;
- flex-shrink: 0;
-}
-.hist-label {
- font-size: 9px;
- letter-spacing: 2px;
- text-transform: uppercase;
- color: var(--dim);
-}
-.hist-barra {
- display: flex;
- align-items: center;
- gap: 6px;
- margin-top: 6px;
- flex-wrap: wrap;
-}
-.hist-vacio {
- font-size: 11px;
- color: var(--dim);
- font-style: italic;
-}
-.hist-punto {
- background: none;
- border: none;
- color: var(--lavanda);
- font-size: 13px;
- cursor: pointer;
- padding: 0 1px;
- line-height: 1;
- opacity: 0.7;
-}
-.hist-punto:hover,
-.hist-punto.activo {
- opacity: 1;
- transform: scale(1.25);
-}
-.hist-punto.marcado {
- color: var(--ambar);
- opacity: 1;
-}
-.hist-ahora {
- font-size: 9px;
- letter-spacing: 1px;
- text-transform: uppercase;
- color: var(--dim);
- margin-left: 4px;
-}
-
-.hist-detalle {
- margin-top: 10px;
- padding: 10px 12px;
- background: var(--panel);
- border-radius: 6px;
-}
-.hist-det-cab {
- display: flex;
- gap: 10px;
- align-items: baseline;
-}
-.hist-det-autor {
- font-size: 11.5px;
- font-weight: 700;
- color: var(--ambar);
-}
-.hist-det-fecha {
- font-size: 10px;
- color: var(--dim);
-}
-/* Lo que se pidió: "por qué pasó este cambio" */
-.hist-det-task {
- font-size: 11.5px;
- color: var(--texto);
- font-style: italic;
- margin-top: 4px;
-}
-.hist-det-files {
- display: flex;
- flex-wrap: wrap;
- gap: 5px;
- margin-top: 7px;
-}
-.hist-file {
- font-size: 10px;
- font-family: ui-monospace, monospace;
- color: var(--dim);
- background: rgba(255, 255, 255, 0.05);
- padding: 2px 6px;
- border-radius: 3px;
- cursor: pointer;
-}
-.hist-file:hover {
- color: var(--texto);
- background: rgba(185, 168, 227, 0.15);
-}
-.hist-acciones {
- display: flex;
- gap: 6px;
- margin-top: 9px;
-}
-.hist-btn {
- font-size: 10px;
- letter-spacing: 1px;
- text-transform: uppercase;
- padding: 6px 10px;
- border-radius: 3px;
- border: 1px solid var(--linea);
- background: transparent;
- color: var(--dim);
- cursor: pointer;
-}
-.hist-btn:hover {
- color: var(--texto);
-}
-.hist-btn.primario {
- background: var(--ambar);
- border-color: var(--ambar);
- color: #3d2a12;
- font-weight: 700;
-}
-
-.hist-diff {
- margin-top: 9px;
- max-height: 220px;
- overflow: auto;
- font-family: ui-monospace, monospace;
- font-size: 10.5px;
- line-height: 1.5;
- background: var(--fondo2);
- border-radius: 4px;
- padding: 8px 10px;
- color: var(--dim);
-}
-.hist-diff .add {
- color: #8fb573;
-}
-.hist-diff .del {
- color: #d95d63;
-}
-
-.hist-confirm {
- margin-top: 10px;
- padding: 10px 12px;
- background: rgba(255, 195, 122, 0.08);
- border: 1px solid rgba(255, 195, 122, 0.3);
- border-radius: 6px;
-}
-.hist-confirm-text {
- font-size: 12px;
- color: var(--texto);
-}
-.hist-confirm-nota {
- font-size: 10.5px;
- color: var(--dim);
- margin-top: 4px;
- line-height: 1.5;
-}
-.hist-confirm-acciones {
- display: flex;
- gap: 6px;
- margin-top: 9px;
-}
-
-/* ── Menú de menciones (@) ────────────────────────────────────────────── */
-.input-wrap {
- position: relative;
- display: flex;
- align-items: center;
- gap: 6px;
-}
-/* La caja se lleva el espacio que sobre; el botón se queda de su tamaño. */
-.input-wrap .caja {
- flex: 1;
- min-width: 0;
-}
-
-/* Adjuntar una imagen. Arrastrar y pegar ya servían, pero no se ven, y en el
- teléfono no existe ninguna de las dos. */
-.adjuntar-btn {
- flex-shrink: 0;
- width: 30px;
- height: 30px;
- border: 1px solid var(--linea);
- border-radius: 6px;
- background: transparent;
- color: var(--dim);
- font-size: 17px;
- line-height: 1;
- cursor: pointer;
- transition: color 0.15s, border-color 0.15s;
- /* Lleva un SVG dentro: sin esto queda pegado a una esquina. */
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-.adjuntar-btn:hover {
- color: var(--texto);
- border-color: var(--dim);
-}
-.mention-menu {
- position: absolute;
- bottom: calc(100% + 6px);
- left: 0;
- right: 0;
- background: var(--fondo2);
- border: 1px solid var(--linea);
- border-radius: 6px;
- overflow: hidden;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
- z-index: 30;
-}
-.mention-item {
- display: flex;
- align-items: baseline;
- gap: 8px;
- padding: 8px 12px;
- cursor: pointer;
-}
-.mention-item:hover {
- background: var(--panel);
-}
-.mention-name {
- font-size: 12px;
- font-weight: 700;
-}
-.mention-hint {
- font-size: 10.5px;
- color: var(--dim);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-/* ── Turnos huérfanos (crash a media tarea) ───────────────────────────── */
-.orphan-card {
- margin: 8px 14px;
- padding: 10px 12px;
- background: rgba(255, 195, 122, 0.08);
- border: 1px solid rgba(255, 195, 122, 0.3);
- border-radius: 6px;
-}
-.orphan-text {
- font-size: 11.5px;
- line-height: 1.5;
- color: var(--texto);
-}
-.orphan-actions {
- display: flex;
- gap: 8px;
- margin-top: 8px;
-}
-.orphan-btn {
- font-size: 10px;
- letter-spacing: 1px;
- text-transform: uppercase;
- padding: 6px 10px;
- border-radius: 3px;
- border: 1px solid var(--linea);
- background: transparent;
- color: var(--dim);
- cursor: pointer;
-}
-.orphan-btn:hover {
- color: var(--texto);
-}
-.orphan-btn.keep {
- background: var(--ambar);
- border-color: var(--ambar);
- color: #3d2a12;
- font-weight: 700;
-}
-
-.chat-scroll {
- flex: 1;
- overflow-y: auto;
- padding: 16px 0;
- display: flex;
- flex-direction: column;
-}
-
-.msg {
- display: flex;
- gap: 12px;
- padding: 8px 16px 2px;
-}
-.msg:hover {
- background: rgba(255, 255, 255, 0.025);
-}
-.msg-system {
- padding: 4px 16px;
- justify-content: center;
-}
-.system-text {
- font-size: 11px;
- color: var(--dim);
- font-style: italic;
-}
-
-.av {
- width: 34px;
- height: 34px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 11px;
- font-weight: 800;
- flex-shrink: 0;
- color: #fff;
-}
-
-.msg-cuerpo {
- min-width: 0;
-}
-.msg-cab {
- margin-bottom: 2px;
-}
-.quien {
- font-size: 12.5px;
- font-weight: 700;
-}
-.burbuja {
- font-size: 13px;
- line-height: 1.55;
- color: var(--texto);
- white-space: pre-wrap;
- word-break: break-word;
-}
-.tool-line {
- font-size: 11px;
- color: var(--dim);
- font-family: ui-monospace, monospace;
- margin-bottom: 4px;
-}
-
-.chat-input {
- padding: 6px 14px 16px;
-}
-.caja {
- width: 100%;
- background: var(--panel);
- border: none;
- border-radius: 6px;
- padding: 12px 14px;
- font-size: 13px;
- color: var(--texto);
-}
-.caja:focus {
- outline: none;
-}
-.caja::placeholder {
- color: var(--dim);
-}
-
-/* Escenario derecha */
-.escenario {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-width: 0;
-}
-
-.barra-sup {
- display: flex;
- align-items: center;
- padding: 12px 24px 0;
-}
-.presencia {
- margin-left: auto;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-.presencia .av {
- width: 28px;
- height: 28px;
- border: 2px solid var(--fondo);
-}
-.invitar {
- font-size: 10px;
- letter-spacing: 2px;
- text-transform: uppercase;
- color: var(--dim);
- border: 1px solid var(--linea);
- padding: 7px 14px;
- border-radius: 3px;
- cursor: pointer;
- background: transparent;
-}
-.invitar:hover {
- color: var(--texto);
- border-color: var(--dim);
-}
-/* Apagado, no escondido: se ve que la opción existe y por qué todavía no. */
-.invitar:disabled {
- opacity: 0.4;
- cursor: not-allowed;
-}
-.invitar:disabled:hover {
- color: var(--dim);
- border-color: var(--linea);
-}
-
-.tabs {
- display: flex;
- gap: 4px;
- padding: 12px 24px 0;
-}
-.tab {
- font-size: 10px;
- font-weight: 700;
- letter-spacing: 2.5px;
- text-transform: uppercase;
- color: var(--dim);
- padding: 9px 18px;
- border-radius: 3px 3px 0 0;
- cursor: pointer;
- border: 0;
- background: transparent;
- font-family: inherit;
-}
-.tab:hover {
- color: var(--texto);
-}
-.tab.activa {
- color: var(--texto);
- background: var(--panel);
-}
-.tab.disabled {
- opacity: 0.4;
- cursor: default;
-}
-
-.lienzo {
- flex: 1;
- margin: 0 24px 24px;
- background: var(--panel);
- border-radius: 0 3px 3px 3px;
- overflow: hidden;
- display: flex;
- position: relative; /* para posicionar cursores/selecciones encima */
-}
-
-/* botón de inspect en la barra superior */
-.barra-sup {
- justify-content: space-between;
-}
-.inspect-btn {
- font-size: 10px;
- letter-spacing: 2px;
- text-transform: uppercase;
- color: var(--dim);
- border: 1px solid var(--linea);
- padding: 7px 14px;
- border-radius: 3px;
- cursor: pointer;
- background: transparent;
-}
-.inspect-btn:hover:not(:disabled) {
- color: var(--texto);
- border-color: var(--dim);
-}
-/* Apagado, no escondido: se ve que la opción existe y que falta una sala. */
-.inspect-btn:disabled,
-.adjuntar-btn:disabled {
- opacity: 0.4;
- cursor: not-allowed;
-}
-.inspect-btn.on {
- color: var(--fondo2);
- background: var(--lavanda);
- border-color: var(--lavanda);
-}
-
-/* Cursores de otros sobre el escenario (estilo Figma, del boceto).
- La flecha es puro CSS: dos bordes formando un triángulo, girado. Toma el color
- de la persona por currentColor, así flecha y etiqueta van siempre a juego. */
-.remote-cursor {
- position: absolute;
- pointer-events: none;
- z-index: 20;
- transition: left 0.05s linear, top 0.05s linear;
- white-space: nowrap;
-}
-.cursor-flecha {
- width: 0;
- height: 0;
- border-left: 10px solid currentColor;
- border-bottom: 10px solid transparent;
- border-top: 4px solid currentColor;
- transform: rotate(-8deg);
- display: inline-block;
-}
-.cursor-name {
- font-size: 8.5px;
- font-weight: 700;
- letter-spacing: 1.5px;
- text-transform: uppercase;
- color: #2b2e4c;
- background: currentColor;
- padding: 3px 8px;
- border-radius: 2px;
- margin-left: 1px;
- vertical-align: top;
-}
-/* El texto va oscuro sobre el color de la persona: los colores de la paleta son
- claros, así que negro encima se lee mejor que blanco. */
-.cursor-name span {
- color: #2b2e4c;
-}
-
-/* badges de selección de otros */
-.others-selections {
- position: absolute;
- top: 8px;
- left: 8px;
- z-index: 15;
- display: flex;
- flex-direction: column;
- gap: 4px;
- pointer-events: none;
-}
-.sel-badge {
- font-size: 10px;
- font-weight: 700;
- padding: 3px 8px;
- border: 1px solid;
- border-radius: 3px;
- background: rgba(43, 46, 76, 0.85);
-}
-
-/* chip de anclaje en el input del chat */
-.anchor-chip {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 10px;
- color: var(--lavanda);
- background: rgba(185, 168, 227, 0.12);
- border: 1px solid rgba(185, 168, 227, 0.3);
- border-radius: 4px;
- padding: 5px 8px;
- margin-bottom: 6px;
-}
-.anchor-x {
- cursor: pointer;
- font-size: 13px;
- margin-left: auto;
- opacity: 0.7;
-}
-.anchor-x:hover {
- opacity: 1;
-}
-
-/* ── Imágenes en el chat ──────────────────────────────────────────────────── */
-
-/* Las que pegaste y todavía no mandas, encima del input. */
-.adjuntos-pendientes {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-bottom: 6px;
-}
-.adjunto-chip {
- position: relative;
- width: 56px;
- height: 56px;
- border-radius: 6px;
- overflow: hidden;
- border: 1px solid rgba(185, 168, 227, 0.3);
-}
-.adjunto-chip img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- display: block;
-}
-.adjunto-x {
- position: absolute;
- top: 2px;
- right: 2px;
- width: 16px;
- height: 16px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- line-height: 1;
- color: #fff;
- background: rgba(0, 0, 0, 0.6);
- border-radius: 50%;
- cursor: pointer;
-}
-.adjunto-x:hover {
- background: rgba(0, 0, 0, 0.85);
-}
-
-/* Cuando algo salió mal con una imagen. Se va al hacer click. */
-.adjunto-error {
- font-size: 10px;
- color: #e8a0a0;
- background: rgba(232, 160, 160, 0.1);
- border: 1px solid rgba(232, 160, 160, 0.25);
- border-radius: 4px;
- padding: 5px 8px;
- margin-bottom: 6px;
- cursor: pointer;
-}
-
-/* Arrastrando un archivo encima del chat. */
-.chat-input.arrastrando {
- outline: 1px dashed var(--lavanda);
- outline-offset: -4px;
- border-radius: 6px;
-}
-
-/* Las que ya se mandaron, dentro del mensaje. */
-.adjuntos-msg {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-top: 4px;
-}
-.adjuntos-msg img {
- max-width: 180px;
- /* Una captura muy alta no debe empujar todo el chat hacia abajo. */
- max-height: 220px;
- border-radius: 6px;
- display: block;
- border: 1px solid rgba(255, 255, 255, 0.08);
-}
-
-/* nota de anclaje en un mensaje ya mandado */
-.anchor-note {
- font-size: 10px;
- color: var(--dim);
- font-style: italic;
- margin-bottom: 3px;
-}
-.preview-frame {
- width: 100%;
- height: 100%;
- border: none;
- background: #fff;
-}
-.preview-loading {
- margin: auto;
- color: var(--dim);
- font-size: 13px;
- text-align: center;
-}
-
-/* El arranque del preview tarda (contenedor, dependencias, dev server). El
- spinner dice que algo está pasando; el texto de abajo dice QUÉ, que es lo que
- convierte una espera muerta en una que se entiende. */
-.preview-spinner {
- width: 22px;
- height: 22px;
- margin: 0 auto 14px;
- border: 2px solid var(--linea);
- border-top-color: var(--lavanda);
- border-radius: 50%;
- animation: girar 0.7s linear infinite;
-}
-@keyframes girar {
- to {
- transform: rotate(360deg);
- }
-}
-/* Respeta a quien pidió menos movimiento: el spinner late en vez de girar. */
-@media (prefers-reduced-motion: reduce) {
- .preview-spinner {
- animation: latir 1.4s ease-in-out infinite;
- }
- @keyframes latir {
- 50% {
- opacity: 0.35;
- }
- }
-}
-
-/* ── El back visual (tab II) ─────────────────────────────────────────────── */
-
-.back-vacio {
- margin: auto;
- text-align: center;
- color: var(--dim);
- font-size: 13px;
- padding: 40px;
-}
-.back-vacio-sub {
- font-size: 12px;
- opacity: 0.7;
- margin-top: 8px;
-}
-
-.back-canvas {
- flex: 1;
- overflow-y: auto;
- padding: 20px 24px 28px;
-}
-
-.back-resumen {
- font-size: 11px;
- letter-spacing: 0.4px;
- color: var(--ambar);
- margin-bottom: 16px;
-}
-
-.back-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
- gap: 14px;
- align-items: start;
-}
-
-.ep-card {
- background: var(--fondo2);
- border: 1px solid var(--linea);
- border-radius: 6px;
- padding: 12px 14px;
-}
-/* El punteado dice "esto todavía no es real" sin necesidad de leer nada. */
-.ep-card.faltante {
- border-style: dashed;
- border-color: var(--ambar);
- background: transparent;
-}
-.ep-card.conectado {
- border-color: #4f7d63;
-}
-.ep-card.huerfano {
- opacity: 0.72;
-}
-
-.ep-head {
- display: flex;
- align-items: center;
- gap: 9px;
- width: 100%;
- background: none;
- border: 0;
- padding: 0;
- cursor: pointer;
- font-family: inherit;
- text-align: left;
- color: var(--texto);
-}
-
-.ep-metodo {
- font-size: 9px;
- font-weight: 700;
- letter-spacing: 1px;
- padding: 3px 6px;
- border-radius: 3px;
- background: var(--linea);
- color: var(--texto);
- flex-shrink: 0;
-}
-.ep-metodo.m-post { background: #4a6b52; }
-.ep-metodo.m-put,
-.ep-metodo.m-patch { background: #6b5a3a; }
-.ep-metodo.m-delete { background: #6e4247; }
-
-.ep-path {
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 12.5px;
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.ep-semaforo {
- width: 9px;
- height: 9px;
- border-radius: 50%;
- flex-shrink: 0;
-}
-.ep-semaforo.conectado { background: #6fbf8e; }
-.ep-semaforo.faltante {
- background: transparent;
- border: 1.5px dashed var(--ambar);
-}
-.ep-semaforo.huerfano { background: var(--dim); }
-
-.ep-estado {
- font-size: 11px;
- color: var(--dim);
- margin-top: 7px;
-}
-
-.ep-detalle {
- margin-top: 12px;
- padding-top: 11px;
- border-top: 1px solid var(--linea);
- font-size: 11px;
-}
-.ep-linea {
- display: flex;
- gap: 7px;
- margin-bottom: 7px;
- color: var(--dim);
-}
-.ep-k {
- color: var(--lavanda);
- flex-shrink: 0;
-}
-.ep-calls {
- display: flex;
- flex-direction: column;
- gap: 3px;
-}
-.ep-detalle code {
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 10.5px;
- color: var(--texto);
-}
-.ep-falta {
- font-style: italic;
- opacity: 0.75;
-}
-
-.ep-anclar {
- margin-top: 6px;
- width: 100%;
- background: var(--linea);
- border: 0;
- border-radius: 4px;
- color: var(--texto);
- font-family: inherit;
- font-size: 11px;
- padding: 8px;
- cursor: pointer;
-}
-.ep-anclar:hover {
- background: var(--lavanda);
- color: #2b2e4c;
-}
-
-.preview-loading-sub {
- font-size: 12px;
- opacity: 0.75;
- margin-top: 10px;
- line-height: 1.7;
-}
-.preview-loading code {
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 11.5px;
- background: rgba(255, 255, 255, 0.06);
- padding: 2px 6px;
- border-radius: 3px;
- color: var(--lavanda);
-}
-
-.preview-loading-sub {
- font-size: 12px;
- opacity: 0.75;
- margin-top: 10px;
- line-height: 1.7;
-}
-.preview-loading code {
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 11.5px;
- background: rgba(255, 255, 255, 0.06);
- padding: 2px 6px;
- border-radius: 3px;
- color: var(--lavanda);
-}
-
-/* ── Tu API key (por persona, solo esta sesión) ──────────────────────────── */
-
-.key-chip {
- font-size: 10px;
- letter-spacing: 1px;
- text-transform: uppercase;
- padding: 6px 11px;
- border-radius: 3px;
- border: 1px solid var(--linea);
- background: transparent;
- color: var(--dim);
- cursor: pointer;
- font-family: inherit;
-}
-.key-chip:hover {
- color: var(--texto);
-}
-.key-chip.listo {
- border-color: #4f7d63;
- color: #8fbfa0;
-}
-.key-chip.falta {
- border-color: var(--ambar);
- color: var(--ambar);
-}
-
-.key-panel {
- position: absolute;
- top: 46px;
- right: 24px;
- z-index: 40;
- width: 340px;
- background: var(--panel);
- border: 1px solid var(--linea);
- border-radius: 8px;
- padding: 14px 16px;
- box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
-}
-
-.key-cab {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
- font-size: 12px;
- font-weight: 700;
-}
-.key-x {
- background: none;
- border: 0;
- color: var(--dim);
- font-size: 10px;
- cursor: pointer;
- font-family: inherit;
-}
-.key-x:hover {
- color: var(--texto);
-}
-
-.key-nota {
- font-size: 11px;
- color: var(--dim);
- line-height: 1.6;
- margin-top: 8px;
-}
-
-.key-fila {
- display: flex;
- gap: 6px;
- margin-top: 11px;
-}
-.key-input {
- flex: 1;
- min-width: 0;
- padding: 8px 10px;
- border-radius: 4px;
- border: 1px solid var(--linea);
- background: var(--fondo2);
- color: var(--texto);
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 12px;
-}
-.key-input:focus {
- outline: none;
- border-color: var(--lavanda);
-}
-.key-guardar {
- border: 0;
- border-radius: 4px;
- background: var(--ambar);
- color: #3d2a12;
- font-family: inherit;
- font-size: 11px;
- font-weight: 700;
- padding: 8px 14px;
- cursor: pointer;
-}
-.key-guardar:disabled {
- opacity: 0.4;
- cursor: default;
-}
-
-.key-error {
- margin-top: 9px;
- font-size: 11px;
- color: #d95d63;
- line-height: 1.5;
-}
-.key-ok {
- margin-top: 9px;
- font-size: 11px;
- color: #8fbfa0;
-}
-.key-link {
- display: inline-block;
- margin-top: 10px;
- font-size: 10.5px;
- color: var(--lavanda);
-}
-
-.key-puesta {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
- margin-top: 10px;
- padding: 8px 10px;
- background: var(--fondo2);
- border-radius: 4px;
-}
-.key-puesta code {
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 11.5px;
- color: #8fbfa0;
-}
-.key-olvidar {
- border: 1px solid var(--linea);
- border-radius: 3px;
- background: transparent;
- color: var(--dim);
- font-family: inherit;
- font-size: 10px;
- padding: 4px 9px;
- cursor: pointer;
-}
-.key-olvidar:hover {
- color: #d95d63;
- border-color: #d95d63;
-}
-
-/* Mensajes seguidos del mismo autor (patrón Discord): sin avatar ni nombre.
- La sangría de 62px alinea el texto bajo el mensaje de arriba
- (16 de padding + 34 del avatar + 12 del gap). */
-.msg-seguido {
- display: flex;
- padding: 1px 16px 1px 62px;
-}
-.msg-seguido:hover {
- background: rgba(255, 255, 255, 0.025);
-}
-.msg-seguido .burbuja {
- font-size: 13px;
- line-height: 1.55;
-}
-
-/* El hover del boceto: da sensación de chat vivo al pasar el mouse. */
-.msg:hover {
- background: rgba(255, 255, 255, 0.025);
-}
-
-/* Etiqueta junto al nombre del agente, para no confundirlo con un humano. */
-.tag-ai {
- font-size: 8px;
- font-weight: 700;
- letter-spacing: 1px;
- background: var(--ambar);
- color: #3d2a12;
- padding: 2px 5px;
- border-radius: 2px;
- text-transform: uppercase;
-}
-
-/* Entrar con el link que te pasaron. Aparece al hacer click en la pista, para
- que la pantalla siga siendo "crear una sala" y no un formulario. */
-.hint-btn {
- background: none;
- border: 0;
- font-family: inherit;
- cursor: pointer;
- padding: 0;
- text-decoration: underline;
- text-underline-offset: 3px;
- text-decoration-color: var(--linea);
-}
-.hint-btn:hover {
- color: var(--texto);
-}
-
-.entrar-bloque {
- margin-top: 18px;
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-.entrar-btn {
- border: 1px solid var(--linea);
- border-radius: 6px;
- background: transparent;
- color: var(--texto);
- font-family: inherit;
- font-size: 12px;
- letter-spacing: 1px;
- text-transform: uppercase;
- padding: 10px;
- cursor: pointer;
-}
-.entrar-btn:hover:not(:disabled) {
- border-color: var(--lavanda);
-}
-.entrar-btn:disabled {
- opacity: 0.4;
- cursor: default;
-}
-.entrar-error {
- font-size: 11px;
- color: #d95d63;
-}
-
-/* "@agente" (crear otro) va al final y separado: lo normal es seguirle hablando
- a quien ya está, no abrir uno nuevo cada vez. */
-.mention-nuevo {
- border-top: 1px solid var(--linea);
-}
-
-/* Campos del panel de credenciales (proveedor / key / modelo). */
-.key-campo {
- display: block;
- margin-top: 11px;
-}
-.key-campo > span {
- display: block;
- font-size: 10px;
- letter-spacing: 1px;
- text-transform: uppercase;
- color: var(--dim);
- margin-bottom: 4px;
-}
-.key-campo .key-input {
- width: 100%;
-}
-.key-select {
- width: 100%;
- padding: 8px 10px;
- border-radius: 4px;
- border: 1px solid var(--linea);
- background: var(--fondo2);
- color: var(--texto);
- font-family: inherit;
- font-size: 12px;
-}
-.key-select:focus {
- outline: none;
- border-color: var(--lavanda);
-}
-
-.key-panel .key-guardar {
- width: 100%;
- margin-top: 13px;
-}
-
-.key-puesta-info {
- display: flex;
- flex-direction: column;
- gap: 3px;
- min-width: 0;
-}
-.key-puesta-modelo {
- font-size: 10px;
- color: var(--dim);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-/* "interrumpir" junto a un agente ocupado: se le PUEDE hablar aunque esté
- trabajando — el mensaje lo detiene y atiende lo nuevo. Sin esta marca parece
- que hay que esperar a que termine. */
-.mention-tag {
- font-size: 8px;
- font-weight: 700;
- letter-spacing: 1px;
- text-transform: uppercase;
- color: var(--ambar);
- border: 1px solid var(--ambar);
- border-radius: 2px;
- padding: 1px 5px;
- margin-left: auto;
- flex-shrink: 0;
-}
-
-/* El rastro de tools: una línea por default, expandible con click. */
-.tool-line-click {
- cursor: pointer;
- display: flex;
- align-items: baseline;
- gap: 6px;
-}
-.tool-line-click:hover {
- color: var(--texto);
-}
-.tool-mas {
- font-size: 9px;
- color: var(--dim);
- background: rgba(255, 255, 255, 0.06);
- border-radius: 8px;
- padding: 1px 6px;
- flex-shrink: 0;
-}
-.tool-trace {
- cursor: pointer;
- border-left: 1px solid var(--linea);
- padding-left: 8px;
- margin: 2px 0;
-}
-.tool-cerrar {
- font-size: 9px;
- color: var(--dim);
- margin-top: 3px;
-}
-.tool-cerrar:hover {
- color: var(--texto);
-}
+/* Multi — la sala. Paleta crepúsculo (Gengar), chat estilo Discord.
+ Traducido de app/sala.html. */
+:root {
+ --fondo: #33365a;
+ --fondo2: #2b2e4c;
+ --panel: #3b3e66;
+ --texto: #eef0fb;
+ --dim: #a9abd0;
+ --linea: #4a4d78;
+ --lavanda: #b9a8e3;
+ --ambar: #ffc37a;
+ --rosa: #c393c9;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: "Segoe UI", system-ui, sans-serif;
+ color: var(--texto);
+ background: linear-gradient(180deg, var(--fondo) 0%, var(--fondo2) 100%);
+ /* Las dos alturas a propósito, y en este orden: `dvh` descuenta la barra del
+ navegador del celular, que `vh` ignora. Sin ella la página queda más alta
+ que la pantalla y lo de abajo se corta, que en la Sala es justo donde está
+ la caja de escribir. El navegador que no entienda `dvh` se queda con `vh`. */
+ height: 100vh;
+ height: 100dvh;
+ overflow: hidden;
+}
+
+/* El grano del hero — el mismo de la landing, más sutil para no cansar la vista
+ en una pantalla donde se pasan horas. Va sobre todo pero no intercepta clicks
+ (el preview y los cursores tienen que seguir respondiendo). */
+body::after {
+ content: "";
+ position: fixed;
+ inset: 0;
+ pointer-events: none;
+ opacity: 0.035;
+ z-index: 90;
+ background-image: repeating-conic-gradient(#fff 0 0.0001%, transparent 0 0.0002%);
+}
+
+#root {
+ height: 100vh;
+ height: 100dvh;
+}
+
+/* ── Pantallas de entrada ─────────────────────────────────────────────── */
+
+.center-screen {
+ height: 100vh;
+ height: 100dvh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.card {
+ text-align: center;
+ padding: 48px 56px;
+ background: rgba(43, 46, 76, 0.5);
+ border: 1px solid var(--linea);
+ border-radius: 10px;
+ max-width: 420px;
+}
+
+.brand {
+ font-family: "Bodoni MT", "Didot", Georgia, serif;
+ font-size: 64px;
+ letter-spacing: 6px;
+}
+
+.sub {
+ color: var(--dim);
+ font-size: 14px;
+ margin-top: 8px;
+}
+
+.room-name {
+ font-family: "Bodoni MT", "Didot", Georgia, serif;
+ font-size: 28px;
+ margin: 8px 0 20px;
+ color: var(--lavanda);
+}
+
+.cta {
+ margin-top: 28px;
+ font-size: 13px;
+ font-weight: 700;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ padding: 15px 32px;
+ border-radius: 4px;
+ border: none;
+ background: var(--texto);
+ color: var(--fondo2);
+ cursor: pointer;
+ transition: opacity 0.15s;
+}
+.cta:hover:not(:disabled) {
+ opacity: 0.85;
+}
+.cta:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+.hint {
+ margin-top: 18px;
+ font-size: 12px;
+ color: var(--dim);
+}
+
+.name-input {
+ margin-top: 8px;
+ width: 100%;
+ padding: 12px 14px;
+ border-radius: 6px;
+ border: 1px solid var(--linea);
+ background: var(--panel);
+ color: var(--texto);
+ font-size: 14px;
+}
+.name-input:focus {
+ outline: none;
+ border-color: var(--lavanda);
+}
+
+/* ── La sala: 2 columnas ──────────────────────────────────────────────── */
+
+.sala {
+ display: flex;
+ height: 100vh;
+ height: 100dvh;
+}
+
+/* Chat izquierda — una sola línea vertical, sin cruces (estilo Discord) */
+.chat {
+ width: 340px;
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ border-right: 1px solid var(--linea);
+ background: rgba(43, 46, 76, 0.5);
+ transition: width 0.22s ease, opacity 0.16s ease;
+}
+
+/* Colapsado: el preview se queda con toda la pantalla.
+ Se anima el ancho en vez de quitar el nodo para que el iframe NO se
+ desmonte: recargarlo costaría segundos y perdería el estado de la app. */
+.sala.chat-colapsado .chat {
+ width: 0;
+ opacity: 0;
+ overflow: hidden;
+ border-right: none;
+ pointer-events: none;
+}
+
+/* El botón de colapsar, primero en la barra. Discreto: es un control de vista,
+ no una acción de la sala. */
+.colapsar-btn {
+ background: none;
+ border: 1px solid var(--linea);
+ border-radius: 3px;
+ color: var(--dim);
+ cursor: pointer;
+ font-size: 12px;
+ line-height: 1;
+ padding: 8px 10px;
+ flex-shrink: 0;
+ transition: color 0.15s, border-color 0.15s;
+}
+.colapsar-btn:hover {
+ color: var(--texto);
+ border-color: var(--dim);
+}
+
+.sala-cab {
+ padding: 16px 18px 12px;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18);
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+}
+.sala-titulo {
+ min-width: 0;
+ /* Empuja el botón de crear sala hasta la derecha de la cabecera. */
+ flex: 1;
+}
+
+/* Crear otra sala. Vive aquí y no dentro del menú porque era justo lo que
+ nadie encontraba: escondido, el único camino visible para llegar a una sala
+ era crear otra, y así se acumulaban las vacías. */
+.sala-nueva {
+ flex-shrink: 0;
+ width: 28px;
+ height: 28px;
+ border: 1px solid var(--linea);
+ border-radius: 6px;
+ background: transparent;
+ color: var(--dim);
+ font-size: 18px;
+ line-height: 1;
+ cursor: pointer;
+ transition: color 0.15s, border-color 0.15s;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+.sala-nueva:hover {
+ color: var(--texto);
+ border-color: var(--dim);
+}
+.sala-nueva:disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+
+/* Sin sala abierta no hay nombre: se deja el hueco con el texto apagado, para
+ que la cabecera no se colapse y se vea que falta elegir. */
+.sala-nombre-vacio {
+ color: var(--dim);
+ font-style: italic;
+}
+
+/* ── Menú de salas ────────────────────────────────────────────────────── */
+/* Desplegable y no sidebar: la lista se abre una o dos veces por sesión, y un
+ panel permanente le quitaría ancho al preview, que es lo que todos miran. */
+.menu-salas {
+ position: relative;
+ flex-shrink: 0;
+}
+.menu-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 6px 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+/* Las tres rayas, con pseudo-elementos: sin SVG ni fuente de iconos. */
+.menu-icono,
+.menu-icono::before,
+.menu-icono::after {
+ display: block;
+ width: 15px;
+ height: 1.5px;
+ background: var(--dim);
+ border-radius: 1px;
+ transition: background 0.15s;
+}
+.menu-icono {
+ position: relative;
+}
+.menu-icono::before,
+.menu-icono::after {
+ content: "";
+ position: absolute;
+ left: 0;
+}
+.menu-icono::before {
+ top: -5px;
+}
+.menu-icono::after {
+ top: 5px;
+}
+.menu-btn:hover .menu-icono,
+.menu-btn:hover .menu-icono::before,
+.menu-btn:hover .menu-icono::after {
+ background: var(--texto);
+}
+
+.menu-panel {
+ position: absolute;
+ top: calc(100% + 8px);
+ left: 0;
+ z-index: 60;
+ width: 268px;
+ max-height: 380px;
+ overflow-y: auto;
+ background: var(--panel);
+ border: 1px solid var(--linea);
+ border-radius: 8px;
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
+ padding: 8px;
+}
+.menu-cab {
+ font-size: 9px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ color: var(--dim);
+ padding: 4px 8px 8px;
+}
+.menu-vacio {
+ font-size: 11.5px;
+ color: var(--dim);
+ font-style: italic;
+ padding: 4px 8px 10px;
+ line-height: 1.5;
+}
+/* Confirma que la sala se borró de verdad, no solo de esta lista. Aparece
+ suave para que se note que es nuevo, y se quita solo a los segundos. */
+.menu-borrada {
+ font-size: 11.5px;
+ color: var(--dim);
+ padding: 2px 8px 8px;
+ animation: aparecer 0.2s ease-out;
+}
+@keyframes aparecer {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+.menu-lista {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+}
+.menu-item {
+ display: flex;
+ align-items: center;
+ border-radius: 5px;
+}
+.menu-item:hover {
+ background: rgba(255, 255, 255, 0.05);
+}
+.menu-link {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ padding: 7px 8px;
+ text-decoration: none;
+ color: var(--texto);
+}
+.menu-sala-id {
+ font-size: 12.5px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.menu-sala-fecha {
+ font-size: 10px;
+ color: var(--dim);
+}
+/* La × aparece al pasar el cursor: quitar una sala es raro, y un botón visible
+ en cada fila compite con el link, que es lo que sí quieres tocar. */
+.menu-olvidar {
+ background: none;
+ border: none;
+ color: var(--dim);
+ cursor: pointer;
+ font-size: 15px;
+ line-height: 1;
+ padding: 6px 9px;
+ opacity: 0;
+ transition: opacity 0.12s, color 0.12s;
+}
+.menu-item:hover .menu-olvidar,
+.menu-olvidar:focus-visible {
+ opacity: 1;
+}
+.menu-olvidar:hover {
+ color: var(--rojo);
+}
+
+/* Es un (crea la sala ahí mismo), así que hay que apagarle lo que el
+ navegador le pone de fábrica: fondo gris, borde y ancho de contenido. */
+.menu-nueva {
+ display: block;
+ width: 100%;
+ text-align: left;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ font: inherit;
+ margin-top: 8px;
+ padding: 9px 8px 4px;
+ border-top: 1px solid var(--linea);
+ font-size: 11.5px;
+ color: var(--dim);
+ text-decoration: none;
+}
+.menu-nueva:hover {
+ color: var(--texto);
+}
+.menu-nueva:disabled {
+ cursor: default;
+ opacity: 0.6;
+}
+/* Es un para que se pueda renombrar con el teclado y lo anuncien los
+ lectores de pantalla, así que hay que apagarle lo que trae de fábrica. */
+.sala-nombre {
+ font-family: "Bodoni MT", "Didot", Georgia, serif;
+ font-size: 19px;
+ letter-spacing: 1px;
+ color: var(--texto);
+ background: transparent;
+ border: none;
+ padding: 0;
+ text-align: left;
+ cursor: text;
+ /* El subrayado invisible reserva su espacio desde el principio: sin esto el
+ título brinca un pixel al pasar el mouse. */
+ border-bottom: 1px solid transparent;
+}
+/* Que se note que se puede tocar. Sin esto nadie descubre que es editable:
+ un título se ve igual sea editable o no. */
+.sala-nombre:hover,
+.sala-nombre:focus-visible {
+ border-bottom-color: var(--dim);
+ outline: none;
+}
+/* El input hereda la misma tipografía y tamaño para que renombrar no mueva
+ nada de sitio: escribes donde estaba el nombre, no en otra caja. */
+.sala-nombre-input {
+ font-family: "Bodoni MT", "Didot", Georgia, serif;
+ font-size: 19px;
+ letter-spacing: 1px;
+ color: var(--texto);
+ background: transparent;
+ border: none;
+ border-bottom: 1px solid var(--lavanda);
+ padding: 0;
+ width: 100%;
+ outline: none;
+}
+.sala-nombre-input::placeholder {
+ color: var(--dim);
+}
+.sala-meta {
+ font-size: 9px;
+ letter-spacing: 2.5px;
+ text-transform: uppercase;
+ color: var(--dim);
+ margin-top: 3px;
+}
+
+/* ── Lista de agentes ─────────────────────────────────────────────────── */
+/* Tres estados con peso visual distinto: waiting es NEUTRO (está en fila,
+ no es un problema); solo stuck usa color de alerta. */
+.agent-list {
+ padding: 8px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18);
+}
+
+.agent-row {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ font-size: 11px;
+ padding: 3px 0;
+}
+.agent-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+.agent-working .agent-dot {
+ animation: pulso 1.6s ease-in-out infinite;
+}
+@keyframes pulso {
+ 50% {
+ opacity: 0.35;
+ }
+}
+.agent-name {
+ font-weight: 700;
+ flex-shrink: 0;
+}
+.agent-status {
+ color: var(--dim);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+/* Esperando: neutro, atenuado. NO es alarma. */
+.agent-waiting .agent-dot {
+ background: var(--dim) !important;
+}
+.agent-waiting .agent-status {
+ font-style: italic;
+}
+/* Atorado: esto SÍ pide atención. */
+.agent-stuck .agent-dot {
+ background: #d95d63 !important;
+}
+.agent-stuck .agent-status {
+ color: #d95d63;
+}
+.agent-idle {
+ opacity: 0.5;
+}
+.agent-fold {
+ font-size: 10px;
+ color: var(--dim);
+ cursor: pointer;
+ padding: 2px 0;
+}
+.agent-fold:hover {
+ color: var(--texto);
+}
+
+/* ── Historial (la línea de tiempo) ───────────────────────────────────── */
+.historial {
+ padding: 10px 24px 16px;
+ flex-shrink: 0;
+}
+.hist-label {
+ font-size: 9px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ color: var(--dim);
+}
+.hist-barra {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-top: 6px;
+ flex-wrap: wrap;
+}
+.hist-vacio {
+ font-size: 11px;
+ color: var(--dim);
+ font-style: italic;
+}
+.hist-punto {
+ background: none;
+ border: none;
+ color: var(--lavanda);
+ font-size: 13px;
+ cursor: pointer;
+ padding: 0 1px;
+ line-height: 1;
+ opacity: 0.7;
+}
+.hist-punto:hover,
+.hist-punto.activo {
+ opacity: 1;
+ transform: scale(1.25);
+}
+.hist-punto.marcado {
+ color: var(--ambar);
+ opacity: 1;
+}
+.hist-ahora {
+ font-size: 9px;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ color: var(--dim);
+ margin-left: 4px;
+}
+
+.hist-detalle {
+ margin-top: 10px;
+ padding: 10px 12px;
+ background: var(--panel);
+ border-radius: 6px;
+}
+.hist-det-cab {
+ display: flex;
+ gap: 10px;
+ align-items: baseline;
+}
+.hist-det-autor {
+ font-size: 11.5px;
+ font-weight: 700;
+ color: var(--ambar);
+}
+.hist-det-fecha {
+ font-size: 10px;
+ color: var(--dim);
+}
+/* Lo que se pidió: "por qué pasó este cambio" */
+.hist-det-task {
+ font-size: 11.5px;
+ color: var(--texto);
+ font-style: italic;
+ margin-top: 4px;
+}
+.hist-det-files {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+ margin-top: 7px;
+}
+.hist-file {
+ font-size: 10px;
+ font-family: ui-monospace, monospace;
+ color: var(--dim);
+ background: rgba(255, 255, 255, 0.05);
+ padding: 2px 6px;
+ border-radius: 3px;
+ cursor: pointer;
+}
+.hist-file:hover {
+ color: var(--texto);
+ background: rgba(185, 168, 227, 0.15);
+}
+.hist-acciones {
+ display: flex;
+ gap: 6px;
+ margin-top: 9px;
+}
+.hist-btn {
+ font-size: 10px;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ padding: 6px 10px;
+ border-radius: 3px;
+ border: 1px solid var(--linea);
+ background: transparent;
+ color: var(--dim);
+ cursor: pointer;
+}
+.hist-btn:hover {
+ color: var(--texto);
+}
+.hist-btn.primario {
+ background: var(--ambar);
+ border-color: var(--ambar);
+ color: #3d2a12;
+ font-weight: 700;
+}
+
+.hist-diff {
+ margin-top: 9px;
+ max-height: 220px;
+ overflow: auto;
+ font-family: ui-monospace, monospace;
+ font-size: 10.5px;
+ line-height: 1.5;
+ background: var(--fondo2);
+ border-radius: 4px;
+ padding: 8px 10px;
+ color: var(--dim);
+}
+.hist-diff .add {
+ color: #8fb573;
+}
+.hist-diff .del {
+ color: #d95d63;
+}
+
+.hist-confirm {
+ margin-top: 10px;
+ padding: 10px 12px;
+ background: rgba(255, 195, 122, 0.08);
+ border: 1px solid rgba(255, 195, 122, 0.3);
+ border-radius: 6px;
+}
+.hist-confirm-text {
+ font-size: 12px;
+ color: var(--texto);
+}
+.hist-confirm-nota {
+ font-size: 10.5px;
+ color: var(--dim);
+ margin-top: 4px;
+ line-height: 1.5;
+}
+.hist-confirm-acciones {
+ display: flex;
+ gap: 6px;
+ margin-top: 9px;
+}
+
+/* ── Menú de menciones (@) ────────────────────────────────────────────── */
+.input-wrap {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+/* La caja se lleva el espacio que sobre; el botón se queda de su tamaño. */
+.input-wrap .caja {
+ flex: 1;
+ min-width: 0;
+}
+
+/* Adjuntar una imagen. Arrastrar y pegar ya servían, pero no se ven, y en el
+ teléfono no existe ninguna de las dos. */
+.adjuntar-btn {
+ flex-shrink: 0;
+ width: 30px;
+ height: 30px;
+ border: 1px solid var(--linea);
+ border-radius: 6px;
+ background: transparent;
+ color: var(--dim);
+ font-size: 17px;
+ line-height: 1;
+ cursor: pointer;
+ transition: color 0.15s, border-color 0.15s;
+ /* Lleva un SVG dentro: sin esto queda pegado a una esquina. */
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+.adjuntar-btn:hover {
+ color: var(--texto);
+ border-color: var(--dim);
+}
+.mention-menu {
+ position: absolute;
+ bottom: calc(100% + 6px);
+ left: 0;
+ right: 0;
+ background: var(--fondo2);
+ border: 1px solid var(--linea);
+ border-radius: 6px;
+ overflow: hidden;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
+ z-index: 30;
+}
+.mention-item {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+ padding: 8px 12px;
+ cursor: pointer;
+}
+.mention-item:hover {
+ background: var(--panel);
+}
+.mention-name {
+ font-size: 12px;
+ font-weight: 700;
+}
+.mention-hint {
+ font-size: 10.5px;
+ color: var(--dim);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* ── Turnos huérfanos (crash a media tarea) ───────────────────────────── */
+.orphan-card {
+ margin: 8px 14px;
+ padding: 10px 12px;
+ background: rgba(255, 195, 122, 0.08);
+ border: 1px solid rgba(255, 195, 122, 0.3);
+ border-radius: 6px;
+}
+.orphan-text {
+ font-size: 11.5px;
+ line-height: 1.5;
+ color: var(--texto);
+}
+.orphan-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 8px;
+}
+.orphan-btn {
+ font-size: 10px;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ padding: 6px 10px;
+ border-radius: 3px;
+ border: 1px solid var(--linea);
+ background: transparent;
+ color: var(--dim);
+ cursor: pointer;
+}
+.orphan-btn:hover {
+ color: var(--texto);
+}
+.orphan-btn.keep {
+ background: var(--ambar);
+ border-color: var(--ambar);
+ color: #3d2a12;
+ font-weight: 700;
+}
+
+.chat-scroll {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.msg {
+ display: flex;
+ gap: 12px;
+ padding: 8px 16px 2px;
+}
+.msg:hover {
+ background: rgba(255, 255, 255, 0.025);
+}
+.msg-system {
+ padding: 4px 16px;
+ justify-content: center;
+}
+.system-text {
+ font-size: 11px;
+ color: var(--dim);
+ font-style: italic;
+}
+
+.av {
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: 800;
+ flex-shrink: 0;
+ color: #fff;
+}
+
+.msg-cuerpo {
+ min-width: 0;
+}
+.msg-cab {
+ margin-bottom: 2px;
+}
+.quien {
+ font-size: 12.5px;
+ font-weight: 700;
+}
+.burbuja {
+ font-size: 13px;
+ line-height: 1.55;
+ color: var(--texto);
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+.tool-line {
+ font-size: 11px;
+ color: var(--dim);
+ font-family: ui-monospace, monospace;
+ margin-bottom: 4px;
+}
+
+.chat-input {
+ padding: 6px 14px 16px;
+}
+.caja {
+ width: 100%;
+ background: var(--panel);
+ border: none;
+ border-radius: 6px;
+ padding: 12px 14px;
+ font-size: 13px;
+ color: var(--texto);
+}
+.caja:focus {
+ outline: none;
+}
+.caja::placeholder {
+ color: var(--dim);
+}
+
+/* Escenario derecha */
+.escenario {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+}
+
+.barra-sup {
+ display: flex;
+ align-items: center;
+ padding: 12px 24px 0;
+}
+.presencia {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.presencia .av {
+ width: 28px;
+ height: 28px;
+ border: 2px solid var(--fondo);
+}
+.invitar {
+ font-size: 10px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ color: var(--dim);
+ border: 1px solid var(--linea);
+ padding: 7px 14px;
+ border-radius: 3px;
+ cursor: pointer;
+ background: transparent;
+}
+.invitar:hover {
+ color: var(--texto);
+ border-color: var(--dim);
+}
+/* Apagado, no escondido: se ve que la opción existe y por qué todavía no. */
+.invitar:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+.invitar:disabled:hover {
+ color: var(--dim);
+ border-color: var(--linea);
+}
+
+.tabs {
+ display: flex;
+ gap: 4px;
+ padding: 12px 24px 0;
+}
+.tab {
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 2.5px;
+ text-transform: uppercase;
+ color: var(--dim);
+ padding: 9px 18px;
+ border-radius: 3px 3px 0 0;
+ cursor: pointer;
+ border: 0;
+ background: transparent;
+ font-family: inherit;
+}
+.tab:hover {
+ color: var(--texto);
+}
+.tab.activa {
+ color: var(--texto);
+ background: var(--panel);
+}
+.tab.disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+
+.lienzo {
+ flex: 1;
+ margin: 0 24px 24px;
+ background: var(--panel);
+ border-radius: 0 3px 3px 3px;
+ overflow: hidden;
+ display: flex;
+ position: relative; /* para posicionar cursores/selecciones encima */
+}
+
+/* botón de inspect en la barra superior */
+.barra-sup {
+ justify-content: space-between;
+}
+.inspect-btn {
+ font-size: 10px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ color: var(--dim);
+ border: 1px solid var(--linea);
+ padding: 7px 14px;
+ border-radius: 3px;
+ cursor: pointer;
+ background: transparent;
+}
+.inspect-btn:hover:not(:disabled) {
+ color: var(--texto);
+ border-color: var(--dim);
+}
+/* Apagado, no escondido: se ve que la opción existe y que falta una sala. */
+.inspect-btn:disabled,
+.adjuntar-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+.inspect-btn.on {
+ color: var(--fondo2);
+ background: var(--lavanda);
+ border-color: var(--lavanda);
+}
+
+/* Cursores de otros sobre el escenario (estilo Figma, del boceto).
+ La flecha es puro CSS: dos bordes formando un triángulo, girado. Toma el color
+ de la persona por currentColor, así flecha y etiqueta van siempre a juego. */
+.remote-cursor {
+ position: absolute;
+ pointer-events: none;
+ z-index: 20;
+ transition: left 0.05s linear, top 0.05s linear;
+ white-space: nowrap;
+}
+.cursor-flecha {
+ width: 0;
+ height: 0;
+ border-left: 10px solid currentColor;
+ border-bottom: 10px solid transparent;
+ border-top: 4px solid currentColor;
+ transform: rotate(-8deg);
+ display: inline-block;
+}
+.cursor-name {
+ font-size: 8.5px;
+ font-weight: 700;
+ letter-spacing: 1.5px;
+ text-transform: uppercase;
+ color: #2b2e4c;
+ background: currentColor;
+ padding: 3px 8px;
+ border-radius: 2px;
+ margin-left: 1px;
+ vertical-align: top;
+}
+/* El texto va oscuro sobre el color de la persona: los colores de la paleta son
+ claros, así que negro encima se lee mejor que blanco. */
+.cursor-name span {
+ color: #2b2e4c;
+}
+
+/* badges de selección de otros */
+.others-selections {
+ position: absolute;
+ top: 8px;
+ left: 8px;
+ z-index: 15;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ pointer-events: none;
+}
+.sel-badge {
+ font-size: 10px;
+ font-weight: 700;
+ padding: 3px 8px;
+ border: 1px solid;
+ border-radius: 3px;
+ background: rgba(43, 46, 76, 0.85);
+}
+
+/* chip de anclaje en el input del chat */
+.anchor-chip {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 10px;
+ color: var(--lavanda);
+ background: rgba(185, 168, 227, 0.12);
+ border: 1px solid rgba(185, 168, 227, 0.3);
+ border-radius: 4px;
+ padding: 5px 8px;
+ margin-bottom: 6px;
+}
+.anchor-x {
+ cursor: pointer;
+ font-size: 13px;
+ margin-left: auto;
+ opacity: 0.7;
+}
+.anchor-x:hover {
+ opacity: 1;
+}
+
+/* ── Imágenes en el chat ──────────────────────────────────────────────────── */
+
+/* Las que pegaste y todavía no mandas, encima del input. */
+.adjuntos-pendientes {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-bottom: 6px;
+}
+.adjunto-chip {
+ position: relative;
+ width: 56px;
+ height: 56px;
+ border-radius: 6px;
+ overflow: hidden;
+ border: 1px solid rgba(185, 168, 227, 0.3);
+}
+.adjunto-chip img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+.adjunto-x {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ width: 16px;
+ height: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ line-height: 1;
+ color: #fff;
+ background: rgba(0, 0, 0, 0.6);
+ border-radius: 50%;
+ cursor: pointer;
+}
+.adjunto-x:hover {
+ background: rgba(0, 0, 0, 0.85);
+}
+
+/* Cuando algo salió mal con una imagen. Se va al hacer click. */
+.adjunto-error {
+ font-size: 10px;
+ color: #e8a0a0;
+ background: rgba(232, 160, 160, 0.1);
+ border: 1px solid rgba(232, 160, 160, 0.25);
+ border-radius: 4px;
+ padding: 5px 8px;
+ margin-bottom: 6px;
+ cursor: pointer;
+}
+
+/* Arrastrando un archivo encima del chat. */
+.chat-input.arrastrando {
+ outline: 1px dashed var(--lavanda);
+ outline-offset: -4px;
+ border-radius: 6px;
+}
+
+/* Las que ya se mandaron, dentro del mensaje. */
+.adjuntos-msg {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-top: 4px;
+}
+.adjuntos-msg img {
+ max-width: 180px;
+ /* Una captura muy alta no debe empujar todo el chat hacia abajo. */
+ max-height: 220px;
+ border-radius: 6px;
+ display: block;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+/* nota de anclaje en un mensaje ya mandado */
+.anchor-note {
+ font-size: 10px;
+ color: var(--dim);
+ font-style: italic;
+ margin-bottom: 3px;
+}
+.preview-frame {
+ width: 100%;
+ height: 100%;
+ border: none;
+ background: #fff;
+}
+.preview-loading {
+ margin: auto;
+ color: var(--dim);
+ font-size: 13px;
+ text-align: center;
+}
+
+/* El arranque del preview tarda (contenedor, dependencias, dev server). El
+ spinner dice que algo está pasando; el texto de abajo dice QUÉ, que es lo que
+ convierte una espera muerta en una que se entiende. */
+.preview-spinner {
+ width: 22px;
+ height: 22px;
+ margin: 0 auto 14px;
+ border: 2px solid var(--linea);
+ border-top-color: var(--lavanda);
+ border-radius: 50%;
+ animation: girar 0.7s linear infinite;
+}
+@keyframes girar {
+ to {
+ transform: rotate(360deg);
+ }
+}
+/* Respeta a quien pidió menos movimiento: el spinner late en vez de girar. */
+@media (prefers-reduced-motion: reduce) {
+ .preview-spinner {
+ animation: latir 1.4s ease-in-out infinite;
+ }
+ @keyframes latir {
+ 50% {
+ opacity: 0.35;
+ }
+ }
+}
+
+/* ── El back visual (tab II) ─────────────────────────────────────────────── */
+
+.back-vacio {
+ margin: auto;
+ text-align: center;
+ color: var(--dim);
+ font-size: 13px;
+ padding: 40px;
+}
+.back-vacio-sub {
+ font-size: 12px;
+ opacity: 0.7;
+ margin-top: 8px;
+}
+
+.back-canvas {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px 24px 28px;
+}
+
+.back-resumen {
+ font-size: 11px;
+ letter-spacing: 0.4px;
+ color: var(--ambar);
+ margin-bottom: 16px;
+}
+
+.back-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+ gap: 14px;
+ align-items: start;
+}
+
+.ep-card {
+ background: var(--fondo2);
+ border: 1px solid var(--linea);
+ border-radius: 6px;
+ padding: 12px 14px;
+}
+/* El punteado dice "esto todavía no es real" sin necesidad de leer nada. */
+.ep-card.faltante {
+ border-style: dashed;
+ border-color: var(--ambar);
+ background: transparent;
+}
+.ep-card.conectado {
+ border-color: #4f7d63;
+}
+.ep-card.huerfano {
+ opacity: 0.72;
+}
+
+.ep-head {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ width: 100%;
+ background: none;
+ border: 0;
+ padding: 0;
+ cursor: pointer;
+ font-family: inherit;
+ text-align: left;
+ color: var(--texto);
+}
+
+.ep-metodo {
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: 1px;
+ padding: 3px 6px;
+ border-radius: 3px;
+ background: var(--linea);
+ color: var(--texto);
+ flex-shrink: 0;
+}
+.ep-metodo.m-post { background: #4a6b52; }
+.ep-metodo.m-put,
+.ep-metodo.m-patch { background: #6b5a3a; }
+.ep-metodo.m-delete { background: #6e4247; }
+
+.ep-path {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 12.5px;
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ep-semaforo {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+.ep-semaforo.conectado { background: #6fbf8e; }
+.ep-semaforo.faltante {
+ background: transparent;
+ border: 1.5px dashed var(--ambar);
+}
+.ep-semaforo.huerfano { background: var(--dim); }
+
+.ep-estado {
+ font-size: 11px;
+ color: var(--dim);
+ margin-top: 7px;
+}
+
+.ep-detalle {
+ margin-top: 12px;
+ padding-top: 11px;
+ border-top: 1px solid var(--linea);
+ font-size: 11px;
+}
+.ep-linea {
+ display: flex;
+ gap: 7px;
+ margin-bottom: 7px;
+ color: var(--dim);
+}
+.ep-k {
+ color: var(--lavanda);
+ flex-shrink: 0;
+}
+.ep-calls {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+.ep-detalle code {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 10.5px;
+ color: var(--texto);
+}
+.ep-falta {
+ font-style: italic;
+ opacity: 0.75;
+}
+
+.ep-anclar {
+ margin-top: 6px;
+ width: 100%;
+ background: var(--linea);
+ border: 0;
+ border-radius: 4px;
+ color: var(--texto);
+ font-family: inherit;
+ font-size: 11px;
+ padding: 8px;
+ cursor: pointer;
+}
+.ep-anclar:hover {
+ background: var(--lavanda);
+ color: #2b2e4c;
+}
+
+.preview-loading-sub {
+ font-size: 12px;
+ opacity: 0.75;
+ margin-top: 10px;
+ line-height: 1.7;
+}
+.preview-loading code {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 11.5px;
+ background: rgba(255, 255, 255, 0.06);
+ padding: 2px 6px;
+ border-radius: 3px;
+ color: var(--lavanda);
+}
+
+.preview-loading-sub {
+ font-size: 12px;
+ opacity: 0.75;
+ margin-top: 10px;
+ line-height: 1.7;
+}
+.preview-loading code {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 11.5px;
+ background: rgba(255, 255, 255, 0.06);
+ padding: 2px 6px;
+ border-radius: 3px;
+ color: var(--lavanda);
+}
+
+/* ── Tu API key (por persona, solo esta sesión) ──────────────────────────── */
+
+.key-chip {
+ font-size: 10px;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ padding: 6px 11px;
+ border-radius: 3px;
+ border: 1px solid var(--linea);
+ background: transparent;
+ color: var(--dim);
+ cursor: pointer;
+ font-family: inherit;
+}
+.key-chip:hover {
+ color: var(--texto);
+}
+.key-chip.listo {
+ border-color: #4f7d63;
+ color: #8fbfa0;
+}
+.key-chip.falta {
+ border-color: var(--ambar);
+ color: var(--ambar);
+}
+
+.key-panel {
+ position: absolute;
+ top: 46px;
+ right: 24px;
+ z-index: 40;
+ width: 340px;
+ background: var(--panel);
+ border: 1px solid var(--linea);
+ border-radius: 8px;
+ padding: 14px 16px;
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
+}
+
+.key-cab {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ font-size: 12px;
+ font-weight: 700;
+}
+.key-x {
+ background: none;
+ border: 0;
+ color: var(--dim);
+ font-size: 10px;
+ cursor: pointer;
+ font-family: inherit;
+}
+.key-x:hover {
+ color: var(--texto);
+}
+
+.key-nota {
+ font-size: 11px;
+ color: var(--dim);
+ line-height: 1.6;
+ margin-top: 8px;
+}
+
+.key-fila {
+ display: flex;
+ gap: 6px;
+ margin-top: 11px;
+}
+.key-input {
+ flex: 1;
+ min-width: 0;
+ padding: 8px 10px;
+ border-radius: 4px;
+ border: 1px solid var(--linea);
+ background: var(--fondo2);
+ color: var(--texto);
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 12px;
+}
+.key-input:focus {
+ outline: none;
+ border-color: var(--lavanda);
+}
+.key-guardar {
+ border: 0;
+ border-radius: 4px;
+ background: var(--ambar);
+ color: #3d2a12;
+ font-family: inherit;
+ font-size: 11px;
+ font-weight: 700;
+ padding: 8px 14px;
+ cursor: pointer;
+}
+.key-guardar:disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+
+.key-error {
+ margin-top: 9px;
+ font-size: 11px;
+ color: #d95d63;
+ line-height: 1.5;
+}
+.key-ok {
+ margin-top: 9px;
+ font-size: 11px;
+ color: #8fbfa0;
+}
+.key-link {
+ display: inline-block;
+ margin-top: 10px;
+ font-size: 10.5px;
+ color: var(--lavanda);
+}
+
+.key-puesta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ margin-top: 10px;
+ padding: 8px 10px;
+ background: var(--fondo2);
+ border-radius: 4px;
+}
+.key-puesta code {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 11.5px;
+ color: #8fbfa0;
+}
+.key-olvidar {
+ border: 1px solid var(--linea);
+ border-radius: 3px;
+ background: transparent;
+ color: var(--dim);
+ font-family: inherit;
+ font-size: 10px;
+ padding: 4px 9px;
+ cursor: pointer;
+}
+.key-olvidar:hover {
+ color: #d95d63;
+ border-color: #d95d63;
+}
+
+/* Mensajes seguidos del mismo autor (patrón Discord): sin avatar ni nombre.
+ La sangría de 62px alinea el texto bajo el mensaje de arriba
+ (16 de padding + 34 del avatar + 12 del gap). */
+.msg-seguido {
+ display: flex;
+ padding: 1px 16px 1px 62px;
+}
+.msg-seguido:hover {
+ background: rgba(255, 255, 255, 0.025);
+}
+.msg-seguido .burbuja {
+ font-size: 13px;
+ line-height: 1.55;
+}
+
+/* El hover del boceto: da sensación de chat vivo al pasar el mouse. */
+.msg:hover {
+ background: rgba(255, 255, 255, 0.025);
+}
+
+/* Etiqueta junto al nombre del agente, para no confundirlo con un humano. */
+.tag-ai {
+ font-size: 8px;
+ font-weight: 700;
+ letter-spacing: 1px;
+ background: var(--ambar);
+ color: #3d2a12;
+ padding: 2px 5px;
+ border-radius: 2px;
+ text-transform: uppercase;
+}
+
+/* Entrar con el link que te pasaron. Aparece al hacer click en la pista, para
+ que la pantalla siga siendo "crear una sala" y no un formulario. */
+.hint-btn {
+ background: none;
+ border: 0;
+ font-family: inherit;
+ cursor: pointer;
+ padding: 0;
+ text-decoration: underline;
+ text-underline-offset: 3px;
+ text-decoration-color: var(--linea);
+}
+.hint-btn:hover {
+ color: var(--texto);
+}
+
+.entrar-bloque {
+ margin-top: 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.entrar-btn {
+ border: 1px solid var(--linea);
+ border-radius: 6px;
+ background: transparent;
+ color: var(--texto);
+ font-family: inherit;
+ font-size: 12px;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ padding: 10px;
+ cursor: pointer;
+}
+.entrar-btn:hover:not(:disabled) {
+ border-color: var(--lavanda);
+}
+.entrar-btn:disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+.entrar-error {
+ font-size: 11px;
+ color: #d95d63;
+}
+
+/* "@agente" (crear otro) va al final y separado: lo normal es seguirle hablando
+ a quien ya está, no abrir uno nuevo cada vez. */
+.mention-nuevo {
+ border-top: 1px solid var(--linea);
+}
+
+/* Campos del panel de credenciales (proveedor / key / modelo). */
+.key-campo {
+ display: block;
+ margin-top: 11px;
+}
+.key-campo > span {
+ display: block;
+ font-size: 10px;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ color: var(--dim);
+ margin-bottom: 4px;
+}
+.key-campo .key-input {
+ width: 100%;
+}
+.key-select {
+ width: 100%;
+ padding: 8px 10px;
+ border-radius: 4px;
+ border: 1px solid var(--linea);
+ background: var(--fondo2);
+ color: var(--texto);
+ font-family: inherit;
+ font-size: 12px;
+}
+.key-select:focus {
+ outline: none;
+ border-color: var(--lavanda);
+}
+
+.key-panel .key-guardar {
+ width: 100%;
+ margin-top: 13px;
+}
+
+.key-puesta-info {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ min-width: 0;
+}
+.key-puesta-modelo {
+ font-size: 10px;
+ color: var(--dim);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* "interrumpir" junto a un agente ocupado: se le PUEDE hablar aunque esté
+ trabajando — el mensaje lo detiene y atiende lo nuevo. Sin esta marca parece
+ que hay que esperar a que termine. */
+.mention-tag {
+ font-size: 8px;
+ font-weight: 700;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ color: var(--ambar);
+ border: 1px solid var(--ambar);
+ border-radius: 2px;
+ padding: 1px 5px;
+ margin-left: auto;
+ flex-shrink: 0;
+}
+
+/* El rastro de tools: una línea por default, expandible con click. */
+.tool-line-click {
+ cursor: pointer;
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+}
+.tool-line-click:hover {
+ color: var(--texto);
+}
+.tool-mas {
+ font-size: 9px;
+ color: var(--dim);
+ background: rgba(255, 255, 255, 0.06);
+ border-radius: 8px;
+ padding: 1px 6px;
+ flex-shrink: 0;
+}
+.tool-trace {
+ cursor: pointer;
+ border-left: 1px solid var(--linea);
+ padding-left: 8px;
+ margin: 2px 0;
+}
+.tool-cerrar {
+ font-size: 9px;
+ color: var(--dim);
+ margin-top: 3px;
+}
+.tool-cerrar:hover {
+ color: var(--texto);
+}
/* ── Variables del proyecto (.env) ────────────────────────────────────────
Mismo patrón que el panel de la key: un botón en la barra que abre una caja
@@ -1931,3 +1939,187 @@ body::after {
opacity: 0.5;
cursor: default;
}
+
+/* ── En el celular ────────────────────────────────────────────────────────
+ La Sala nació para pantalla ancha: el chat a la izquierda y el preview a la
+ derecha, los dos a la vez. En un teléfono eso no cabe, así que el chat pasa a
+ ser una pestaña más junto a "La app" y "El back", y se ve una a la vez.
+
+ Todo lo de aquí vive dentro de la media query: en escritorio no cambia nada. */
+
+/* La pestaña del chat solo tiene sentido donde el chat no cabe al lado. */
+.tab-chat {
+ display: none;
+}
+
+@media (max-width: 820px) {
+ .tab-chat {
+ display: block;
+ }
+
+ /* De dos columnas a una sola: lo que se ve lo decide la pestaña. */
+ .sala {
+ flex-direction: column;
+ }
+ /**
+ * El chat se queda SIEMPRE en pantalla, pero de dos tamaños: entero cuando su
+ * pestaña está activa, y solo su cabecera cuando no.
+ *
+ * Entero no se puede esconder del todo porque la cabecera vive dentro (el
+ * nombre de la sala, el menú, el botón de crear), y esconderlo se los llevaba
+ * a todos. Y nunca se desmonta: perdería el scroll y lo que llevaras escrito.
+ */
+ .chat {
+ width: 100%;
+ border-right: none;
+ }
+ .sala.ver-chat .chat {
+ flex: 1;
+ min-height: 0;
+ }
+ /**
+ * Con el chat en pantalla, el preview no ocupa espacio. Las pestañas se
+ * salvan porque están fijas abajo: viven dentro del escenario, y esconderlo
+ * entero se las llevaba, dejándote en el chat sin manera de volver.
+ */
+ .sala.ver-chat .escenario > *:not(.tabs) {
+ display: none;
+ }
+ .sala.ver-chat .escenario {
+ flex: 0 0 auto;
+ }
+
+ .escenario {
+ min-height: 0;
+ }
+ /**
+ * Las pestañas se anclan abajo, como la barra de navegación de cualquier app
+ * de teléfono.
+ *
+ * En el orden del documento viven dentro del escenario, o sea después del
+ * chat, así que en una columna caerían en medio de la pantalla o al final del
+ * scroll. Fijas abajo están siempre donde el pulgar las busca, y no hay que
+ * moverlas en el JSX (en escritorio están bien donde están).
+ */
+ .tabs {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 50;
+ padding: 0;
+ gap: 0;
+ background: var(--fondo2);
+ border-top: 1px solid var(--linea);
+ }
+ .tab {
+ flex: 1;
+ padding: 14px 8px;
+ letter-spacing: 1.5px;
+ border-radius: 0;
+ }
+ /**
+ * El hueco para la barra se hace ACORTANDO la sala, no con padding.
+ *
+ * Con padding la sala seguía midiendo una pantalla completa y crecía hacia
+ * abajo; el `overflow: hidden` del body recortaba ese sobrante, y lo que se
+ * recortaba era justo la barra de pestañas: quedabas en el chat sin manera de
+ * volver a la app.
+ */
+ .sala {
+ height: calc(100vh - 46px);
+ height: calc(100dvh - 46px);
+ }
+
+ /**
+ * La cabecera vive dentro del chat, así que al esconder el chat se iba con él
+ * y desaparecían el nombre de la sala, el menú y el botón de crear.
+ *
+ * En vez de moverla en el JSX (que cambiaría el escritorio, donde está bien),
+ * el chat se muestra SIEMPRE pero encogido a solo su cabecera cuando la
+ * pestaña activa es otra. Así el nombre no se va y los mensajes no estorban.
+ */
+ .sala:not(.ver-chat) .chat {
+ flex: 0 0 auto;
+ }
+ .sala:not(.ver-chat) .chat > *:not(.sala-cab) {
+ display: none;
+ }
+ .sala-cab {
+ padding: 12px 14px 10px;
+ }
+
+ /**
+ * La barra: en una pantalla chica no caben siete controles en fila.
+ *
+ * Se dejan bajar de renglón, y además se les quita el ancho mínimo que traen
+ * de fábrica: sin eso los botones no se encogen, empujan la fila, y la barra
+ * entera se sale de la pantalla por la izquierda. Se veía con "SELECCIONAR
+ * ELEMENTO" y "PUBLICAR" cortados a la mitad.
+ */
+ .sala,
+ .escenario,
+ .barra-sup,
+ .presencia {
+ max-width: 100%;
+ min-width: 0;
+ }
+ .barra-sup {
+ padding: 10px 12px 0;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+ .presencia {
+ /* Sin el margin-left: auto, que en una fila que ya envuelve solo empuja
+ cosas fuera de la pantalla. */
+ margin-left: 0;
+ flex-wrap: wrap;
+ gap: 6px;
+ }
+ /* Los botones de la barra, más chicos y sin obligar a un ancho mínimo. */
+ .invitar,
+ .inspect-btn {
+ font-size: 9px;
+ letter-spacing: 1px;
+ padding: 7px 9px;
+ min-width: 0;
+ }
+ /* Colapsar el chat es un control de escritorio: en móvil las pestañas ya
+ hacen ese trabajo. */
+ .colapsar-btn {
+ display: none;
+ }
+ /* Y si alguien colapsó en la compu y luego achicó la ventana, ese estado no
+ debe dejar el chat en cero: aquí manda la pestaña. */
+ .sala.chat-colapsado .chat {
+ width: 100%;
+ opacity: 1;
+ overflow: visible;
+ pointer-events: auto;
+ }
+
+ /* Los paneles flotantes medían más que la pantalla: se ajustan a lo que hay
+ y se anclan a la derecha para no salirse por el otro lado. */
+ .env-caja,
+ .publicar-caja,
+ .key-panel,
+ .menu-panel {
+ width: auto;
+ min-width: 0;
+ max-width: calc(100vw - 24px);
+ right: 0;
+ left: auto;
+ }
+
+ /* El preview muestra la app de la sala, que puede ser más ancha que un
+ teléfono: se contiene aquí para que no empuje la Sala entera de lado. */
+ .lienzo {
+ margin: 0 12px 12px;
+ max-width: 100%;
+ min-width: 0;
+ overflow: hidden;
+ }
+ .preview-frame {
+ max-width: 100%;
+ }
+}