/* ═══════════════════════════════════════════════════════════════════════════
   componentes.css
   ───────────────────────────────────────────────────────────────────────────
   Estilos de los dos COMPONENTES que se dibujan solos: el widget del Asistente
   IA y el control de tema del membrete. Estaban dentro de su JavaScript,
   inyectados en un <style>, y se movieron acá sin cambiar una línea.

   ⚠ Un componente que trae su propio CSS es autocontenido y eso tiene su
   valor: se lleva a otro proyecto de una pieza. Se movió igual porque la regla
   del proyecto pasó a ser una sola —el CSS no vive en JavaScript— y una
   excepción sin test es una excepción que se olvida.

   ⚠ El <link> va ÚLTIMO en el <head>: el <style> inyectado llegaba en tiempo de
   ejecución, después de todos los <link>. Adelantarlo cambiaría quién gana los
   empates de especificidad, y eso no da error — se ve en la pantalla, torcida.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── el widget del Asistente IA ────────────────────────────────────────────
 *
 * Rediseñado el 17-08-2026 para la identidad corporativa (login.html): azul
 * de marca #003A70, esquinas duras, DM Sans/DM Mono. Tres cambios de fondo,
 * no solo de color:
 *
 *  1. El gradiente azul→cian (Tailwind, ajeno a la marca) se reemplaza por el
 *     mismo par sólido navy/blanco que ya usan los botones primarios del
 *     piloto (`--mx-acento-solido` / `--mx-sobre-acento`, en
 *     estilo-corporativo.css) — ese par ya está auditado por contraste ahí,
 *     así que reusarlo no agrega una combinación nueva que verificar.
 *  2. El panel dejó de estar hardcodeado en oscuro. Antes `#0f1626` no se
 *     movía nunca, así que el widget quedaba oscuro con el tema en claro. Se
 *     lee de `--mx-sup`/`--mx-linea`/`--mx-tinta`, que sí invierten con
 *     `data-tema`.
 *  3. El coral de marca NO aparece acá: la regla del proyecto es que aparece
 *     UNA sola vez en toda la pantalla (la línea bajo el topbar). El gesto
 *     del "panel coronado" del login (`border-top: 3px solid coral`) se toma
 *     prestado igual, pero coronado en NAVY.
 *
 * ⚠ Todo token lleva su reserva `var(--mx-x, #hex)`: `data-diseno` lo pone
 * siempre el <head>, pero la reserva es lo que deja volver atrás sin que el
 * widget quede roto (mismo patrón que el resto de este archivo). El hex de
 * reserva es el valor de tema OSCURO — el que tenía el widget antes de esto—
 * para que quitar el atributo devuelva la pantalla más parecida a la de ayer.
 */

