/* ==========================================================================
   LeadSync · Consola de Operador — hoja de estilos
   Temas claro/oscuro por variables CSS. El tema se fija en <html data-theme>.
   ========================================================================== */

/* --- Tema oscuro (por defecto) --- */
:root,
:root[data-theme="dark"] {
  --bg: #0b141a;
  --bg-panel: #111b21;
  --bg-elev: #202c33;
  --bg-hover: #2a3942;
  --border: #2a3942;
  --text: #e9edef;
  --text-dim: #8696a0;
  --accent: #00a884;
  --accent-dim: #025c4c;
  --accent-strong: #06c294;
  --on-accent: #04140f;
  --client: #202c33;
  --bot: #144d38;
  --operator: #005c4b;
  --system: #1d2a30;
  --danger: #f15c6d;
  /* Borde un punto más marcado, para que un contorno siga leyéndose cuando el fondo
     de debajo cambia (p. ej. una fila con el ratón encima). */
  --border-strong: #3f4d55;
  --warn: #e2b53e;
  --warn-bg: #3a2e12;
  /* Resaltado de coincidencias de la lupa. Va aparte de --warn porque en claro este
     necesita ser un amarillo CLARO de rotulador y --warn es un ocre oscuro (item 35). */
  --hit-bg: #e2b53e;
  --hit-fg: #17212b;
  --info: #4a9eff;
  --info-bg: rgba(58, 142, 230, .14);
  --info-border: #3a8ee6;
  --chat-bg: #0b141a;
  --shadow: rgba(0, 0, 0, .4);
  --author-client: #7fb3ff;
  --author-bot: #7bf2d3;
  --badge-human-bg: #5a3a12;
  /* Colores por TIPO de notificación del bot (item 29). Se eligen bien separados entre sí
     —no en degradado del mismo tono— porque su trabajo es distinguirse de un vistazo. */
  --n-transferencia: #ffa94d;
  --n-espera: #e2b53e;
  --n-presupuesto: #06c294;
  --n-cliente: #7fb3ff;
  --n-vinculo: #c99cf5;
  --n-memoria: #6ed0d0;
  /* Píldora de la hora de un sticker (item 32): el sticker va sin burbuja, así que
     la hora necesita su propio fondo para leerse sobre el chat. */
  --sticker-meta-bg: rgba(17, 27, 33, .75);
  --debug-w: 380px; /* ancho del panel de depuración (ajustable por arrastre) */
  --sidebar-w: 340px; /* ancho de la lista de chats (ajustable por arrastre, item 30) */
  --theme-t: .25s;  /* duración ÚNICA de la transición claro/oscuro (item 13) */
  /* Scrollbar neutro e IGUAL en ambos temas: así no "salta" al alternar tema
     (los scrollbars no admiten transition; la solución es que no cambien). */
  --scrollbar-thumb: rgba(128, 138, 148, .45);
  --scrollbar-thumb-hover: rgba(128, 138, 148, .72);
  /* La corona del admin tiene COLOR PROPIO y no reusa el ámbar de advertencia: una corona
     no avisa de nada, y compartir variable con los estados haría que cambiar el color de
     los avisos cambiara también la corona. En claro, además, ese ámbar sale casi marrón
     sobre fondo blanco y no se lee como dorado. */
  --corona: #f5c542;
  --notif: #e5484d;
}

/* --- Tema claro --- */
:root[data-theme="light"] {
  /* JERARQUÍA DE SUPERFICIES (convención de industria; en oscuro ya se cumplía y en
     claro NO): el FONDO de la página es el más oscuro de los tres y lo que se apoya
     encima es más claro. Antes --bg y --bg-elev eran EL MISMO gris (#f0f2f5), así que
     toda tarjeta, entrada de texto y panel desaparecía contra la página en tema claro.
       · --bg        el suelo: gris. Nunca lleva contenido directamente.
       · --bg-panel  las columnas grandes (sidebar, chat): blanco.
       · --bg-elev   lo que FLOTA sobre el suelo (tarjetas, inputs, encabezados de
                     tabla): blanco, para despegarse del gris de abajo.
       · --bg-hover  el gris de interacción, más marcado que el suelo para que se note. */
  --bg: #eef1f4;
  --bg-panel: #ffffff;
  --bg-elev: #ffffff;
  --bg-hover: #e6eaee;
  --border: #dfe3e7;
  --corona: #d99e00;
  --notif: #d32f38;
  --text: #111b21;
  --text-dim: #667781;
  --accent: #00a884;
  --accent-dim: #d1f4e0;
  --accent-strong: #017561;
  --on-accent: #ffffff;
  --client: #ffffff;
  --bot: #eaf7f0;
  --operator: #d9fdd3;
  --system: #fff3d6;
  --danger: #d3364a;
  --border-strong: #c3cad1;
  --warn: #9a7a12;
  --warn-bg: #fdf6dd;
  --hit-bg: #ffe680;
  --hit-fg: #2b2200;
  --info: #1667c7;
  --info-bg: rgba(22, 103, 199, .10);
  --info-border: #2f7fd6;
  --chat-bg: #efeae2;
  --shadow: rgba(0, 0, 0, .15);
  --author-client: #2b6cb0;
  --author-bot: #087f5b;
  --badge-human-bg: #fbe7c6;
  /* Los mismos tipos, oscurecidos: sobre fondo claro los tonos del tema oscuro no tienen
     contraste suficiente para leerse. */
  --n-transferencia: #c9600a;
  --n-espera: #9a7a12;
  --n-presupuesto: #0a7a5e;
  --n-cliente: #1667c7;
  --n-vinculo: #7a3fb5;
  --n-memoria: #14807f;
  --sticker-meta-bg: rgba(255, 255, 255, .9);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body { height: 100%; }

body {
  /* "Noto Color Emoji" va de último: gracias al unicode-range de su @font-face solo
     aplica a codepoints de emoji que las fuentes previas no tienen (evita las
     "cajitas" en Windows sin afectar dígitos/texto, que resuelven las fuentes de
     antes). Item 1 (heredado). */
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Noto Color Emoji";
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
}

/* Transición uniforme de colores al alternar el tema. La clase .theme-anim se
   añade a <html> solo durante el cambio (ver toggleTheme en ui.js) para que TODA
   la página cruce a la vez, sin discrepancias de duración entre elementos. */
html.theme-anim, html.theme-anim *, html.theme-anim *::before, html.theme-anim *::after {
  transition:
    background-color var(--theme-t) ease, color var(--theme-t) ease,
    border-color var(--theme-t) ease, outline-color var(--theme-t) ease,
    text-decoration-color var(--theme-t) ease, fill var(--theme-t) ease,
    stroke var(--theme-t) ease, box-shadow var(--theme-t) ease,
    accent-color var(--theme-t) ease !important;
}

.hidden { display: none !important; }
/* item 49: acción que el rol actual no puede ejecutar. Clase aparte de .hidden porque a
   esa la gobiernan otras reglas (la papelera se oculta en "Mensajes guardados") y, al
   compartirla, volver a mostrar por un motivo revivía lo oculto por el otro. */
.role-hidden { display: none !important; }

/* Icono SVG genérico. flex:0 0 auto evita que se encoja dentro de contenedores
   flex (los iconos nunca deben deformarse). */
.ic { width: 1em; height: 1em; display: inline-block; vertical-align: middle; flex: 0 0 auto; }

/* Scrollbars con color NEUTRO (igual en claro/oscuro) para que no salten al
   alternar el tema mientras el resto hace crossfade. */
* { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); background-clip: padding-box; }

/* Botón-icono (tema, acciones sin texto). */
.icon-btn {
  background: transparent;
  border: none;
  color: var(--text-dim);
  width: 34px;
  height: 34px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.icon-btn:hover { background: var(--bg-hover); color: var(--text); }
.icon-btn .ic { width: 20px; height: 20px; }

/* --- Overlay de autenticación --- */
/* Pantalla partida: a la izquierda la consola dibujada, a la derecha el formulario. La
   vitrina es lo primero que cede: por debajo de 1000px desaparece y el formulario ocupa
   la pantalla, que es como se entra desde un móvil. */
.overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: stretch;
  background: var(--bg);
  z-index: 100;
}
.auth-card {
  flex: 0 0 420px;
  background: var(--bg-panel);
  padding: 0 56px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 12px;
  box-shadow: -1px 0 0 var(--border);
}
.auth-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 22px; }
.auth-brand span { font-weight: 700; font-size: 18px; letter-spacing: -.01em; }
.auth-card h1 { font-size: 24px; font-weight: 650; letter-spacing: -.01em; margin-bottom: 2px; }
.auth-card p { color: var(--text-dim); font-size: 13.5px; margin-bottom: 12px; }
.auth-field { display: flex; flex-direction: column; gap: 6px; }
.auth-field > span {
  font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-dim);
}
.auth-card .auth-logo { width: 34px; height: 34px; margin: 0; }

/* --- Vitrina: la consola dibujada --- */
.auth-showcase {
  flex: 1; min-width: 0; position: relative; overflow: hidden;
  display: flex; align-items: center; padding-left: 62px;
  /* Dos focos del verde de marca y una trama de puntos: da profundidad sin competir con
     la maqueta, que es lo que hay que mirar. */
  background:
    radial-gradient(120% 90% at 8% 0%, color-mix(in srgb, var(--accent) 20%, transparent), transparent 58%),
    radial-gradient(90% 80% at 100% 100%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 55%),
    var(--bg);
}
.auth-showcase::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .5;
  background-image: radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--text-dim) 22%, transparent) 1px, transparent 0);
  background-size: 22px 22px;
}
/* La maqueta se dibuja a tamaño real (1120x700) y se ENCOGE con transform: así hereda las
   medidas exactas de la consola —altos de cabecera, tipografías, paddings— en vez de una
   versión aproximada a mano. La caja de fuera reserva el tamaño ya escalado. */
.auth-shot {
  position: relative; z-index: 1;
  width: calc(1120px * var(--shot-s, .62));
  height: calc(700px * var(--shot-s, .62));
  border-radius: 12px; overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: -20px 34px 70px rgba(0, 0, 0, .42);
  transform: perspective(1600px) rotateY(-13deg) rotateX(3deg) rotate(-1deg);
}
.shot-app {
  width: 1120px; height: 700px;
  transform: scale(var(--shot-s, .62)); transform-origin: top left;
  display: flex; background: var(--bg); cursor: default;
}
.shot-app .shot-sidebar { width: 320px; min-width: 0; }
/* Nada de la vitrina reacciona al ratón: es un dibujo, no una consola. */
.shot-app * { pointer-events: none; }
.shot-app .session-item:hover { background: none; }
.shot-app .session-item.active:hover { background: var(--bg-elev); }
.shot-app .messages { padding: 18px 24px; }
/* El buscador de la vitrina es un texto, no un <input>: un campo de verdad se podría
   enfocar con el tabulador desde el formulario de al lado. */
.shot-input {
  flex: 1; min-width: 0;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 12px; color: var(--text-dim); font-size: 13.5px;
}
.auth-pitch { position: absolute; left: 62px; bottom: 44px; z-index: 2; max-width: 420px; }
.auth-pitch h2 { font-size: 21px; font-weight: 650; letter-spacing: -.01em; margin-bottom: 6px; }
.auth-pitch p { color: var(--text-dim); font-size: 13.5px; }

/* El botón de tema flota en la esquina de la pantalla, no dentro del formulario: es un
   ajuste de la aplicación, no un campo del acceso. */
.login-theme { position: absolute; top: 16px; right: 18px; z-index: 3; }

/* Pantallas medianas: la maqueta encoge antes que desaparecer. */
@media (max-width: 1400px) { .auth-showcase { --shot-s: .52; padding-left: 44px; } }
@media (max-width: 1180px) { .auth-showcase { --shot-s: .44; padding-left: 32px; } }

/* Móvil: en vez de perder el producto, se APILA. Una franja arriba con la consola y el
   formulario debajo, montado sobre ella con las esquinas redondeadas — la franja se ve
   recortada a propósito, como una foto: a este tamaño no se trata de leerla, sino de que
   se vea que detrás hay una herramienta. */
@media (max-width: 1000px) {
  .overlay { flex-direction: column; }
  .auth-showcase {
    flex: 0 0 32vh; min-height: 190px; max-height: 300px;
    padding: 0; align-items: center; justify-content: center;
    --shot-s: .38;
  }
  /* Casi de frente y sin sombra lateral: la inclinación fuerte de escritorio, recortada
     por los dos lados, se lee como un error de maquetación. */
  .auth-shot {
    transform: perspective(1400px) rotateX(4deg) rotate(-1deg);
    box-shadow: 0 18px 40px rgba(0, 0, 0, .35);
  }
  .auth-pitch { display: none; } /* el formulario ya dice lo que hay que hacer aquí */
  .auth-card {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: 30px 26px 26px;
    border-radius: 22px 22px 0 0;
    margin-top: -22px; /* monta sobre la franja, que es lo que une las dos piezas */
    box-shadow: 0 -10px 30px var(--shadow);
    justify-content: flex-start; align-items: center;
  }
  .auth-card > * { width: 100%; max-width: 360px; }
  .auth-brand { justify-content: center; margin-bottom: 16px; }
  .auth-card h1, .auth-card p { text-align: center; }
}
/* Pantalla muy baja (un móvil en horizontal): la franja estorba más de lo que aporta. */
@media (max-height: 480px) and (max-width: 1000px) {
  .auth-showcase { display: none; }
  .auth-card { margin-top: 0; border-radius: 0; box-shadow: none; }
}
.auth-card input, .composer input {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: border-color .15s ease;
}
.auth-card input:focus { border-color: var(--accent); }
.auth-card button {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 8px;
  padding: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .15s ease;
}
.auth-card button:hover { background: var(--accent-strong); }
.auth-error { color: var(--danger); font-size: 13px; min-height: 16px; }
/* Botón-enlace (recuperación de acceso). Se escopa a .auth-card para ganar en
   especificidad a `.auth-card button` (que lo pintaría como botón sólido verde).
   Sin fondo, fuente verde y un verde algo más oscuro al pasar el cursor. */
/* Botón-enlace GLOBAL (texto en acento, sin caja): lo usan el login ("¿Perdiste el
   acceso?") y la bandeja de notificaciones ("Marcar todas leídas"). Vive fuera de
   .auth-card a propósito: scopearlo dejaba a los demás usos con el botón blanco del
   agente de usuario. */
.link-btn {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 6px;
}
.link-btn:hover { background: var(--accent-dim); }
.auth-card .link-btn {
  /* background/color EXPLÍCITOS aquí (0,2,0) para ganarle a `.auth-card button` (0,1,1),
     que si no pintaría este enlace como botón verde sólido. El `.link-btn` global (0,1,0)
     no basta contra `.auth-card button`. */
  background: none;
  color: var(--accent);
  padding: 2px;
  font-weight: 600;
  text-decoration: none;
  transition: color .15s ease, filter .15s ease;
}
.auth-card .link-btn:hover { background: none; filter: brightness(.82); text-decoration: underline; }
.auth-logo { width: 64px; height: 64px; object-fit: contain; margin: 0 auto 2px; }

/* Marca (logo + nombre) en la cabecera del sidebar. */
/* La marca sale UNA sola vez. En escritorio la pone el rail (R5), así que la de la
   cabecera de la lista se esconde: dos logos a 60px uno del otro no es "más marca", es
   ruido. En móvil el rail no existe como columna y esta vuelve a ser la única. */
.brand { display: none; align-items: center; gap: 8px; min-width: 0; overflow: hidden; }
.brand-logo { width: 26px; height: 26px; object-fit: contain; flex: 0 0 auto; }
/* En la VITRINA del login sí se pinta: ese dibujo no tiene rail, así que la marca de la
   cabecera es la única que hay. (En la consola de verdad, en móvil, ese sitio lo ocupa el
   botón de menú, que ES el logo — ver .marca-menu.)
   Solo el isotipo: con el buscador al lado, el nombre se cortaba en "Lea…", y el rótulo
   completo ya está en el formulario de al lado. */
.shot-app .shot-brand { display: flex; }
.shot-app .shot-brand .logo { display: none; }

/* --- Layout principal --- */
.app { display: flex; height: 100vh; }

/* Divisores arrastrables para redimensionar los paneles. */
.resizer {
  flex: 0 0 auto;
  width: 5px;
  cursor: col-resize;
  background: transparent;
  transition: background-color .12s ease;
  z-index: 8;
  /* SIN ESTO NO HAY ARRASTRE EN TÁCTIL: el navegador se queda el gesto para hacer scroll y
     el `pointermove` deja de llegar a la primera. Es la otra mitad del cambio a eventos de
     puntero de attachResizer. */
  touch-action: none;
}
.resizer:hover, body.resizing .resizer { background: var(--accent); }
.resizer-abs {
  position: absolute;
  top: 60px; bottom: var(--dock-h, 68px); /* acompaña al panel sobre el dock */
  right: var(--debug-w);
  width: 8px;
  margin-right: -4px;
}
body.resizing { cursor: col-resize; user-select: none; }

.sidebar {
  width: var(--sidebar-w);
  min-width: 240px;
  background: var(--bg-panel);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
}
.sidebar-header {
  height: 60px;
  /* 12px a los lados: con el buscador dentro, el sangrado de 18px de la izquierda solo
     servía cuando ahí vivía la marca. */
  padding: 0 12px;
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: space-between;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  container-type: inline-size; /* permite ocultar el wordmark según su propio ancho */
}
/* Las acciones (punto de conexión + botones) nunca se encogen; la marca cede. */
/* `margin-left: auto` y no confiar en el space-between del padre: al esconder la marca en
   escritorio (R5) la cabecera se quedó con un solo hijo, y con space-between un solo hijo
   se va al PRINCIPIO. La campanita apareció a la izquierda sin que nadie la moviera. */
.sidebar-header-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; margin-left: auto; }
.logo {
  font-weight: 700; color: var(--accent); font-size: 18px; letter-spacing: .3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* Al angostar el panel de chats, si "LeadSync" no cabe junto al punto y los
   botones, se oculta por completo (queda solo el logo) en vez de superponerse. */
@container (max-width: 252px) {
  .sidebar-header .logo { display: none; }
}
.conn-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--text-dim); transition: background-color .2s ease; cursor: pointer; }
.conn-dot.ok { background: var(--accent); }
.conn-dot.warn { background: var(--warn); }
.conn-dot.err { background: var(--danger); }
.conn-dot.unknown { background: var(--text-dim); }
/* Pulso sutil cuando algo no está del todo bien, para llamar la atención. */
.conn-dot.warn, .conn-dot.err { box-shadow: 0 0 0 0 currentColor; animation: connPulse 1.8s ease-out infinite; }
.conn-dot.warn { color: var(--warn); }
.conn-dot.err { color: var(--danger); }
@keyframes connPulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 60%, transparent); }
  70% { box-shadow: 0 0 0 6px color-mix(in srgb, currentColor 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 0%, transparent); }
}

/* Popover de estado de conexión (al pasar el mouse por el punto). */
.conn-popover {
  position: fixed;
  z-index: 260;
  min-width: 220px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 10px 30px var(--shadow);
  padding: 10px 12px;
  animation: menuIn .12s ease;
  pointer-events: none; /* solo informativo; el clic en el punto abre la modal */
}
.conn-pop-title { font-size: 12px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 8px; }
.conn-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.conn-row-label { flex: 1; font-size: 13px; color: var(--text); }
.conn-row-state { font-size: 12px; color: var(--text-dim); }
.conn-pop-foot { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); font-size: 11px; color: var(--text-dim); text-align: center; }
.conn-status-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; background: var(--text-dim); }
.conn-status-dot.ok { background: var(--accent); }
.conn-status-dot.warn { background: var(--warn); }
.conn-status-dot.err { background: var(--danger); }
.conn-status-dot.unknown { background: var(--text-dim); }
/* Desactivado: hueco, no relleno. Apagado y roto tienen que distinguirse de un vistazo,
   y un círculo vacío se lee como "aquí no hay nada encendido" sin necesidad de leyenda. */
.conn-status-dot.off { background: transparent; box-shadow: inset 0 0 0 1.5px var(--text-dim); }
.conn-row-sub { padding-left: 0; }
.conn-row-sub .conn-row-label { font-size: 12px; color: var(--text-dim); }

/* --- Modal "Estado del sistema": el árbol de capacidades (item 97) --- */
.conn-crit { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--danger); border: 1px solid var(--danger); border-radius: 6px; padding: 1px 5px; }

/* El veredicto va arriba y separado: responde "¿qué hago?", que es con lo que se abre
   el panel. El árbol de abajo responde "¿cómo está todo?", que es otra pregunta. */
.caps-verdict { border-radius: 10px; padding: 10px 12px; font-size: 13px; line-height: 1.5; border: 1px solid var(--border); background: var(--bg-elev); }
.caps-verdict.ok { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
.caps-verdict.err { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.caps-verdict.unknown { color: var(--text-dim); }
.caps-verdict-title { font-weight: 700; color: var(--danger); margin-bottom: 6px; }
.caps-causes { margin: 0; padding-left: 18px; }
.caps-causes li { margin-bottom: 6px; }
.caps-causes li:last-child { margin-bottom: 0; }
.caps-why { display: block; color: var(--text-dim); font-size: 12.5px; margin-top: 2px; }

.caps-tree { margin-top: 14px; }
.caps-group { border-bottom: 1px solid var(--border); }
.caps-group:last-child { border-bottom: none; }
.caps-group > summary { list-style: none; cursor: pointer; border-radius: 8px; transition: background-color .12s ease; }
.caps-group > summary::-webkit-details-marker { display: none; }
.caps-group > summary:hover { background: var(--bg-elev); }
.caps-row { display: flex; align-items: center; gap: 8px; padding: 9px 6px; }
.caps-child { padding-left: 25px; }
.caps-child .caps-name { font-weight: 400; color: var(--text-dim); }
.caps-name { font-weight: 600; font-size: 13.5px; }
.caps-state { margin-left: auto; font-size: 12px; text-align: right; color: var(--text-dim); }
.caps-state.ok { color: var(--accent); }
.caps-state.warn { color: var(--warn); }
.caps-state.err { color: var(--danger); }
.caps-state.off { color: var(--text-dim); font-style: italic; }
/* La flechita gira al desplegar; sin ella el summary no se lee como plegable. */
.caps-group > summary .caps-row::after {
  content: ""; width: 6px; height: 6px; flex: 0 0 auto; margin-left: 2px;
  border-right: 1.5px solid var(--text-dim); border-bottom: 1.5px solid var(--text-dim);
  transform: rotate(45deg) translate(-2px, -2px); transition: transform .18s ease;
}
.caps-group[open] > summary .caps-row::after { transform: rotate(225deg) translate(-1px, -1px); }
@media (prefers-reduced-motion: reduce) {
  .caps-group > summary .caps-row::after, .caps-group > summary { transition: none; }
}

.session-list { overflow-y: auto; flex: 1; }
.session-item {
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 11px;
}
.session-item:hover { background: var(--bg-hover); }
/* EL CHAT ABIERTO SE TIENE QUE VER (me lo señalaste): con --bg-elev sobre el panel la
   diferencia era de dos puntos de luminosidad y en una lista larga no se encontraba. Un
   tinte del color de marca dice "estás aquí" sin competir con la selección múltiple, que
   lleva además la barra lateral. */
.session-item.active {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-elev));
  box-shadow: inset -3px 0 0 var(--accent);
}
/* El cuerpo es lo que antes era la fila entera: dos líneas apiladas. Ahora convive con el
   avatar, que ocupa el alto de las dos. */
