/* Launcher del asistente de IA (FAB + panel deslizante) — asset COMPARTIDO del RCL InsCore.Ui.Gadgets.
   Pareja CSS de AiLauncher.razor. Self-contained con var(--token, fallback): no depende de los tokens
   de ningún host. Escalera de z-index: reconnect 20000 > tooltip 19000 > diálogos Mud 1400 (el helper
   "¿Qué puedo pedirte?" se abre por DialogService y DEBE quedar por ENCIMA del panel) > ESTE panel 1350
   > popover 1300 > drawer/appbar. Por eso el panel va por DEBAJO de 1400. */

/* ── Modo embebido del pane de chat ────────────────────────────────────────────────
   .ins-fill normalmente calcula su altura contra el viewport (100dvh - chrome). Dentro del panel
   deslizante debe llenar el 100% del cuerpo del panel. Selector compuesto (0,2,0) para ganar el
   override sin depender del orden de carga de las hojas. */
.ins-fill.ins-fill--embedded {
    height: 100%;
    min-height: 0;
}

/* ── FAB (botón flotante) ──────────────────────────────────────────────────────────
   Abajo-derecha, por encima del contenido y del appbar; por debajo del panel y de los overlays altos. */
.ins-ai-fab {
    position: fixed;
    right: 24px;
    bottom: 24px;
    z-index: 1250;
    /* Arrastrable (insAiDrag.js): touch-action:none evita que el gesto haga scroll en móvil; el cursor da
       feedback de que se puede mover. Al arrastrar, insAiDrag.js fija left/top (y pone right/bottom:auto). */
    touch-action: none;
    cursor: grab;
}
.ins-ai-fab.ins-ai-fab--dragging {
    cursor: grabbing;
    user-select: none;
}

/* ── 🖱️ El FAB RESERVA su esquina: ninguna acción primaria aterriza debajo (M-310) ──────────────────
   Medido a 1280×720 sobre el asistente de emisión: el FAB queda ENCIMA del botón «Siguiente» y el
   navegador entrega el clic al FAB —Playwright lo dice literal, «ins-ai-fab … intercepts pointer
   events», y `force:true` tampoco sirve porque el evento sigue llegando al de encima—. No es cosmético:
   el corredor no recibe NINGÚN aviso (ni error ni rechazo), así que se lee como «el botón no funciona».

   🌳 La causa no es de ninguna pantalla: es que un flotante GLOBAL vive en la misma esquina donde la
   plataforma pone, por convención, la acción primaria de toda superficie con barra de acciones fija.
   Con `z-index:1250` contra el 2/5 de esas barras, el reparto del clic ya estaba decidido. Y el
   interruptor que lo oculta (`AiLauncherPrefs`) NACE ENCENDIDO, o sea que el caso por defecto era el roto.

   Por eso el arreglo lo declara el FAB —quien ocupa la esquina es quien la reserva— y no cada pantalla:
   así el número vive en UN sitio y mover el FAB no deja catorce superficies desactualizadas. Ya había
   DOS pantallas que se habían escrito su propio `margin-right:76px` a mano (el editor de textos de
   documento y el de condicionado): eso es la clase parcheándose de una en una, y aquí se absorbe.

   ⚖️ Por qué se reserva a lo ANCHO y no se sube el FAB: la altura de la barra no es conocida
   —`.ins-form-actions` envuelve a dos líneas cuando no cabe—, así que un desplazamiento vertical fijo
   puede quedarse corto y volver a solapar. El FAB ocupa SIEMPRE los 80 px más a la derecha del viewport
   (56 de botón + 24 de margen; 16 en móvil), así que reservar 88 px al final de la barra es suficiente
   por construcción, cualquiera que sea el ancho de la pantalla o de la columna.

   Se activa solo cuando el FAB está EN PANTALLA (`:has`): oculto por el corredor, con el cajón abierto o
   dentro de la propia página del chat no hay nada que esquivar, y reservar a ciegas dejaría un hueco
   muerto al final de la barra. Si el corredor ARRASTRA el FAB a otro sitio, la reserva se queda: es su
   elección explícita y la esquina sigue siendo suya.

   ⚠️ El nombre y el número son CONTRATO: los consumen el host del corredor y el del backoffice. */
:root {
    --ins-ai-fab-clearance: 88px;
}