/* FAB escondido en la "pared" derecha: en reposo solo asoma una pestaña
   pegada al borde; al pasar el mouse (o recibir foco por teclado) se desliza
   hacia afuera con animación. Cuando el panel está abierto queda totalmente
   visible. Esquinas del lado visible más cuadradas que la pastilla anterior
   (10px, no 29px): es el mismo gesto de "control", no de burbuja.

   ⚠ Quinta tanda (18-08-2026): oscilación reportada al borde derecho — "se
   colapsa y se expande mucho" con el mouse quieto ahí. El despliegue lo
   disparaba `:hover` sobre el PROPIO BOTÓN, que se traslada 60px al abrirse:
   el mouse quedaba sobre la pestaña asomada (a la derecha), el botón se
   movía hacia la izquierda, el mouse quedaba AFUERA de la nueva posición,
   `:hover` se apagaba, el botón volvía a la derecha, el mouse volvía a
   quedar encima → bucle sin fin mientras el puntero esté en esa franja. Un
   elemento que se traslada fuera de su propia área sensible al hover oscila
   por diseño, no por casualidad.
   Medido con Playwright, con el mouse "quieto" reenviado a la MISMA
   coordenada cada 25ms (así se comporta un mouse real: el sensor manda
   micro-eventos aun sin desplazamiento real) — 1,5s por punto:
     clientWidth-2, -8, -16   → 18-19 transiciones (oscila sin parar)
     clientWidth-24 en adelante → 0 transiciones (nunca se dispara)

   El arreglo NO usa `:hover` en absoluto para el despliegue: un contenedor
   fijo del tamaño del recorrido (probado primero) fija el área sensible,
   pero al ser un elemento con `pointer-events` TAPA lo que haya debajo
   mientras el botón está colapsado — medido a 1920×1080: 32×13px de la fila
   "🔗 Factorizada" de la tabla de Pago de Facturas quedaban debajo,
   bloqueados de forma permanente en vez de solo mientras el botón está
   realmente abierto (que es su posición aprobada, sin tocar).
   La clase `.mx-fab-cerca` (chat-asistente.js:`_actualizarCercania`) la pone
   un listener de `mousemove` en `document` — pasivo, no intercepta ningún
   evento — comparando la posición del mouse contra el rectángulo medido de
   las DOS posiciones del botón. Sin un elemento nuevo de por medio, no hay
   nada que pueda tapar un clic ajeno. `--mx-fab-peek` es la única fuente del
   desplazamiento: la usan el `transform` de acá abajo, el 0% de `mxPeek`, y
   el JS que mide el rectángulo — cambiarla en un solo lugar no la desalinea. */
:root{ --mx-fab-peek: 60px; }
#mx-chat-fab{position:fixed;bottom:20px;right:20px;width:56px;height:56px;
  border-radius:10px 0 0 10px;
  background:var(--mx-acento-solido,#1B4A78);color:var(--mx-sobre-acento,#fff);border:none;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 6px 20px rgba(0,58,112,.35);z-index:10000;
  transform:translateX(var(--mx-fab-peek));opacity:.9;
  transition:transform .25s cubic-bezier(.2,.8,.3,1), opacity .25s ease, background .15s ease}