.session-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.session-item-top { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.session-name-wrap { display: flex; align-items: center; gap: 6px; min-width: 0; }
.session-phone { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.session-time { color: var(--text-dim); font-size: 11px; flex: 0 0 auto; }

/* Badge que marca una conversación en manos del bot. */
.chat-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .3px;
  padding: 1px 6px;
  border-radius: 8px;
  background: var(--accent-dim);
  color: var(--author-bot);
  flex: 0 0 auto;
}
.chat-badge .ic { width: 11px; height: 11px; }
/* item 5, estado B: bot activo pero no disponible (sin conexión/sin créditos) → amarillo. */
.chat-badge.unavailable { background: var(--warn-bg); color: var(--warn); }
/* item 5: transferida por el bot y nadie la ha atendido aún (esperando_humano). */
.chat-badge.waiting { background: var(--warn-bg); color: var(--warn); }
/* item 132: atendida y cerrada. En gris a propósito: es lo ÚNICO de la bandeja que no pide
   hacer nada, y competir en color con "Esperando" —que sí lo pide— sería mentir al ojo. */
.chat-badge.cerrada { background: var(--bg-hover); color: var(--text-dim); }

/* Item fijo "Mensajes guardados". */
/* El marcador pasó al círculo del avatar (.avatar-saved); aquí solo queda el nombre. */
.saved-item .session-phone { color: var(--accent); }
/* item 51: el previo y el tiempo restante de la ventana comparten la fila de abajo, así
   el contador cae justo debajo de la hora. min-width:0 es obligatorio: sin él el previo
   se niega a encoger y empuja el contador fuera de la fila. */
/* --- Avatar con iniciales de la lista de chats --- */
/* El color lo elige ui.js a partir del teléfono (siempre el mismo para la misma persona).
   El texto va SIEMPRE oscuro: los ocho tonos son claros a propósito, así el contraste no
   depende del tema y el círculo se ve igual de nítido en claro y en oscuro. */
.avatar {
  flex: 0 0 auto;
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 14px; font-weight: 700; letter-spacing: .01em;
  color: #06231b;
  user-select: none;
}
.avatar .ic { width: 19px; height: 19px; }
.av-1 { background: #7fb3ff; }
.av-2 { background: #6ed0d0; }
.av-3 { background: #c99cf5; }
.av-4 { background: #ffa94d; }
.av-5 { background: #8fd694; }
.av-6 { background: #f5a3c7; }
.av-7 { background: #e2c65e; }
.av-8 { background: #9fb0f0; }
/* "Mensajes guardados" no es una persona: no lleva color de contacto ni iniciales, solo
   el marcador que ya lo identificaba, dentro del mismo círculo para que la columna de
   avatares no se rompa en la primera fila. */
.avatar-saved { background: var(--accent-dim); color: var(--accent); }
/* Tres tamaños para los sitios donde ya había un círculo con el icono de persona: la ficha
   del contacto, la ficha de Odoo y la cabecera de un destacado. Son el MISMO componente a
   propósito — la misma persona sale del mismo color en los cuatro sitios, y eso es lo que
   hace que sirva para reconocerla. */
.avatar-lg { width: 96px; height: 96px; font-size: 34px; }
.avatar-lg .ic { width: 46px; height: 46px; }
.avatar-md { width: 40px; height: 40px; }
.avatar-md .ic { width: 21px; height: 21px; }
.avatar-sm { width: 26px; height: 26px; font-size: 10.5px; }
.avatar-sm .ic { width: 14px; height: 14px; }

.session-bottom { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.session-preview { flex: 1; min-width: 0; color: var(--text-dim); font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.session-preview .role-tag { color: var(--accent); }
/* item 63: "Fulano está escribiendo…" entre operadores. Va en cursiva y en el color de
   acento porque no es un mensaje: es un estado que dura segundos, y tiene que distinguirse
   de un vistazo del último mensaje al que sustituye. */
.typing-line { color: var(--accent); font-style: italic; }
.session-preview .draft-tag { color: var(--danger); font-weight: 600; }
.session-window {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 3px;
  color: var(--text-dim); font-size: 11px; white-space: nowrap;
}
.session-window .ic { width: 12px; height: 12px; }
.session-window.low { color: var(--warn); } /* queda menos de 1 h, como el punto y la barra */

/* --- Buscador de la lista de chats --- */
/* EL BUSCADOR VIVE EN LA CABECERA, en el hueco que dejó la marca al mudarse al rail
   (petición tuya, 2026-08-12): tenía una fila propia para un solo campo, y en la bandeja
   —que es la pantalla donde se mira todo el día— esa fila vale media conversación más.
   La lupa y la "X" van DENTRO del campo, en posición absoluta: fuera se comían 25px del
   ancho útil justo donde menos sobra. */
.sidebar-search {
  position: relative;
  display: flex; align-items: center;
  flex: 1 1 auto; min-width: 0;
}
.sidebar-search-ic {
  position: absolute; left: 10px;
  display: inline-flex; color: var(--text-dim); pointer-events: none;
}
.sidebar-search-ic .ic { width: 16px; height: 16px; }
.sidebar-search input {
  flex: 1; min-width: 0;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  /* Los laterales dejan sitio a la lupa y a la "X", que van encima del campo. */
  padding: 7px 30px 7px 32px;
  color: var(--text);
  font-size: 13.5px;
  outline: none;
  transition: border-color .15s ease;
}
.sidebar-search input:focus { border-color: var(--accent); }
.chat-search-clear {
  position: absolute; right: 4px;
  background: transparent; border: none; color: var(--text-dim);
  cursor: pointer; width: 24px; height: 24px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
}
.chat-search-clear:hover { color: var(--text); background: var(--bg-hover); }
.chat-search-clear .ic { width: 16px; height: 16px; }

/* --- Filtros por etiqueta (lista de chats, notificaciones…) ---
   ENVUELVEN en varias filas, nunca scroll horizontal: con scroll, cada clic devolvía la
   barra al principio y elegir varias etiquetas era incómodo. */
.tag-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* Modo AGRUPADO (item 137): el control segmentado arriba y, debajo, solo las etiquetas del
   grupo que se está mirando. Se apila, así que el contenedor cambia de eje. */
.tag-filters.con-grupos { flex-direction: column; align-items: stretch; gap: 8px; }
.tag-filters-head { display: flex; flex-direction: column; gap: 4px; }
/* El titulito dice de qué va la fila de abajo. Pequeño y en mayúsculas de caja pequeña:
   es una etiqueta de sección, no un encabezado que compita con el nombre de un chat. */
.tag-filters-titulo {
  display: inline-flex; align-items: center; gap: 5px; align-self: flex-start;
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: 10.5px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase;
  color: var(--text-dim);
}
.tag-filters-titulo:hover { color: var(--text); }
.tag-filters-titulo:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
/* El chevron GIRA en vez de cambiar de dibujo: el giro dice de dónde viene y a dónde va lo
   que se pliega; dos iconos distintos solo parpadearían. */
.tf-chev { display: inline-flex; transition: transform 180ms ease; }
.tf-chev svg { width: 11px; height: 11px; }
.tag-filters.plegado .tf-chev { transform: rotate(-90deg); }
/* Plegado y con algo marcado: el puntito. Los filtros siguen actuando aunque no se vean. */
.tf-punto { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
/* EL CUERPO SE ANIMA CON `grid-template-rows` (0fr → 1fr): es la forma de llegar hasta "lo
   que mida el contenido" sin medirlo con JS ni inventarse un max-height que recorte el día
   que haya un filtro más. El hijo necesita `min-height: 0` o no se deja encoger. */
.tag-filters-body {
  display: grid; grid-template-rows: 1fr;
  transition: grid-template-rows 200ms ease, opacity 160ms ease;
}
.tag-filters-body > * { min-height: 0; overflow: hidden; }
.tag-filters.plegado .tag-filters-body { grid-template-rows: 0fr; opacity: 0; }
.tag-filters.plegado { gap: 0; }
@media (prefers-reduced-motion: reduce) {
  .tag-filters-body, .tf-chev { transition: none; }
}
.tag-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* Cambiar de pestaña DESLIZA las etiquetas en el sentido del movimiento (idea tuya): entran
   por el lado del que vienen, como un carrusel. Animación y no transición: si no llega a
   correr, el elemento se queda en su estado natural en vez de invisible para siempre. */
.tag-chips.entra-der { animation: chips-der 180ms ease-out; }
.tag-chips.entra-izq { animation: chips-izq 180ms ease-out; }
@keyframes chips-der { from { opacity: 0; transform: translateX(18px); } }
@keyframes chips-izq { from { opacity: 0; transform: translateX(-18px); } }
@media (prefers-reduced-motion: reduce) {
  .tag-chips.entra-der, .tag-chips.entra-izq { animation: none; }
}
/* Una etiqueta que existirá y todavía no ("Cerrados", hasta el item 132): se ve, se explica
   en su title y no se puede marcar. */
.filter-tab:disabled { opacity: .45; cursor: not-allowed; }
.filter-tab:disabled:hover { background: transparent; }
/* El candado de la etiqueta fija: no es que falte una opción, es que esa es tu bandeja. */
.filter-candado { display: inline-flex; opacity: .75; }
.filter-candado svg { width: 11px; height: 11px; }
/* Separador entre grupos de filtros: una línea fina, no un hueco más grande. El hueco solo
   se lee cuando las dos cosas están en la misma fila, y estos chips envuelven. */
.filter-sep {
  flex: 0 0 auto; width: 1px; height: 18px; margin: 0 2px;
  background: var(--border);
}
/* El punto del departamento: mismo color que su franja en la lista y que su chip en el
   chat. Es lo único que hace falta para que no se confunda con un estado. */
.filter-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; margin-right: 1px; }
.chat-filters {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-panel);
  /* El deslizamiento al cambiar de pestaña entra desde fuera de la caja: sin esto, esos
     18px asomarían y darían scroll horizontal a la bandeja durante la animación. */
  overflow: hidden;
}
.filter-tab {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 12px; border-radius: 16px;
  border: 1px solid var(--border);
  background: transparent; color: var(--text-dim);
  font-size: 12.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
  transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.filter-tab:hover { background: var(--bg-hover); }
.filter-tab.active { background: var(--accent-dim); color: var(--author-bot); border-color: transparent; }
/* TERCER ESTADO: la etiqueta EXCLUYE (item T5). No basta con otro color —quien no distinga
   el rojo vería un chip cualquiera—: lleva además un "−" delante y el nombre tachado, que
   son las dos convenciones de "esto está quitado". El borde de peligro es el remate, no la
   información. */
.filter-tab.negado {
  background: var(--danger-dim, color-mix(in srgb, var(--danger) 14%, transparent));
  color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}
.filter-tab.negado .filter-label { text-decoration: line-through; text-decoration-thickness: 1px; }
.filter-menos { font-weight: 800; line-height: 1; margin-right: -1px; }
.filter-tab.negado .filter-count { background: var(--danger); color: #fff; }
.filter-count {
  font-size: 11px; font-weight: 700;
  background: var(--accent); color: var(--on-accent);
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
}
.filter-tab.active .filter-count { background: var(--accent-strong); }
/* El numerito ROJO es el de lo que está SIN ATENDER (mensajes sin leer). El verde queda para
   los que solo cuentan cuántas hay de algo. Son dos preguntas distintas y antes las
   respondía el mismo color. */
.filter-count.alerta, .filter-tab.active .filter-count.alerta { background: var(--notif); color: #fff; }

/* "Mostrando 20 de 50 conversaciones". Es una nota al pie, no un encabezado: pequeña, en
   texto atenuado y sin fondo propio, para que no compita con los filtros de arriba ni con
   la primera fila de la lista. */
.chat-count {
  padding: 5px 12px 6px;
  font-size: 11.5px; color: var(--text-dim);
  border-bottom: 1px solid var(--border); background: var(--bg-panel);
  font-variant-numeric: tabular-nums;
}
.chat-count.hidden { display: none; }

/* Estado vacío de la lista de chats. */
.session-empty { color: var(--text-dim); text-align: center; padding: 24px 16px; font-size: 13px; }

/* --- Chat --- */
.chat { flex: 1; display: flex; flex-direction: column; position: relative; overflow: hidden; }
.empty-state {
  flex: 1; display: flex; align-items: center; justify-content: center;
  color: var(--text-dim);
}
.conversation { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; transition: padding-right .2s ease; }
/* Panel de depuración fijado: se achica el CUERPO del chat (mensajes y sus
   barras) mediante el padding de la conversación, pero la cabecera y el composer
   se devuelven a lo ancho completo con un margen negativo (el panel absoluto va
   del borde inferior de la cabecera al superior del composer, así que no los
   pisa). El margen negativo se usa porque `.messages` es un flex-item que crece
   y no responde a margin-right; el padding del contenedor sí lo encoge. */
.conversation.debug-pinned { padding-right: var(--debug-w); }
.conversation.debug-pinned .chat-header,
.conversation.debug-pinned .composer { margin-right: calc(-1 * var(--debug-w)); }
.conversation.debug-pinned .scroll-bottom { right: calc(24px + var(--debug-w)); }
/* El botón flota SIEMPRE 16px sobre el dock (--dock-h ya incluye la barra de respuesta
   cuando está abierta y el alto real del composer): sin casos especiales. */
.chat-header, .composer { transition: margin-right .2s ease; }

/* --- Botón "ir al final" con contador de mensajes nuevos --- */
.scroll-bottom {
  position: absolute;
  right: 24px;
  bottom: calc(var(--dock-h, 68px) + 16px);
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 14px var(--shadow);
  z-index: 5;
  transition: opacity .18s ease, transform .18s ease, background-color .15s ease, color .15s ease;
}
.scroll-bottom:hover { background: var(--bg-hover); color: var(--text); }
.scroll-bottom.hidden { display: none !important; }
.scroll-bottom .ic { width: 22px; height: 22px; }
.scroll-bottom-badge {
  position: absolute;
  top: -7px;
  right: -3px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 700;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.scroll-bottom-badge.hidden { display: none !important; }

/* item R6: la cabecera va con MIN-HEIGHT, no con height. Desde que los badges tienen su
   propia fila (items 6 y 7) el bloque de títulos son TRES renglones —badges, nombre,
   teléfono— y en una caja de alto fijo se salían por arriba: por eso el nombre aparecía
   pisado por los chips (b_22.png). Con min-height la cabecera crece lo que haga falta y
   se queda en 60px cuando no hay badges, que es el caso normal. */
.chat-header {
  min-height: 60px;
  /* Escala del manual (4-8-12-16-24), no valores a ojo: 8px de aire vertical y 16px de
     margen lateral, que es el padding estándar de un item de lista y lo alinea con el
     resto de la consola. Antes era 6/18, que no está en la escala y no casaba con nada. */
  padding: 8px 16px;
  position: relative; /* la barra de búsqueda se posiciona contra ella */
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
/* El reparto del ancho, que era el otro fallo: sin estas dos declaraciones las acciones
   crecían con su contenido (el botón "Transferir" y el switch del bot) y estrujaban al
   bloque de títulos hasta 30px — el nombre del cliente desaparecía antes que un botón. */
/* EL NOMBRE DEL CLIENTE TIENE SUELO. Sin un min-width, el bloque de títulos encoge hasta
   cero y las acciones nunca llegan a "no caber": crecen a costa de él y la barra que mide
   el desbordamiento no se entera de nada (era el fallo de b_27). Con estos 140px, cuando
   el sitio se acaba quien deja de caber son las ACCIONES, y ahí sí actúa overflowbar.js.
   160 es lo que ocupan un nombre normal y su teléfono debajo sin cortarse a la mitad; por
   debajo de eso, la cabecera deja de decir con quién estás hablando, que es su único
   trabajo. Cuanto más alto es este suelo, antes empieza a llenarse el menú — que es
   exactamente el comportamiento que se quiere. */
.chat-header-info { display: flex; align-items: center; gap: 12px; min-width: 160px; flex: 1 1 auto; }
.chat-header-actions { flex: 0 1 auto; min-width: 0; overflow: hidden; }
/* AQUÍ ESTABA LA RAÍZ DE "TODO SE PEGA MÁS Y MÁS". Por defecto un hijo flex tiene
   `flex-shrink: 1`, así que cuando faltaba sitio los botones NO desbordaban: se
   COMPRIMÍAN. Y comprimidos siempre cabían, de modo que la comprobación de desbordamiento
   daba "sí cabe" y no se mandaba nada al menú — mientras la fila se veía cada vez más
   apretada. Con `flex: 0 0 auto` cada botón mide lo que mide, la barra desborda de verdad
   y ahí sí actúa overflowbar.js: el espaciado y el tamaño se quedan constantes y lo que
   cambia es CUÁNTOS botones se ven, que es como se hace un responsive que se sostiene. */
.chat-header-actions > * { flex: 0 0 auto; }
.chat-phone { font-weight: 600; font-size: 15px; }
.chat-titles { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.chat-name { font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-sub { font-size: 12px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Badge "BOT" de la cabecera (item 20): mismo look que en la lista, no se encoge. */
.header-bot-badge { flex: 0 0 auto; }
#delete-btn:hover { color: var(--danger); }
.state-badge {
  font-size: 11px;
  padding: 3px 9px;
  border-radius: 10px;
  text-transform: uppercase;
  letter-spacing: .4px;
  font-weight: 600;
}
.state-badge.bot_activo { background: var(--accent-dim); color: var(--author-bot); }
.state-badge.humano_activo { background: var(--badge-human-bg); color: var(--warn); }
.state-badge.esperando_humano { background: var(--warn-bg); color: var(--warn); }

/* item 5 + b_5: fila de configuración con control a la derecha (la fila del switch
   hereda el flex de .bot-toggle; esta clase lo aporta para selects/otros controles). */
.cfg-row { display: flex; align-items: center; }
.cfg-row > span:first-child { flex: 1; min-width: 0; }
/* --- Modal de Configuración con secciones (item 8) ------------------------- */
/* La modal crece a lo ancho: el panel izquierdo necesita sitio y así el derecho no
   estrangula los textos de ayuda. */
/* `.modal-card` fija width:420px y se declara MÁS ABAJO, así que con la misma
   especificidad ganaría por orden: hay que calificar con .modal-card (como .starred-modal). */
.modal-card.settings-modal { width: min(96vw, 860px); max-width: 860px; }
.modal-card.settings-modal .modal-body { padding: 0; overflow: hidden; }
.settings-shell {
  display: grid;
  grid-template-columns: 226px 1fr;
  /* La fila se acota EXPLÍCITAMENTE. Con el `auto` implícito se dimensiona al contenido,
     así que crecía más que el shell y el max-height solo la RECORTABA: por eso "Usuarios"
     salía partido en b_10.png (item 48) en vez de aparecer un scroll. */
  grid-template-rows: minmax(0, 1fr);
  min-height: 62vh;
  max-height: 72vh;
}
/* Y cada panel scrollea por su cuenta. `min-height: 0` es la otra mitad: un hijo de grid
   arranca en min-height:auto, que le impide encogerse por debajo de su contenido. */
.settings-rail, .settings-pane { min-height: 0; overflow-y: auto; }

.settings-rail {
  border-right: 1px solid var(--border); background: var(--bg);
  padding: 12px 10px; display: flex; flex-direction: column; gap: 3px;
}
.rail-group {
  margin: 12px 8px 5px; font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-dim); display: flex; align-items: center; gap: 7px;
}
.rail-group:first-child { margin-top: 2px; }
.rail-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 10px; border: none; background: transparent; border-radius: 8px;
  cursor: pointer; color: var(--text); font: inherit; transition: background-color .12s ease;
}
.rail-item:hover { background: var(--bg-hover); }
.rail-item[aria-selected="true"] { background: var(--accent-dim); color: var(--accent-strong); font-weight: 600; }
.rail-item .ic { width: 17px; height: 17px; flex: 0 0 auto; }
.rail-lbl { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-tag {
  font-size: 9.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 5px; background: var(--warn-bg); color: var(--warn);
  border: 1px solid var(--warn);
}

.settings-pane { padding: 18px 22px 24px; }
.settings-pane .pane-head { display: flex; align-items: center; gap: 10px; }
.settings-pane .pane-head h3 { margin: 0; font-size: 16px; font-weight: 650; }
.settings-pane .pane-sub { margin: 5px 0 16px; color: var(--text-dim); font-size: 13px; }
.settings-back { display: none; }

.cfg-field { padding: 12px 0; border-top: 1px solid var(--border); }
.cfg-field:first-child { border-top: none; padding-top: 2px; }
.cfg-field-row { display: flex; align-items: center; gap: 14px; }
.cfg-field-main { flex: 1; min-width: 0; }
.cfg-field-name { font-weight: 600; }
.cfg-field-help { color: var(--text-dim); font-size: 12.5px; margin-top: 3px; }
.cfg-sw { flex: 0 0 auto; }
/* item 59: píldora de estado de un ajuste que NO se decide aquí (el permiso de avisos lo
   da el navegador). Neutra por defecto, verde cuando está en orden y roja cuando bloquea. */
.cfg-state {
  flex: 0 0 auto;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px;
  padding: 3px 9px; border-radius: 8px;
  background: var(--bg-hover); color: var(--text-dim);
}
.cfg-state.ok { background: var(--accent-dim); color: var(--author-bot); }
.cfg-state.off { color: var(--danger); }

/* item 60: titularidad y condiciones de uso. Discreto en el inicio de sesión —tiene que
   estar, no tiene que gritar— y legible en la ventana de condiciones. */
.auth-legal { display: block; margin-top: 14px; font-size: 11px; color: var(--text-dim); }
.legal-doc { max-height: 60vh; overflow-y: auto; }
.legal-h { margin: 16px 0 4px; font-size: 13px; font-weight: 700; color: var(--text); }
.legal-h:first-of-type { margin-top: 12px; }
.legal-foot { margin-top: 18px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--text-dim); }
/* Herencia global → personal: mientras manda el equipo, el bloque se ve pero no se toca. */
.cfg-inherit { transition: opacity .16s ease; }
.cfg-inherit.locked { opacity: .45; }
.modal-note {
  margin-top: 14px; padding: 10px 12px; border-radius: 9px; font-size: 12.5px; line-height: 1.5;
  background: var(--info-bg); border: 1px solid var(--info-border); color: var(--text);
}

/* Cambio de sección en escritorio: un matiz corto para que el ojo siga el cambio. */
@keyframes cfgSwap { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
.settings-pane.swapping { animation: cfgSwap .16s ease; }

/* En móvil el detalle SUSTITUYE a la lista, así que entra y sale con movimiento: si
   apareciera de golpe no se vería de dónde salió. Animación CSS y no transición desde
   rAF: si el frame no llega a correr, una transición dejaría el panel invisible. */
@keyframes cfgPaneIn  { from { opacity: 0; transform: translateX(26%); } to { opacity: 1; transform: none; } }
@keyframes cfgPaneOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(26%); } }
@keyframes cfgRailIn  { from { opacity: 0; transform: translateX(-18%); } to { opacity: 1; transform: none; } }

/* item 77: navegación lista↔chat en móvil. El chat entra desde la derecha y sale por ahí
   mismo; la lista vuelve desde la izquierda. Los porcentajes son suaves a propósito: un
   100% se siente como un cambio de página y esto es una sola pantalla que se desplaza. */
@keyframes chatIn  { from { opacity: 0; transform: translateX(22%); } to { opacity: 1; transform: none; } }
@keyframes chatOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateX(22%); } }
@keyframes listIn  { from { opacity: 0; transform: translateX(-16%); } to { opacity: 1; transform: none; } }

@media (max-width: 720px) {
  .modal-card.settings-modal { width: 96vw; }
  .settings-shell { grid-template-columns: 1fr; min-height: 60vh; overflow: hidden; }
  .settings-rail { border-right: none; }
  .settings-pane { display: none; padding: 16px 14px 20px; }
  .settings-shell.drill .settings-rail { display: none; }
  .settings-shell.drill .settings-pane { display: block; animation: cfgPaneIn .2s ease; }
  /* item 75: en móvil, estando en la LISTA no hay ninguna sección abierta, así que ninguna
     puede salir resaltada. En escritorio el resaltado sí dice algo —el panel de esa sección
     está a la vista, a la derecha—; aquí, al volver, señalaba "estás en Acerca de" cuando
     ya no estabas. Es el mismo fallo que el del ojito: un estado visual que sobrevive a la
     navegación que lo hizo falso.
     Se corrige en CSS y no tocando `actual` a propósito: el estado sigue siendo verdad
     (esa ES la última sección abierta y a la que se vuelve), lo único que no vale es
     PINTARLO cuando su panel no se ve. */
  .settings-shell:not(.drill) .rail-item[aria-selected="true"] {
    background: none;
    color: inherit;
    font-weight: inherit;
  }
  /* Durante la salida el panel sigue montado: se le deja terminar antes de ocultarlo. */
  .settings-shell.leaving .settings-pane { animation: cfgPaneOut .18s ease forwards; }
  .settings-rail.entering { animation: cfgRailIn .2s ease; }
  .settings-back {
    display: inline-flex; align-items: center; gap: 4px; background: none; border: none;
    color: var(--accent-strong); cursor: pointer; padding: 0 0 10px; font: inherit;
    font-size: 13px; font-weight: 600;
  }
  .settings-back .ic { width: 16px; height: 16px; }
}

/* Fila de ajuste con un botón a la derecha (item 36: horario de atención). */
.settings-row { display: flex; align-items: center; gap: 12px; }
.settings-row > span:first-child { flex: 1; min-width: 0; }
/* Editor del horario de atención. */
.hours-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.hours-label { flex: 1; min-width: 0; }
.hours-sep { color: var(--text-dim); font-size: 13px; }
.hours-preview { margin-top: 14px; padding: 10px 12px; border-radius: 8px; background: var(--info-bg); border: 1px solid var(--info-border); color: var(--text); font-size: 13px; line-height: 1.45; }
.cfg-select {
  flex: 0 0 auto;
  min-width: 112px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  color: var(--text);
  font-size: 13px;
  outline: none;
  cursor: pointer;
  transition: border-color .15s ease;
}
.cfg-select:focus { border-color: var(--accent); }
.cfg-select option { background: var(--bg-panel); color: var(--text); }

/* Instrucciones del bot (item 40): textos largos, así que el control va DEBAJO del rótulo
   y a todo el ancho, no a la derecha como los interruptores y los selects. Lo mismo vale
   para los campos de una línea de "Mi cuenta" (.cfg-input, item 58): comparten el aspecto
   para que el panel no tenga dos estilos de caja de texto. */
.cfg-textarea,
.cfg-input {
  display: block;
  width: 100%;
  margin-top: 10px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  line-height: 1.5;
  /* Sin agarradera de redimensionar: el alto lo decide el diseño (rows), y arrastrarla
     descuadra el panel de configuración. */
  resize: none;
  outline: none;
  transition: border-color .15s ease;
}
.cfg-textarea:focus,
.cfg-input:focus { border-color: var(--accent); }
.cfg-textarea::placeholder,
.cfg-input::placeholder { color: var(--text-dim); }
.cfg-count-warn { color: var(--warn, #e0a33e); }
.cfg-btn-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
@keyframes dirty-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
/* El plegable de "avanzado" comparte armazón con el de Odoo: mismo componente, un solo
   sitio donde tocarlo (mandato de componentes globales). */
.cfg-advanced { border-top: 1px solid var(--border); padding-top: 10px; margin-top: 14px; }
/* item 66: los <details> de la consola abrían y cerraban de golpe. La animación de ENTRADA
   corre sola al aparecer los hijos; la de SALIDA la habilita wireDetails (ui.js), que
   retrasa el cierre real hasta que termina. Animación y no transición: si no llega a
   correr, el elemento queda en su estado natural en vez de a medias. */
.acc-anim[open] > :not(summary) { animation: acc-in .24s ease-out; }
.acc-anim.closing > :not(summary) { animation: acc-out .18s ease-in; }
@keyframes acc-in { from { opacity: 0; transform: translateY(-6px); } }
@keyframes acc-out { to { opacity: 0; transform: translateY(-6px); } }
/* Quien pide menos movimiento se queda sin animación, pero el acordeón sigue funcionando. */
@media (prefers-reduced-motion: reduce) {
  .acc-anim[open] > :not(summary), .acc-anim.closing > :not(summary) { animation: none; }
}

/* --- Sección "Ayuda" de Configuración --- */
/* Es el único sitio de la consola que se LEE de arriba abajo, así que aquí manda la
   legibilidad: renglón más alto, ancho de línea acotado y nada de controles. */
.help-intro { margin: 0 0 14px; color: var(--text-dim); font-size: 13px; line-height: 1.6; }
.help-search { margin-bottom: 12px; }
.help-search input {
  width: 100%; box-sizing: border-box;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 12px; color: var(--text); font-size: 13.5px; outline: none;
  transition: border-color .15s ease;
}
.help-search input:focus { border-color: var(--accent); }
.help-search input::-webkit-search-cancel-button { filter: invert(.5); }

/* La línea divisoria de antes la sustituye la TARJETA (ver más abajo, .help-item): con
   filas separadas por una raya, seis acordeones seguidos se leían como una lista. */
/* 12px POR LOS CUATRO LADOS. Los 2px de los costados venían de cuando esto era una lista
   plana, donde el aire lo ponía el ancho del panel; dentro de una tarjeta ese mismo valor
   deja el texto pegado al borde. Es el escalón de la escala que toca para una tarjeta. */
.help-item > summary {
  display: flex; align-items: center; gap: 12px;
  padding: 12px; cursor: pointer; list-style: none; user-select: none;
}
.help-item > summary::-webkit-details-marker { display: none; }
.help-item > summary:hover .help-t { color: var(--accent); }
.help-ic { flex: 0 0 auto; display: flex; color: var(--text-dim); }
.help-ic .ic { width: 17px; height: 17px; }
.help-head { flex: 1; min-width: 0; }
.help-t { display: block; font-weight: 600; transition: color .12s ease; }
.help-s { display: block; margin-top: 2px; color: var(--text-dim); font-size: 12.5px; }
/* La flecha gira al abrir: es la señal de que ese renglón esconde algo, y girar cuesta
   menos de leer que cambiar el icono por otro. */
.help-chev { flex: 0 0 auto; display: flex; color: var(--text-dim); transition: transform .2s ease; }
.help-chev .ic { width: 16px; height: 16px; }
.help-item[open] > summary .help-chev { transform: rotate(180deg); }

/* El cuerpo arranca donde arranca el TÍTULO (12 de padding + 17 del icono + 12 del hueco
   = 41), no en el borde de la tarjeta: así el texto cuelga del nombre del tema en vez de
   flotar por debajo del icono. */
.help-body { padding: 0 12px 12px 41px; font-size: 13px; line-height: 1.6; }
.help-body p { margin: 0 0 10px; color: var(--text-dim); }
.help-body b { color: var(--text); font-weight: 600; }
.help-body code {
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 12px;
  background: var(--bg-hover); padding: 1px 5px; border-radius: 5px; color: var(--text);
}
/* Cada "cosa" y su explicación en dos columnas: el ojo baja por los nombres y solo lee
   la fila que buscaba. En móvil se apilan, que a 375px dos columnas dejan tres palabras. */
.help-def {
  display: grid; grid-template-columns: 148px 1fr; gap: 4px 12px;
  padding: 6px 0; border-top: 1px solid var(--border);
}
.help-def:first-of-type { border-top: none; }
.help-def > b { color: var(--text); font-weight: 600; }
.help-def > span { color: var(--text-dim); }
.help-def .rail-tag { margin-left: 4px; vertical-align: 1px; }
.help-empty { margin: 18px 2px; color: var(--text-dim); font-size: 13px; }

/* Filtrar cambia la lista entera, así que el resultado ENTRA en vez de aparecer de golpe:
   sin eso no se distingue "se filtró" de "no pasó nada". Animación y no transición, para
   que si no llega a correr la lista quede visible igualmente. */
.help-list.filtering { animation: helpFilter .16s ease-out; }
@keyframes helpFilter { from { opacity: .35; transform: translateY(3px); } }
@media (prefers-reduced-motion: reduce) {
  .help-list.filtering { animation: none; }
  .help-chev { transition: none; }
}
@media (max-width: 640px) {
  .help-def { grid-template-columns: 1fr; gap: 2px; }
  .help-body { padding-left: 12px; }
}

/* --- Notificaciones de acciones del bot (item 3) --- */
/* Campanita con contador. */
.notif-btn { position: relative; }
/* ROJO y no verde (decisión del 11-08): el verde es el color de la marca y está por
   todos lados —el badge de no leídos, los botones—, así que un contador verde sobre un
   icono verde no destaca. El rojo de aviso contrasta con TODO lo demás de la consola, y
   además es lo que ya usa el favicon con no leídos: mismo significado, mismo color. */
.notif-count {
  position: absolute; top: -3px; right: -5px;
  min-width: 15px; height: 15px; padding: 0 4px;
  border-radius: 8px; background: var(--notif); color: #fff;
  font-size: 9px; font-weight: 700; line-height: 15px; text-align: center;
  pointer-events: none;
}
/* Circulito en la lista: notificaciones del bot pendientes (≠ no leídos). Mismo rojo que
   el contador de la campanita: son la misma cosa contada en dos sitios. */
.notif-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--notif); flex: 0 0 auto;
}
/* Bandeja global bajo la campanita. */
.notif-panel {
  position: fixed; z-index: 260;
  width: 340px; max-width: calc(100vw - 16px); max-height: 60vh;
  display: flex; flex-direction: column;
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 10px 30px var(--shadow);
  animation: menuIn .13s ease;
}
.notif-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px; border-bottom: 1px solid var(--border);
  font-weight: 600; font-size: 13px;
}
.notif-panel-list { overflow-y: auto; padding: 4px; }
/* Fila de notificación (bandeja y pestaña). */
.notif-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 9px 10px; border-radius: 8px;
  /* Sin este margen, dos filas resaltadas seguidas se ven como un solo bloque de color. */
  margin-bottom: 4px;
}
.notif-row:last-child { margin-bottom: 0; }
.notif-panel .notif-row { cursor: pointer; }
.notif-panel .notif-row:hover { background: var(--bg-hover); }
/* item 12: en la pestaña del chat, la fila con ancla lleva a su burbuja. */
.notif-row.linkable { cursor: pointer; }
.notif-row.linkable:hover { background: var(--bg-hover); }
.notif-row.unread { background: var(--accent-dim); }
/* Un color por TIPO de notificación (item 29): distinguirlas de un vistazo, sin tener que
   leer el texto de cada una. El icono va en un círculo teñido de su propio color. */
.notif-ic {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  color: var(--accent);
  background: color-mix(in srgb, currentColor 16%, transparent);
}
.notif-ic .ic { width: 15px; height: 15px; }
.notif-ic.k-transferencia { color: var(--n-transferencia); }
.notif-ic.k-espera { color: var(--n-espera); }
.notif-ic.k-presupuesto { color: var(--n-presupuesto); }
.notif-ic.k-cliente { color: var(--n-cliente); }
.notif-ic.k-vinculo { color: var(--n-vinculo); }
.notif-ic.k-memoria { color: var(--n-memoria); }
/* Barra de filtros por tipo: reusa el chip de la lista de chats (.filter-tab). */
.notif-filters { padding: 2px 2px 8px; }
.notif-panel .notif-filters { padding: 6px 6px 8px; }
.notif-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.notif-chat { font-weight: 600; font-size: 12.5px; }
.notif-text { font-size: 12.5px; color: var(--text); overflow-wrap: anywhere; }
.notif-time { font-size: 11px; color: var(--text-dim); }
/* --- Pestaña Memoria (item 23) --- */
.mem-add { display: flex; gap: 8px; }
.mem-input {
  flex: 1; background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: 8px; padding: 9px 12px; color: var(--text); font-size: 13px; outline: none;
}
.mem-input:focus { border-color: var(--accent); }
.mem-list { display: flex; flex-direction: column; gap: 8px; }
.mem-row {
  border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
  display: flex; flex-direction: column; gap: 6px; background: var(--bg-panel);
}
.mem-text { font-size: 13px; overflow-wrap: anywhere; }
.mem-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-dim); }
.mem-by { display: inline-flex; align-items: center; gap: 3px; font-weight: 600; }
.mem-by.bot { color: var(--accent); }
.mem-by .ic { width: 12px; height: 12px; }
.mem-actions { margin-left: auto; display: inline-flex; gap: 2px; }
.mem-actions .icon-btn { width: 26px; height: 26px; }
.mem-actions .ic { width: 14px; height: 14px; }
.mem-edit-input { width: 100%; }
/* Memoria apartada (item 40): sigue ahí y se puede leer y reactivar, pero el bot no la
   usa. Se apaga sin esconderla — tacharla la haría ilegible, y el punto es poder leerla. */
.mem-off { opacity: .55; border-style: dashed; }
.mem-off .mem-text { text-decoration: line-through; text-decoration-thickness: 1px; }
.mem-off-tag {
  border: 1px solid var(--border); border-radius: 999px;
  padding: 1px 7px; font-size: 10.5px; text-transform: lowercase;
}

/* Numerito de la pestaña Notificaciones del panel. */
.tab-count {
  display: inline-block; min-width: 15px; padding: 0 4px; margin-left: 2px;
  border-radius: 8px; background: var(--accent); color: #fff;
  font-size: 9px; font-weight: 700; line-height: 15px; text-align: center;
}

/* 12px entre acciones: son un grupo de controles relacionados, no bloques distintos. Con
   16px se leían como cuatro islas sueltas y en móvil con 8px se tocaban entre sí (b_23);
   el mismo valor en las dos pantallas es lo que las hace consistentes. */
.chat-header-actions { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* La usa overflowbar.js para sacar algo de la barra. `display: none` y no `visibility`:
   tiene que dejar de OCUPAR, que es de lo que va todo esto. */
.of-oculto { display: none !important; }
/* "Ver más" — la puerta a la ficha de Odoo, las notificaciones y la memoria del cliente.
   Estaba en gris sobre gris, con un contorno que casi no se distinguía del fondo de su
   contenedor: siendo la entrada a media consola, tiene que verse. Va con el color de
   acento —el mismo que ya manda en el resto de la interfaz, así resalta sin inventarse un
   color nuevo—: contorno en reposo, y RELLENO al pasar por encima y mientras el panel está
   abierto, que es un estado y no un hover. */
.ghost-btn {
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  font-weight: 600;
  padding: 6px 12px;
  border-radius: 6px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
/* El texto sobre el acento va con --on-accent, el token que el tema ya define para eso
   (en claro es blanco; en oscuro, casi negro, que es lo que se lee sobre este verde). */
.ghost-btn:hover, .ghost-btn.active { background: var(--accent); color: var(--on-accent); }
.ghost-btn .ic { width: 16px; height: 16px; }

.bot-toggle { display: flex; align-items: center; gap: 8px; cursor: pointer; color: var(--text-dim); }
.bot-toggle input { display: none; }
.slider {
  width: 38px; height: 20px; background: var(--bg-hover); border-radius: 20px;
  position: relative; transition: background-color .15s ease;
  /* Nunca encoger: junto a una etiqueta larga (los switches de configuración) el flex
     lo estrujaba y el knob, que viaja 18px, se salía del carril. */
  flex: 0 0 auto;
}
.slider::before {
  content: ""; position: absolute; width: 16px; height: 16px; border-radius: 50%;
  background: var(--text-dim); top: 2px; left: 2px; transition: transform .15s ease, background-color .15s ease;
}
.bot-toggle input:checked + .slider { background: var(--accent-dim); }
.bot-toggle input:checked + .slider::before { transform: translateX(18px); background: var(--accent); }
/* item 5, estado B: switch activo pero en amarillo cuando el bot no está disponible. */
.bot-toggle.bot-unavailable input:checked + .slider { background: var(--warn-bg); }
.bot-toggle.bot-unavailable input:checked + .slider::before { background: var(--warn); }

/* --- Mensajes --- */
.messages {
  flex: 1;
  overflow-y: auto;
  padding: 20px 8%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--chat-bg);
}
/* Fila de mensaje: ocupa todo el ancho para que el menú/responder/pulsación larga
   funcionen en el espacio horizontal completo, no solo sobre la burbuja (item 34). */
/* item 38: touch-action pan-y deja el arrastre vertical al navegador (es el scroll del
   chat) y nos entrega el horizontal, que es el gesto de responder. position:relative por
   el icono que asoma al arrastrar. */
.msg-row { display: flex; width: 100%; gap: 8px; touch-action: pan-y; position: relative; }
/* La flecha aparece a la izquierda según se arrastra y se enciende al llegar al límite:
   así se sabe cuándo soltar sin tener que adivinar. */
.msg-row::before {
  content: "↩";
  position: absolute; left: 6px; top: 50%;
  transform: translateY(-50%);
  color: var(--text-dim); font-size: 17px; line-height: 1;
  opacity: 0; transition: opacity .12s ease, color .12s ease;
  pointer-events: none;
}
.msg-row.swiping::before { opacity: .45; }
.msg-row.swipe-reply::before { opacity: 1; color: var(--accent); }
.msg-row.client, .msg-row.bot { justify-content: flex-start; }
.msg-row.operator { justify-content: flex-end; }

/* Adjunto saliente que aún se está subiendo: la burbuja ya se ve con el archivo y su
   relojito, y el archivo lleva encima un spinner mientras dura la subida (item 46). */
.bubble-media.pending-media { position: relative; }
.media-uploading {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .35);
  border-radius: inherit;
}

/* Botón de reenviar junto a la burbuja de un adjunto, fuera de ella (item 42).
   Va centrado con la burbuja, como en WhatsApp (fa_25.png). */
.msg-forward-btn {
  flex: 0 0 auto;
  align-self: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text-dim);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.msg-forward-btn:hover { background: var(--bg-hover); color: var(--text); }
.msg-forward-btn .ic { width: 15px; height: 15px; }
/* En los salientes el botón va a la IZQUIERDA de la burbuja (su lado libre). Se hace
   con order y NO con flex-direction:row-reverse: row-reverse invierte también el eje
   principal, así que justify-content:flex-end empujaba TODA la fila a la izquierda y
   los mensajes propios dejaban de salir a la derecha (regresión de la Fase 58). */
.msg-row.operator .msg-forward-btn { order: -1; }
/* En modo selección el clic debe seleccionar el mensaje, no reenviarlo. */
.messages.selecting .msg-forward-btn { display: none; }
.bubble {
  max-width: 68%;
  padding: 8px 12px;
  border-radius: 10px;
  position: relative;
  word-wrap: break-word;
  white-space: pre-wrap;
  line-height: 1.4;
}
.bubble .meta { display: block; font-size: 10px; color: var(--text-dim); margin-top: 4px; text-align: right; }
.bubble .author { display: block; font-size: 11px; font-weight: 600; margin-bottom: 2px; }

/* Multimedia entrante (item 2): marcador de descarga, media renderizada, lightbox. */
.bubble-media { margin: 2px 0 6px; border-radius: 8px; overflow: hidden; }
.bubble-media.placeholder, .bubble-media.loading, .bubble-media.error {
  display: flex; align-items: center; gap: 8px; min-height: 40px; padding: 8px 10px;
  background: rgba(0, 0, 0, .16); border-radius: 8px;
}
.bubble-media .media-btn {
  display: inline-flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; color: inherit; cursor: pointer; font: inherit; padding: 0;
}
.bubble-media .media-kind { font-size: 12.5px; font-weight: 600; }
.bubble-media .media-hint { margin-left: auto; display: inline-flex; opacity: .8; }
.bubble-media .ic { width: 18px; height: 18px; flex: 0 0 auto; }
.bubble-media.error { color: var(--text-dim); }
.media-img {
  display: block; max-width: 260px; max-height: 320px; width: 100%;
  border-radius: 8px; cursor: zoom-in; object-fit: cover;
}
.bubble-media.kind-sticker .media-img { max-width: 140px; }
.media-video { display: block; max-width: 280px; max-height: 340px; border-radius: 8px; background: #000; }
/* --- Reproductor de audio propio (item 27) --- */
.audio-player { display: flex; align-items: center; gap: 8px; width: 250px; max-width: 100%; }
.audio-play {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%; border: none; cursor: pointer;
  background: var(--accent); color: var(--on-accent);
}
.audio-play .ic { width: 15px; height: 15px; }
.audio-mid { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* item 33: onda de la nota de voz (canvas) en vez de una línea de progreso. */
.audio-wave { width: 100%; height: 26px; display: block; cursor: pointer; }
.audio-sub { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.audio-time { font-size: 10.5px; color: var(--text-dim); }
.audio-mic { display: inline-flex; color: var(--text-dim); }
.audio-mic .ic { width: 11px; height: 11px; }
/* La velocidad solo sale en la nota que está sonando: en las demás no significaría nada. */
.audio-speed {
  flex: 0 0 auto; min-width: 34px; padding: 3px 6px;
  border-radius: 10px; border: none; cursor: pointer; font: inherit;
  font-size: 11px; font-weight: 700;
  background: var(--bg-hover); color: var(--text-dim);
}
.audio-speed:hover { color: var(--text); }

/* Micrófono en la lista mientras suena un audio de ese chat (item 27). */
.mic-ic { display: inline-flex; color: var(--accent); flex: 0 0 auto; }
.mic-ic .ic { width: 13px; height: 13px; }

/* item 10: el ojito de "alguien más está en este chat". Va con los otros iconos de estado
   de la fila (micrófono, silenciado, bloqueado) porque es lo mismo: información sobre el
   chat, no una acción. Solo se pinta cuando hay alguien, así que no necesita estado
   apagado. Es pulsable (abre el popover con los nombres) sin parecer un botón. */
.presence-eye {
  display: inline-flex; align-items: center; gap: 2px;
  flex: 0 0 auto; cursor: pointer;
  color: var(--info); border-radius: 6px; padding: 0 2px;
  transition: background-color .12s ease;
}
.presence-eye:hover, .presence-eye:focus-visible { background: var(--bg-hover); outline: none; }
.presence-eye .ic { width: 13px; height: 13px; }
.presence-count { font-size: 10.5px; font-weight: 700; line-height: 1; }
/* El popover reusa el armazón del menú flotante; solo cambia el rótulo de arriba. */
.presence-pop-head {
  padding: 8px 12px 6px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .3px; color: var(--text-dim);
}
/* Hereda de .msg-menu-wrap la posición fija, el z-index y la animación; aquí solo la caja
   (el hueco de 8px entre cabecera y lista sobra: son una sola tarjeta). */
.presence-pop-wrap {
  gap: 0;
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 10px 30px var(--shadow); overflow: hidden;
}
/* Dentro del envoltorio, el menú ya no pone su propia caja: sería un borde sobre otro. */
.presence-pop-wrap .presence-pop {
  position: static; background: none; border: none; box-shadow: none; min-width: 168px;
}
/* Las filas son nombres, no acciones: sin puntero de "pulsable" ni resalte al pasar. */
.presence-pop .msg-menu-item { cursor: default; }
.presence-pop .msg-menu-item:hover { background: none; }

/* Barra del audio que sigue sonando fuera de su chat (item 27, fa_22.png). Va fija arriba
   y empuja la app: con box-sizing:border-box el padding-top le come alto a las 100vh, así
   que no hace falta reestructurar el layout.
   item 30: solo debe abarcar el panel del chat, no la lista. Arranca donde termina el
   sidebar (--sidebar-w, ajustable por arrastre) más los 5px del divisor. En móvil (una
   columna) se resetea a left:0 en la media query, porque ahí no hay lista al lado. */
.audio-bar {
  position: fixed; top: 0; left: calc(var(--sidebar-w) + 5px); right: 0; z-index: 300;
  height: 52px; display: flex; align-items: center; gap: 8px; padding: 0 10px;
  background: var(--bg-panel); border-bottom: 1px solid var(--border);
  box-shadow: 0 2px 12px var(--shadow);
  /* item 36: entra deslizándose desde arriba y sale hacia arriba (como la barra de
     responder). El hueco (padding del .chat) se reserva de golpe a propósito —la
     compensación de scroll del item 29 lo necesita—, así que la barra se desliza dentro
     del hueco ya reservado en vez de animar el layout. */
  transition: transform .22s ease;
}
/* Se anima el transform, así que NO puede ser display:none: se deja renderizada pero
   desplazada fuera de la vista (arriba). Gana a la .hidden global por especificidad. */
.audio-bar.hidden { display: flex !important; transform: translateY(-100%); }
/* item 35: la barra solo tapa el chat, así que solo el chat debe bajar. Empujar toda la
   .app (como antes) bajaba también la lista y dejaba un hueco vacío sobre "LeadSync". En
   móvil (barra a todo el ancho, una sola columna a la vez) sí se empuja la .app entera:
   ver la media query. */
.app.with-audio-bar .chat { padding-top: 52px; }
.ab-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%; border: none; cursor: pointer;
  background: transparent; color: var(--text);
}
.ab-btn:hover { background: var(--bg-hover); }
.ab-btn .ic { width: 17px; height: 17px; }
.ab-who {
  flex: 1; min-width: 0; display: flex; align-items: center; justify-content: center; gap: 6px;
  background: none; border: none; cursor: pointer; font: inherit; color: var(--text);
}
.ab-name { font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ab-mic { display: inline-flex; color: var(--accent); flex: 0 0 auto; }
.ab-mic .ic { width: 14px; height: 14px; }
/* La barra de progreso va pegada al borde de abajo, como en fa_22.png. */
.ab-track { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: var(--bg-hover); cursor: pointer; }
.ab-fill { height: 100%; background: var(--accent); width: 0; }
.media-doc {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 10px;
  background: rgba(0, 0, 0, .16); border-radius: 8px; color: inherit; text-decoration: none;
}
.media-doc .media-kind { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.media-spinner {
  width: 18px; height: 18px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .35); border-top-color: currentColor;
  animation: media-spin .7s linear infinite;
}
@keyframes media-spin { to { transform: rotate(360deg); } }
.lightbox-overlay {
  position: fixed; inset: 0; z-index: 400;
  display: flex; flex-direction: column;
  background: rgba(0, 0, 0, .9); opacity: 0; transition: opacity .15s ease;
}
.lightbox-overlay.show { opacity: 1; }
.lightbox-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 16px; flex: 0 0 auto; }
.lb-top-right { display: flex; gap: 6px; }
.lb-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
  background: rgba(255, 255, 255, .12); color: #fff;
}
.lb-btn:hover { background: rgba(255, 255, 255, .22); }
.lb-btn .ic { width: 20px; height: 20px; }
/* item 44: centrado SEGURO. Un `center` normal en flex reparte el sobrante a los dos
   lados, y cuando el hijo es MÁS GRANDE que el contenedor ese reparto se vuelve negativo:
   la parte de arriba y de la izquierda quedan fuera del área scrolleable y no hay forma de
   llegar a ellas. `safe` hace que, al desbordar, se alinee al inicio en vez de centrar. */
.lightbox-stage {
  flex: 1; display: flex; min-height: 0; padding: 8px 24px; cursor: zoom-out;
  align-items: safe center;
  justify-content: safe center;
}
.lightbox-media { max-width: 100%; max-height: 100%; border-radius: 6px; box-shadow: 0 8px 40px rgba(0, 0, 0, .6); }
.lightbox-bottom { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 20px; flex: 0 0 auto; }
.lb-action {
  display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px;
  border-radius: 22px; border: none; cursor: pointer; font: inherit; font-weight: 600; font-size: 13.5px;
  background: rgba(255, 255, 255, .14); color: #fff;
}
.lb-action:hover { background: rgba(255, 255, 255, .24); }
.lb-action .ic { width: 18px; height: 18px; }
/* Ya reaccionado: el botón muestra SOLO el emoji, sin el texto "Reaccionar" (item 47). */
.lb-action.reacted { padding: 6px 14px; }
.lb-emoji { font-size: 22px; line-height: 1.1; }

/* item 34: los gestos del carrusel los lleva el JS. Sin esto el navegador se queda el
   arrastre (scroll/zoom) antes de que podamos decidir qué es. Solo en la imagen: el video
   necesita que sus controles nativos sigan recibiendo el toque. */
.lightbox-stage img.lightbox-media { touch-action: none; }
/* item 25: zoom por pasos. Crece el TAMAÑO real (width/height en px), no un transform,
   para que el escenario tenga algo que scrollear de verdad (item 44). Y en píxeles y no en
   max-* porcentual porque max-width solo limita: sobre un sticker, más pequeño que la
   caja, no hacía nada (item 50). */
.lightbox-media.zoomed { transition: width .18s ease, height .18s ease; cursor: zoom-in; }
.lightbox-stage.zoomed { overflow: auto; cursor: default; }
/* Con la imagen ampliada, el flex no debe encogerla para que quepa. */
.lightbox-stage.zoomed .lightbox-media { flex: 0 0 auto; }
.lb-btn:disabled { opacity: .35; cursor: default; }

/* item 36: separador de día. position:sticky da el efecto de WhatsApp sin una línea de
   JS: la píldora se queda arriba del chat mientras se leen los mensajes de ese día y la
   empuja hacia arriba la del día siguiente. pointer-events:none para que no se coma el
   clic derecho de la burbuja que quede debajo. */
/* El grupo hereda la separación entre burbujas de .messages: sin esto, envolverlas se
   llevaría por delante el gap de 8px del chat. */
.day-group { display: flex; flex-direction: column; gap: 8px; }
.date-divider {
  /* top: 0 y no 6px — con el margen se veía despegado del borde de arriba al quedarse
     pegado (item 76). El separador es una etiqueta de encabezado: pegado al borde se lee
     como parte del marco del chat; a 6px parece que flota por su cuenta. */
  position: sticky; top: 0; z-index: 5;
  display: flex; justify-content: center;
  margin-bottom: 2px; pointer-events: none;
}
.date-divider span {
  padding: 4px 12px; border-radius: 12px;
  background: var(--bg-panel); border: 1px solid var(--border);
  color: var(--text-dim); font-size: 11.5px; font-weight: 600;
  box-shadow: 0 2px 8px var(--shadow);
}

/* item 37: el archivo que llega entra por el lado del que viene. Sin fill-mode a
   propósito: al acabar, el elemento se queda en su estado normal, así que aunque el
   navegador no llegue a pintar la animación nunca queda una imagen invisible. */
@keyframes lbEnterRight { from { transform: translateX(40px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes lbEnterLeft  { from { transform: translateX(-40px); opacity: 0; } to { transform: none; opacity: 1; } }
.lightbox-media.lb-enter-right { animation: lbEnterRight .18s ease; }
.lightbox-media.lb-enter-left  { animation: lbEnterLeft .18s ease; }

/* --- Carrusel del visor (item 53) --- */
/* Autor y fecha del archivo que se está viendo, como en fa_27.png. */
.lb-who { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; color: #fff; }
.lb-who b { font-size: 14px; }
.lb-who span { font-size: 12px; color: rgba(255, 255, 255, .7); }
/* Flechas de navegación: van sobre el escenario, ancladas a los lados. */
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%; border: none; cursor: pointer;
  background: rgba(255, 255, 255, .12); color: #fff;
}
.lb-nav:hover { background: rgba(255, 255, 255, .24); }
.lb-nav .ic { width: 24px; height: 24px; }
.lb-nav.prev { left: 16px; }
.lb-nav.next { right: 16px; }
/* Tira de miniaturas: el historial de multimedia del chat. Scrollea sola en horizontal
   (es la única de la app que puede: son muchos archivos en una fila). */
.lb-strip {
  flex: 0 0 auto; display: flex; gap: 8px; justify-content: center;
  padding: 10px 16px; overflow-x: auto; overflow-y: hidden;
}
.lb-thumb {
  position: relative; flex: 0 0 auto; width: 56px; height: 56px; padding: 0;
  border: 2px solid transparent; border-radius: 6px; overflow: hidden; cursor: pointer;
  background: rgba(255, 255, 255, .1); line-height: 0;
}
.lb-thumb.current { border-color: var(--accent); }
.lb-thumb img, .lb-thumb-vid { width: 100%; height: 100%; object-fit: cover; }
/* item 35: la miniatura de un video es el propio video (pinta su primer fotograma). No
   debe reaccionar al clic: quien manda es el botón que lo envuelve. */
.lb-thumb-vid { pointer-events: none; background: #000; }
.lb-thumb-ph { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; color: rgba(255, 255, 255, .6); }
.lb-thumb-ph .ic { width: 20px; height: 20px; }
/* Marca de video sobre la miniatura, como en fa_27.png. */
.lb-thumb-badge {
  position: absolute; left: 3px; bottom: 3px;
  display: inline-flex; padding: 2px; border-radius: 3px;
  background: rgba(0, 0, 0, .6); color: #fff;
}
.lb-thumb-badge .ic { width: 11px; height: 11px; }
@media (max-width: 720px) {
  .lb-nav { width: 36px; height: 36px; }
  .lb-nav.prev { left: 6px; }
  .lb-nav.next { right: 6px; }
  .lb-thumb { width: 44px; height: 44px; }
  .lb-strip { justify-content: flex-start; }
}
/* Miniatura de video en la burbuja con botón de reproducir (item 32). */
.media-video-thumb { position: relative; display: inline-block; cursor: pointer; line-height: 0; }
.media-video-thumb .media-video { max-width: 260px; max-height: 320px; border-radius: 8px; background: #000; display: block; }
.media-play {
  position: absolute; inset: 0; margin: auto; width: 54px; height: 54px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .5); border-radius: 50%; color: #fff; pointer-events: none;
}
.media-play .ic { width: 30px; height: 30px; }

/* Modal de envío de adjunto (item 30). */
.attach-preview {
  display: flex; align-items: center; justify-content: center;
  min-height: 120px; max-height: 320px; padding: 10px; border-radius: 10px;
  background: var(--bg-hover); overflow: hidden;
}
.attach-prev-img, .attach-prev-video { max-width: 100%; max-height: 300px; border-radius: 8px; }
.attach-prev-audio { width: 100%; }
.attach-prev-file { display: flex; align-items: center; justify-content: center; color: var(--text-dim); }
.attach-prev-file .ic { width: 56px; height: 56px; }
.attach-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 8px; font-size: 12.5px; color: var(--text-dim);
}
.attach-meta .attach-name { font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attach-meta .attach-size { flex: 0 0 auto; }
.upload-limits-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
.upload-limits-grid label { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12.5px; color: var(--text-dim); }
.upload-limits-grid input {
  width: 68px; padding: 5px 8px; border-radius: 6px; text-align: right;
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
}
.bubble.client { align-self: flex-start; background: var(--client); border-top-left-radius: 2px; }
.bubble.client .author { color: var(--author-client); }
.bubble.bot { align-self: flex-start; background: var(--bot); border-top-left-radius: 2px; }
.bubble.bot .author { color: var(--author-bot); }
.bubble.operator { align-self: flex-end; background: var(--operator); border-top-right-radius: 2px; }
.bubble.operator .author { color: var(--text); }

/* --- Stickers (item 32, f_26.png) -----------------------------------------
   En WhatsApp el sticker NO va dentro de una burbuja: se pinta suelto sobre el fondo
   del chat, porque muchos son transparentes y una caja detrás los delataría. Al
   quitarle el fondo, la hora se queda sin sitio legible, así que se despega en su
   propia píldora bajo el sticker (como en la captura).
   OJO: este bloque va DESPUÉS de .bubble.client/.bot/.operator a propósito. Tiene su
   misma especificidad (0,2,0), así que si fuera antes ganarían ellos y el sticker
   seguiría con su caja de color. */
.bubble.sticker { background: none; padding: 0; border-radius: 0; }
.bubble.sticker .bubble-media { margin: 0; background: none; border-radius: 0; }
/* contain, NO cover: un sticker no tiene por qué ser cuadrado y cover lo recortaría. */
.bubble.sticker .media-img { object-fit: contain; border-radius: 0; }
.bubble.sticker .meta {
  width: fit-content;
  margin: 4px 0 0 auto; /* la píldora se pega al borde derecho del sticker */
  padding: 2px 7px;
  border-radius: 8px;
  background: var(--sticker-meta-bg);
}
/* El marcador de descarga sí necesita su caja: sin fondo no se distinguiría el botón. */
.bubble.sticker .bubble-media.placeholder,
.bubble.sticker .bubble-media.loading,
.bubble.sticker .bubble-media.error { background: var(--sticker-meta-bg); border-radius: 8px; }
.bubble.system {
  align-self: center; background: var(--system); color: var(--text-dim);
  font-size: 12px; text-align: center; max-width: 80%; font-weight: 600;
}

/* --- Panel de debug (deslizante / fijable) --- */
.debug-panel {
  position: absolute;
  /* El borde inferior sigue al dock (barra de respuesta + composer, que crece con el
     texto): --dock-h la publican los observers de initDockObserver. */
  top: 60px; right: 0; bottom: var(--dock-h, 68px);
  width: var(--debug-w);
  /* item 32: sin z-index quedaba en 'auto' y los separadores de día (.date-divider) y
     el botón "ir al final" (.scroll-bottom), ambos z-5, se pintaban ENCIMA del panel.
     z-7 lo sube por encima de ellos y de las barras de búsqueda/selección (z-6), pero
     lo deja por debajo de su propio divisor arrastrable (.resizer z-8), que debe seguir
     agarrable, y de la bandeja de emojis / modales (z-20+). */
  z-index: 7;
  background: var(--bg-panel);
  border-left: 1px solid var(--border);
  overflow-y: auto;
  box-shadow: -8px 0 24px var(--shadow);
  transform: translateX(100%);
  transition: transform .25s ease;
  will-change: transform;
}
.debug-panel.open { transform: translateX(0); }
/* Fijado: dock sin sombra (ya no flota sobre el chat). */
.debug-panel.pinned { box-shadow: none; }
.debug-header {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px 8px 16px; font-weight: 600;
  border-bottom: 1px solid var(--border); color: var(--accent);
}
#debug-pin-btn.active { color: var(--accent); background: var(--accent-dim); }
.debug-content { padding: 12px 16px; display: flex; flex-direction: column; gap: 12px; }
.debug-item { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.debug-item .tool-name { background: var(--bg-elev); padding: 6px 10px; font-weight: 600; color: var(--warn); font-size: 13px; }
.debug-item pre {
  padding: 8px 10px; font-size: 11px; color: var(--text-dim);
  white-space: pre-wrap; word-break: break-word; font-family: "SFMono-Regular", Consolas, monospace;
}
.debug-item .kv { border-top: 1px solid var(--border); }
.debug-item .kv-label { color: var(--accent); font-size: 10px; padding: 6px 10px 0; text-transform: uppercase; }
.debug-empty { color: var(--text-dim); text-align: center; padding: 20px; }

/* --- Panel "Más información": pestañas Odoo/Depuración (item 21) --- */
/* La cabecera queda fija arriba para que las pestañas no se vayan al hacer scroll de una
   lista larga de pedidos (el panel entero es el que scrollea). */
.debug-header { position: sticky; top: 0; background: var(--bg-panel); z-index: 2; padding-left: 10px; }
/* b_6: con 4 pestañas ya no caben — scroll horizontal contenido en su caja (sin
   desbordar sobre actualizar/fijar), con barra finita y la activa siempre a la vista. */
.info-tabs {
  display: flex; gap: 2px; flex: 1; min-width: 0;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin;
}
.info-tabs::-webkit-scrollbar { height: 4px; }
.info-tabs::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.info-tab {
  appearance: none; background: none; border: none; cursor: pointer;
  padding: 8px 12px; font-size: 13px; font-weight: 600; color: var(--text-dim);
  border-bottom: 2px solid transparent; transition: color .15s, border-color .15s;
  flex: 0 0 auto; white-space: nowrap;
}
.info-tab:hover { color: var(--text); }
.info-tab.active { color: var(--accent); border-bottom-color: var(--accent); }

.odoo-content { padding: 12px; display: flex; flex-direction: column; gap: 12px; }
/* item 18: el botón de actualizar vive en la cabecera del panel, junto al pin. */
#odoo-refresh-btn { width: 30px; height: 30px; flex: 0 0 auto; }

/* Estados vacíos / error / cargando. */
.odoo-note {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  text-align: center; color: var(--text-dim); padding: 24px 16px; font-size: 13px;
}
.odoo-note .ic { width: 22px; height: 22px; opacity: .8; }
.odoo-note .odoo-hint { font-size: 12px; opacity: .8; }
.odoo-err { color: var(--danger); }
.odoo-loading .ic { animation: spin 1s linear infinite; }

/* Tarjeta de la ficha del contacto. */
.odoo-card {
  border: 1px solid var(--border); border-radius: 10px; padding: 14px;
  display: flex; flex-direction: column; gap: 12px; background: var(--bg-panel);
}
.odoo-card-head { display: flex; align-items: center; gap: 10px; }
.odoo-id { display: flex; flex-direction: column; min-width: 0; }
.odoo-name { font-weight: 600; color: var(--text); font-size: 15px; overflow-wrap: anywhere; }
.odoo-ref { font-size: 12px; color: var(--text-dim); }

.odoo-flags { display: flex; flex-wrap: wrap; gap: 6px; }
.odoo-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--border);
}
.odoo-flag .ic { width: 13px; height: 13px; }
.odoo-flag.ls { color: var(--accent); border-color: var(--accent-dim); background: var(--accent-dim); }
.odoo-flag.ai { color: var(--info); border-color: var(--info-border); background: var(--info-bg); }

.odoo-fields { display: flex; flex-direction: column; gap: 8px; }
.odoo-field { display: flex; flex-direction: column; gap: 1px; }
.odoo-field-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); }
.odoo-field-val { font-size: 13px; color: var(--text); overflow-wrap: anywhere; }

.odoo-link {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px; border-radius: 8px; border: 1px solid var(--border);
  color: var(--accent); font-size: 13px; font-weight: 600; text-decoration: none;
  transition: background .15s;
}
.odoo-link:hover { background: var(--bg-hover); }
.odoo-link .ic { width: 15px; height: 15px; }

/* Resumen (cantidad / acumulado / último). */
.odoo-summary { display: flex; gap: 8px; }
.odoo-stat {
  flex: 1; min-width: 0; text-align: center; padding: 10px 6px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-elev);
  display: flex; flex-direction: column; gap: 2px;
}
.odoo-stat-num { font-size: 14px; font-weight: 700; color: var(--text); overflow-wrap: anywhere; }
.odoo-stat-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-dim); }

/* Lista de pedidos. */
.odoo-orders-head { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); font-weight: 600; }
.odoo-orders { display: flex; flex-direction: column; gap: 6px; }
.odoo-order {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px;
  text-decoration: none; color: var(--text); transition: background .15s;
}
.odoo-order:hover { background: var(--bg-hover); }
.odoo-order-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.odoo-order-name { font-weight: 600; font-size: 13px; display: flex; align-items: center; gap: 5px; }
.odoo-ls-dot { display: inline-flex; color: var(--accent); }
.odoo-ls-dot .ic { width: 12px; height: 12px; }
.odoo-order-date { font-size: 11px; color: var(--text-dim); }
.odoo-order-side { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: 0 0 auto; }
.odoo-order-total { font-size: 13px; font-weight: 600; white-space: nowrap; }
.odoo-order-state {
  font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 999px;
  white-space: nowrap; background: var(--bg-elev); color: var(--text-dim);
}
.odoo-order-state.st-sale { color: var(--accent); background: var(--accent-dim); }
.odoo-order-state.st-draft { color: var(--warn); background: var(--warn-bg); }
.odoo-order-state.st-sent { color: var(--info); background: var(--info-bg); }
.odoo-order-state.st-cancel { color: var(--danger); background: var(--bg-elev); }

/* Vincular contacto de Odoo por ID (item 43). */
.odoo-linkbox { display: flex; flex-direction: column; gap: 8px; }
.odoo-link-form { display: flex; gap: 8px; align-items: center; }
.odoo-id-input {
  flex: 1; min-width: 0; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg-elev); color: var(--text); font-size: 13px;
}
.odoo-id-input:focus { outline: none; border-color: var(--accent); }
.odoo-link-form .modal-btn.sm { flex: 0 0 auto; }
.odoo-relink { border-top: 1px solid var(--border); padding-top: 10px; }
.odoo-relink summary, .cfg-advanced summary {
  cursor: pointer; font-size: 12px; color: var(--text-dim); list-style: none; padding: 2px 0;
}
.odoo-relink summary:hover, .cfg-advanced summary:hover { color: var(--text); }
.odoo-relink summary::-webkit-details-marker, .cfg-advanced summary::-webkit-details-marker { display: none; }
.odoo-relink[open] summary, .cfg-advanced[open] summary { margin-bottom: 8px; color: var(--accent); }
.odoo-unlink-btn {
  margin-top: 8px; width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 10px; border-radius: 8px; border: 1px solid var(--border);
  background: none; color: var(--danger); font-size: 12px; font-weight: 600; cursor: pointer;
  transition: background .15s;
}
.odoo-unlink-btn:hover { background: var(--bg-hover); }
.odoo-unlink-btn .ic { width: 14px; height: 14px; }

/* --- Composer --- */
.composer {
  position: relative;
  min-height: 68px;
  padding: 12px 16px;
  background: var(--bg-elev);
  border-top: 1px solid var(--border);
  display: flex;
  align-items: flex-end; /* item 7: el editor crece hacia arriba; botones abajo */
  gap: 10px;
}
.composer input { flex: 1; }

/* --- La píldora del composer (item R10, d_7.png) ---------------------------
   El campo y sus iconos son UNA sola pieza: el borde y el fondo los pone la píldora, no
   el editor. Así el foco enciende el conjunto (focus-within) en vez de dibujar un
   recuadro dentro de otro, y los iconos quedan alineados con la ÚLTIMA línea del texto
   cuando el mensaje crece (align-items: flex-end). */
.composer-pill {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  padding: 3px 4px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  /* Radio grande pero NO 50%: con varias líneas la píldora crece y un radio en
     porcentaje se deformaría en un óvalo. */
  border-radius: 24px;
  transition: border-color .15s ease;
}
.composer-pill:focus-within { border-color: var(--accent); }
/* Los iconos de dentro son más pequeños que los sueltos de antes: comparten caja con el
   texto y a 34px la píldora se hacía innecesariamente alta. */
.pill-btn { width: 36px; height: 36px; border-radius: 50%; }
.pill-btn .ic { width: 19px; height: 19px; }
/* Encendido = hay algo activo detrás (hoy, la firma). Se marca en los tres puntos porque
   es el botón que lo esconde: si no, el estado quedaría invisible al cerrar el popover. */
.pill-btn.on { color: var(--accent-strong); background: color-mix(in srgb, var(--accent) 14%, transparent); }

/* --- Editor con estilos de WhatsApp (item 7, f_27/28) --- */
.composer-editor {
  flex: 1;
  min-width: 0;
  /* item 4: altura tipo Twitter — mínimo 1 línea, máximo 10 y luego scroll (el
     contenteditable crece solo con el contenido; los topes los ponen estas cotas). */
  /* CON UNA SOLA LÍNEA, el editor tiene que medir LO MISMO que los botones (36px), o la
     píldora la define él y los botones —alineados abajo— quedan 8px por debajo del centro
     (b_25). 1.45em de línea + 8px arriba y abajo = 36,3px: la altura del botón.
     Al saltar de línea crece él y los botones se quedan abajo, que es lo correcto. */
  min-height: calc(1.45em + 16px);
  max-height: calc(14.5em + 16px);
  overflow-y: auto;
  background: transparent;
  border: none;
  padding: 8px;
  color: var(--text);
  font-size: 14px;
  line-height: 1.45;
  outline: none;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  transition: border-color .15s ease;
}
.composer-editor:focus { outline: none; }
/* EL PLACEHOLDER NO PARTE EN DOS LÍNEAS (lo cazaste tú): el editor tiene `white-space:
   pre-wrap` porque el MENSAJE sí tiene que envolver, y el texto de ayuda lo heredaba — en
   móvil el composer se abría a dos renglones estando vacío y rompía la forma de píldora.
   Aquí se invierte solo para el texto de ayuda: una línea y elipsis. Que no se lea entero da
   igual; es una pista, no contenido. */
.composer-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--text-dim);
  pointer-events: none;
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.composer-editor[contenteditable="false"] { opacity: .6; cursor: not-allowed; }
/* Símbolos de formato tenues; el contenido va estilizado (f_27/28). */
.composer-editor .wa-esym { opacity: .45; }
.composer-editor b { font-weight: 700; }
.composer-editor i { font-style: italic; }
.composer-editor s { text-decoration: line-through; }
.composer-editor code {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: .92em;
  background: color-mix(in srgb, currentColor 12%, transparent);
  border-radius: 4px;
  padding: 0 3px;
}
.composer-editor .wa-eline-b { font-weight: 700; }

/* Barra de formato sobre la selección (f_27). */
.wa-format-bar {
  position: fixed; z-index: 300;
  display: flex; gap: 2px; padding: 4px;
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 8px 24px var(--shadow);
  animation: menuIn .12s ease;
}
.wa-fmt-btn {
  background: transparent; border: none; cursor: pointer;
  min-width: 32px; height: 30px; padding: 0 7px;
  border-radius: 7px; color: var(--text);
  font-size: 14px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.wa-fmt-btn:hover { background: var(--bg-hover); }
.send-btn {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: 0 0 auto;
  transition: background-color .15s ease, transform .1s ease;
}
.send-btn:hover { background: var(--accent-strong); }
.send-btn:active { transform: scale(.92); }
.send-btn .ic { width: 20px; height: 20px; }

/* --- Grabar notas de voz (item 30, f_22.png y f_23.png) ---------------------
   El micrófono sale cuando no hay texto escrito y, en cuanto se escribe algo, deja
   su sitio al botón de enviar. En reposo va desnudo (solo el icono) y al pasar el
   ratón se enciende en verde, como en la captura. */
.mic-btn {
  flex: 0 0 auto;
  width: 44px; height: 44px; border-radius: 50%;
  border: none; cursor: pointer;
  background: transparent; color: var(--text-dim);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background-color .15s ease, color .15s ease;
}
.mic-btn:hover:not(:disabled) { background: var(--accent); color: var(--on-accent); }
.mic-btn:active:not(:disabled) { transform: scale(.92); }
.mic-btn:disabled { opacity: .5; cursor: not-allowed; }
.mic-btn .ic { width: 20px; height: 20px; }

/* La barra de grabación TAPA el composer en vez de convivir con él: mientras se graba
   no hay nada que escribir, y así no hay que reordenar sus hijos. */
.rec-bar {
  position: absolute; inset: 0;
  display: flex; align-items: center; gap: 10px;
  padding: 0 16px;
  background: var(--bg-elev);
}
.rec-discard { color: var(--text-dim); flex: 0 0 auto; }
.rec-discard:hover { color: var(--danger); }
.rec-play, .rec-toggle { flex: 0 0 auto; color: var(--text); }
.rec-toggle { color: var(--danger); }
.rec-toggle.rec-paused { color: var(--text); }
.rec-play:disabled { opacity: .5; cursor: not-allowed; }
/* Punto rojo que late mientras se graba. */
.rec-dot {
  flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%;
  background: var(--danger);
  animation: rec-latido 1.4s ease-in-out infinite;
}
@keyframes rec-latido { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
.rec-time { flex: 0 0 auto; font-size: 13px; font-variant-numeric: tabular-nums; color: var(--text); }
.rec-time-end { color: var(--text-dim); }
.rec-wave { flex: 1; min-width: 0; height: 34px; cursor: pointer; }
/* El composer conserva su alto mientras se graba: la barra va encima en absoluto. */
.composer.recording > :not(.rec-bar) { visibility: hidden; }

/* --- Bandeja de emojis (categorías, recientes, tono de piel) --- */
.emoji-tray {
  position: absolute;
  /* Anclada al TOPE del composer (su contenedor): crece él, sube ella — sin JS. */
  bottom: calc(100% - 6px);
  left: 12px;
  /* 320px SIEMPRE que quepan. Antes había un `width: calc(100vw - 24px)` a partir de
     720px que la estiraba de lado a lado en cuanto la pantalla no era de escritorio
     (b_24): una bandeja de emojis de 900px de ancho no se lee mejor, se lee peor. Con
     min() el tope es el mismo en todas las pantallas y solo cede donde de verdad no cabe. */
  width: min(320px, calc(100vw - 24px));
  max-height: 300px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 24px var(--shadow);
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: translateY(8px) scale(.98);
  transform-origin: bottom left;
  pointer-events: none;
  transition: opacity .16s ease, transform .16s ease;
  z-index: 20;
}
.emoji-tray.open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.emoji-nav {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 6px 8px;
  border-bottom: 1px solid var(--border);
}
.emoji-nav-btn {
  background: transparent; border: none; cursor: pointer;
  font-size: 18px; line-height: 1; padding: 5px; border-radius: 8px; flex: 0 0 auto;
  transition: background-color .12s ease;
}
.emoji-nav-btn:hover { background: var(--bg-hover); }
.emoji-tone-btn {
  margin-left: auto;
  background: var(--bg-elev); border: 1px solid var(--border); cursor: pointer;
  font-size: 17px; line-height: 1; padding: 4px 7px; border-radius: 8px; flex: 0 0 auto;
  transition: background-color .12s ease;
}
.emoji-tone-btn:hover { background: var(--bg-hover); }
/* overflow-x:hidden mata el scroll horizontal; sin padding-top para que el
   título sticky quede pegado arriba sin dejar asomar emojis por encima. */
.emoji-body { overflow-y: auto; overflow-x: hidden; padding: 0 8px 8px; }
.emoji-section-h {
  font-size: 11px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--text-dim); font-weight: 600;
  /* margin negativo para cubrir el padding lateral del body (llega a right:0). */
  padding: 8px 10px 4px; margin: 0 -8px; position: sticky; top: 0; z-index: 2;
  background: var(--bg-panel);
}
/* minmax(0,1fr) permite que las columnas encojan y recorta emojis anchos
   (tono de piel / ZWJ) que antes desbordaban el ancho de la bandeja. */
.emoji-grid { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 2px; }
.skin-tone-pop {
  position: absolute; top: 42px; right: 8px; z-index: 21;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 8px 24px var(--shadow); padding: 6px; display: flex; gap: 2px;
}
.skin-tone-pop .emoji-cell.active { background: var(--accent-dim); }
.emoji-cell {
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  padding: 5px;
  border-radius: 8px;
  min-width: 0;        /* permite encoger en el grid sin desbordar */
  overflow: hidden;    /* recorta glifos anchos en vez de empujar la columna */
  transition: background-color .12s ease;
}
.emoji-cell:hover { background: var(--bg-hover); }
/* Los <button>/<input> NO heredan font-family (usan la del UA, p. ej. Arial, sin la
   fuente de emojis) → los emojis salían como cajitas en la bandeja y las reacciones,
   pese a que sí funcionan en las burbujas (que son <span> y sí heredan). Forzamos
   inherit para que usen "Noto Color Emoji" del stack del body (item 35). */
.emoji-cell, .emoji-nav-btn, .emoji-tone-btn, .msg-reaction, #composer-input { font-family: inherit; }
/* Bandeja de reacciones flotante: misma bandeja del composer, posicionada por JS y
   por encima del lightbox (item 35). */
/* La bandeja de reacciones se CREA ya abierta (no existe antes en el DOM), así que la
   transición de .emoji-tray.open no tiene estado inicial que animar y aparecía de golpe
   (item 22). Se le da una ANIMACIÓN de entrada, que sí corre al insertar el elemento —y
   sin depender de un rAF, que no dispara con la pestaña de fondo (escollo de la F78). */
.emoji-tray.reaction-tray {
  position: fixed; bottom: auto; left: auto; z-index: 500; transform-origin: top left;
  animation: trayIn .16s ease;
}
@keyframes trayIn { from { opacity: 0; transform: translateY(-8px) scale(.98); } to { opacity: 1; transform: none; } }
/* Backdrop invisible bajo la bandeja de reacción: cierra solo la bandeja (item 38). */
.reaction-backdrop { position: fixed; inset: 0; z-index: 499; }

/* --- Toasts --- */
.toast-container {
  position: fixed;
  bottom: 22px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  z-index: 200;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--bg-elev);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 11px 16px;
  box-shadow: 0 6px 22px var(--shadow);
  min-width: 240px;
  max-width: 90vw;
  font-size: 13.5px;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .22s ease, transform .22s ease;
  pointer-events: auto;
  cursor: pointer;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast-ic { display: inline-flex; flex: 0 0 auto; }
.toast-ic .ic { width: 18px; height: 18px; }
.toast-success .toast-ic { color: var(--accent); }
.toast-error .toast-ic { color: var(--danger); }
.toast-warn .toast-ic { color: var(--warn); }
.toast-info .toast-ic { color: var(--text-dim); }
/* item 32: "Deshacer" dentro del toast. Se separa del texto con un filete para que se lea
   como una acción y no como parte del mensaje, y va en verde: es la salida, no el aviso. */
.toast-with-action { padding-right: 4px; }
.toast-action {
  margin-left: 4px; padding: 6px 12px;
  background: none; border: none; border-left: 1px solid var(--border);
  color: var(--accent); font: inherit; font-size: 13px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .3px;
  cursor: pointer; border-radius: 0 8px 8px 0; flex: 0 0 auto;
}
.toast-action:hover { background: var(--bg-hover); }

/* --- Modales --- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 300;
  opacity: 0;
  transition: opacity .2s ease;
}
.modal-overlay.show { opacity: 1; }
.modal-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  width: 420px;
  max-width: 100%;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px var(--shadow);
  transform: translateY(10px) scale(.98);
  transition: transform .2s ease;
}
.modal-overlay.show .modal-card { transform: none; }
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 18px 20px 10px; }
.modal-header h2 { font-size: 18px; color: var(--text); }
.modal-body { padding: 6px 20px 8px; overflow-y: auto; }
.modal-text { color: var(--text-dim); line-height: 1.5; }
/* Resalta la parte del aviso que más se malinterpreta (p. ej. "el cliente aún podrá verlo"). */
.modal-text .warn-em { color: var(--warn); font-weight: 700; }
.modal-hint { color: var(--text-dim); font-size: 12px; margin-top: 2px; }
.modal-footer { display: flex; justify-content: flex-end; gap: 10px; padding: 14px 20px 18px; }
/* item 13: el pie tiene DOS juegos de botones y solo se ve uno. En reposo "Cerrar"; con
   cambios sin guardar, "Descartar"/"Guardar". Los dos se pintan al abrir y aquí se elige
   cuál: conmutar no reconstruye nodos, así que nadie se queda con una referencia muerta.
   Se toca SOLO `display` (nunca se fuerza el del botón visible), y la entrada es una
   ANIMACIÓN: una transición que no llegue a correr dejaría el pie invisible. */
.modal-footer.sucio > .fb-limpio { display: none; }
.modal-footer:not(.sucio) > .fb-sucio { display: none; }
.modal-footer.sucio > .fb-sucio { animation: dirty-in .16s ease-out; }
@media (prefers-reduced-motion: reduce) { .modal-footer.sucio > .fb-sucio { animation: none; } }

/* --- Variantes del armazón de la modal (item 48) ---------------------------
   Se piden con `options` en openModal y llegan como data-options. El selector por
   ~= casa una palabra suelta dentro de la lista, así que se combinan libremente
   ("no-footer mini-header") sin multiplicar clases. */
.modal-card[data-options~="no-footer"] .modal-footer { display: none; }
/* Sin pie, el CUERPO es el último elemento de la tarjeta, y su fondo —el panel gris de
   Configuración— tapaba las esquinas redondeadas de abajo: se veía un rectángulo asomando
   por debajo del borde curvo. Hereda el radio de la tarjeta y recorta lo que lleve dentro. */
.modal-card[data-options~="no-footer"] .modal-body {
  border-bottom-left-radius: 14px; border-bottom-right-radius: 14px; overflow: hidden;
}
.modal-card[data-options~="mini-header"] .modal-header { padding: 12px 16px 10px; }
.modal-card[data-options~="mini-header"] .modal-header h2 { font-size: 15.5px; }
.modal-btn {
  padding: 9px 18px;
  border-radius: 8px;
  font-weight: 600;
  font-size: 13.5px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color .15s ease, border-color .15s ease, filter .15s ease;
}
.modal-btn.secondary { background: transparent; border-color: var(--border); color: var(--text); }
.modal-btn.secondary:hover { background: var(--bg-hover); }
.modal-btn.primary { background: var(--accent); color: var(--on-accent); }
.modal-btn.primary:hover { background: var(--accent-strong); }
.modal-btn.danger { background: var(--danger); color: #fff; }
.modal-btn.danger:hover { filter: brightness(1.08); }
/* Estado deshabilitado: se ve atenuado y no responde al hover (item 25). */
.modal-btn:disabled { opacity: .5; cursor: not-allowed; }
.modal-btn.secondary:disabled:hover { background: transparent; }
.modal-btn.primary:disabled:hover { background: var(--accent); }
.modal-btn.danger:disabled:hover { filter: none; }

/* PROHIBIDO EL TIRADOR DE REDIMENSIONAR (mandato tuyo, y van dos veces que se me cuela):
   arrastrar la esquina de un textarea es incomodísimo y deja el formulario descuadrado. La
   altura la decide el CONTENIDO, como en el composer: crece con las líneas hasta un tope y
   a partir de ahí hace scroll (lo cablea autoAltoTextarea, en ui.js). */
textarea { resize: none; }

/* Campos de formulario dentro de modales. */
.modal-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.modal-field label { font-size: 12px; color: var(--text-dim); font-weight: 600; }
.modal-field input {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 11px 13px;
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: border-color .15s ease;
}
.modal-field input:focus { border-color: var(--accent); }

/* --- Contraseña con ojo (mostrar/ocultar) --- */
.pw-wrap { position: relative; display: flex; }
.pw-wrap input { flex: 1; padding-right: 42px; width: 100%; }
.pw-eye {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  transition: background-color .15s ease, color .15s ease;
}
.pw-eye:hover { color: var(--text); background: var(--bg-hover); }
.pw-eye .ic { width: 20px; height: 20px; }

/* Ojo del login: sin fondo, icono en color de texto y más grande. */
#login-pass-eye { color: var(--text); background: transparent; width: 36px; height: 36px; }
#login-pass-eye:hover { background: transparent; opacity: .8; }
#login-pass-eye .ic { width: 27px; height: 27px; }

/* --- Checklist de requisitos de contraseña --- */
.pw-checks { list-style: none; display: flex; flex-direction: column; gap: 7px; margin: 8px 0 4px; transition: opacity .15s ease; }
.pw-checks.dimmed { opacity: .45; } /* atenuada mientras no se escribe contraseña */
.pw-check { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-dim); transition: color .15s ease; }
.pw-check-dot {
  width: 17px;
  height: 17px;
  border-radius: 50%;
  border: 2px solid var(--text-dim);
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.pw-check-dot .ic { width: 10px; height: 10px; }
.pw-check.ok { color: var(--accent); }
.pw-check.ok .pw-check-dot { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }

/* --- Panel "Para guardar, falta corregir:" --- */
.form-errors {
  background: var(--warn-bg);
  border-left: 4px solid var(--warn);
  border-radius: 8px;
  padding: 12px 14px;
  margin-top: 6px;
}
.form-errors h4 { font-size: 13px; color: var(--warn); margin-bottom: 6px; }
.form-errors ul { list-style: disc; padding-left: 20px; }
.form-errors li { font-size: 13px; color: var(--warn); margin: 2px 0; }

/* --- Wizard de recuperación de acceso (item 13) --- */
.rec-code-inputs { display: flex; align-items: center; justify-content: center; gap: 6px; margin: 8px 0 4px; }
.rec-code-box {
  width: 34px; height: 44px;
  text-align: center;
  font-size: 20px; font-weight: 600;
  text-transform: uppercase;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-elev);
  color: var(--text);
  caret-color: var(--accent);
}
.rec-code-box:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-dim); }
.rec-code-dash { color: var(--text-dim); font-weight: 700; padding: 0 2px; }
.rec-code-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.rec-countdown { font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.rec-countdown.expired { color: var(--danger); }
.rec-gens { font-size: 12px; color: var(--text-dim); }
.rec-alert {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border-radius: 10px;
  font-size: 13.5px; line-height: 1.45;
}
.rec-alert-ic { display: inline-flex; flex: 0 0 auto; margin-top: 1px; }
.rec-alert-ic .ic { width: 20px; height: 20px; }
.rec-alert.danger { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
.rec-alert.info { background: var(--info-bg); color: var(--info); }
.rec-block-countdown { text-align: center; margin-top: 14px; color: var(--text-dim); font-size: 13px; font-variant-numeric: tabular-nums; }

/* --- Config: secciones, usuarios, roles --- */
.settings-section { border-top: 1px solid var(--border); padding-top: 16px; margin-top: 16px; }
.settings-section:first-child { border-top: none; padding-top: 0; margin-top: 0; }
/* Las filas de una sección respiran entre sí (el reporte: quedaban pegadas). */
.settings-section > .switch-row { margin-bottom: 14px; }
.settings-section > .switch-row:last-child { margin-bottom: 0; }
.settings-section > .switch-row > span:first-child { line-height: 1.45; }
.settings-section h3 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--text-dim);
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.settings-section h3 .ic { width: 15px; height: 15px; }
.modal-field select {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 11px 13px;
  color: var(--text);
  font-size: 14px;
  outline: none;
}
.modal-btn.sm { padding: 7px 12px; font-size: 12.5px; }
.modal-btn.block { width: 100%; justify-content: center; }
.switch-row { width: 100%; justify-content: space-between; gap: 12px; color: var(--text); }
.switch-row > span:first-child { font-size: 13.5px; }
.user-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 8px;
}
.user-row .user-meta { display: flex; align-items: center; gap: 10px; min-width: 0; }
.user-row .user-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* item 57: con nombre de equipo la fila se apila —nombre arriba, usuario de acceso debajo
   y atenuado—; sin nombre solo hay una línea y queda como estaba. */
.user-row .user-ids { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.user-row .user-login {
  font-size: 11px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.role-pill {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 8px;
  background: var(--bg-hover);
  color: var(--text-dim);
  flex: 0 0 auto;
}
.role-pill.admin { background: var(--accent-dim); color: var(--author-bot); }
.icon-btn.danger:hover { color: var(--danger); }
/* Badge que marca al usuario actual ("Tú") en la lista de usuarios. */
.me-pill {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .3px;
  padding: 2px 8px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  flex: 0 0 auto;
}
.user-row.me { border-color: var(--accent); }

/* --- Estados de entrega (checks) --- */
.bubble .meta { display: flex; align-items: center; justify-content: flex-end; gap: 3px; }
.msg-status { display: inline-flex; color: var(--text-dim); }
.msg-status .ic { width: 15px; height: 15px; }
.msg-status.read { color: #53bdeb; }
.msg-status.failed { color: var(--danger); }
/* Mensaje saliente optimista (item 10): relojito mientras sale, (!) si falla. */
.msg-status.pending .ic { width: 13px; height: 13px; }
.bubble.pending { opacity: .92; }
.bubble.pending.error { cursor: pointer; }
.bubble.pending.error .bubble-text { opacity: .8; }

/* --- Formato de WhatsApp dentro de las burbujas (item 7, f_29.png) --- */
.bubble-text b { font-weight: 700; }
.bubble-text i { font-style: italic; }
.bubble-text s { text-decoration: line-through; }
.bubble-text code {
  font-family: "SFMono-Regular", Consolas, monospace;
  font-size: .92em;
  background: color-mix(in srgb, currentColor 12%, transparent);
  border-radius: 4px;
  padding: 0 4px;
}
.wa-num, .wa-bullet { display: inline-block; min-width: 14px; text-align: right; }
.wa-bullet { font-weight: 700; }
/* Citación: barra vertical y texto atenuado, como en WhatsApp. */
.wa-quote {
  display: inline-block;
  border-left: 3px solid color-mix(in srgb, currentColor 45%, transparent);
  padding-left: 8px;
  opacity: .85;
}

/* --- Divisor "Mensajes no leídos" --- */
.unread-divider {
  align-self: center;
  background: var(--bg-elev);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  padding: 4px 14px;
  border-radius: 10px;
  margin: 6px 0;
  text-transform: uppercase;
  letter-spacing: .4px;
}

/* --- Carga por lotes e indicador de carga del chat (items 15 y 16) --- */
.load-older {
  align-self: center;
  background: var(--bg-elev);
  color: var(--text-dim);
  border: 1px solid var(--border);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 12px;
  margin: 4px 0 8px;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.load-older:hover { background: var(--bg-hover); color: var(--text); }
.chat-loading {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--text-dim);
  font-size: 14px;
}
/* Fallo al cargar la conversación (item 49): se apila para que el botón de reintentar
   quede debajo del aviso y no compita con él en la misma línea. */
.chat-error { flex-direction: column; gap: 12px; text-align: center; padding: 0 20px; }
.chat-error-ic { display: inline-flex; color: var(--warn); }
.chat-error-ic .ic { width: 26px; height: 26px; }
.spinner {
  width: 18px; height: 18px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --- Badge de mensajes no leídos en la lista --- */
.session-meta-right { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* LOS SIN LEER VAN EN ROJO (petición tuya, repetida): el verde de marca es el color de "todo
   en orden" y lo lleva media consola, así que el numerito se perdía entre lo demás. Rojo es
   la convención de "esto está sin atender" en cualquier bandeja, y aquí además coincide con
   el color de las notificaciones, que es lo mismo dicho en otro sitio. */
.unread-badge {
  background: var(--notif);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* Marcado "no leído" a mano: círculo verde sin número, como WhatsApp (item 42). */
.unread-badge.dot { min-width: 0; width: 12px; height: 12px; padding: 0; border-radius: 50%; }

/* Indicadores de silencio (junto al nombre) y de fijado (junto a la hora). */
.session-name-wrap .mute-ic { display: inline-flex; color: var(--text-dim); flex: 0 0 auto; }
.session-name-wrap .mute-ic .ic { width: 14px; height: 14px; }
.session-meta-right .pin-ic { display: inline-flex; color: var(--text-dim); flex: 0 0 auto; }
.session-meta-right .pin-ic .ic { width: 14px; height: 14px; }

/* Cabecera del chat: nombre clicable para ver la información del contacto. */
.chat-titles.clickable { cursor: pointer; }
.chat-titles.clickable:hover .chat-name { text-decoration: underline; }

/* Menú contextual de un chat (misma base visual que el de mensajes). Va fijo:
   openChatMenu lo agrega directo al <body> y positionFloating fija left/top, que
   solo aplican con position no-estática (antes quedaba static → fuera de pantalla). */
.chat-menu { min-width: 232px; position: fixed; z-index: 250; }

/* --- Modal de información del contacto --- */
.contact-info { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 4px 0 16px; }
.contact-name-big { font-size: 20px; font-weight: 600; text-align: center; }
.contact-number { color: var(--text-dim); font-size: 15px; }
.contact-badges { margin-top: 4px; }
.contact-actions { display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--border); padding-top: 14px; }
.contact-actions .modal-btn { display: inline-flex; align-items: center; gap: 10px; justify-content: flex-start; }
.contact-actions .modal-btn .ic { width: 17px; height: 17px; }

/* --- Menú contextual de mensajes (clic derecho / pulsación larga) --- */
.msg-menu-wrap {
  position: fixed;
  z-index: 250;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* item 38: el menú de click derecho crece al abrir y se achica al cerrar. Se anima el
   contenedor más externo de cada menú —el wrap del menú de mensaje, o el .msg-menu suelto
   del menú de chat/otros—; el .msg-menu interno del wrap no re-anima. Es animación CSS (no
   disparada desde rAF, que no corre con la pestaña de fondo — escollo ya visto en la F78). */
.msg-menu-wrap, .msg-menu { animation: menuIn .13s ease; }
.msg-menu-wrap .msg-menu { animation: none; }
.msg-menu-wrap.menu-closing, .msg-menu.menu-closing { animation: menuOut .12s ease forwards; pointer-events: none; }
@keyframes menuIn { from { opacity: 0; transform: scale(.86); } to { opacity: 1; transform: none; } }
@keyframes menuOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.9); } }

/* Bandeja de reacciones rápida. */
.msg-reactions {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 24px;
  padding: 5px 8px;
  box-shadow: 0 8px 24px var(--shadow);
}
.msg-reaction {
  background: transparent;
  border: none;
  cursor: pointer;
  font-size: 22px;
  line-height: 1;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background-color .12s ease, transform .1s ease;
}
.msg-reaction:hover { background: var(--bg-hover); transform: scale(1.15); }
.msg-reaction.active { background: var(--accent-dim); }
.msg-reaction.more { color: var(--text-dim); }
.msg-reaction.more .ic { width: 20px; height: 20px; }

/* Lista de opciones. */
.msg-menu {
  min-width: 210px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 10px 30px var(--shadow);
  padding: 6px;
}
.msg-menu-item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 9px 12px;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
  border-radius: 8px;
  text-align: left;
  transition: background-color .12s ease;
}
.msg-menu-item:hover { background: var(--bg-hover); }
.msg-menu-item.danger { color: var(--danger); }
.msg-menu-ic { display: inline-flex; color: var(--text-dim); flex: 0 0 auto; }
.msg-menu-item.danger .msg-menu-ic { color: var(--danger); }
.msg-menu-ic .ic { width: 18px; height: 18px; }
.msg-menu-sep { height: 1px; background: var(--border); margin: 5px 8px; }

/* Selector completo de reacciones (emoji). */
.reaction-picker {
  position: fixed;
  z-index: 500; /* por encima del lightbox (400) al reaccionar desde ahí (item 32) */
  width: 300px;
  max-height: 240px;
  overflow-y: auto;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 24px var(--shadow);
  padding: 8px;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 2px;
  animation: menuIn .12s ease;
}

/* --- Cita de respuesta dentro de la burbuja --- */
.reply-quote {
  display: flex;
  flex-direction: column;
  gap: 1px;
  border-left: 3px solid var(--accent);
  background: rgba(0, 0, 0, .12);
  border-radius: 5px;
  padding: 4px 8px;
  margin-bottom: 5px;
  max-width: 100%;
  cursor: pointer; /* doble-clic salta al mensaje original */
}
:root[data-theme="light"] .reply-quote { background: rgba(0, 0, 0, .05); }
/* Destello breve al saltar a un mensaje (desde la cita de respuesta). */
@keyframes bubble-flash {
  0%   { box-shadow: 0 0 0 3px var(--accent-strong); }
  100% { box-shadow: 0 0 0 3px transparent; }
}
.bubble.flash { animation: bubble-flash 1.1s ease-out; }
.reply-quote-author { font-size: 11px; font-weight: 600; color: var(--accent); }
.reply-quote-text {
  font-size: 12.5px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* item 27 (f_32): la cita de una foto o un video lleva su miniatura a la derecha. La cita
   pasa a fila; el cuerpo (autor + texto) se apila dentro y cede el ancho a la miniatura. */
.reply-quote.with-thumb { flex-direction: row; align-items: center; gap: 8px; padding-right: 4px; }
.reply-quote-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.reply-quote:not(.with-thumb) .reply-quote-body { display: contents; }
.reply-quote-thumb {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 4px; overflow: hidden;
  background: rgba(0, 0, 0, .18);
}
.reply-quote-thumb img, .reply-quote-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- Chip de reacción sobre la burbuja --- */
.bubble.has-reaction { margin-bottom: 14px; }
.bubble-reaction {
  position: absolute;
  bottom: -12px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1px 5px;
  font-size: 13px;
  line-height: 1.4;
  box-shadow: 0 2px 5px var(--shadow);
}
.bubble.client .bubble-reaction, .bubble.bot .bubble-reaction { left: 8px; }
.bubble.operator .bubble-reaction { right: 8px; }
/* item 13: con más de una reacción, el número va a la derecha de los emojis. */
.reaction-count { margin-left: 3px; font-size: 11px; font-weight: 600; color: var(--text-dim); vertical-align: middle; }

/* --- Mensaje eliminado por el cliente (estilo WhatsApp) --- */
.bubble.deleted .bubble-text.deleted-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
  font-style: italic;
}
.bubble.deleted .deleted-text .ic { width: 15px; height: 15px; }

/* --- Marcadores (editado / reenviado) --- */
.edited-tag { color: var(--text-dim); font-style: italic; margin-right: 2px; }
/* item 33: estrellita del mensaje destacado, junto a la hora. Rellena para que se lea de
   un vistazo entre los checks y el texto. */
.star-tag { display: inline-flex; color: var(--text-dim); flex: 0 0 auto; }
.star-tag .ic { width: 12px; height: 12px; fill: currentColor; }

/* --- Panel "Mensajes destacados" (item 33; rediseñado en el 31) --------------
   Como el de WhatsApp (f_24.png, f_25.png): buscador arriba y, por cada destacado,
   una cabecera ("de ▸ a" + hora + flecha) con la BURBUJA de verdad debajo, separadas
   por una línea. Antes era una lista de texto a una línea y no se parecía en nada. */
.modal-card.starred-modal .modal-body { overflow: hidden; display: flex; flex-direction: column; min-height: 0; }
.starred-panel { display: flex; flex-direction: column; gap: 10px; min-height: 0; }
.starred-search {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-radius: 18px; background: var(--bg-elev);
}
.starred-search .ic { width: 16px; height: 16px; color: var(--text-dim); flex: 0 0 auto; }
.starred-search input {
  flex: 1; min-width: 0; background: none; border: none; outline: none;
  color: var(--text); font: inherit; font-size: 13.5px;
}
/* La lista scrollea sola: el cuerpo de la modal no, o saldrían dos scrolls (item 17). */
.starred-list { flex: 1 1 auto; display: flex; flex-direction: column; overflow-y: auto; min-height: 0; }
.starred-item {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  width: 100%; padding: 10px 6px; text-align: left;
  background: none; border: none; border-bottom: 1px solid var(--border);
  color: var(--text); cursor: pointer; font: inherit;
  transition: background-color .12s ease;
}
.starred-item:last-child { border-bottom: none; }
.starred-item:hover { background: var(--bg-hover); }
.starred-head { display: flex; align-items: center; gap: 6px; width: 100%; }
.starred-who { flex: 1; min-width: 0; display: flex; align-items: center; gap: 5px; font-size: 12.5px; }
.starred-who b { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.starred-arrow { color: var(--text-dim); flex: 0 0 auto; }
.starred-when { flex: 0 0 auto; font-size: 11px; color: var(--text-dim); }
.starred-go { flex: 0 0 auto; display: inline-flex; color: var(--text-dim); }
.starred-go .ic { width: 15px; height: 15px; }
/* La burbuja va SIEMPRE a la izquierda, aunque sea nuestra (f_25.png): en el panel no
   hay dos lados que distinguir, porque el "de ▸ a" de la cabecera ya dice de quién es.
   Solo se conserva el color. Los 3 niveles de especificidad ganan a .bubble.operator
   (align-self:flex-end) sin depender del orden del archivo. */
.starred-panel .starred-item .bubble {
  align-self: flex-start;
  max-width: 100%;
  margin-left: 32px; /* alineada bajo el nombre, no bajo el avatar */
}
.starred-panel .starred-item .bubble .meta { display: flex; align-items: center; gap: 4px; justify-content: flex-end; }
/* Adjunto de un destacado: arranca como etiqueta y pintarStarredMedia la cambia por la
   miniatura si consigue resolverlo. */
.starred-media {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 2px 0 6px; padding: 6px 8px;
  background: rgba(0, 0, 0, .16); border-radius: 8px;
}
.starred-media .ic { width: 16px; height: 16px; flex: 0 0 auto; }
.starred-media .media-kind { font-size: 12.5px; font-weight: 600; }
.starred-media.ready { display: block; position: relative; padding: 0; background: none; }
.starred-thumb { display: block; max-width: 180px; max-height: 180px; border-radius: 6px; }
.starred-thumb-play {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(0, 0, 0, .55); color: #fff; pointer-events: none;
}
.starred-nohits { flex: 0 0 auto; color: var(--text-dim); font-size: 12.5px; text-align: center; padding: 10px 0; }
.fwd-tag {
  display: flex; align-items: center; gap: 4px;
  color: var(--text-dim); font-size: 12px; font-style: italic; margin-bottom: 3px;
}
.fwd-tag .ic { width: 13px; height: 13px; }

/* --- Barra de respuesta (mensaje citado antes de enviar) --- */
.reply-bar {
  --reply-t: .18s; /* también lo usa .reply-bar.hidden: es el mismo elemento */
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  background: var(--bg-elev);
  border-top: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  /* item 41: la barra entra creciendo desde el composer (de abajo hacia arriba) y sale
     al revés, como en WhatsApp. Se anima el alto porque display:none no transiciona;
     va pegada al composer, así que al crecer empuja su borde superior hacia arriba. */
  overflow: hidden;
  max-height: 64px;
  transition: max-height var(--reply-t) ease, opacity var(--reply-t) ease,
              padding-top var(--reply-t) ease, padding-bottom var(--reply-t) ease,
              visibility 0s;
}
.reply-bar.hidden {
  /* .hidden global es display:none !important; hay que revertirlo (con !important, es
     la única forma de ganarle) para que la SALIDA se pueda animar en vez de cortarse. */
  display: flex !important;
  max-height: 0;
  opacity: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-top-width: 0;
  /* visibility se aplica al TERMINAR la animación: así la barra oculta no recibe foco
     al tabular ni clics, pero se sigue viendo mientras se desliza hacia abajo. */
  visibility: hidden;
  transition: max-height var(--reply-t) ease, opacity var(--reply-t) ease,
              padding-top var(--reply-t) ease, padding-bottom var(--reply-t) ease,
              visibility 0s linear var(--reply-t);
}
.reply-bar-icon { display: inline-flex; color: var(--accent); flex: 0 0 auto; }
.reply-bar-icon .ic { width: 18px; height: 18px; }
.reply-bar-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
/* item 43: miniatura del adjunto citado, misma idea que en la burbuja. */
.reply-bar-thumb {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 4px; overflow: hidden;
  background: rgba(0, 0, 0, .18);
}
.reply-bar-thumb.hidden { display: none; }
.reply-bar-thumb img, .reply-bar-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.reply-bar-author { font-size: 12px; font-weight: 600; color: var(--accent); }
.reply-bar-text {
  font-size: 13px; color: var(--text-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* --- Firma del operador (item R8) ---
   La barra REUSA .reply-bar entera: es el mismo hecho (algo que se va a añadir al mensaje
   antes de enviarlo) y, sobre todo, la misma animación ya resuelta — crece desde el
   composer y sale al revés, sin display:none, que no transiciona. Aquí solo cambia lo
   propio: el color (no es una cita, es una decisión activa) y el tamaño, más bajito porque
   puede quedarse fija durante toda la jornada. */
.sign-bar {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--bg-elev));
  padding-top: 6px;
  padding-bottom: 6px;
  max-height: 52px;
}
/* La etiqueta va en --text y no en --text-dim: medido sobre el fondo teñido de la barra,
   el atenuado daba 4,49:1 en tema claro y AA pide 4,5. La jerarquía la sostiene igual el
   prefijo, que es el que va en negrita, monoespaciado y con el color de marca. */
.sign-bar-label { font-size: 11.5px; color: var(--text); }
/* El prefijo se enseña LITERAL y en monoespaciada: es texto que le llega al cliente, no
   una etiqueta de la consola. Con la tipografía normal se confundiría con lo segundo. */
.sign-bar-prefix {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* --accent-strong y no --accent: el verde de marca sobre el fondo claro de la barra
     da 2,78:1 y esto es TEXTO que hay que poder leer (AA pide 4,5). --accent-strong es la
     variante legible por tema, la misma pareja que ya usa la entrada activa del rail. */
  font-size: 12.5px; font-weight: 600; color: var(--accent-strong);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* El popover de los tres puntos (item R10). El `position: fixed` es OBLIGATORIO y no
   decorativo: `.msg-menu` no lo trae —lo pone cada caja concreta, igual que `.chat-menu`—
   y sin él positionFloating escribe un top/left que el navegador ignora, así que el
   popover sale a lo ancho de la página y por debajo del borde inferior. */
.composer-more { min-width: 250px; position: fixed; z-index: 250; }

/* La fila de la firma dentro del popover de los tres puntos (items R8 + R10). */
.menu-firma { padding: 8px 12px 10px; min-width: 250px; }
.menu-firma-top { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.menu-firma-ic { display: inline-flex; color: var(--text-dim); }
.menu-firma-ic .ic { width: 18px; height: 18px; }
.menu-firma-lbl { flex: 1; font-size: 13.5px; color: var(--text); }
.menu-firma-demo { margin: 6px 0 0 28px; font-size: 11.5px; color: var(--text-dim); line-height: 1.45; }
.menu-firma-demo code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--accent-strong); font-weight: 600;
}

/* Ejemplo de Configuración: una burbuja de mentira con la firma puesta. */
.firma-demo { margin-top: 14px; display: flex; justify-content: flex-end; }
.firma-demo-bubble {
  display: flex; flex-direction: column; gap: 2px;
  max-width: 340px; padding: 8px 11px; border-radius: 10px 10px 2px 10px;
  background: var(--operator); color: var(--text);
  font-size: 13.5px; line-height: 1.45;
}
.firma-demo-firma { font-weight: 700; }
/* La barra de firma entra y sale animada como la de respuesta; con el movimiento
   reducido, aparece y desaparece sin transición (misma regla que .reply-bar). */
@media (prefers-reduced-motion: reduce) {
  .sign-bar, .sign-bar.hidden { transition: none; }
}

/* --- Barra de búsqueda dentro de la conversación --- */
.chat-find-bar {
  position: absolute;
  /* Tapa la cabecera ENTERA porque es HIJA suya: inset:0 mide lo que mida ella. Con el
     `height: 60px` de antes se quedaba corta en cuanto la cabecera crecía con la fila de
     badges, y los chips asomaban por debajo (item R6). */
  inset: 0;
  padding: 0 12px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 6;
}
.chat-find-ic { display: inline-flex; color: var(--text-dim); flex: 0 0 auto; }
.chat-find-ic .ic { width: 18px; height: 18px; }
.chat-find-bar input {
  flex: 1; min-width: 0;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: border-color .15s ease;
}
.chat-find-bar input:focus { border-color: var(--accent); }
.chat-find-count {
  color: var(--text-dim); font-size: 12.5px;
  font-variant-numeric: tabular-nums; flex: 0 0 auto;
  min-width: 36px; text-align: center;
}

/* El hueco del icono de una fila de menú admite también un avatar (el popover del ojito):
   el círculo manda su propio tamaño y no debe estirarse al ancho del hueco. */
.msg-menu-ic .avatar { width: 18px; height: 18px; font-size: 8.5px; }

/* El popover de "¿quién es este círculo?": solo dice un nombre y un @usuario, así que no
   necesita el ancho de un menú de acciones. */
.msg-menu.quien-pop { min-width: 0; padding: 6px 4px; }
.quien-pop .msg-menu-title { text-transform: none; letter-spacing: 0; font-size: 12.5px; color: var(--text); }
.quien-pop .msg-menu-title + .msg-menu-title { color: var(--text-dim); font-weight: 600; margin-top: -2px; }
/* El círculo se ve pulsable: cursor de mano y un aro al enfocarlo con el teclado. */
.avatar-quien { cursor: pointer; }
.avatar-quien:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Resaltado de coincidencias de búsqueda. */
mark.search-hit { background: var(--hit-bg); color: var(--hit-fg); border-radius: 3px; padding: 0 1px; }
mark.search-hit.current { background: var(--accent); color: var(--on-accent); }

/* --- Barra de modo selección (superpone la cabecera del chat) --- */
.selection-bar {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 60px;
  padding: 0 16px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 14px;
  z-index: 6;
}
.sel-count { font-weight: 600; flex: 1; }
.selection-bar-actions { display: flex; align-items: center; gap: 6px; }

/* Burbujas en modo selección. */
.messages.selecting .bubble { cursor: pointer; user-select: none; }
.messages.selecting .bubble:hover { filter: brightness(1.06); }
.bubble.selected { box-shadow: 0 0 0 2px var(--accent); }

/* --- Selección múltiple de chats en el sidebar (item 7) --- */
.chat-selection-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.chat-selection-bar .sel-count { flex: 1 1 auto; font-size: 13px; font-weight: 600; white-space: nowrap; }
.chat-sel-actions { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.session-list.selecting .session-item { cursor: pointer; user-select: none; }
.session-item.selected { background: var(--accent-dim); box-shadow: inset 3px 0 0 var(--accent); }
.session-item.selected:hover { background: var(--accent-dim); }

/* --- Modal de reenvío: lista de chats --- */
.fwd-list { max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; transition: opacity .15s ease; }
/* Mientras se reenvía, se bloquea la lista para evitar envíos duplicados. */
.fwd-list.sending { pointer-events: none; opacity: .5; }
.fwd-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border: none;
  background: transparent;
  color: var(--text);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  transition: background-color .12s ease;
}
.fwd-row:hover { background: var(--bg-hover); }
/* item 20: destino con la ventana de 24 h cerrada. El candado va a la derecha, en su
   propia columna, por eso la fila pasa a rejilla en vez de columna. */
.fwd-row.locked {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: 10px;
  opacity: .5;
  cursor: not-allowed;
}
.fwd-row.locked:hover { background: transparent; }
.fwd-row.locked .fwd-name, .fwd-row.locked .fwd-sub { grid-column: 1; }
.fwd-lock { grid-column: 2; grid-row: 1 / span 2; display: inline-flex; color: var(--text-dim); }
.fwd-lock .ic { width: 16px; height: 16px; }
.fwd-name { font-weight: 600; }
.fwd-sub { font-size: 12px; color: var(--text-dim); }
.fwd-empty { color: var(--text-dim); text-align: center; padding: 18px; }

/* item 31: imagen desproporcionada — se avisa en vez de pintarla y colgar la consola. */
.media-huge {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  max-width: 260px; padding: 12px;
  background: var(--warn-bg); border: 1px solid var(--warn); border-radius: 10px;
}
.media-huge-ic { display: inline-flex; color: var(--warn); }
.media-huge-ic .ic { width: 20px; height: 20px; }
.media-huge-txt { font-size: 13px; line-height: 1.4; color: var(--text); }
.media-huge-btn {
  border: 1px solid var(--warn); background: transparent; color: var(--warn);
  border-radius: 8px; padding: 5px 10px; font-size: 12px; font-weight: 600; cursor: pointer;
}
.media-huge-btn:hover { background: color-mix(in srgb, var(--warn) 14%, transparent); }

/* Textarea (edición de mensajes). */
.modal-textarea {
  width: 100%;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 11px 13px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  outline: none;
  resize: vertical;
  transition: border-color .15s ease;
}
.modal-textarea:focus { border-color: var(--accent); }

/* --- Ventana de servicio de 24 h --- */
.window-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  font-size: 13px;
  border-bottom: 1px solid var(--border); /* fijada bajo la cabecera de la conversación */
}
.window-bar .window-ic { display: inline-flex; flex: 0 0 auto; }
.window-bar .window-ic .ic { width: 17px; height: 17px; }
.window-bar .window-msg { flex: 1; min-width: 0; }
.window-bar .window-count { font-variant-numeric: tabular-nums; }
/* Estados por color: verde (con tiempo) → amarillo (queda poco) → azul (cerrada). */
.window-bar.active {
  background: var(--accent-dim);
  color: var(--author-bot);
  border-bottom-color: var(--accent);
}
.window-bar.low {
  background: var(--warn-bg);
  color: var(--warn);
  border-bottom-color: var(--warn);
}
.window-bar.expired {
  background: var(--info-bg);
  color: var(--info);
  border-bottom-color: var(--info-border);
}
/* En la barra amarilla el conteo va en color de texto; en la azul "plantillas"
   hereda el azul de la barra (item 23) y queda en negrita por el <b>. */
.window-bar.low .window-msg b { color: var(--text); }
/* Botón "(?)" de ayuda de la ventana de 24 h. */
.window-help {
  flex: 0 0 auto; background: transparent; border: none; cursor: pointer;
  color: inherit; opacity: .8; padding: 2px; border-radius: 6px; display: inline-flex;
}
.window-help:hover { opacity: 1; background: rgba(127, 127, 127, .15); }
.window-help .ic { width: 17px; height: 17px; }
/* Punto de estado de la ventana en la lista de chats. */
.win-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; display: inline-block; }
.win-dot.low { background: var(--warn); }
.win-dot.expired { background: var(--info); }
.window-info p { margin: 0 0 10px; line-height: 1.5; }
.window-info p:last-child { margin-bottom: 0; }
.window-tmpl-btn {
  flex: 0 0 auto;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 8px;
  padding: 7px 14px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  transition: background-color .15s ease;
}
.window-tmpl-btn:hover { background: var(--accent-strong); }

/* Composer bloqueado (fuera de la ventana de 24 h). */
.composer.blocked .composer-pill {
  opacity: .55;
  cursor: not-allowed;
}
.composer.blocked .icon-btn,
.composer.blocked .send-btn { opacity: .5; cursor: not-allowed; }

/* --- Selector de plantillas --- */
.tmpl-list { display: flex; flex-direction: column; gap: 8px; max-height: 340px; overflow-y: auto; }
.tmpl-loading, .tmpl-empty { color: var(--text-dim); text-align: center; padding: 18px 8px; line-height: 1.5; }
.tmpl-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-elev);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
}
.tmpl-card:hover:not(.disabled) { border-color: var(--accent); }
.tmpl-card.disabled { opacity: .6; cursor: not-allowed; }
.tmpl-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tmpl-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tmpl-cat {
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .3px;
  padding: 2px 8px; border-radius: 8px; background: var(--bg-hover); color: var(--text-dim); flex: 0 0 auto;
}
.tmpl-body {
  font-size: 13px; color: var(--text-dim); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.tmpl-card-foot { font-size: 12px; }
.tmpl-vars { color: var(--warn); font-weight: 600; }
.tmpl-vars.ok { color: var(--accent); }
.tmpl-note { color: var(--danger); }
.tmpl-preview {
  background: var(--chat-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  white-space: pre-wrap;
  line-height: 1.45;
  font-size: 13.5px;
  color: var(--text);
}

/* item 17: en la modal "Enviar plantilla" solo scrollea la lista, no el cuerpo. */
.modal-card.tmpl-modal .modal-body { overflow: hidden; display: flex; flex-direction: column; min-height: 0; }
.modal-card.tmpl-modal .tmpl-list { max-height: none; flex: 1 1 auto; min-height: 0; }

/* item 18: variables {{n}} editables dentro del propio texto de la plantilla. */
.tmpl-var-chip {
  display: inline;
  background: var(--warn-bg);
  color: var(--warn);
  border-radius: 4px;
  padding: 0 4px;
  outline: none;
  cursor: text;
  box-shadow: inset 0 0 0 1px var(--warn);
  white-space: pre-wrap;
}
.tmpl-var-chip:focus { box-shadow: inset 0 0 0 2px var(--accent); background: var(--accent-dim); color: var(--text); }
.tmpl-var-chip:empty::before { content: attr(data-ph); color: var(--warn); opacity: .7; }
.tmpl-var-demo {
  background: var(--warn-bg);
  color: var(--warn);
  border-radius: 4px;
  padding: 0 4px;
  box-shadow: inset 0 0 0 1px var(--warn);
}

/* item 19: acordeón de plantillas no disponibles (cerrado por defecto). */
.tmpl-accordion { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 6px; }
.tmpl-acc-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: none; /* item 22 (nuevo): quitar el border por defecto del botón */
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  padding: 8px 4px;
  cursor: pointer;
  text-align: left;
}
.tmpl-acc-head:hover { color: var(--text); }
.tmpl-acc-ic { display: inline-flex; transition: transform .2s ease; }
.tmpl-accordion.open .tmpl-acc-ic { transform: rotate(180deg); }
/* Transición suave al desplegar/plegar (item 22 nuevo). */
.tmpl-acc-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  padding-top: 0;
  transition: max-height .28s ease, opacity .2s ease, padding-top .28s ease;
}
.tmpl-accordion.open .tmpl-acc-body { max-height: 1200px; opacity: 1; padding-top: 6px; }

/* Elementos solo para móvil (ocultos en escritorio). */
.mobile-only { display: none; }

/* --- Responsive: una sola columna estilo WhatsApp/Telegram --- */
@media (max-width: 720px) {
  .mobile-only { display: inline-flex; }
  .resizer { display: none; } /* sin redimensionado manual en móvil (una columna) */
  /* El divisor del rail SÍ vive en tablet (ahí el menú es una columna de verdad y se puede
     querer más ancho), y NO en el móvil: aquí el menú se abre ENCIMA del contenido y se
     cierra al elegir, así que su ancho no es una preferencia, es un tamaño de paso.
     Estaba apagado desde 1024px, que se llevaba por delante también a las tablets. */
  .rail-resizer { display: none; }
  /* item 20: fijar el panel de depuración no aporta nada en móvil (ocupa todo). */
  #debug-pin-btn { display: none; }

  .sidebar {
    width: 100% !important;
    min-width: 0;
    border-right: none;
  }
  /* item 30: en móvil solo hay una columna a la vez (lista O chat), así que la barra
     de audio vuelve a ocupar todo el ancho: no hay lista al lado de la que separarse. */
  .audio-bar { left: 0; }
  /* item 35: como la barra ocupa todo el ancho en móvil, empuja la columna visible
     (lista o chat) desde la .app, no solo el chat. */
  .app.with-audio-bar .chat { padding-top: 0; }
  .app.with-audio-bar { padding-top: 52px; }
  /* Por defecto se ve la lista; al abrir un chat se muestra la conversación. */
  .app .chat { display: none; }
  .app.chat-open .sidebar { display: none; }
  .app.chat-open .chat { display: flex; }

  /* item 77: la navegación lista↔chat entraba de golpe, y es el caso de libro donde el
     movimiento HACE FALTA: sin él no se sabe si el chat vino de la derecha o sustituyó a
     la lista, y volver se siente como un salto. Mismo patrón que el panel de Configuración.
     Al SALIR el chat tiene que seguir montado mientras se va, de ahí la clase "leaving" y
     el temporizador de app.js: quitar chat-open de una vez lo haría desaparecer sin animar.
     Animación y no transición: si no llega a correr, el elemento queda en su estado natural
     y no a medias. */
  .app.chat-open .chat { animation: chatIn .2s ease; }
  .app.leaving-chat .chat { display: flex; animation: chatOut .18s ease forwards; }
  .app.leaving-chat .sidebar { display: none; }
  .app.entering-list .sidebar { animation: listIn .2s ease; }
  @media (prefers-reduced-motion: reduce) {
    .app.chat-open .chat, .app.leaving-chat .chat, .app.entering-list .sidebar { animation: none; }
  }

  .messages { padding: 14px 12px; }
  .bubble { max-width: 85%; }
  /* item 32: en móvil el composer mide 56px (no 68 como en escritorio), así que el
     bottom:68px heredado dejaba un hueco de 12px bajo el panel. Se iguala a 56px para
     que el panel llegue justo hasta el composer, sin espacio muerto. */
  .debug-panel { width: 100%; top: 56px; bottom: var(--dock-h, 56px); }
  /* En móvil el panel es un overlay a ancho completo: sin inset del cuerpo. */
  .conversation.debug-pinned { padding-right: 0; }
  .conversation.debug-pinned .chat-header,
  .conversation.debug-pinned .composer { margin-right: 0; }
  .chat-header { min-height: 56px; }
  .sidebar-header { height: 56px; }
  /* item 18: el composer va con min-height, NO con height. Con altura fija, cualquier
     contenido que saltara de línea se salía de su caja — se veía con el aviso de ventana
     cerrada, que en móvil no cabe en una línea, y también al escribir varias líneas. Con
     min-height crece como en escritorio y --dock-h (que se mide en vivo) lo sigue. */
  .composer { min-height: 56px; }
  /* Con las tres filas de títulos hace falta aire arriba y abajo: sin él, los badges y el
     teléfono quedan pegados al borde (item R6, b_23). 8px vertical y 12px lateral son los
     escalones de la escala que tocan aquí; el gap NO se reduce, para que la separación
     entre acciones se lea igual que en escritorio. */
  .chat-header { padding: 8px 12px; }
  /* Área táctil de 44px (Apple HIG) / 48dp (Material): a 34px estos botones se fallan con
     el pulgar. El icono no crece, crece su caja. */
  .chat-header .icon-btn, .chat-header .mic-btn { width: 44px; height: 44px; }
  /* En móvil se oculta el TEXTO de "Ver más" (su icono, la ⓘ, es convención y además la
     acción está por su nombre en el menú de la cabecera).
     PERO NO EL DE TOMAR/SOLTAR/TRANSFERIR (b_23, dicho dos veces): esas acciones NO se
     entienden con el icono solo —una flecha no dice "transferir a otro departamento"— y
     además cambian el estado de una conversación delante de un cliente. La regla del
     manual es explícita: icono + texto si la acción no es obvia solo con icono. El sitio
     sale del menú de la cabecera, que es donde se fueron buscar/renombrar/eliminar. */
  /* NINGÚN BOTÓN PIERDE SU TEXTO POR SER MÓVIL (b_23, dicho dos veces): una flecha no
     dice "transferir a otro departamento". Si no cabe, se va ENTERO al menú, con su
     nombre escrito. */
  /* items 48/50/56: el texto de .bot-toggle NO se oculta aquí. Dejaba el switch del bot
     sin etiqueta en la cabecera y, como los switches de configuración reusan .bot-toggle,
     borraba también sus textos ("Bot activo por defecto…", "Modo debug…"), que son la
     única pista de qué hace cada uno. Hay sitio: a 320-430px la cabecera no desborda
     porque el nombre del chat cede el espacio con su elipsis. */
  /* item 29 + R6: en móvil el lápiz, la basura y la lupa ceden su sitio a lo que SÍ tiene
     que estar a la vista (Tomar/Transferir con su texto, y el switch del bot). Las tres
     acciones están, con su nombre escrito, en el menú "Más acciones" de la cabecera. */
  /* AQUÍ YA NO SE ESCONDE NADA A MANO: lo que cabe lo decide overflowbar.js midiendo, y
     lo que no cabe se va al menú de los tres puntos CON SU NOMBRE. Las reglas por
     breakpoint que había acertaban en un móvil concreto y fallaban en cuanto se estrechaba
     la lista o se fijaba el panel lateral — que es lo que enseñaban b_23 y b_27. */
  /* "Transferir" también se va al menú, con su nombre. "VER MÁS" NO (petición tuya): no
     declara `data-of-label`, así que no participa y se queda al final de la fila, siempre
     visible — es el interruptor de un panel que está en pantalla, y esconderlo dentro de un
     menú obligaba a abrir un menú para cerrar lo que se está viendo. */
  /* El selector es LARGO a propósito. `.dept-actions .ghost-btn { display: inline-flex }`
     vive 200 líneas más abajo, así que con la misma especificidad ganaba por orden y esta
     regla quedaba escrita sin hacer nada. Con tres clases gana siempre, esté donde esté:
     una regla que depende de en qué línea la escribiste se rompe al reordenar el archivo. */
  .scroll-bottom { bottom: calc(var(--dock-h, 56px) + 20px); right: 16px; }
  .reaction-picker { width: calc(100vw - 24px); }
  .msg-menu { min-width: 180px; }
  .selection-bar { height: 56px; }
}

/* Pantallas muy angostas: burbujas más anchas. */
@media (max-width: 420px) {
  .bubble { max-width: 90%; }
  .chat-name { font-size: 14px; }
}

/* Separador de día en listas agrupadas por fecha (item 51): notificaciones y cotizaciones.
   Mismo componente en los dos sitios para que se lean igual. */
.notif-day, .odoo-day {
  font-size: 11px; font-weight: 700; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: .04em;
  padding: 10px 4px 5px;
}
.notif-day:first-child, .odoo-day:first-child { padding-top: 2px; }

/* item 62: el popover de presencia agrupa por rol. El título del grupo y el "··· y N más"
   no son acciones, así que se distinguen del resto de filas del menú: sin hover, sin
   cursor de pulsable y en tono atenuado. */
.msg-menu-title {
  padding: 6px 12px 2px; font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .3px; color: var(--text-dim);
}
.msg-menu-more { padding: 2px 12px 6px 34px; font-size: 12px; color: var(--text-dim); }
/* Lista completa (modal "Ver todos"): el popover flota junto a una fila, así que crecer sin
   límite lo sacaría de la pantalla; a partir de cierto número se enseña aquí. */
.presence-all-head {
  margin: 12px 0 4px; font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .3px; color: var(--text-dim);
}
.presence-all-head:first-child { margin-top: 0; }
.presence-all-list { list-style: none; margin: 0; padding: 0; }
.presence-all-list li { display: flex; align-items: center; gap: 8px; padding: 6px 0; }
.presence-all-list .ic { width: 15px; height: 15px; color: var(--text-dim); }

/* ==========================================================================
   Mensajes rápidos (item 125)
   ========================================================================== */

/* --- El buscador que abre "/" en el composer --- */
/* Anclado al TOPE del composer, como la bandeja de emojis: crece el editor, sube él.
   La entrada va por ANIMATION y no por una transición disparada desde JS: una animación
   sin fill-mode termina siempre en el estado natural del elemento, así que si el navegador
   no llega a correr el primer frame (pestaña de fondo) no queda invisible para siempre. */
.qr-picker {
  position: absolute;
  bottom: calc(100% - 6px);
  left: 12px;
  right: 12px;
  max-width: 560px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 24px var(--shadow);
  overflow: hidden;
  z-index: 40;
  animation: qr-in 160ms ease-out;
}
.qr-picker.leaving { animation: qr-out 140ms ease-in forwards; }
@keyframes qr-in  { from { opacity: 0; transform: translateY(8px) scale(.985); } }
@keyframes qr-out { to   { opacity: 0; transform: translateY(6px) scale(.985); } }

.qr-list { max-height: 260px; overflow-y: auto; padding: 4px; }
.qr-item {
  display: flex; align-items: baseline; gap: 8px;
  padding: 8px 10px; border-radius: 8px; cursor: pointer;
  /* Cambio de estado, no de sitio: 120 ms y solo el color (el mandato de movimiento pide
     animar lo que se desplaza; un hover que se mueve marea). */
  transition: background-color 120ms ease;
}
.qr-item.active, .qr-item:hover { background: var(--bg-hover); }
.qr-shortcut {
  flex: 0 0 auto; font-weight: 700; font-size: 13px; color: var(--accent);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.qr-preview {
  flex: 1; min-width: 0; font-size: 13px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.qr-tag {
  flex: 0 0 auto; font-size: 10px; font-weight: 700; letter-spacing: .3px;
  text-transform: uppercase; padding: 2px 6px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--text-dim);
}
/* El cajón se distingue por la FORMA además del color (relleno vs. contorno): quien no
   separa bien los tonos sigue leyendo cuál es del equipo. */
.qr-tag-team { background: var(--bg-hover); color: var(--text); }
.qr-tag-mine { background: transparent; }
.qr-empty { padding: 18px 14px; font-size: 13px; color: var(--text-dim); text-align: center; }
.qr-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 10px; border-top: 1px solid var(--border); background: var(--bg-elev);
}
.qr-hint { font-size: 11px; color: var(--text-dim); }
.qr-manage {
  display: inline-flex; align-items: center; gap: 5px;
  background: none; border: 0; cursor: pointer;
  font-size: 12px; color: var(--text-dim); padding: 4px 6px; border-radius: 6px;
  transition: background-color 120ms ease, color 120ms ease;
}
.qr-manage:hover { background: var(--bg-hover); color: var(--text); }
.qr-manage .ic { width: 14px; height: 14px; }

/* --- Gestión (la misma pieza en Configuración y en la modal del composer) --- */
.qr-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.qr-search { position: relative; flex: 1; display: flex; align-items: center; }
.qr-search .ic {
  position: absolute; left: 10px; width: 15px; height: 15px;
  color: var(--text-dim); pointer-events: none;
}
.qr-search input {
  flex: 1; width: 100%;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 12px 9px 32px; color: var(--text); font-size: 13px;
  transition: border-color 120ms ease;
}
.qr-search input:focus { border-color: var(--accent); outline: none; }
.qr-manage-list { max-height: 46vh; overflow-y: auto; }
.qr-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 8px; border-bottom: 1px solid var(--border);
  /* Las filas ENTRAN al filtrar (el contenedor se repinta entero): sin esto aparecen de
     golpe y no se ve que la lista cambió. */
  animation: qr-row-in 150ms ease-out;
}
.qr-row:last-child { border-bottom: 0; }
@keyframes qr-row-in { from { opacity: 0; transform: translateY(-4px); } }
/* La cabecera de la fila ES el disparador del acordeón (item 19), así que es un <button>:
   un <div> con onclick no se alcanza con el teclado ni lo anuncia un lector de pantalla.
   Se le quitan los estilos de botón porque no parece uno ni debe parecerlo. */
.qr-row-main {
  flex: 1; min-width: 0;
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.qr-row-top { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
.qr-chevron {
  margin-left: auto; display: inline-flex; color: var(--text-dim);
  transition: transform 160ms ease;
}
.qr-chevron .ic { width: 15px; height: 15px; }
.qr-row.abierta .qr-chevron { transform: rotate(180deg); }
/* CERRADA una línea; ABIERTA el texto entero. Se anima el max-height y no se cambia el
   white-space de golpe: el salto de "nowrap" a "pre-wrap" sin transición es justo lo que
   hace que un acordeón se sienta roto. */
.qr-row-text {
  font-size: 13px; color: var(--text-dim); line-height: 1.5;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-height: 1.5em; transition: max-height 180ms ease;
}
.qr-row.abierta .qr-row-text { white-space: pre-wrap; max-height: 30em; }
/* El texto CRUDO (con sus {{variables}}) solo cuando está desplegada: quien gestiona
   necesita ver dónde están, pero cerrada sería una segunda línea de ruido en cada fila. */
.qr-row-crudo {
  font-size: 11.5px; color: var(--text-dim); line-height: 1.5;
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height 180ms ease, opacity 180ms ease, margin-top 180ms ease;
}
.qr-row.abierta .qr-row-crudo { max-height: 12em; opacity: 1; margin-top: 6px; }
.qr-row-crudo code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--accent-strong);
}
.qr-uses { font-size: 11px; color: var(--text-dim); }
.qr-uses-none { font-style: italic; opacity: .75; }
/* Editar y borrar de una fila/tarjeta (departamentos y mensajes rápidos: la misma pieza).
   El dibujo era de 15px dentro de un botón de 34: en un móvil eso es un blanco que se falla,
   y son las dos únicas acciones de la tarjeta. */
.qr-row-acts { display: flex; gap: 4px; flex: 0 0 auto; }
.qr-row-acts .ic { width: 17px; height: 17px; }
/* En táctil, 44px de área — el mínimo de Apple HIG y Material, y aquí se nota porque el
   vecino es "eliminar". */
@media (max-width: 720px) {
  .qr-row-acts .icon-btn { width: 44px; height: 44px; }
  .qr-row-acts .ic { width: 19px; height: 19px; }
}
.qr-locked {
  font-size: 11px; color: var(--text-dim); font-style: italic;
  white-space: nowrap; padding: 0 4px;
}

/* --- Editor --- */
.qr-shortcut-field { display: flex; align-items: center; }
.qr-slash {
  padding: 11px 2px 11px 12px; font-weight: 700; color: var(--text-dim);
  background: var(--bg-elev); border: 1px solid var(--border); border-right: 0;
  border-radius: 8px 0 0 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.qr-shortcut-field input { flex: 1; border-radius: 0 8px 8px 0 !important; }
.qr-modal .modal-card, .qr-edit-modal { max-width: 620px; }
.qr-edit-modal textarea {
  width: 100%; resize: vertical;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
  padding: 11px 13px; color: var(--text); font-size: 14px; font-family: inherit;
  transition: border-color 120ms ease;
}
.qr-edit-modal textarea:focus { border-color: var(--accent); outline: none; }
.qr-vars { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: -4px 0 12px; }
.qr-var {
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 999px;
  padding: 3px 9px; font-size: 11.5px; color: var(--text-dim); cursor: pointer;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.qr-var:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
.qr-preview-box { margin-top: 4px; }
.qr-preview-box label { font-size: 12px; color: var(--text-dim); font-weight: 600; }
.qr-preview-text {
  margin-top: 6px; padding: 10px 12px;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
  font-size: 13.5px; color: var(--text); white-space: pre-wrap; word-break: break-word;
  min-height: 42px;
}
.qr-preview-text.empty { color: var(--text-dim); font-style: italic; }

/* El movimiento explica de dónde sale algo; a quien pide menos movimiento no le explica
   nada y le molesta. Se quedan los cambios de color, que no desplazan nada. */
@media (prefers-reduced-motion: reduce) {
  .qr-picker, .qr-picker.leaving, .qr-row { animation: none; }
  .qr-chevron, .qr-row-text, .qr-row-crudo { transition: none; }
}

/* ==========================================================================
   Departamentos (item 126)
   ========================================================================== */

/* --- El chip de la lista y la cabecera --- */
/* El punto de color va ADEMÁS del nombre, nunca en su lugar: un departamento identificado
   solo por color deja fuera a quien no los distingue, y a cualquiera con más de cuatro. */
.dept-chip {
  display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
  font-size: 11px; font-weight: 600; color: var(--text-dim);
  padding: 1px 7px 1px 5px; border: 1px solid var(--border); border-radius: 999px;
  /* 200px y no 130: "Soporte técnico" no entraba y se leía "Soporte téc…", que es peor que
     no poner nada — un nombre a medias hay que adivinarlo. Sigue habiendo tope porque un
     equipo con nombre kilométrico no puede comerse la fila entera. */
  max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dept-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }

/* --- Tomar / Soltar / Transferir --- */
.dept-actions { display: inline-flex; align-items: center; gap: 6px; }
.dept-actions .ghost-btn { display: inline-flex; align-items: center; gap: 5px; }
.dept-actions .ic { width: 14px; height: 14px; }
/* Entran con movimiento porque APARECEN Y DESAPARECEN solos: si un compañero toma el chat
   que tienes abierto, "Tomar" se va y llega su nombre. Sin animación parece un parpadeo. */
.dept-actions > * { animation: dept-act-in 160ms ease-out; }
@keyframes dept-act-in { from { opacity: 0; transform: translateY(-3px); } }
.dept-owner {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--text-dim);
  padding: 4px 8px; border: 1px dashed var(--border); border-radius: 8px;
}
.dept-owner .ic { width: 13px; height: 13px; }

/* Etiqueta FIJA de la hilera de filtros (item 137): dice en qué bandeja estás. Se
   distingue de un filtro a propósito —sin borde ni fondo de pastilla— para que nadie
   intente pulsarla esperando que filtre algo. */
.filter-fija {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  font-size: 12px; font-weight: 600; color: var(--text-dim);
  padding: 5px 2px 5px 0; margin-right: 2px;
}
.filter-fija-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }

/* Cada tema de la Ayuda es una TARJETA: con solo una línea divisoria, seis acordeones
   seguidos se leían como una lista y no como seis cosas que se abren. El fondo y el radio
   dicen "esto es una pieza" antes de leer nada. */
.help-item {
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px;
  margin-bottom: 10px; overflow: hidden;
  transition: border-color .13s ease;
}
.help-item:hover { border-color: var(--border-strong, var(--accent)); }
.help-item[open] { border-color: var(--accent); }

/* --- Control segmentado (item 20) ---
   Un riel con las opciones dentro y la activa en relieve. El fondo del carril es lo que
   dice que las tres van juntas y que solo una puede estar puesta; sin él serían tres
   botones sueltos que parecen acciones. */
.seg-control {
  display: flex; gap: 2px; padding: 3px; margin-bottom: 12px;
  background: var(--bg-hover); border-radius: 10px;
}
.seg-opt {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 7px 8px; border: 0; border-radius: 8px; cursor: pointer;
  background: transparent; color: var(--text-dim);
  font: inherit; font-size: 12.5px; font-weight: 600;
  white-space: nowrap;
  transition: background-color .13s ease, color .13s ease;
}
/* Se recorta EL TEXTO, no el botón: con el recorte en el botón, el puntito de "tiene
   filtros activos" se iba con los puntos suspensivos en la pestaña más larga. */
.seg-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.seg-opt:hover { color: var(--text); }
/* La activa lleva SUPERFICIE, no solo color: es lo que se lee como "estás aquí" de un
   vistazo y lo que la distingue en escala de grises. */
.seg-opt.on { background: var(--bg-panel); color: var(--text); box-shadow: 0 1px 3px var(--shadow); }
/* El puntito avisa de que ESA pestaña, que no estás mirando, tiene algo marcado. Es la
   contrapartida de que los filtros se acumulen entre grupos: sin él la lista saldría
   recortada por una condición que no está en pantalla. */
.seg-punto {
  flex: 0 0 auto; /* nunca se encoge ni se recorta: es el aviso, no el adorno */
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
}
/* En la bandeja el segmentado es la cabecera de los filtros, no un separador de secciones:
   sin el margen de abajo (que allí sí hace falta) y algo más compacto, que la columna es
   estrecha. */
.chat-filters .seg-control { margin-bottom: 0; }
.chat-filters .seg-opt { padding: 6px 6px; font-size: 12px; }

/* La versión, al lado del nombre del producto en "Acerca de" (item 111). Discreta: es un
   dato de referencia para reportar un fallo, no un titular. */
.cfg-version {
  font-size: 11.5px; font-weight: 600; color: var(--text-dim);
  background: var(--bg-hover); border-radius: 999px; padding: 1px 8px; margin-left: 6px;
  vertical-align: middle; font-variant-numeric: tabular-nums;
}

/* Subtítulo dentro de una sección de Configuración: separa dos ajustes que conviven en
   el mismo apartado (cómo se reparte / quién puede ver). Sin él, los dos bloques de radios
   se leían como un solo grupo de cuatro opciones. */
.cfg-sub {
  margin: 22px 0 10px; font-size: 12px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-dim);
}
.cfg-sub:first-child { margin-top: 0; }

/* --- Gestión (Configuración → Departamentos) --- */
.dept-mode { margin-bottom: 16px; }
.dept-mode-opt {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 9px 10px; border: 1px solid var(--border); border-radius: 8px;
  margin-bottom: 6px; cursor: pointer; font-size: 13px; line-height: 1.45;
  transition: background-color 120ms ease, border-color 120ms ease;
}
.dept-mode-opt:hover { background: var(--bg-hover); }
.dept-mode-opt input { margin-top: 2px; flex: 0 0 auto; }
.dept-mode-opt b { color: var(--text); }
.dept-mode-opt span { color: var(--text-dim); }
/* La acción principal de una pantalla va ARRIBA Y A LA IZQUIERDA (petición tuya): es
   donde empieza la lectura, y a la derecha competía con el scroll de la lista. La misma
   clase la usan Departamentos y Usuarios para que las dos pantallas se comporten igual. */
.dept-toolbar, .cfg-toolbar { display: flex; justify-content: flex-start; margin-bottom: 12px; }
/* El icono y el texto del botón, con su aire. Iba solo en la barra de Departamentos, así
   que en "Añadir usuario" el dibujo salía pegado a la palabra: mismo componente, dos
   aspectos. Las dos barras comparten regla justamente para que eso no vuelva a pasar. */
.dept-toolbar .modal-btn, .cfg-toolbar .modal-btn { display: inline-flex; align-items: center; gap: 7px; }
.dept-toolbar .ic, .cfg-toolbar .ic { width: 15px; height: 15px; }
.dept-list { max-height: 42vh; overflow-y: auto; }
.dept-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 8px; border-bottom: 1px solid var(--border);
  animation: qr-row-in 150ms ease-out;
}
.dept-row:last-child { border-bottom: 0; }
.dept-row .dept-dot { width: 10px; height: 10px; }
.dept-row-main { flex: 1; min-width: 0; }
.dept-row-top { display: flex; align-items: center; gap: 8px; }
.dept-row-sub {
  font-size: 12px; color: var(--text-dim); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Un departamento sin nadie NO se disimula: es un buzón donde se acumulan conversaciones
   que no atiende nadie, y verlo es justo el objetivo. */
.dept-warn { display: inline-flex; align-items: center; gap: 5px; color: var(--warn, #d97706); }
.dept-warn .ic { width: 13px; height: 13px; }
.dept-members {
  max-height: 200px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 8px; padding: 4px;
}
.dept-member {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 8px; border-radius: 6px; cursor: pointer; font-size: 13px;
  transition: background-color 120ms ease;
}
.dept-member:hover { background: var(--bg-hover); }
.dept-member-role { display: block; font-size: 11px; color: var(--text-dim); }
.dept-edit-modal input[type="color"] {
  width: 56px; height: 34px; padding: 2px; cursor: pointer;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
}

@media (prefers-reduced-motion: reduce) {
  .dept-actions > *, .dept-row { animation: none; }
}

/* ==========================================================================
   Métricas de atención (item 127)
   ========================================================================== */

.met-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
.met-periodos { display: flex; gap: 2px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px; padding: 2px; }
.met-per {
  background: none; border: 0; cursor: pointer; border-radius: 6px;
  padding: 6px 11px; font-size: 12.5px; color: var(--text-dim);
  transition: background-color 120ms ease, color 120ms ease;
}
.met-per:hover { color: var(--text); }
.met-per.active { background: var(--bg-panel); color: var(--text); font-weight: 600; }
.met-refresh { margin-left: auto; }

.met-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 14px; }
.met-card {
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-elev);
  animation: qr-row-in 180ms ease-out;
}
.met-card-val { font-size: 26px; font-weight: 700; line-height: 1.1; }
.met-card-tit { font-size: 12px; color: var(--text-dim); line-height: 1.35; }
.met-card-pie { font-size: 11px; color: var(--text-dim); opacity: .8; margin-top: 2px; }

/* El aviso de lo que NO está contado va arriba y con forma propia: quien lee tiene que
   verlo antes de sacar conclusiones, no en una nota al pie que nadie lee. */
.met-aviso {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 10px 12px; margin-bottom: 14px; border-radius: 8px;
  background: color-mix(in srgb, var(--warn, #d97706) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn, #d97706) 35%, transparent);
  font-size: 12.5px; line-height: 1.45; color: var(--text);
}
.met-aviso .ic { width: 15px; height: 15px; flex: 0 0 auto; margin-top: 1px; color: var(--warn, #d97706); }

/* Una tabla ancha scrollea DENTRO de su caja; la página nunca scrollea en horizontal. */
/* La tabla es una TARJETA, no texto suelto sobre el fondo (corrección del 11-08): contorno,
   esquinas redondeadas y superficie propia, como cualquier app moderna. En tema claro,
   además, sin superficie propia no se veía dónde empezaba ni acababa. */
.met-tabla-wrap {
  overflow-x: auto;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.met-tabla { width: 100%; border-collapse: collapse; font-size: 13px; }
/* El encabezado se distingue del cuerpo con su propio tono: es la referencia fija mientras
   se leen las filas, y sin separación visual se lee como una fila más. */
.met-tabla thead th { background: var(--bg-hover); }
.met-tabla thead th:first-child { border-top-left-radius: 11px; }
.met-tabla thead th:last-child { border-top-right-radius: 11px; }
.met-tabla th {
  text-align: left; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .3px; color: var(--text-dim); padding: 8px 10px;
  border-bottom: 1px solid var(--border); white-space: nowrap; cursor: help;
}
.met-tabla td { padding: 9px 10px; border-bottom: 1px solid var(--border); }
.met-tabla tr:last-child td { border-bottom: 0; }
.met-tabla tbody tr { transition: background-color 120ms ease; }
.met-tabla tbody tr:hover { background: var(--bg-hover); }
.met-nombre { font-weight: 600; }
.met-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.met-dim { color: var(--text-dim); }
.met-nota { margin-top: 12px; line-height: 1.5; }

@media (prefers-reduced-motion: reduce) { .met-card { animation: none; } }

/* ==========================================================================
   Rail de navegación (item 129, fase 1)
   ========================================================================== */

/* El HUECO del rail no se mueve nunca: .nav-rail reserva el ancho compacto en el flujo y
   lo que crece al desplegar es .rail-inner, que flota encima. Ese es el punto del item
   129, no un detalle de estilo: si creciera el hueco, abrir el menú empujaría el chat que
   alguien está leyendo. Se probó al revés primero (absolute sobre el propio rail) y la
   bandeja saltaba al borde izquierdo, que es exactamente lo que había que evitar. */
.nav-rail {
  flex: 0 0 var(--rail-w, 56px);
  width: var(--rail-w, 56px);
  position: relative;
  z-index: 60;
}
.rail-inner {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: var(--rail-w, 56px);
  background: var(--bg-panel);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 8px;
  overflow: hidden;
  transition: width 170ms ease, box-shadow 170ms ease;
}
/* El ancho DESPLEGADO vive en --rail-open-w (item 129 + petición del 12-08). Es una
   variable y no un 232px repetido porque lo consumen cuatro reglas —el hueco fijado, el
   panel flotante y sus dos versiones de móvil—: con el número escrito a mano, arrastrar
   el divisor movía una y dejaba las otras tres donde estaban. */
.nav-rail.open .rail-inner {
  width: var(--rail-open-w, 232px);
  box-shadow: 4px 0 24px var(--shadow);
}
/* El divisor solo existe con el menú abierto: plegado no hay nada que redimensionar y un
   borde con cursor de arrastre invitaría a un gesto que no hace nada. */
.rail-resizer {
  position: absolute;
  top: 0; bottom: 0; right: 0;
  width: 6px; margin-right: -3px;
  cursor: col-resize; z-index: 10;
  background: transparent;
  opacity: 0; pointer-events: none;
  transition: background-color .12s ease, opacity .12s ease;
  touch-action: none; /* igual que .resizer: sin esto el dedo hace scroll y no arrastra */
}
/* El divisor va donde ESTÁ el borde que se arrastra. Plegado el rail mide 56px, pero al
   desplegarse flotando el hueco NO crece (crece .rail-inner encima), así que con `right:0`
   el divisor se quedaba en el borde del hueco —a 56px— mientras el panel llegaba a 232.
   Anclándolo a --rail-open-w cae en el borde real en los dos casos: flotando y fijado. */
.nav-rail.open .rail-resizer, .nav-rail.pinned .rail-resizer {
  opacity: 1; pointer-events: auto;
  left: var(--rail-open-w, 232px); right: auto;
}
.rail-resizer:hover, body.resizing .rail-resizer { background: var(--accent); }
/* El fondo del menú en móvil: DETRÁS del rail y delante de todo lo demás.
   Y por eso NO puede ser hijo de `.nav-rail`: el rail tiene z-index propio, así que crea
   su propio contexto de apilamiento y un hijo con z-index menor sigue pintándose ENCIMA de
   sus hermanos de dentro. El velo acababa oscureciendo el propio menú, que es lo que se
   veía. Es hermano del rail, y ahí sí manda el z-index. */
.rail-backdrop {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(0, 0, 0, .45);
  animation: backdropIn 160ms ease;
}
.rail-backdrop[hidden] { display: none; }
@keyframes backdropIn { from { opacity: 0; } }
/* En escritorio el rail no tapa nada: se despliega al lado y el trabajo de detrás sigue
   siendo legible. Un velo ahí sería apagar la pantalla para abrir un menú. */
@media (min-width: 721px) { .rail-backdrop { display: none !important; } }
/* MIENTRAS SE ARRASTRA, sin transición. El rail anima su ancho en 170ms para plegarse y
   desplegarse; con esa animación puesta, el borde persigue al cursor con retraso y el
   arrastre se siente pegajoso. La animación sigue estando para lo suyo. */
body.resizing .nav-rail, body.resizing .rail-inner { transition: none; }
/* Al RESTAURAR con doble clic, los tres paneles vuelven animados. Es solo para ese gesto:
   se pone la clase, se quita la variable y se retira la clase al acabar. Durante el
   arrastre no puede haber transición (el borde iría por detrás del cursor), y por eso no
   se declara en la regla normal de cada panel. */
body.restaurando .sidebar,
body.restaurando .info-panel,
body.restaurando .nav-rail,
body.restaurando .rail-inner { transition: width 180ms ease, flex-basis 180ms ease; }

/* SOLO ESTA LISTA SCROLLEA. Con el menú entero desbordando, lo primero que se salía de
   la pantalla eran el tema, la configuración y la cuenta —que están al final— y quedaban
   inalcanzables. Así la marca se queda arriba, el pie abajo, y lo que cede es la lista de
   secciones, que es lo que puede crecer.
   `min-height: 0` es obligatorio: sin él un hijo flexible no encoge por debajo de su
   contenido y el scroll no llega a aparecer nunca. */
.rail-items {
  display: flex; flex-direction: column; gap: 4px;
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
  scrollbar-width: thin;
}
/* LOS BOTONES NO SE ENCOGEN: cuando falta alto, la LISTA hace scroll (petición tuya). Un
   flex reparte el hueco que falta entre sus hijos, así que sin `flex-shrink: 0` las filas se
   aplastaban una por una y el scroll no aparecía nunca — se veían diez botones enanos en vez
   de seis normales y un scroll.
   El pie (tema, configuración y tu cuenta) queda FUERA de esa lista a propósito: son las tres
   cosas que tienen que estar siempre a la vista, sin buscarlas bajando. */
.rail-items > * { flex: 0 0 auto; }
.rail-btn {
  display: flex; align-items: center; gap: 12px;
  width: 100%; height: 40px; padding: 0 10px;
  background: none; border: 0; border-radius: 10px; cursor: pointer;
  color: var(--text-dim); font-size: 13.5px; text-align: left;
  white-space: nowrap; overflow: hidden;
  transition: background-color 130ms ease, color 130ms ease;
}
.rail-btn:hover { background: var(--bg-hover); color: var(--text); }
.rail-btn.active { background: var(--bg-hover); color: var(--text); font-weight: 600; }
.rail-ic { flex: 0 0 20px; display: inline-flex; align-items: center; justify-content: center; }
.rail-ic .ic { width: 19px; height: 19px; }
/* El texto existe SIEMPRE en el DOM (para lectores de pantalla y para que no haya que
   repintar al desplegar); lo que cambia es si se ve. */
.rail-txt { opacity: 0; transition: opacity 130ms ease; }
.nav-rail.open .rail-txt { opacity: 1; }
/* La fila de la marca: el nombre a la izquierda y los botones al final, como pediste.
   `margin-left: auto` en el primero los empuja a los dos. */
/* 6px en TODA la fila, y el mismo hueco en `.rail-brand-acts`: el punto de estado tenía 4px
   a la izquierda (2 de la fila + 2 de su margen) y 8 a la derecha (2 + los 6 del grupo), y
   esa diferencia de 4px se ve — un punto descentrado parece mal colocado aunque nadie sepa
   decir por qué. Un solo valor para los dos lados y no hay nada que cuadrar a mano. */
.rail-brand-fila { display: flex; align-items: center; gap: 6px; }
.rail-brand-fila .rail-brand { flex: 1 1 auto; min-width: 0; }
.rail-brand-acts { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* PLEGADO NO CABE NADA MÁS QUE EL LOGO. Se intentó dejar el punto de estado visible ahí y
   el resultado fue el de la captura: 56px con logo + contador de sin leer + punto, todo
   pisándose. Plegado, el rail es una tira de iconos; el estado vuelve en cuanto se despliega,
   que es a un gesto de distancia. */
.nav-rail:not(.open):not(.pinned) .rail-brand-acts { display: none; }
/* LA CHINCHETA MIDE LO MISMO QUE LA MARCA (lo cazaste tú): la marca es una fila de 40px y el
   botón un .icon-btn de 34, así que las dos cajas redondeadas de la misma fila no cuadraban
   de alto. El ancho se queda en 34: lo que se lee desalineado es el alto. */
.rail-brand-fila .icon-btn { height: 40px; }
/* Junto a la marca, el punto de estado necesita área de pulsación propia: 10px de círculo se
   fallan con el ratón y no se ven con el foco del teclado. */
.rail-brand-acts .conn-dot { outline-offset: 4px; } /* sin margen propio: el hueco lo pone el gap */
/* R5: la marca del rail, que ADEMÁS es el botón de menú (como Gemini). En reposo se ve el
   logo; al pasar el cursor se convierte en la hamburguesa y al pulsar despliega. Son la
   misma cosa porque ocupaban dos filas para lo mismo: la de arriba del todo, que es donde
   la gente ya va a buscar el menú.
   El icono NO se mueve al cambiar: los dos viven apilados en la misma caja de 20px y lo
   único que cambia es cuál se ve. Si uno sustituyera al otro en el DOM, la fila daría un
   salto de un píxel en cada hover. */
.rail-brand {
  display: flex; align-items: center; gap: 12px;
  width: 100%; height: 40px; padding: 0 10px; margin-bottom: 4px;
  background: none; border: 0; border-radius: 10px; cursor: pointer;
  color: var(--text-dim); font: inherit; text-align: left;
  overflow: hidden; white-space: nowrap;
  transition: background-color 130ms ease;
}
.rail-brand:hover { background: var(--bg-hover); }
.rail-brand-ic { position: relative; flex: 0 0 20px; height: 20px; }
/* El contador sobre el isotipo, abajo a la derecha. `overflow: visible` en la caja del icono
   es obligatorio: mide 20px y el punto se sale por diseño. */
.marca-badge {
  position: absolute; right: -6px; bottom: -5px; z-index: 2;
  min-width: 15px; height: 15px; padding: 0 4px;
  border-radius: 999px; background: var(--notif); color: #fff;
  font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;
  /* El aro del color del fondo lo separa del dibujo del logo, que si no se leen pegados. */
  box-shadow: 0 0 0 2px var(--bg-elev);
  animation: marca-badge-in 140ms ease-out;
}
@keyframes marca-badge-in { from { opacity: 0; transform: scale(.6); } }
@media (prefers-reduced-motion: reduce) { .marca-badge { animation: none; } }
.marca-badge.hidden { display: none; }
/* Los DOS iconos que se turnan (logo y hamburguesa), apilados. Se nombran uno a uno y no
   con `> *`: ese selector alcanzaba también al contador de sin leer, y el `inset: 0` lo
   estiraba hasta ocupar el icono entero. */
.rail-brand-ic > .rail-brand-logo,
.rail-brand-ic > .rail-brand-menu {
  position: absolute; inset: 0;
  display: inline-flex; align-items: center; justify-content: center;
  transition: opacity 130ms ease;
}
.rail-brand-ic .ic { width: 20px; height: 20px; }
.rail-brand-menu { opacity: 0; color: var(--text); }
/* El cambio logo⇄hamburguesa cuelga de `.marca-menu` y NO de `.rail-brand`: la misma pieza
   la llevan el primer botón del rail y, en móvil, los botones de menú de la bandeja y de una
   sección (#rail-movil, #panel-menu). Atarlo a `.rail-brand` habría obligado a repetir las
   reglas o a copiar el marcado, que es justo como se torció el interruptor del popover. */
.marca-menu:hover .rail-brand-menu, .marca-menu:focus-visible .rail-brand-menu { opacity: 1; }
.marca-menu:hover .rail-brand-logo, .marca-menu:focus-visible .rail-brand-logo { opacity: 0; }
/* En la cabecera de la bandeja el botón es un .icon-btn (34px) y la marca vive centrada
   dentro, sin el hueco de 20px del rail. */
.icon-btn.marca-menu { padding: 0; }
.icon-btn.marca-menu .rail-brand-ic { flex: 0 0 20px; width: 20px; }
/* 20px, el ancho del hueco de icono del rail: así el isotipo comparte eje con Mensajes,
   Departamentos y el resto, en vez de quedar descolgado como le pasaba al avatar (R3). */
.rail-brand-logo { width: 20px; height: 20px; object-fit: contain; }
.rail-brand .logo { font-size: 15px; }

/* Las vistas: la bandeja y el panel de sección ocupan el mismo hueco. */
.app-view { flex: 1; display: flex; min-width: 0; }
.app-view.hidden { display: none; }
.app-panel {
  flex-direction: column; overflow-y: auto; padding: 26px 30px 40px;
  /* SUELO gris: las tarjetas y la tabla son blancas y se apoyan encima. Sin esto, panel y
     tarjetas eran del mismo blanco y la única separación era el contorno — que es justo lo
     que el usuario señaló que no se apreciaba. */
  background: var(--bg);
}
/* La cabecera de una sección lleva su botón de menú a la IZQUIERDA del título (solo en
   móvil, ver #panel-menu): es la única puerta de vuelta al resto de apartados. */
.panel-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 18px; }
.panel-head-txt { min-width: 0; flex: 1 1 auto; }
#panel-menu { flex: 0 0 auto; margin-top: 2px; }
.panel-head h2 { font-size: 21px; margin: 0 0 4px; }
.panel-sub { margin: 0; color: var(--text-dim); font-size: 13px; max-width: 70ch; line-height: 1.5; }
.panel-body { max-width: 900px; }
/* Entra con movimiento porque SUSTITUYE a la bandeja: sin él, cambiar de sección es un
   corte seco y no se ve de dónde salió lo nuevo. */
/* La MISMA animación para las tres vistas: la bandeja y las secciones entran igual.
   Antes solo la llevaba .app-panel y volver a Mensajes era un corte seco. */
.app-view.swapping { animation: panel-in 190ms ease-out; }
@keyframes panel-in { from { opacity: 0; transform: translateX(10px); } }

@media (prefers-reduced-motion: reduce) {
  .rail-inner, .rail-txt { transition: none; }
  .app-view.swapping { animation: none; }
}

/* ==========================================================================
   Departamentos y Métricas: forma, no solo texto (rediseño 2026-08-11)
   ========================================================================== */

/* --- Esqueletos de carga --- */
/* Se pinta la FORMA de lo que viene, no un "Cargando…" centrado: así la página no pega el
   salto al llegar los datos. El pulso es lento a propósito; uno rápido parece un error. */
.esqueleto { pointer-events: none; }
.esq-bloque {
  display: block; width: 100%; height: 100%; min-height: 34px; border-radius: 8px;
  background: var(--bg-hover); animation: esq-pulso 1.6s ease-in-out infinite;
}
@keyframes esq-pulso { 0%, 100% { opacity: .45; } 50% { opacity: .85; } }
.met-fila-esq { height: 44px; padding: 5px 0; }

/* --- Departamentos: tarjetas --- */
.dept-intro { margin: 0 0 14px; }
/* OJO: `.dept-toolbar` ESTABA DECLARADA DOS VECES, aquí y 300 líneas más arriba, y esta
   —que la alineaba a la DERECHA— ganaba por estar debajo. Por eso "Nuevo departamento"
   seguía a la derecha después de haberlo movido. La regla vive ARRIBA, junto a la de
   Usuarios, porque son la misma pieza; aquí solo queda el margen propio de esta pantalla. */
.dept-toolbar { margin-bottom: 14px; }
.dept-list { display: flex; flex-direction: column; gap: 10px; max-height: none; }
.dept-row {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px 14px 0;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--bg-elev);
  position: relative; overflow: hidden;
  animation: qr-row-in 160ms ease-out;
  transition: border-color 130ms ease, transform 130ms ease;
}
.dept-row:hover { border-color: color-mix(in srgb, var(--dept-color, #6b7280) 55%, var(--border)); }
/* La barra de color es el MISMO color del chip de la bandeja: la identidad del
   departamento se aprende en un sitio y se reconoce en el otro. */
.dept-barra { position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--dept-color, #6b7280); }
.dept-row-main { flex: 1; min-width: 0; padding-left: 16px; }
.dept-row-top { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.dept-nombre { font-size: 15px; }
.dept-badge {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px;
  padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--dept-color, #6b7280) 16%, transparent);
  color: color-mix(in srgb, var(--dept-color, #6b7280) 75%, var(--text));
  border: 1px solid color-mix(in srgb, var(--dept-color, #6b7280) 35%, transparent);
}
.dept-badge.mio { background: var(--bg-hover); color: var(--text-dim); border-color: var(--border); }
.dept-caras { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.avatar-xs { width: 24px; height: 24px; font-size: 10px; flex: 0 0 auto; }
.dept-mas { font-size: 11px; color: var(--text-dim); margin-left: 3px; }
.dept-cifras { display: flex; gap: 14px; margin-top: 8px; }
.dept-cifra { font-size: 12px; color: var(--text-dim); }
.dept-cifra b { font-size: 15px; color: var(--text); margin-right: 3px; }
/* Una cola con gente esperando se ve DISTINTA, no solo con otro número: es lo único de
   esta pantalla que pide hacer algo hoy. */
.dept-cifra.urge b { color: var(--warn, #d97706); }
.dept-vacio {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 42px 24px; text-align: center; color: var(--text-dim);
  border: 1px dashed var(--border); border-radius: 12px;
}
.dept-vacio .ic { width: 30px; height: 30px; opacity: .5; margin-bottom: 4px; }
.dept-vacio p { margin: 0; max-width: 46ch; line-height: 1.5; font-size: 13px; }

/* --- Métricas --- */
.met-card { position: relative; overflow: hidden; }
/* Cada tarjeta lleva su tinte, y son SEMÁNTICOS (no de marca): el bot y las personas se
   distinguen igual en toda la consola. */
.met-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--tono, transparent); }
.met-humano { --tono: var(--author-client, #3b82f6); }
.met-bot { --tono: var(--author-bot, #10b981); }
.met-espera { --tono: var(--warn, #d97706); }
.met-card-val { font-variant-numeric: tabular-nums; }

/* --- Gráficos (charts.js) -------------------------------------------------
   Dos bloques que caben lado a lado en escritorio y se apilan en móvil. La tarta es
   pequeña a propósito: responde una pregunta de proporción, no compite con la tabla. */
.met-graficos {
  display: grid; gap: 18px; margin: 20px 0 4px;
  grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
  align-items: start;
}
.met-grafico h4 { margin: 0 0 3px; font-size: 13.5px; }
.met-grafico .modal-hint { margin: 0 0 10px; }
.chart { position: relative; }
.chart-svg { width: 100%; height: auto; display: block; overflow: visible; }
.chart-donut .chart-svg { max-width: 200px; margin: 0 auto; }
/* La leyenda de la TARTA se centra con ella (el gráfico ya va centrado con margin:auto).
   Alineada a la izquierda mientras el dibujo estaba centrado, el bloque se leía torcido.
   La de la LÍNEA no: allí el gráfico empieza en su eje y la leyenda lo acompaña. */
.chart-donut .chart-leyenda { justify-content: center; }
/* El texto del centro y los ejes van con TOKENS DE TEXTO, nunca con el color de la serie:
   el color identifica marcas, y una cifra coloreada se lee como si fuera otra categoría. */
.chart-donut-num { fill: var(--text); font-size: 26px; font-weight: 700; }
.chart-donut-pie { fill: var(--text-dim); font-size: 9.5px; }
.chart-eje { fill: var(--text-dim); font-size: 10px; }
/* La rejilla es la referencia, no el dato: tiene que verse menos que las líneas. */
.chart-grid { stroke: var(--border); stroke-width: 1; }
.chart-guia { stroke: var(--text-dim); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; }
.chart-leyenda {
  display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px;
  font-size: 12px; color: var(--text-dim);
}
.chart-leyenda-item { display: inline-flex; align-items: center; gap: 6px; }
.chart-punto { display: inline-block; width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
/* El globo sigue al cursor sobre la línea. `translateX(-50%)` lo centra en el día, y el
   margen del contenedor evita que se salga por los lados. */
.chart-tip {
  position: absolute; top: 4px; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 2px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 10px; font-size: 11.5px; color: var(--text);
  box-shadow: 0 6px 18px var(--shadow); pointer-events: none; white-space: nowrap; z-index: 2;
}
/* `display: flex` GANA al atributo [hidden] (que solo trae display:none del user-agent),
   así que sin esta línea el globo se queda pintado en la esquina desde que carga. */
.chart-tip[hidden] { display: none; }
.chart-tip span { display: inline-flex; align-items: center; gap: 6px; }
.chart-tip b { font-weight: 700; }

@media (max-width: 720px) {
  .met-graficos { grid-template-columns: 1fr; }
}

.met-persona { display: inline-flex; align-items: center; gap: 8px; }
/* La barra de volumen hace que la tabla se lea de un vistazo: sin ella hay que comparar
   cifras a mano, fila por fila. */
/* EL FALLO QUE ROMPIA LA TABLA (b_20.png): esto era `display:flex` sobre el propio <td>.
   Una celda con display:flex DEJA DE SER CELDA — sale del algoritmo de tabla — y con ella
   se va la alineación de la columna y el borde inferior de la fila, que dejaba de llegar
   de lado a lado. El flex va en un envoltorio DENTRO de la celda; el td se queda siendo td. */
.met-vol-in { display: flex; align-items: center; justify-content: flex-end; gap: 9px; }
.met-barra { display: block; width: 70px; height: 6px; border-radius: 999px; background: var(--bg-hover); overflow: hidden; }
.met-barra-fill { display: block; height: 100%; background: var(--author-client, #3b82f6); border-radius: 999px; }
.met-destacado { font-weight: 600; }
.met-vacio {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 42px 24px; text-align: center; color: var(--text-dim);
  border: 1px dashed var(--border); border-radius: 12px;
}
.met-vacio .ic { width: 30px; height: 30px; opacity: .5; margin-bottom: 6px; }
.met-vacio p { margin: 0; }

/* --- Pie del rail --- */
/* El pie NO entra en el scroll y NO se encoge: tema, configuración y tu cuenta se quedan
   fijas abajo aunque la lista de arriba desborde (lo pediste así). */
.rail-foot { margin-top: auto; flex: 0 0 auto; display: flex; flex-direction: column; gap: 4px; padding-top: 8px; border-top: 1px solid var(--border); }
.rail-foot > * { flex: 0 0 auto; }
/* R3 (b_21.png). El problema y su arreglo, que costó dos intentos:
   · El hueco de icono del rail mide 20px y el círculo de la cuenta 24. Un hijo MÁS ANCHO
     que su caja desborda hacia la derecha, así que el círculo quedaba descentrado respecto
     a la columna por la que pasan los demás iconos.
   · El primer intento fue sacarlo del flujo (position:absolute). Centró, pero dejó a
     `.rail-ic` SIN ALTO, y la corona —que cuelga de ese hueco con top:-7px— se vino al
     centro del círculo en vez de quedarse encima. Un arreglo que rompe al vecino.
   Lo que se hace ahora: el hueco de ESTA fila mide 24px (lo que mide el círculo) y se
   compensa con -2px a cada lado. Su centro cae exactamente donde el de un hueco de 20px,
   así que la columna queda alineada; y como el círculo sigue EN EL FLUJO, el hueco vuelve
   a tener su alto y la corona a tener de dónde colgar. */
.rail-account .rail-ic {
  position: relative;
  flex: 0 0 24px; height: 24px; margin: 0 -2px;
  display: inline-flex; align-items: center; justify-content: center;
}
.rail-inicial {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  font-size: 11px; font-weight: 700; line-height: 1;
}
/* La corona va ARRIBA Y CENTRADA sobre la inicial, no en la esquina: en la esquina se
   confundía con un badge de notificación. */
/* La corona se ancla al CÍRCULO (que ahora es el contexto posicionado), no al hueco: así
   sigue sentada encima de la inicial esté donde esté esta. */
.rail-corona {
  position: absolute; top: -7px; left: 50%; transform: translateX(-50%);
  color: var(--corona); line-height: 0;
}
.rail-corona .ic { width: 12px; height: 12px; }
/* "Cerrar sesión" en el pie de "Mi cuenta": SOLO EL ICONO, sin fondo, a la izquierda del
   todo. Rojo únicamente al pasar por encima. Es la acción más destructiva del panel y a la
   vez la que la gente viene a buscar aquí: tiene que estar, sin ser lo primero que grita.
   `margin-right: auto` lo separa de los botones de guardar/cerrar, para que no se pulse por
   inercia al ir a por el de al lado. */
.cuenta-salir { margin-right: auto; color: var(--text-dim); }
.cuenta-salir:hover, .cuenta-salir:focus-visible { color: var(--danger); background: var(--danger-dim, transparent); }
.cuenta-salir .ic { width: 17px; height: 17px; }

@media (prefers-reduced-motion: reduce) {
  .esq-bloque, .dept-row { animation: none; }
}

/* --- Rail: el tercer estado, FIJADO --------------------------------------- */
/* Flotando tapa el contenido y se cierra solo; FIJADO entra en el flujo y lo empuja. Por
   eso el ancho fijado va en .nav-rail (el hueco) y no solo en .rail-inner: es la única
   diferencia real entre los dos estados, y es justo la que el usuario quería. */
.nav-rail { transition: flex-basis 170ms ease, width 170ms ease; }
.nav-rail.pinned { flex-basis: var(--rail-open-w, 232px); width: var(--rail-open-w, 232px); }
.nav-rail.pinned .rail-inner { width: var(--rail-open-w, 232px); box-shadow: none; }
.nav-rail.pinned .rail-txt { opacity: 1; }
/* PLEGADO, la chincheta NO OCUPA SITIO. Antes compartía fila con el botón de menú y le
   robaba la mitad del ancho, así que el menú salía cortado (b_ del 11-08). Ahora sale del
   flujo con position:absolute mientras el rail está compacto, y entra en la fila solo
   cuando hay ancho de sobra. */
.rail-top { display: flex; align-items: center; gap: 4px; margin-bottom: 6px; position: relative; }
.rail-top .rail-toggle { margin-bottom: 0; flex: 1 1 auto; min-width: 0; }
.rail-pin {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  flex: 0 0 auto; opacity: 0; pointer-events: none; transition: opacity 130ms ease;
}
.nav-rail.open .rail-pin, .nav-rail.pinned .rail-pin { position: static; transform: none; }
.nav-rail.open .rail-pin, .nav-rail.pinned .rail-pin { opacity: 1; pointer-events: auto; }
.rail-pin.active { color: var(--accent); background: var(--bg-hover); }
.rail-pin .ic { width: 15px; height: 15px; }

@media (prefers-reduced-motion: reduce) { .nav-rail, .rail-pin { transition: none; } }

/* ==========================================================================
   Filas de badges: lista y cabecera (corrección del 11-08, b_11/b_12/b_13)
   ========================================================================== */

/* Los badges tienen FILA PROPIA y son más pequeños que el nombre. Antes compartían línea
   con él: en cuanto había departamento, el nombre se truncaba ("~ Taller ...") y el chip
   se montaba encima de la hora. Un chat que no puedes identificar de un vistazo no sirve. */
.session-tags {
  display: flex; align-items: center; gap: 5px;
  margin-bottom: 3px; min-width: 0;
}
.session-tags .session-time { margin-left: auto; flex: 0 0 auto; }
/* Sin badges no hay fila que pintar, solo la hora — y se sube a la línea del nombre para
   no gastar alto de más en los chats normales, que son la mayoría. */
.session-tags.solo-hora { margin-bottom: 0; height: 0; overflow: visible; }
.session-tags.solo-hora .session-time { position: relative; top: 17px; }
.tags-flecha { display: inline-flex; flex: 0 0 auto; color: var(--text-dim); opacity: .5; }
.tags-flecha .ic { width: 12px; height: 12px; }
/* Los badges de esta fila van UN PUNTO más pequeños que en el resto de la consola: aquí
   son contexto, no el contenido.
   AQUÍ SOLO SE CAMBIA EL TAMAÑO. El relleno es del COMPONENTE y tiene que ser el mismo en
   todas partes: cuando esta regla lo pisaba, el círculo del asignado salía pegado al borde
   de su píldora en la cabecera del chat (1px) y despegado en la lista (6px) — el mismo chip
   con dos aspectos, que es exactamente lo que un componente existe para evitar. Lo mismo le
   pasaba al punto del departamento (5px arriba, 4px en la lista). */
.session-tags .chat-badge,
.session-tags .dept-chip,
.session-tags .asig-chip { font-size: 10px; max-width: 46%; }
.session-item-top { min-width: 0; }
.session-name-wrap { min-width: 0; }

/* Chip de "quién lo atiende": avatar + nombre. Ver asignadoChipHTML para por qué es un
   avatar y no un icono genérico. */
.asig-chip {
  display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
  font-size: 11px; font-weight: 600; color: var(--text-dim);
  padding: 1px 7px 1px 1px; border: 1px solid var(--border); border-radius: 999px;
  overflow: hidden; white-space: nowrap;
}

.asig-chip.mia { border-color: var(--accent); color: var(--accent); }
/* EL CÍRCULO MIDE 15px Y NO 17 (lo cazaste tú): con 17 la píldora del asignado salía 20px
   de alto contra los 17 de la del departamento, y dos chips hermanos en la misma fila con
   alturas distintas se leen como dos cosas de distinto rango. El alto lo fija ahora la
   pareja de reglas de abajo, así que no vuelve a depender de lo que lleve dentro. */
.asig-chip .avatar { width: 15px; height: 15px; font-size: 7.5px; flex: 0 0 auto; }
/* LOS DOS CHIPS DE LA FILA MIDEN LO MISMO DE ALTO, venga dentro un punto de color o un
   avatar. Fijarlo aquí —y no confiar en que el contenido case— es lo que evita que
   cualquier cambio futuro (un icono, una inicial más grande) los vuelva a descuadrar. */
.dept-chip, .asig-chip { min-height: 19px; box-sizing: border-box; }
/* El AVATAR va pegado al borde izquierdo de la píldora y el nombre se centra en lo que
   queda (pedido tuyo). Antes los dos iban en bloque, así que el círculo flotaba hacia el
   centro y los chips de chats seguidos no alineaban sus círculos.
   EL RELLENO DE 1px DE LA IZQUIERDA ES PARTE DE ESO: quien reduzca este chip en algún sitio
   cambia `font-size` y `max-width`, NUNCA el padding — si no, el círculo se despega en una
   pantalla y no en otra. */
.asig-nom {
  flex: 1 1 auto; text-align: center; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
}

/* --- Cabecera del chat ---
   OJO: `.chat-titles`, `.chat-header-info` y `.chat-header-actions` SE DECLARAN ARRIBA,
   con el resto de la cabecera. Aquí estaban otra vez, 3300 líneas después y con valores
   distintos (`min-width: 0` y `flex: 0 0 auto`), así que ganaban por orden y dejaban sin
   efecto lo de allí — el nombre del cliente volvía a poder encogerse hasta cero y la barra
   que mide el desbordamiento nunca se enteraba (b_27). Un mismo hecho declarado en dos
   sitios acaba SIEMPRE en dos verdades: si hay que tocar el reparto de la cabecera, se
   toca arriba. */
/* LA FILA DE BADGES NUNCA SE PARTE EN DOS (petición tuya, 12-08). Con `wrap`, en cuanto
   coincidían departamento + asignado + estado, la fila se abría a dos líneas y empujaba el
   nombre del cliente hacia abajo: la cabecera daba un salto de alto cada vez que alguien
   tomaba un chat. En una sola línea, lo que no cabe se recorta con puntos suspensivos —el
   mismo criterio que la fila de la lista, que ya iba así—: primero cede el departamento y
   después el nombre de quien atiende, porque el ESTADO es lo que no se puede perder.
   `min-width: 0` en los hijos es obligatorio: sin él un flex no baja de su contenido y la
   fila desborda en vez de recortarse. */
.chat-tags { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; min-width: 0; flex-wrap: nowrap; overflow: hidden; }
.chat-tags > .chat-dept { min-width: 0; flex: 0 1 auto; overflow: hidden; }
.chat-tags .dept-chip, .chat-tags .asig-chip { min-width: 0; flex: 0 1 auto; }
.chat-tags .state-badge { flex: 0 0 auto; }
.chat-dept { display: inline-flex; align-items: center; gap: 6px; }
.chat-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ==========================================================================
   Responsive (corrección del 11-08, b_14/b_15)
   ========================================================================== */

/* --- Tablet y móvil apaisado: el rail compacto SÍ cabe --- */
@media (max-width: 1024px) {
  .nav-rail.pinned { flex-basis: var(--rail-w, 56px); width: var(--rail-w, 56px); }
  .nav-rail.pinned .rail-inner { width: var(--rail-open-w, 232px); box-shadow: 4px 0 24px var(--shadow); }
  /* LA CHINCHETA SOLO EXISTE EN ESCRITORIO (lo cazaste tú): de 1024px para abajo el menú
     abierto FLOTA sobre el contenido en vez de empujarlo, así que "fijarlo" no fijaba nada —
     el botón estaba, se pulsaba y no pasaba nada visible. Un control que no cumple lo que
     promete es peor que no tenerlo. Estaba escondido solo en móvil (720px), y las tablets se
     quedaban con él. */
  .rail-pin { display: none; }
}

/* --- Móvil: el rail NO es una columna --- */
/* Con 375 px de ancho, 56 px de rail son el 15% de la pantalla gastados en navegación que
   se usa tres veces al día. Aquí el rail desaparece del flujo y se abre ENCIMA desde el
   botón que hay junto al logo; fijarlo no tiene sentido y se esconde la chincheta. */
/* El botón de menú móvil se declara oculto ANTES del media query: si se declarara después,
   ganaría por orden y no aparecería nunca en móvil (que es justo lo que pasó al probarlo). */
#rail-movil { display: none; }
@media (max-width: 720px) {
  .nav-rail, .nav-rail.pinned { flex-basis: 0; width: 0; }
  /* FIJADO NO EXISTE EN MÓVIL. Con `pinned` recordado de escritorio, el menú se quedaba
     abierto tapando la pantalla nada más entrar. Aquí solo `.open` abre, y `.open` siempre
     lo enciende una pulsación. */
  .rail-inner, .nav-rail.pinned .rail-inner { width: 0; padding: 10px 0; border-right: 0; box-shadow: none; }
  .nav-rail.open .rail-inner {
    width: min(272px, 84vw); padding: 10px 8px;
    border-right: 1px solid var(--border); box-shadow: 4px 0 28px var(--shadow);
  }
  .nav-rail.open .rail-txt { opacity: 1; }
  .nav-rail.pinned:not(.open) .rail-txt { opacity: 0; }
  /* EL VELO ERA ESTE PSEUDO-ELEMENTO Y POR ESO NO SE PODÍA CERRAR TOCÁNDOLO. Un `::before`
     no es un nodo propio: los clics que caen sobre él los recibe SU ANFITRIÓN, o sea el
     propio `.nav-rail`. Y como la regla de cerrar dice "si el clic fue dentro del rail, no
     cierres" —que es lo correcto para el menú—, tocar el fondo contaba como tocar el menú.
     Encima, al cubrir `inset: 0`, hacía que cualquier punto de la pantalla respondiera
     `#nav-rail` en una prueba de impacto.
     Ahora el velo es un elemento de verdad (#rail-backdrop), HERMANO del rail: recibe sus
     propios clics y los cierra. Ver .rail-backdrop más arriba. */
  /* El botón que lo abre vive en la cabecera de la lista, junto al logo. */
  #rail-movil { display: inline-flex; }
}

/* Métricas en móvil: la tabla no cabe y recortarla escondía columnas enteras (b_15). Se
   convierte en tarjetas, con la etiqueta de cada dato delante — que es lo que hace la
   industria con tablas densas en pantallas estrechas. */
@media (max-width: 720px) {
  /* En móvil la wrap deja de ser tarjeta: cada FILA es la tarjeta, o habría un contorno
     dentro de otro. */
  .met-tabla-wrap { background: none; border: 0; border-radius: 0; overflow: visible; }
  .met-tabla thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .met-tabla, .met-tabla tbody, .met-tabla tr, .met-tabla td { display: block; width: 100%; }
  .met-tabla tr {
    border: 1px solid var(--border); border-radius: 10px;
    padding: 10px 12px; margin-bottom: 8px; background: var(--bg-elev);
  }
  /* Sin esto, la barra de volumen y su número se pegan al borde derecho de la tarjeta. */
  .met-tabla .met-vol-in { gap: 10px; flex: 1 1 auto; }
  .met-barra { flex: 1 1 auto; min-width: 60px; }
  .met-tabla td { border: 0; padding: 3px 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
  .met-tabla td::before {
    content: attr(data-col); font-size: 11px; text-transform: uppercase;
    letter-spacing: .3px; color: var(--text-dim); font-weight: 700;
  }
  .met-tabla .met-nombre { justify-content: flex-start; padding-bottom: 8px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
  .met-tabla .met-nombre::before { content: none; }
  .met-vol-in { justify-content: space-between; flex: 1 1 auto; }
  .met-barra { width: 90px; }
  .met-cards { grid-template-columns: 1fr 1fr; }
  .app-panel { padding: 18px 14px 32px; }
}

/* Móvil: los botones de canalización se quedan SOLO con su icono. Con el texto, "Tomar" y
   "Transferir" ocupaban tanto que se montaban sobre el nombre del cliente (medido: las
   acciones empezaban 30 px antes de que acabara el nombre). El icono con su `title` dice
   lo mismo en un cuarto del espacio. */
/* Móvil: los botones de canalización CONSERVAN su texto. Se probó dejarlos con solo el
   icono para ganar sitio y el usuario lo rechazó con razón: un tick y una flecha no dicen
   "tomar" y "transferir" a quien no lo sabe ya, y equivocarse aquí tiene consecuencias.
   El sitio lo saca overflowbar.js mandando al menú lo que no quepa. */
@media (max-width: 720px) {
  /* Los botones de canalización miden LO MISMO que en escritorio: encogerlos era otra
     forma de apretar. Si no caben, se van enteros al menú con su nombre. */
  /* EL ESPACIADO NO CAMBIA POR SER MÓVIL, Y ESTE ERA EL FALLO DE FONDO. Aquí había
     `gap: 2px` (contra los 12px de escritorio) y los botones de canalización encogían de
     tamaño y de letra. Con eso, la barra "cabía" a base de aplastarse: la comprobación de
     desbordamiento decía siempre que sí y NUNCA se mandaba nada al menú — que es justo lo
     que se veía, todo pegado y nada escondido.
     La regla ahora es la de siempre en un diseño responsive que se sostiene: el
     espaciado es CONSTANTE y lo que se adapta es CUÁNTAS cosas se enseñan. Sin
     `flex-wrap`, además: una barra que envuelve tampoco desborda nunca. */
  .chat-header-actions { flex-wrap: nowrap; justify-content: flex-end; }
}

/* --- Cuenta y salida en la misma fila (estilo Gemini) --- */
.rail-cuenta-fila { display: flex; align-items: center; gap: 2px; }
.rail-cuenta-fila .rail-account { flex: 1 1 auto; min-width: 0; }
/* Plegado, el botón de salir NO ocupa: si compartiera los 40 px con la cuenta, la inicial
   se quedaría sin sitio (es el mismo fallo que tenía el botón de menú con la chincheta). */
.rail-logout { flex: 0 0 auto; width: 0; padding: 0; overflow: hidden; opacity: 0; transition: opacity 130ms ease; }
.nav-rail.open .rail-logout, .nav-rail.pinned .rail-logout { width: 34px; padding: 0 8px; opacity: 1; }
.rail-logout:hover { color: var(--danger); }

/* --- Respiración en la lista (b_17/b_18/b_19) --- */
/* La fila de badges y la hora quedaban pegadas al borde: el chip parecía cortado por la
   ventana y con el viewport estrecho llegaba a superponerse. */
.session-item { padding-right: 14px; }
.session-tags { padding-right: 2px; gap: 6px; }
/* La flechita al RAS del contorno del primer chip: el hueco que dejaba su caja la
   despegaba del grupo y parecía de otra cosa. */
.tags-flecha { margin-left: -2px; margin-right: -1px; }
/* Con el ratón encima, el contorno de los badges se aclara. Antes el borde y el fondo del
   hover eran el mismo tono, así que al pasar por encima los chips se disolvían en la fila. */
.session-item:hover .dept-chip,
.session-item:hover .asig-chip,
.session-item.active .dept-chip,
.session-item.active .asig-chip { border-color: var(--border-strong); }
/* Con poco ancho, los badges ceden antes que la hora: la hora es una sola palabra y no
   puede partirse; un chip sí puede recortarse con puntos suspensivos (b_19). */
.session-tags .dept-chip, .session-tags .asig-chip { min-width: 0; flex: 0 1 auto; }
.session-tags .session-time { padding-left: 4px; }

/* Distintivo de instancia de pruebas (item 134). Va junto al logo, que es lo primero que se
   mira, y en ámbar: es un aviso, no un estado del sistema. Solo aparece en sandbox — un
   adorno permanente se vuelve invisible en una semana y dejaría de avisar cuando importa. */
.env-chip {
  margin-left: 8px; flex: 0 0 auto;
  font-size: 10px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
  color: var(--warn, #d97706);
  background: color-mix(in srgb, var(--warn, #d97706) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn, #d97706) 40%, transparent);
}

/* Guardas de layout para nombres largos (item 6 de la revisión). Un tope en el servidor
   evita lo absurdo; esto evita que lo LARGO PERO LEGÍTIMO rompa la fila. */
.rail-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.met-persona span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.met-nombre { max-width: 260px; }
.dept-row-top b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* ==========================================================================
   Ajustes de la revisión: #3 (achicar), #21 (combobox)
   ========================================================================== */

/* #3 — La fila de badges NO envuelve ni desborda. Al estrechar la ventana, la hora se
   queda (es una sola palabra, no se puede partir) y los chips ceden por orden inverso de
   importancia: primero se recorta el nombre del departamento, luego el de quien atiende.
   Antes se salían de la caja y se montaban sobre la hora. */
.session-tags { flex-wrap: nowrap; overflow: hidden; }
/* CUANDO LA LISTA SE ESTRECHA, LOS BADGES SE QUEDAN EN SU FIGURA (petición tuya): el punto
   del departamento, el círculo con la inicial de quien atiende y el icono del estado. Antes
   los nombres se recortaban y acababan solapándose con la hora.
   Va con CONTAINER QUERY y no con media query porque lo que se estrecha es LA LISTA —se
   arrastra su divisor—, no la pantalla: una media query no se entera de eso.
   Los nombres completos siguen en el `title` de cada chip. */
/* El contenedor de consulta es LA COLUMNA, no la lista: `container-type` sobre la lista la
   dejaba con ancho cero (contiene su propio tamaño en línea y ahí dentro no había de dónde
   sacarlo). La columna sí tiene ancho propio (--sidebar-w), que es justo lo que se arrastra. */
.sidebar { container-type: inline-size; }
@container (max-width: 320px) {
  .session-tags .chip-txt, .session-tags .asig-nom { display: none; }
  .session-tags .dept-chip, .session-tags .asig-chip, .session-tags .chat-badge { gap: 0; padding-left: 3px; padding-right: 3px; }
  .session-tags .asig-chip { padding-left: 1px; }
}
.session-tags .session-time { flex: 0 0 auto; margin-left: auto; }
.session-tags .dept-chip { max-width: 40%; }
.session-tags .asig-chip { max-width: 34%; }
/* Con la ventana muy estrecha el chip se queda en el punto de color y la inicial: el
   nombre sobra antes que el dato de que EXISTE un departamento o un responsable. */
@media (max-width: 1100px) {
  .session-tags .dept-chip { max-width: 92px; }
  .session-tags .asig-chip { max-width: 76px; }
}
@media (max-width: 900px) {
  .session-tags .dept-chip, .session-tags .asig-chip { max-width: 64px; }
}

/* #21 — Los `select` seguían con el aspecto del navegador, que en Windows es un control
   gris de otra época y desentona con el resto de campos. Se les da la misma superficie,
   contorno y radio que a los inputs, con la flecha dibujada (un SVG en data: URI, que no
   pide red y sigue el color del tema). */
.modal-field select, .campo select, select.entrada {
  appearance: none; -webkit-appearance: none;
  width: 100%; font: inherit; font-size: 14px;
  padding: 11px 36px 11px 13px;
  color: var(--text); background-color: var(--bg-elev);
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 15px;
  transition: border-color 120ms ease;
}
.modal-field select:focus, .campo select:focus { outline: none; border-color: var(--accent); }
.modal-field select:hover, .campo select:hover { border-color: var(--border-strong); }
/* El desplegable nativo hereda el fondo del sistema en algunos navegadores: se fuerza para
   que no salga una lista blanca sobre un tema oscuro. */
.modal-field select option, .campo select option { background: var(--bg-panel); color: var(--text); }

/* Sección Soporte (item 129/130). Dos tarjetas, no un formulario: hoy no hay a dónde
   enviarlo, y un campo de texto que no llega a nadie es peor que decir la verdad. */
.sop-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
.sop-card {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  padding: 18px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-elev);
}
.sop-card h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 15px; }
.sop-card h3 .ic { width: 17px; height: 17px; color: var(--text-dim); }
.sop-card p { margin: 0; color: var(--text-dim); font-size: 13px; line-height: 1.5; }
.sop-card .modal-btn { margin-top: auto; text-decoration: none; }
.sop-nota { margin-top: 14px; }


/* ==========================================================================
   Segunda revisión del 11-08
   ========================================================================== */

/* R2 — El botón de copiar se va AL FINAL de su línea, no pegado al título. */
.caps-verdict-title { display: flex; align-items: center; gap: 8px; }
.caps-copiar { margin-left: auto; flex: 0 0 auto; }
.caps-copiar .ic { width: 15px; height: 15px; }

/* R2 — El toast salía POR DETRÁS de la modal, así que copiar no daba señal de haber
   funcionado. Un aviso que no se ve es lo mismo que no avisar. */
.toast-container { z-index: 1000; }

/* R1 — Franja del color del departamento a la izquierda de cada chat. El color se
   relaciona más rápido que leer el nombre del badge; el badge se queda porque el color
   solo no basta (ver la nota del .dept-chip). */
.session-item { position: relative; }
.session-item[data-dept-color]::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--dept-color, transparent);
}

/* R4 — En tema claro la lista de usuarios era plana: fondo igual al de la página. En
   oscuro ya se veía bien, así que solo cambia el claro. */
:root[data-theme="light"] .user-row,
:root[data-theme="light"] .qr-row,
:root[data-theme="light"] .dept-row { background: var(--bg-panel); }

/* R9 — El campo del composer tenía en claro el mismo fondo que su footer. */
:root[data-theme="light"] .composer-editor { background: var(--bg-panel); }