body:has(.ins-ai-fab) .stepper-actions,
body:has(.ins-ai-fab) .ins-form-actions,
body:has(.ins-ai-fab) .ins-sticky-actions {
    padding-inline-end: var(--ins-ai-fab-clearance);
}

/* ── 🖱️ M-551 · LA CUARTA VÍA A LA MISMA ESQUINA: LA ÚLTIMA COLUMNA DE UNA TABLA ────────────────────
   La reserva de arriba nombra las TRES barras de acciones fijas, y la plataforma pone acciones en la
   esquina inferior derecha por una cuarta vía que ninguna de ellas cubre: la columna «Acciones» de una
   tabla, que va pegada al borde derecho del contenido. Es la clase de M-310 destapándose POR OTRA
   ESCRITURA — el mismo modo de fallo que la cuarta escritura de M-546, que no salió leyendo los
   manejadores sino al renombrar.

   📏 Medido a 1280×720 en /external-products, recién cargado: de los 4 botones de la primera fila,
   TRES entregaban su clic al FAB (`elementFromPoint` sobre su centro devuelve `.ins-ai-fab`). Y como en
   M-310, el corredor no recibe aviso ninguno: se lee como «el botón no funciona».

   ⚠️ Por qué la reserva y no «que lo arrastre»: el FAB es arrastrable y su posición se persiste
   (`ins-ai-fab-pos`), pero eso es un remedio POSTERIOR a encontrarse el botón muerto, vive por navegador
   y —clampado al viewport— reubica la zona muerta en lugar de quitarla. Aquí no hay nada que descubrir.

   ⚖️ Se reserva sobre el CONTENEDOR y no sobre la celda: el ancho de la columna de acciones lo decide su
   contenido, así que empujar por dentro de la celda la ensancharía y desalinearía su cabecera. Reservando
   al final del contenedor, cabecera y cuerpo se mueven juntos y el número sigue siendo el mismo de
   M-310 — que es lo que aquel arreglo pedía: que viva en UN sitio. Lo vigila
   `s68-m551-fab-no-tapa-acciones.spec.ts`, por impacto real y con positivo de control. */
/* 🎯 M-605 — LA RESERVA SOLO ALCANZA A LO QUE EL FAB PUEDE TAPAR: UNA TABLA CON BOTONES DENTRO.
   Escrita sobre TODA `.mud-table-container`, esta regla cobraba 88px de peaje a tablas que el FAB no
   roza en su vida — y ese peaje se lo cobra al ANCHO DESPLAZABLE, porque un `padding` dentro de un
   contenedor con `overflow-x:auto` suma al `scrollWidth`. Reportado por el en vivo: «creo que estas
   cards no necesitan scroll horizontal».

   🔬 MEDIDO el 18-ago-2026 en `:5001/` (tarjetas «Renovaciones pendientes» y «Recibos impagados
   recientes», `MudSimpleTable` de 3 columnas dentro de un `MudItem md=4`):
     · a 1280 → contenedor `clientWidth` 363 y `scrollWidth` 369 y 383 ⇒ desbordaba **6 y 20 px**;
       la tabla medía 281,2 y 294,9. 281,2 + 88 = 369,2 y 294,9 + 88 = 382,9: **el desbordamiento ERA
       exactamente esta reserva**, ni un pixel de contenido.
     · a 1536 → 449 contra 449: no desbordaba, y por eso el sintoma solo salia en pantallas medianas.
   Y se descarto la otra candidata en el mismo sitio: unir cifra y divisa con espacio duro (M-602)
   **empeora** el desbordamiento (a 36 y 29 px), no lo arregla. La raiz era esta.

   ⚖️ Por que `:has(tbody button)` y no una clase de listado: el hecho que la reserva protege son
   BOTONES DE ACCION —M-310 y M-551, cuyo guard mide `tbody tr button[aria-label]` con impacto real—.
   Una tabla sin un solo boton en su cuerpo no tiene nada que el FAB pueda robarle; una con botones lo
   conserva, este donde este la columna. La regla pasa a nombrar SU PROPIO HECHO en vez de «toda tabla».
   El guard de M-551 es la direccion contraria de este cambio y sigue verde. */
body:has(.ins-ai-fab) .mud-table-container:has(tbody button) {
    padding-inline-end: var(--ins-ai-fab-clearance);
}