#mx-chat-fab svg{width:24px;height:24px;flex:none}
#mx-chat-fab.mx-fab-cerca, #mx-chat-fab:focus-visible, #mx-chat-fab.mx-open{
  transform:translateX(0);opacity:1;background:var(--mx-acento-hondo,#0A1828)}
/* pequeño "tirón" al asomar para llamar la atención sin ser invasivo. El
   0% arranca en el mismo translateX del reposo (ver más abajo, cuarta
   tanda): si difieren, el primer cuadro del hover salta en vez de partir de
   donde el botón ya estaba.
   ⚠ `:not(.mx-open):not(:focus-visible)` (18-08-2026, defecto reportado como
   "medio bugeado al expandirse y colapsarse"). Arrancar una animación CSS
   fija su valor al del 0% del keyframe EN EL INSTANTE en que arranca,
   pisando lo que sea que el `transform` valiera antes — no transiciona desde
   ahí. Con el panel abierto (`.mx-open`) o el botón enfocado por teclado
   (`:focus-visible`) el botón YA está en `translateX(0)` por la regla de
   arriba; sin esta guarda, pasar el mouse por encima (algo tan común como
   moverse hacia el botón para cerrar el panel) reinicia `mxPeek` y el botón
   SALTA 60px hacia la pared (translateX(60px), su posición oculta de
   reposo) para recién ahí empezar a deslizarse de vuelta con el "tirón" —
   un salto que no correspondía a ningún cambio real de estado. Medido con
   Playwright: sin la guarda, `x` pasaba de 1833 a 1893 en el primer cuadro
   tras el hover y volvía a 1833 en ~270ms. Con hover puro (sin `.mx-open` ni
   foco) el keyframe sigue arrancando en la MISMA posición en la que ya
   estaba (el reposo, 60px), así que ahí no cambia nada. */
@media (prefers-reduced-motion: no-preference){
  #mx-chat-fab.mx-fab-cerca:not(.mx-open):not(:focus-visible){animation:mxPeek .25s ease}
  @keyframes mxPeek{0%{transform:translateX(var(--mx-fab-peek))}70%{transform:translateX(-4px)}100%{transform:translateX(0)}}
}
/* Segunda tanda (posicionamiento, 17-08-2026): el panel tenía `right:12px`
   mientras el FAB, ya aprobado y sin tocar, queda con su borde derecho en
   `right:0` cuando está desplegado (`transform:translateX(0)`). Medido con
   `getBoundingClientRect`: 12px de desalineación entre el borde derecho del
   botón y el del panel — el "salto visual" entre dónde se hace clic y dónde
   aparece el panel. Se corrige igualando `right:0` en los dos: ahora
   comparten el mismo borde y el panel se abre pegado al botón, no corrido.
   El hueco vertical entre los dos (botón arriba, panel abajo) ya medía 8px y
   se deja igual — es lo bastante ajustado para leerse como una sola pieza. */
/* Tercera tanda (diseño del panel abierto, 17-08-2026). El tamaño de base
   subió un poco (380×560 → 396×600): a 1366×768 sigue sin tocar el techo del
   `max-height` (100vh - 120px = 648px) ni el `max-width`, y da algunos
   milímetros más de ancho de línea para leer una respuesta de varios
   párrafos. `.big` sube en la misma proporción (620→640) para que siga
   leyéndose como "un paso más grande", no como "casi lo mismo". */
/* Cuarta tanda (18-08-2026): dos ajustes pedidos después de ver el panel
   armado.
   1. El tamaño bajó (396×600 → 360×520): el 396×600 se sentía grande en la
      pantalla. 520px de alto sigue dejando ~380px de conversación después
      de descontar membrete, pie y aviso legal — muchas más de tres líneas —
      y el `textarea` del pie sigue creciendo hasta su propio techo de 4
      líneas sin que el panel se sienta apretado. `.big` baja en la misma
      proporción (640×760 → 580×680): la relación grande/base se mantiene
      casi igual (1,61× de ancho, 1,31× de alto, contra 1,62× y 1,27× de
      antes), así que sigue leyéndose como "un paso más grande" y no "el
      mismo panel". A 1366×768 el modo grande sigue superando el techo de
      `max-height` (680 > 652) igual que antes (760 > 648): es la misma
      relación de siempre, el techo lo recorta un poco en pantallas bajas
      y eso ya pasaba con el tamaño viejo.
   2. El panel estaba en `right:0`, pegado al canto de la ventana — a
      propósito, para igualar el borde del botón flotante (ver la segunda
      tanda). Eso resolvía el desfase entre los dos pero dejaba a los DOS
      pegados al borde físico de la pantalla, que es justo lo que se pidió
      corregir. Ahora los dos comparten `right:20px` en vez de `right:0`
      (el FAB, más abajo) — el mismo borde entre sí, con aire respecto del
      canto. `max-width` pasa de `calc(100vw - 24px)` a
      `calc(100vw - 40px)`: con el borde derecho fijo en 20px, limitar el
      ancho a `100vw - 40px` garantiza que el borde IZQUIERDO tenga
      también ≥20px en pantallas angostas, no solo el derecho.
      El borde de ABAJO sigue el mismo criterio: el botón baja de
      `bottom:24px` a `bottom:20px` (mismo margen que el derecho, para que
      el widget quede parejo en las dos direcciones), y el panel lo sigue:
      `bottom:84px` = 20 (margen del botón) + 56 (alto del botón) + 8 (el
      hueco entre los dos, sin tocar). `max-height` baja en la misma medida,
      de `calc(100vh - 120px)` a `calc(100vh - 116px)`, para conservar el
      mismo colchón de 32px arriba que tenía antes. */
#mx-chat-panel{position:fixed;bottom:84px;right:20px;width:360px;max-width:calc(100vw - 40px);
  height:520px;max-height:calc(100vh - 116px);
  background:var(--mx-sup,#10233A);border:1px solid var(--mx-linea,#1f2b45);
  border-top:var(--mx-rule-w,3px) solid var(--mx-acento-solido,#1B4A78);
  border-radius:6px;box-shadow:0 16px 48px rgba(14,26,38,.35), 0 2px 8px rgba(14,26,38,.18);
  z-index:10000;display:none;
  flex-direction:column;overflow:hidden;font-family:var(--font,'DM Sans',system-ui,sans-serif)}
#mx-chat-panel.open{display:flex}
#mx-chat-panel.big{width:580px;height:680px}
.mx-chat-expand{background:none;border:none;color:var(--mx-sobre-acento,#fff);font-size:16px;cursor:pointer;opacity:.85;line-height:1}
.mx-chat-expand:hover{opacity:1}
/* El membrete del panel es siempre navy sólido, en los dos temas: es la
   marca, igual que el topbar no sigue al tema (ver estilo-corporativo.css).
   Por eso su texto blanco no necesita un token de tema — el fondo tampoco lo
   tiene. Achicado (14px → 11px de relleno vertical): en un panel de 360px de
   ancho cada píxel de encabezado es un píxel menos de conversación, y acá no
   hay nada más que recortar sin perder la marca (el ícono + título) o los
   controles (⤢ / ×) — así que se quedó, pero más angosto. */
.mx-chat-head{padding:11px 14px;background:var(--mx-acento-solido,#1B4A78);color:var(--mx-sobre-acento,#fff);
  display:flex;align-items:center;justify-content:space-between}
.mx-chat-head h3{margin:0;font-size:13.5px;font-weight:700;letter-spacing:-.01em;display:flex;gap:8px;align-items:center}
.mx-chat-head h3 svg{width:15px;height:15px;flex:none}
.mx-chat-head .sub{font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;opacity:.75}
.mx-chat-close{background:none;border:none;color:var(--mx-sobre-acento,#fff);font-size:20px;cursor:pointer;opacity:.85;line-height:1}
.mx-chat-body{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:13px}

/* Cada fila agrupa el avatar (si es del asistente) con su burbuja. Antes la
   burbuja sola llevaba el color/alineación como única marca de "quién habla";
   ahora además hay un avatar redondo con el ícono del FAB en las filas del
   asistente — se nota más rápido en una respuesta larga, donde el color de
   fondo (más discreto a propósito, para no competir con el texto) no
   alcanza. El usuario no lleva avatar: repetir "sos vos" en cada mensaje
   propio es ruido en un panel angosto, no información nueva. */
.mx-msg-row{display:flex;gap:8px;max-width:88%}
.mx-msg-row.user{align-self:flex-end;justify-content:flex-end}
.mx-msg-row.bot{align-self:flex-start;align-items:flex-start}
.mx-avatar{width:22px;height:22px;border-radius:50%;flex:none;
  background:var(--mx-acento-solido,#1B4A78);color:var(--mx-sobre-acento,#fff);
  display:flex;align-items:center;justify-content:center}
.mx-avatar svg{width:12px;height:12px}
.mx-msg{min-width:0;padding:10px 13px;border-radius:10px;font-size:13px;line-height:1.55;white-space:pre-wrap;word-wrap:break-word}
.mx-msg.user{background:var(--mx-acento-solido,#1B4A78);color:var(--mx-sobre-acento,#fff);border-bottom-right-radius:3px}
.mx-msg.bot{background:var(--mx-sup-2,#1a2540);color:var(--mx-tinta,#e2e8f0);border-bottom-left-radius:3px}
.mx-msg.err{background:var(--mx-error-bg,rgba(255,138,128,.12));color:var(--mx-error,#fca5a5);border-bottom-left-radius:3px}
/* El ícono de alerta lo pone el CSS, no el texto: antes el mensaje empezaba
   con un "⚠ " escrito a mano, pegado a lo que devolviera el servidor — acá
   se separa la marca visual (el ícono) de la prosa (la frase completa que
   arma chat-asistente.js, "No pude responder: <motivo>."), así el texto se
   lee como una explicación y no como un fragmento crudo con un símbolo pegado. */
.mx-msg.err::before{content:"⚠";margin-right:6px}

/* "Está pensando": tres puntos que rebotan, dentro de una burbuja como
   cualquier otra del asistente (mismo avatar, mismo fondo) — el lenguaje
   visual que ya reconoce cualquiera de WhatsApp/Slack/Teams, más rápido de
   leer que una palabra en cursiva. `role="status"` + `aria-label` en el
   marcado le dan a un lector de pantalla el mismo aviso en texto. */
.mx-typing{display:flex;align-items:center;gap:4px;padding:12px 13px}
.mx-typing span{width:6px;height:6px;border-radius:50%;background:var(--mx-tinta-2,#94A8BC);
  opacity:.4;animation:mxTypingDot 1.1s ease-in-out infinite}
.mx-typing span:nth-child(2){animation-delay:.15s}
.mx-typing span:nth-child(3){animation-delay:.3s}
@keyframes mxTypingDot{0%,60%,100%{opacity:.35;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}
@media (prefers-reduced-motion: reduce){.mx-typing span{animation:none;opacity:.7}}

/* ── Estado inicial: sugerencias, no una pantalla vacía ──────────────────
 * Cuatro preguntas reales (chat-asistente.js las auto-envía con un clic),
 * elegidas porque cada una corresponde a una herramienta de
 * tesoreriaTools.js que NO necesita un nombre de proveedor/cliente inventado
 * — no hay que prometer una respuesta con un dato de ejemplo que no existe.
 * Forma de PÍLDORA (20px), a propósito distinta del resto de los controles
 * del panel (2px, el lenguaje del piloto corporativo): estos son atajos
 * conversacionales, no un formulario — la píldora es la forma que cualquier
 * chat usa para "esto es una sugerencia, no un campo". */
.mx-suggs-label{font-size:11px;color:var(--mx-tinta-2,#94A8BC);margin:0 0 6px}
.mx-suggs{display:flex;flex-wrap:wrap;gap:6px}
.mx-sugg{background:var(--mx-sup-2,#16203a);border:1px solid var(--mx-linea,#25324f);color:var(--mx-acento,#93c5fd);
  font-family:inherit;font-size:11.5px;text-align:left;border-radius:20px;padding:6px 12px;cursor:pointer;
  transition:background .12s ease,border-color .12s ease}
.mx-sugg:hover{background:var(--mx-sup-hund,#1d2b4d);border-color:var(--mx-acento,#6FB2E8)}

.mx-chat-foot{display:flex;gap:8px;padding:10px;border-top:1px solid var(--mx-linea,#1f2b45);background:var(--mx-sup-2,#0c1322)}
/* Antes era un <input> de una línea (ver chat-asistente.js): con tres líneas
   de texto el contenido se desplazaba hacia los costados dentro de una caja
   diminuta. Ahora es un <textarea> que el JS agranda con `scrollHeight` hasta
   un techo, así que acá alcanza con NO forzarle una altura fija y dejar que
   el navegador la calcule línea a línea (`resize:none` para que no aparezca
   la manija de redimensionar de serie, que en un pie de 8px de alto no cabe). */
.mx-chat-foot textarea{flex:1;background:var(--mx-sup,#16203a);border:1px solid var(--mx-linea,#25324f);border-radius:8px;
  padding:9px 12px;color:var(--mx-tinta,#e2e8f0);font-size:13px;line-height:1.4;outline:none;font-family:inherit;
  resize:none;overflow-y:hidden}
.mx-chat-foot textarea::placeholder{color:var(--mx-tinta-2,#94A8BC)}
/* El anillo de foco (además del cambio de borde) es lo que hace notar que la
   caja es editable ANTES de tocarla con el mouse, no solo después — la única
   señal que había antes era el cambio de color del borde, sutil a un
   costado del ojo. */
.mx-chat-foot textarea:focus{border-color:var(--mx-acento,#6FB2E8);
  box-shadow:0 0 0 3px var(--mx-acento-tenue,rgba(111,178,232,.18))}
/* El botón de enviar se atenúa mientras la caja está vacía (no hay "paso
   siguiente" todavía) y se enciende en cuanto hay algo que mandar — la clase
   la pone `ajustarAltoInput()` en cada tecla. `:disabled` va DESPUÉS a
   propósito: durante la espera de una respuesta tiene que ganarle a
   `.mx-listo`, que puede seguir puesta si quedó texto sin limpiar. */
.mx-chat-foot button{background:var(--mx-acento-solido,#1B4A78);border:none;color:var(--mx-sobre-acento,#fff);border-radius:8px;padding:0 16px;
  cursor:pointer;font-size:14px;opacity:.55;transition:background .15s ease,opacity .15s ease}
.mx-chat-foot button.mx-listo{opacity:1}
.mx-chat-foot button:hover:not(:disabled){background:var(--mx-acento-hondo,#0A1828)}
.mx-chat-foot button:disabled{opacity:.5;cursor:default}
.mx-chat-disc{font-size:10px;color:var(--mx-tinta-2,#475569);text-align:center;padding:0 0 8px}
@media (prefers-reduced-motion: reduce){
  #mx-chat-fab{transition:none}
}

/* ── el switch de tema del membrete ─────────────────────────────────────────
 *
 * Un riel con el sol a la izquierda, la luna a la derecha, y un pomo que se
 * desliza al lado activo. Reemplazó (17-08-2026) a un grupo de dos botones con
 * `aria-pressed` que se veía —y se anunciaba— como un par de pestañas.
 *
 * ⚠ Todo se pinta en grises translúcidos sobre el fondo que tenga, y los iconos
 * usan `color: currentColor`. Hoy el membrete es siempre azul, así que podría
 * fijarse en blanco — se deja heredando para que el switch siga al membrete si
 * algún día cambia de color, sin que nadie tenga que acordarse de este archivo.
 *   Eso depende de que el topbar declare su `color` junto con su `background`.
 *   Cuando no lo hacía, el control quedó en 1,06:1 y era ilegible.
 */
.mxsd { display: inline-flex; align-items: center; }

.mxst {
  position: relative;
  appearance: none; border: 0; padding: 0; cursor: pointer;
  width: 52px; height: 26px; border-radius: 13px;
  display: inline-flex; align-items: center; justify-content: space-between;
  background: rgba(128,128,128,.18);
  outline: 1px solid rgba(128,128,128,.30);
  color: inherit;
  transition: background .18s ease;
}
.mxst:hover { background: rgba(128,128,128,.26); }
.mxst:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

/* Los dos iconos, fijos dentro del riel. Están SIEMPRE los dos: es lo que
   permite ver de un vistazo a qué se cambia, y no solo dónde estás. */
.mxst-ico {
  position: relative; z-index: 2;
  width: 14px; height: 14px; margin: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  pointer-events: none;
  transition: opacity .18s ease;
}
.mxst-ico svg { width: 100%; height: 100%; display: block; }
/* El icono del tema activo va opaco y el otro atenuado. ⚠ .55 es el piso: por
   debajo, sobre el membrete azul, el icono inactivo desaparece y el switch
   parece tener uno solo — que es justo lo que este diseño quería evitar. */
.mxst-sol  { opacity: 1; }
.mxst-luna { opacity: .55; }
.mxst.mxst-on .mxst-sol  { opacity: .55; }
.mxst.mxst-on .mxst-luna { opacity: 1; }

/* ⚠ El icono del lado ACTIVO queda ENCIMA del pomo, que es blanco — y los iconos
 * heredan el color del membrete, que también es blanco. Resultado: blanco sobre
 * blanco, invisible. En las primeras capturas se veía UN solo icono y el diseño
 * acordado pedía los dos.
 *
 * Se resuelve tiñendo de azul profundo el icono que le toca estar sobre el pomo.
 * Además de arreglarlo, deja el switch más legible que con los dos iguales: el
 * icono del tema activo se lee en negativo y el del otro tema, atenuado, sobre el
 * riel. Con un vistazo se sabe dónde estás y a qué cambiás. */
.mxst:not(.mxst-on) .mxst-sol { color: var(--mx-marco-fondo, #003A70); opacity: 1; }
.mxst.mxst-on       .mxst-luna { color: var(--mx-marco-fondo, #003A70); opacity: 1; }

/* El pomo. Se mueve con `transform` y no con `left`: `transform` se compone en
   la GPU y no obliga al navegador a recalcular el layout en cada cuadro. */
.mxst-pomo {
  position: absolute; z-index: 1;
  top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%;
  background: rgba(255,255,255,.92);
  box-shadow: 0 1px 3px rgba(0,0,0,.35);
  transition: transform .18s cubic-bezier(.3,.8,.4,1);
}
.mxst.mxst-on .mxst-pomo { transform: translateX(26px); }

/* ⚠ El switch NO se oculta en pantallas chicas, al revés que el control que
   reemplazó: mide 52 px, entra en cualquier ancho, y esconderlo dejaría a quien
   trabaja en un portátil sin forma de cambiar el tema. */
@media (prefers-reduced-motion: reduce) {
  .mxst, .mxst-ico, .mxst-pomo { transition: none; }
}

/* ── el aviso de la primera vez ─────────────────────────────────────────────
 *
 * Un globo anclado bajo el switch, que se muestra una sola vez por persona.
 *
 * ⚠ `position: fixed` y colgado del `<body>`, no del membrete. El membrete es
 * sticky y un globo adentro se recortaría en su borde inferior — es la misma
 * razón por la que el globo de "fila no restaurada" de Pago de Facturas vive a
 * nivel de página.
 */
.mxav {
  position: fixed; z-index: 10050;
  width: 260px; padding: 13px 15px 12px;
  background: var(--navy-2, #111827);
  border: 1px solid var(--border-2, #2f4156);
  border-radius: 12px;
  box-shadow: 0 14px 40px rgba(0,0,0,.45), 0 2px 8px rgba(0,0,0,.25);
  color: var(--text, #f1f5f9);
  animation: mxavIn .22s cubic-bezier(.2,.9,.3,1.15);
}
.mxav-out { animation: mxavOut .18s ease forwards; }

/* La flecha que apunta al switch. Su posición horizontal la calcula el
   JavaScript, porque el globo se corre si no entra por el borde derecho: dejarla
   centrada haría que apuntara al aire en esos casos. */
.mxav-flecha {
  position: absolute; bottom: 100%;
  border: 7px solid transparent;
  border-bottom-color: var(--border-2, #2f4156);
}
.mxav-flecha::after {
  content: ''; position: absolute;
  left: -6px; top: -5px;
  border: 6px solid transparent;
  border-bottom-color: var(--navy-2, #111827);
}

.mxav-tit {
  margin: 0 0 5px; font-size: 13px; font-weight: 650;
  color: var(--blue-light-txt, var(--blue-light, #60a5fa));
}
.mxav-txt { margin: 0 0 11px; font-size: 12px; line-height: 1.5; color: var(--text-2, #aab6c6); }
.mxav-txt b { color: var(--text, #f1f5f9); font-weight: 600; }

.mxav-ok {
  appearance: none; border: 0; cursor: pointer;
  display: block; margin-left: auto;
  font-family: inherit; font-size: 12px; font-weight: 600;
  padding: 6px 14px; border-radius: 7px;
  background: var(--blue, #2563eb); color: #fff;
  transition: background .14s;
}
.mxav-ok:hover { background: var(--blue-light, #3b82f6); }
.mxav-ok:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

@keyframes mxavIn  { from { opacity: 0; transform: translateY(-8px) scale(.97); }
                     to   { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes mxavOut { to   { opacity: 0; transform: translateY(-6px) scale(.98); } }

@media (prefers-reduced-motion: reduce) {
  .mxav { animation: none; }
  .mxav-out { animation: none; opacity: 0; }
}

/* ── Los íconos dibujados (componentes/iconos.js) ──────────────────────────
 * Reemplazan a los emoji del sistema. Tres cosas que el SVG necesita del CSS:
 *
 * ⚠ `vertical-align: -.14em` no es un ajuste al gusto. Un `<svg>` en línea se
 *   apoya en la LÍNEA BASE, y el dibujo ocupa todo su lienzo: sin bajarlo, el
 *   ícono queda flotando sobre el texto que acompaña. El emoji no tenía este
 *   problema porque la fuente ya trae su métrica.
 * ⚠ `flex-shrink: 0` porque muchos viven dentro de un flex con texto al lado:
 *   sin esto el ícono se comprime a un óvalo cuando el rótulo es largo.
 * ⚠ El color NO se declara acá: es `currentColor` en el SVG, o sea el color del
 *   texto que lo acompaña. Fijarlo acá volvería a atar el ícono a un tema, que
 *   es exactamente el defecto del que se viene. */
.mx-ico {
  display: inline-block; vertical-align: -.14em; flex-shrink: 0;
}
.kpi-label .mx-ico, .kpi-sub .mx-ico,
.card-sub .mx-ico, .modal-kpi-label .mx-ico {
  width: 1.3em; height: 1.3em; vertical-align: -.28em;
}
/* ⚠ El título de card va aparte, y NO es un detalle de gusto: `kpi-label` mide 10-11px
 * y `card-title` 14px en negrita, así que el MISMO 1,3em da 13px en un lado (bien) y
 * 18,2px en el otro — medido—, donde el ícono empieza a competir con el título en vez
 * de acompañarlo. El factor tiene que ser inverso al tamaño de la letra: la etiqueta
 * chica necesita el empujón, el título ya se ve. */
.card-title .mx-ico {
  width: 1.1em; height: 1.1em; vertical-align: -.2em;
}
/* El único ícono que se mueve: el anillo de carga. La clase la pone `mxIco` solo
 * cuando se pide 'cargando', así que no hay que acordarse de agregarla. */
.mx-ico-gira { animation: mxIcoGira .9s linear infinite; transform-origin: 50% 50%; }
@keyframes mxIcoGira { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* Sin giro, pero el ícono se queda: es la señal de que algo está en curso. */
  .mx-ico-gira { animation: none; }
}


/* ── El cambio de tema: sin cruces a medias ──────────────────────────────────
 *
 * ⚠ Esta clase la pone y la quita `switch-tema.js` durante el cambio de tema, y
 *   existe porque hoy el cambio YA se anima sin que nadie lo haya decidido: las
 *   reglas de `:hover` de `tbody tr` (`transition:background .1s`), `.btn`
 *   (`transition:all .15s`) y `.kpi-card` se disparan también cuando cambian los
 *   tokens. Resultado medido con las 1.967 filas del feed: 9.846 transiciones
 *   arrancando, los botones y las filas cruzando ~186 ms mientras el fondo, el
 *   membrete y todo el texto cambian de golpe.
 *   Apagándolas: el recálculo baja de 834 a 585 ms y el PEOR caso de 1.074 a 627.
 *
 * ⚠ Lleva `!important` a propósito. La especificidad de `html.mx-tema-cambiando *`
 *   ya le gana a `.btn` y a `.kpi-card`, pero no a una regla futura anclada en un
 *   `#id`. Como la clase es TEMPORAL y su único trabajo es suprimir, acá el
 *   `!important` es lo correcto y no un parche.
 *
 * ⚠ Y por eso `aplicar()` la quita en un `finally`: si quedara puesta, TODOS los
 *   `:hover` de la aplicación dejarían de animar, para siempre y sin ningún error.
 *
 * No hace falta un bloque `prefers-reduced-motion`: esta regla QUITA movimiento,
 * así que va en la misma dirección que esa preferencia. */
html.mx-tema-cambiando *,
html.mx-tema-cambiando *::before,
html.mx-tema-cambiando *::after { transition: none !important; }
