/* Utilidades de LAYOUT/SCROLL transversales — asset COMPARTIDO del RCL InsCore.Ui.Gadgets.
   Único hogar para los 3 hosts (broker / backoffice / portal-cliente). Self-contained con
   var(--token, fallback): no depende de los tokens de ningún host en concreto.

   Razón de ser: el shell (MudLayout → MudMainContent → MudContainer) ya aporta el scroll de la
   PÁGINA. Cualquier hijo que declare su propio height/max-height + overflow crea una SEGUNDA barra
   ("doble scroll"). Estas clases dan el patrón de scroll ÚNICO por superficie. */

/* ── 📐 M-207 · UNA SUPERFICIE QUE LLENA LA PANTALLA SE MIDE, NO SE ADIVINA ─────────────────
   Aquí vivían las dos restas mágicas de la casa: `calc(100dvh - var(--ins-chrome, 13rem))` para el pane
   de altura completa, y `calc(100dvh - 22rem)` para la rejilla de listado (insList.css). Las dos salían
   de MEDIR UNA pantalla. 22rem eran la barra superior MÁS los rellenos del contenedor MÁS la cabecera de
   página MÁS la banda de filtros de UNA composición concreta; en cualquier otra —una cabecera con dos
   renglones de acciones, la banda de filtros desplegada, un aviso de plan por encima— la resta sobra o
   falta, y el resultado es hueco muerto al pie o una barra que no aparece hasta que la lista ya se salió
   de la vista. El número correcto no existe: depende de lo que haya encima, y eso cambia por página.

   🌳 LA RAÍZ: el alto no se calcula, SE REPARTE. El armazón —layout → contenido principal → contenedor
   de página— se declara COLUMNA FLEX ACOTADA al viewport, y la superficie que llena la pantalla pide el
   espacio que sobra (`flex` + `min-height: 0`). El navegador mide el cromo REAL de esa página, el que sea
   y el día que sea, porque lo tiene delante. No queda ninguna cifra que mantener ni que volver a acertar.

   🎫 SE OPTA POR LA FORMA DE LA PÁGINA, NO POR UNA LISTA DE RUTAS, y tampoco por una clase nueva que
   alguien pueda olvidar poner en la pantalla número 33: `:has()` enciende la columna acotada solo donde
   ya hay una superficie que la pide (`.ins-listado` de un listado, `.ins-fill` de un pane). Un formulario
   o una ficha de detalle no se enteran de nada y siguen rodando la página entera, que es lo suyo.

   🚪 FALLA ABIERTA, Y ESO ES PARTE DE LA REGLA, NO UN ADORNO. El contenedor conserva `overflow-y: auto`
   y el listado su `min-height`: si el cromo de una página no cabe —viewport bajo, tres avisos apilados,
   móvil— la rejilla se planta en su mínimo y la PÁGINA vuelve a rodar. Y si algún día MudBlazor cambiara
   el marcado y la escalera se rompiera por un peldaño, el efecto es el mismo: la rejilla crece con su
   contenido y rueda la página. El peor caso posible es «vuelve el scroll de página», jamás «hay algo a
   lo que no se llega» — que es exactamente lo que sí podía pasar con una resta adivinada de más.

   ⚠️ LA EXCEPCIÓN, ESCRITA DONDE ESTÁ: un panel PEGAJOSO (`position: sticky`) NO puede tomar su alto de
   un padre acotado, porque su columna mide lo que mide la columna de al lado y no lo que mide la ventana.
   Ésos sí miran al viewport — lo que no hacen es ADIVINAR: ver `.ins-sticky-pane` aquí debajo. */

/* Peldaño 0 · el documento tiene altura definida, o `flex` no tiene contra qué repartir. */
html:has(.mud-container > .ins-listado, .mud-container > .ins-fill),
html:has(.mud-container > .ins-listado, .mud-container > .ins-fill) > body {
    height: 100%;
}

/* Peldaño 1 · el layout es la columna acotada. La barra superior y los cajones son `position: fixed`
   (MudBlazor), así que no están en el flujo: el único hijo que reparte es el contenido principal. */