/* ── Cajón ACOPLADO (derecha) ──────────────────────────────────────────────────────
   Anclado al viewport (el host lo monta fuera de MudLayout). Keep-alive: cerrado se desliza fuera y
   deja de capturar eventos, pero permanece en el DOM (conserva la conversación).

   NO es un modal y no lleva backdrop: mientras está abierto la aplicación entera sigue usable y el
   corredor puede teclear detrás (que es justo lo que se hace con un asistente: mirar un dato mientras
   se le pregunta). Antes había un scrim a pantalla completa con pointer-events:auto y un @onclick que
   cerraba el panel, así que la app quedaba inerte y CUALQUIER clic en ella lo cerraba: un asistente
   que se cierra al ir a buscar lo que le ibas a preguntar no sirve de asistente.
   El ancho se declara como variable porque lo consume también el modo acoplado (abajo). */
.ins-ai-panel {
    --ins-ai-panel-width: min(440px, 100vw);
    position: fixed;
    top: 0;
    right: 0;
    z-index: 1350;
    width: var(--ins-ai-panel-width);
    height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--mud-palette-surface, #fff);
    border-left: 1px solid var(--mud-palette-lines-default, rgba(0, 0, 0, 0.12));
    box-shadow: -8px 0 28px rgba(0, 0, 0, 0.22);
    transform: translateX(105%);
    transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
    pointer-events: none;
}
.ins-ai-panel--open {
    transform: translateX(0);
    pointer-events: auto;
}

/* ── Modo FLOTANTE: ventana movible y redimensionable ──────────────────────────────
   El otro modo, elegido a mano y recordado. Acoplado el panel es una franja del borde y la aplicación le
   cede el sitio; flotante es una ventana que se pone donde no estorbe y que SÍ tapa lo que hay debajo —
   por eso es una elección explícita y no el estado por defecto. La geometría (dónde y cuánto) la resuelve
   insAiPanel.js en estas cuatro variables; aquí solo está lo que significa cada una.
   El cerrado NO puede usar el translateX del modo acoplado: deslizar hacia la derecha a una ventana que
   está en mitad de la pantalla la pasea por delante del contenido. Se apaga en su sitio. */
.ins-ai-panel--floating {
    top: var(--ins-ai-float-y, 10vh);
    left: var(--ins-ai-float-x, auto);
    right: auto;
    height: var(--ins-ai-float-height, 70dvh);
    max-height: calc(100dvh - 16px);
    border: 1px solid var(--mud-palette-lines-default, rgba(0, 0, 0, 0.12));
    border-radius: 12px;
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.34);
    transform: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.16s ease, visibility 0.16s;
}
.ins-ai-panel--floating.ins-ai-panel--open {
    transform: none;
    opacity: 1;
    visibility: visible;
}
/* Flotante, la cabecera es el asa. El cursor lo dice antes de que nadie lo intente. */
.ins-ai-panel--floating .ins-ai-panel__head {
    cursor: move;
    user-select: none;
}

/* Mientras dura un gesto no hay transiciones: con ellas, el panel persigue al puntero con retraso. */
.ins-ai-panel--resizing,
.ins-ai-panel--resizing * {
    transition: none !important;
    user-select: none;
}

/* ── Agarres ───────────────────────────────────────────────────────────────────────
   Franja estrecha pero con área de puntero holgada (::after lo ensancha sin ensanchar lo que se ve): un
   agarre de 4 px es un agarre que nadie encuentra. Se tiñen al pasar por encima para anunciarse, sin
   pintar una barra permanente en el borde del panel. */
.ins-ai-panel__grip {
    position: absolute;
    z-index: 2;
    background: transparent;
    transition: background 0.15s ease;
}
.ins-ai-panel__grip:hover,
.ins-ai-panel--resizing .ins-ai-panel__grip {
    background: var(--mud-palette-primary, #1976d2);
    opacity: 0.55;
}
/* Ancho: todo el borde izquierdo, en los dos modos. */
.ins-ai-panel__grip--w {
    top: 0;
    left: 0;
    width: 5px;
    height: 100%;
    cursor: ew-resize;
    touch-action: none;
}
/* Esquina inferior izquierda: ancho y alto a la vez. Solo tiene sentido flotando — acoplado, el alto lo
   manda el viewport, así que ni se muestra (un agarre que no hace nada es peor que no tenerlo). */
.ins-ai-panel__grip--corner {
    display: none;
    bottom: 0;
    left: 0;
    width: 18px;
    height: 18px;
    cursor: nesw-resize;
    touch-action: none;
    border-bottom-left-radius: 12px;
}
.ins-ai-panel--floating .ins-ai-panel__grip--corner { display: block; }

/* Cabecera fija del panel: icono + título + ocultar + cerrar. */
.ins-ai-panel__head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 10px 8px 10px 16px;
    border-bottom: 1px solid var(--mud-palette-lines-default, rgba(0, 0, 0, 0.12));
}
.ins-ai-panel__title {
    flex: 1 1 auto;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--mud-palette-text-primary, inherit);
}