.mud-layout:has(.mud-container > .ins-listado, .mud-container > .ins-fill) {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Peldaño 2 · el contenido principal ya trae `flex: 1 1 auto` de MudBlazor —estaba INERTE porque
   `.mud-layout` no era flex—; aquí solo se le permite encoger. Su `padding-top` es el hueco de la barra
   fija, y al ser `border-box` el descuento de la barra lo hace el navegador, no una resta nuestra. */
.mud-layout:has(.mud-container > .ins-listado, .mud-container > .ins-fill) > .mud-main-content {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Peldaño 3 · el contenedor de página reparte lo que queda entre el cromo y la superficie. */
.mud-container:has(> .ins-listado, > .ins-fill) {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Cabecera, avisos y migapán conservan su alto: son cromo, no reparto. */
.mud-container:has(> .ins-listado, > .ins-fill) > * {
    flex: 0 0 auto;
}

/* Y la superficie pide el resto. ⚖️ Los dos verbos NO son el mismo, y la diferencia es deliberada:
   · el LISTADO **cede** (`0 1 auto`): con pocas filas sigue midiendo lo que mide su contenido —el papel
     abraza la tabla, igual que hasta hoy— y solo encoge cuando no cabe. Eso es exactamente la semántica
     que tenía el `max-height`, pero MEDIDA en vez de adivinada;
   · el PANE de altura completa **crece** (`1 1 auto`): un chat cuyo compositor no llega al pie de la
     ventana no es un pane, es un bloque suelto. */
.mud-container:has(> .ins-listado, > .ins-fill) > .ins-listado {
    flex: 0 1 auto;
}

.mud-container:has(> .ins-listado, > .ins-fill) > .ins-fill {
    flex: 1 1 auto;
}

/* ── Pane de altura completa (chat, consolas, paneles "tipo app") ──────────────────
   El panel llena el hueco que le cede el contenedor y SOLO su contenido interno scrollea; la página no.
   Misma filosofía ya probada en .app-drawer .mud-navmenu (flex column + min-height:0 + overflow en el
   hijo). Ya no descuenta ningún "chrome": la variable --ins-chrome murió con M-207, y con ella los dos
   `style="--ins-chrome: 12rem"` que la afinaban a mano en los dos AiChatBody. */
.ins-fill {
    display: flex;
    flex-direction: column;
    min-height: 18rem; /* en viewports muy bajos no colapsa por debajo de lo usable */
}

/* ── 🧲 Panel PEGAJOSO acotado a la ventana (la excepción de arriba, con sus términos DECLARADOS) ──
   Un `position: sticky` se mueve dentro de su columna, y esa columna mide lo que mide la columna de al
   lado: no hay padre acotado del que heredar altura, así que el tope tiene que mirar al viewport. La
   diferencia con la resta mágica que M-207 mató no es el `100dvh`, es DE DÓNDE SALEN LOS SUSTRAENDOS:
   aquí son la altura que publica la propia barra superior (`--mud-appbar-height`, de MudBlazor) y un
   hueco de respiro CON NOMBRE, declarado una vez. Ninguna cifra medida a ojo sobre una pantalla.

   Los 88 px que estaban escritos a mano en dos sitios (`BrandingLetterheadPreview` y la columna de
   navegación de `ClientDetail360`) son precisamente 64 + 24: la barra y el respiro. Ahora se dicen así. */
:root {
    /* 📐 s110 · CUÁNTO SE SEPARA DEL BORDE UNA SUPERFICIE FLOTANTE (desplegable, menú, panel del
       calendario, panel del asistente). Sube aquí desde `insAiLauncher.css`, donde vivía como
       `--ins-ai-float-gutter`, porque no es del asistente: es de cualquier cosa que flote.
       🎯 Y existe porque `AListHeightIsMeasuredNotGuessedTests` tiene razón: una resta al viewport con
       una cifra SUELTA mide una pantalla y se equivoca en todas las demás (M-207). Si el margen tiene
       que cambiar, cambia aquí y en todas a la vez. */
    --ins-float-gutter: 16px;
    --ins-sticky-gutter: 24px;
    --ins-sticky-top: calc(var(--mud-appbar-height, 64px) + var(--ins-sticky-gutter));
    --ins-sticky-available: calc(100dvh - var(--ins-sticky-top) - var(--ins-sticky-gutter));
}

.ins-sticky-pane {
    position: sticky;
    top: var(--ins-sticky-top);
    scroll-margin-top: var(--ins-sticky-top);
    max-height: var(--ins-sticky-available);
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* ── 📄 s120 · M-784 · UN TECHO NO ES UNA ALTURA: EL PANEL PIDE EL ALTO, NO SÓLO SE ABSTIENE DE PASARSE ──
   🔴 Reportado por él con captura sobre `/settings/branding/documents/Quote` (30-ago-2026): *«se recortó
   el carril derecho donde está el PDF sin sentido, achicando la vista cuando ese espacio es vital para
   examinar»*. La previa salía en una franja de ~320 px con su propio scroll y media ventana en blanco
   debajo.

   🌳 LA CAUSA, y es de libro: aquí arriba se declara `max-height` y NINGUNA altura. Un contenedor flex
   sin altura definida se dimensiona por su CONTENIDO, así que el `flex: 1 1 auto` del iframe no tiene
   contra qué repartir y cae a su `min-height` — 320 px, que era un suelo de emergencia y acabó siendo la
   medida real. Un techo impide pasarse; no manda crecer.

   🔌 SE ARREGLA EN LA CLASE, NO EN UNA VARIANTE, y eso lo decide el CENSO DE CONSUMIDORES, no el gusto:
   la montan TRES paneles —`DocumentTextPage` (previa del documento), `BrandingLetterheadPreview` (previa
   del membrete) y `PlatformNotifications` del Backoffice (previa del correo)— y los tres son PREVIAS que
   quieren toda la ventana. No hay contraejemplo que proteger: el índice de secciones del carril, que
   sería el candidato obvio, NO usa esta clase y lo dice en su propio marcado (`SectionIndex.razor`), así
   que una variante habría sido un interruptor que todos encienden.

   📱 Y SÓLO DONDE HAY DOS COLUMNAS: por debajo de `md` la rejilla apila, y un panel de 100dvh apilado
   sobre el editor obligaría a atravesar una ventana entera de previa para llegar a lo que se escribe.
   Ahí el techo sin suelo sigue siendo lo correcto, y por eso esto va dentro de la consulta y no fuera. */
@media (min-width: 960px) {
    .ins-sticky-pane {
        height: var(--ins-sticky-available);
    }
}

/* El cuerpo del panel pegajoso: crece hasta llenarlo y se deja acotar por dentro. Existe como CLASE y no
   como `style=` en la pantalla porque quién cede y quién crece es distribución, y la distribución se
   DECLARA — escrita en línea, la siguiente pantalla la copia con otro reparto y nadie ve la divergencia
   (es lo que vigila `RowsDeclareWhoYieldsTests`). El borde discontinuo sí se queda en línea: es
   decoración de esa previa concreta, no reparto. */
.ins-sticky-pane__body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
}

/* Dentro del panel pegajoso, la zona que rueda es la ÚNICA que rueda y ocupa lo que sobre. */
.ins-sticky-pane__scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Zona desplazable del pane: ocupa el espacio libre y es la ÚNICA que scrollea. min-height:0 es
   imprescindible en flex para que el hijo pueda encoger y mostrar su barra (sin él, desbordaría). */
.ins-fill__scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Pie fijo del pane (input de chat, barra de acciones): no scrollea, queda siempre visible. */
.ins-fill__footer {
    flex: 0 0 auto;
}

/* ── Selects con lista larga (ej. filtro de Ramo) ─────────────────────────────────
   El popover y la MudList interna traen ambos max-height + overflow:auto → DOS scrollbars
   anidados ("doble scroll"). Dejamos que scrollee solo el popover (lista interna sin límite) → un
   único scroll, holgado para que la lista quepa en pantallas normales. Se aplica con
   PopoverClass="ins-tall-popover" en MudSelect/MudAutocomplete. SSOT compartido por los 3 hosts
   (antes duplicado en los theme.css de broker y backoffice; el portal-cliente no lo tenía). */
.ins-tall-popover {
    max-height: 70vh !important;
    overflow-y: auto !important;
}

/* ── 🔎 EL DESPLEGABLE DE UN BUSCADOR SE ACOTA MAS, y no es una preferencia estetica ──────────────
   Reportado por el (28-ago-2026): al teclear en el buscador de tomador, la lista «empieza a parpadear
   arriba y abajo descontroladamente», y termina pegada al borde SUPERIOR de la pantalla, desprendida
   de un campo que esta en la mitad baja.

   El 70vh de arriba tiene sentido en un SELECT de lista cerrada —el filtro de Ramo—, que se abre una
   vez y no cambia de alto. En un BUSCADOR no: la lista se rehace en cada tecla y pasa de doce
   resultados a «Sin resultados». Una caja que pide el 70 % de la pantalla junto a un campo bajo no
   cabe ni debajo ni encima, asi que se voltea, se recorta contra el borde y vuelve a negociar su sitio
   en cada cambio. Eso es el parpadeo: no es pintado, es una caja que no cabe.

   ⇒ 40vh cabe bajo un campo situado en la mitad baja, que es donde suelen quedar los buscadores de un
     formulario por pasos. La lista sigue rodando por dentro, asi que no se pierde ningun resultado:
     lo unico que se pierde es la negociacion. */
.ins-picker-popover {
    max-height: 40vh !important;
    overflow-y: auto !important;
}

/* ── ✖️ EL VELO QUE CIERRA UN AUTOCOMPLETADO AL PULSAR FUERA ───────────────────────────────────────
   Reportado por el (s129): «los select tanto de poblacion como de provincia, si hago clic afuera no se
   cierran; tengo obligatoriamente que elegir algo». Y de nuevo el 04-sep-2026 sobre el selector de pais,
   que se habia quedado fuera de aquel arreglo. Causa aislada con el paquete en la mano: no es el blur —el
   blur ocurre y no cierra—, es que `MudAutocomplete` no monta ningun captador del clic de fuera, a
   proposito, porque cerrar en blur mataria el clic sobre la propia lista. Le falta lo que `MudSelect` si
   trae de fabrica: un velo.

   📐 EL Z-INDEX ES EL ARREGLO ENTERO, y por eso el velo es un div NUESTRO y no un `MudOverlay`. Medido en
   vivo: `MudOverlay` sale con z-index 1601 —su hoja gana al parametro `ZIndex`— y el popover del
   autocompletado esta EXACTAMENTE en 1601 tambien. Empatados, decide el orden del DOM, y el clic no
   acababa donde tenia que acabar. Con un velo propio el numero lo ponemos nosotros, justo por debajo del
   popover: el clic sobre un item llega al item —elegir sigue funcionando, que es la otra mitad, y sin ella
   el remedio mataria el control— y el clic sobre cualquier otro sitio llega al velo. Invisible: no es un
   modal, es un captador.

   🧬 El 1601 es un hecho de MudBlazor, o sea una COPIA que se pudre si ellos lo cambian. No lo vigila este
   comentario: lo vigila `s129-autocompletado-se-cierra.spec.ts`, y sus dos mitades cubren las dos formas
   de equivocarse. Si MudBlazor sube su popover, falla el cierre; si lo baja, falla la eleccion.

   📍 Vive aqui y no en un `<style>` dentro de un componente porque son TRES los sitios que lo montan
   —`InsAddressFields`, `CountryAutocomplete` y el localizador de catastro de `InsCore.Web`—: en el marcado,
   cada instancia del formulario reemitia su propia copia de estas reglas, y varias copias del mismo numero
   son varios sitios donde puede divergir. El tercero es de un HOST y no del RCL, o sea que la regla ya
   cruzaba la frontera del paquete: por eso el host la alcanza por el `<link>` de `App.razor`
   (`_content/InsCore.Ui.Gadgets/insLayout.css`) y no por una copia suya. */
.ins-ac-veil {
    position: fixed;
    inset: 0;
    /* Un punto por debajo del popover del autocompletado (medido: 1601). Ver el comentario de arriba. */
    z-index: 1600;
    background: transparent;
}
.ins-picker-popover .mud-list {
    max-height: none !important;
    overflow-y: visible !important;
}
.ins-tall-popover .mud-list {
    max-height: none !important;
    overflow-y: visible !important;
}

/* ── DOBLE SCROLL, red de seguridad GLOBAL (no opt-in) ─────────────────────────────
   El patrón .ins-tall-popover de arriba solo actúa si el MudSelect/MudAutocomplete recuerda poner
   PopoverClass="ins-tall-popover"; en la práctica se olvida y el doble scroll reaparece (p. ej. los
   filtros de /cima/log). Esta regla lo cubre para CUALQUIER popover que contenga una MudList
   (select, autocomplete, menú) sin depender de ninguna clase por componente: el popover es el ÚNICO
   scroller (cap 70vh) y la lista interna nunca añade su propia barra.

   ⚠️ EL ALCANCE DE ESTA RED ES EL DOBLE SCROLL, NO LA COLOCACIÓN, y esa distinción costó un defecto:
   los popovers SIN lista (date/color/pickers) no tienen .mud-list y no los toca ni esta regla ni el
   único rescate por altura de MudBlazor 8.4 (`placePopover` busca literalmente un '.mud-list'). Medido
   el 22-ago-2026 a 1280×620: el calendario de «Registrar cobro» nacía 73 px FUERA de la pantalla y no
   se llegaba al día 31. Un cap en CSS no lo arregla —70vh de 620 son 434 px, y el panel empezaba en
   top 283: seguiría saliéndose—, porque el cap correcto depende de DÓNDE cayó el panel, y eso el CSS
   no lo puede leer. Por eso aquí no hay ninguna regla para pickers: quien lo cubre, midiendo posición
   y espacio a cada lado, es `insPlacement.js` (mismo RCL, enlazado por los 3 hosts). */
.mud-popover:has(.mud-list) {
    max-height: 70vh !important;
    overflow-y: auto !important;
}
.mud-popover .mud-list {
    max-height: none !important;
    overflow-y: visible !important;
}

/* ── 🫨 UN DESPLEGABLE NACE COLOCADO — nunca un frame en la esquina de la pantalla ─────────────
   Reportado por él en vivo sobre el listado de recibos: «hay un PARPADEO en la parte superior
   IZQUIERDA de la pantalla».

   🔬 MEDIDO el 17-ago-2026 contra la app en pie (muestreo por requestAnimationFrame, 3 aperturas de 3):
   el panel se pinta DOS frames (~20-35 ms) en (0,0) a tamaño completo —214×189 px, opacidad 1,
   `mud-popover-open`— y solo entonces salta a su sitio. En ese frame su `style` inline NO tiene `left`,
   `top` ni `z-index`: solo las transiciones. Blazor pone la clase que lo hace VISIBLE y el colocador de
   MudBlazor escribe las coordenadas uno o dos frames después; un popover sin coordenadas cae en el
   origen. ⇒ No era M-589 falso: hoy dura dos frames en vez de quedarse.

   📊 Y NO ES DE LOS «FIJOS», que fue la primera hipótesis: censado abriendo cada activador de 5 rutas,
   **10 de 25** popovers abiertos se pintan en la esquina — 9 de 23 NO fijos y 1 de 2 fijos. Por eso la
   regla no lleva `.mud-popover-fixed`: apuntarla ahí habría cubierto 1 de los 10.

   🎫 LA PREMISA, con su denominador: esta regla usa el `style` inline como testigo de que el colocador
   ya pasó, así que solo vale si el colocador coloca SIEMPRE por `left/top` y nunca por `transform`.
   Medido sobre la misma población: **24/24** popovers acaban con `top:` y `left:` en el style inline y
   **0/24** colocados por `transform`. Si algún día apareciera uno colocado por transform, esta regla lo
   apagaría y hay que reapuntarla al hecho nuevo — el guard e2e lo cantaría.

   🚪 FALLA ABIERTA, Y ESO ES PARTE DE LA REGLA, NO UN ADORNO. Escrita como un simple `opacity: 0`, un
   popover al que el colocador nunca le escribiera `top:` quedaría invisible PARA SIEMPRE: eso cambia un
   parpadeo de dos frames por un menú que no se abre, que es peor que el defecto. Por eso la opacidad la
   devuelve una animación con retardo: el peor caso posible es «aparece 200 ms tarde», nunca «no
   aparece». En el caso normal el colocador llega en dos frames, el selector deja de casar y no hay
   retardo ninguno.

   ⚠️ Trampa conocida y asumida: `[style*="top:"]` casa por SUBCADENA, así que un `padding-top:` en el
   style inline la engañaría. Hoy no ocurre (MudBlazor solo escribe ahí `max-width`, `left`, `top`,
   `z-index` y las transiciones), y si ocurriera el fallo es hacia el lado seguro —el popover se pinta
   antes de tiempo, o sea vuelve el parpadeo— y lo caza el guard, no lo esconde. */
@keyframes ins-popover-fail-open {
    to { opacity: 1; }
}

.mud-popover.mud-popover-open:not([style*="top:"]) {
    opacity: 0;
    animation: ins-popover-fail-open 1ms linear 200ms forwards;
}

/* ── 🔔 EL CONTADOR DE LA CAMPANA SE PEGA AL ICONO, NO AL BOTON ────────────────────────────────
   Reportado por el en vivo (s76): «el numero de la campanita que quede mas cerca pegado de la
   campanita, que no quede tan suelto».

   🔬 CAUSA, MEDIDA contra la app en pie el 18-ago-2026 (viewport 1440x900, tema claro):
   el `MudBadge` envuelve un `MudIconButton`, y MudBlazor ancla el globo a la caja del BOTON
   (`.mud-badge.mud-badge-top.right.mud-badge-overlap { inset: auto auto calc(100% - 12px) calc(100% - 12px) }`).
   El boton mide 48x48 (padding 12px) y el glifo solo 24x24 centrado, asi que la esquina del globo
   caia exactamente sobre la esquina de la CAJA del glifo — pero la campana dibujada dentro de esa
   caja deja ~4px de margen lateral y ~2px arriba, y el globo se iba ademas a y=0, pegado al borde
   superior de la barra. Cifras: glifo [1044,20 → 1068,44] · globo [1068,0 → 1088,20]. Es decir, el
   globo salia 20px por encima del glifo y 20px a su derecha: flotaba en el aire entre dos iconos.

   🎯 EL ARREGLO: correr el globo hacia dentro 9px en cada eje. 9 = los 12px de relleno del boton
   menos los 3px de solape que queremos sobre el glifo. Su esquina inferior-izquierda cae entonces
   en el CUADRANTE SUPERIOR DERECHO del glifo, que en la campana de Material esta VACIO (la cupula
   no llega ahi), asi que se pega sin tapar nada. Medido tras el cambio: globo [1059,9 → 1079,29].

   ⚠️ VIVE AQUI Y NO EN CADA .razor, Y SIN CLASE QUE ALGUIEN PUEDA OLVIDAR PONER: la pieza esta
   declarada DOS veces byte a byte (`InsCore.Web/Components/Shared/InboxBell.razor` y
   `InsCore.PortalCliente/Components/Layout/InboxBell.razor`). Un `Style` en linea en cada una divergiria
   en cuanto alguien tocase una sola, y una clase de adhesion se olvida en el tercer host. El selector
   apunta al MECANISMO del defecto —un globo anclado a un boton de icono— y no a la captura.

   📊 POBLACION, censada el 18-ago-2026 sobre el repo entero: `<MudBadge` aparece **2 veces**, y
   **2 de 2** envuelven un `MudIconButton` (las dos campanas). O sea, la regla cubre el 100 % de los
   globos que hay y no toca ninguno que no exista. Si algun dia nace un `MudBadge` sobre un boton de
   icono que NO quiera pegarse, se le da una clase de escape aqui — no se retira la regla.

   El area de clic NO se toca: el `transform` mueve el globo, nunca el boton; y `pointer-events:auto`
   del propio globo sigue siendo suyo, sobre el mismo boton que ya habia debajo. */
.mud-badge-root:has(> .mud-icon-button) .mud-badge {
    transform: translate(-9px, 9px);
}