/* Cuerpo del panel: aloja el AiChatBody embebido (que llena el 100% vía .ins-fill--embedded). */
.ins-ai-panel__body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 12px 16px 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* ── Modo ACOPLADO: la aplicación CEDE el ancho del panel, no queda tapada ─────────
   El panel es position:fixed (tiene que serlo: se monta fuera de MudLayout para anclarse al viewport),
   así que "empujar" el contenido no sale solo del flujo: se hace cediendo ese ancho a nivel de página.
   Dos reglas y solo dos, porque solo hay dos cosas que ocupan el ancho completo:
     · el CUERPO, que es flujo normal → basta el padding (arrastra <MudLayout> y su contenido);
     · el APPBAR, que es fixed y por tanto ignora el padding del cuerpo → se le acota el borde derecho.
   El margen izquierdo del appbar lo sigue poniendo MudBlazor según el drawer, y `width:auto` respeta
   ambos a la vez. El !important es deliberado y está acotado a este estado: MudBlazor fija el ancho del
   appbar con selectores más específicos (uno por breakpoint y por lado del drawer) y ganarles con
   especificidad exigiría replicar esa matriz aquí.
   La clase la pone/quita el propio launcher en <html> (insAiDock.js) al abrir y cerrar. */
html.ins-ai-docked body {
    padding-inline-end: var(--ins-ai-panel-width, 440px);
    transition: padding-inline-end 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
}
html.ins-ai-docked .mud-appbar.mud-appbar-fixed-top {
    right: var(--ins-ai-panel-width, 440px);
    width: auto !important;
}
/* El ancho real lo declara .ins-ai-panel; a nivel de página se necesita el MISMO valor, y una variable
   declarada en un hijo no sube. Se declara aquí para <html> con el mismo valor (única duplicación, y por
   eso van juntas: si una cambia, la otra se ve al lado). */
html.ins-ai-docked { --ins-ai-panel-width: min(440px, 100vw); }

/* ── Responsive: en móvil el panel ocupa todo el ancho y NO acopla ──────────────────
   Ceder 100vw dejaría la aplicación sin sitio; en pantalla estrecha el cajón se superpone (patrón
   habitual en móvil) y se cierra con su botón, no tapando nada permanentemente. */
@media (max-width: 600px) {
    .ins-ai-panel { --ins-ai-panel-width: 100vw; }
    .ins-ai-fab { right: 16px; bottom: 16px; }
    html.ins-ai-docked body { padding-inline-end: 0; }
    html.ins-ai-docked .mud-appbar.mud-appbar-fixed-top { right: 0; width: 100% !important; }

    /* En una pantalla de teléfono no hay «donde no estorbe»: una ventana flotante ahí es una ventana que
       tapa todo y encima se puede arrastrar fuera. El modo se conserva —al volver a una pantalla grande
       sigue flotando, que es lo que el usuario eligió—, pero mientras tanto se presenta como el cajón a
       pantalla completa de siempre, con sus agarres apagados. */
    .ins-ai-panel--floating {
        top: 0;
        left: 0;
        right: 0;
        width: 100vw;
        height: 100dvh;
        max-height: none;
        border-radius: 0;
        border: none;
        opacity: 1;
        visibility: visible;
        transform: translateX(105%);
        transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
    }
    .ins-ai-panel--floating.ins-ai-panel--open { transform: translateX(0); }
    .ins-ai-panel--floating .ins-ai-panel__head { cursor: default; }
    .ins-ai-panel__grip { display: none !important; }
}

/* Accesibilidad: sin animación de deslizamiento ni de empuje si el usuario la reduce. */
@media (prefers-reduced-motion: reduce) {
    .ins-ai-panel { transition: none; }
    html.ins-ai-docked body { transition: none; }
}
